{"openapi":"3.1.0","info":{"title":"Roundr API","version":"1.0.0","description":"Roundr runs racket-sport tournaments and match days.\n\nConventions that hold across every endpoint:\n\n- Public identifiers are UUIDv7. Timestamps are UTC, ISO 8601. Venue-local\n  reasoning derives from each tournament's IANA time zone.\n- Errors carry a stable `error_code` alongside a human-readable `message`. Branch\n  on the code; the message is localizable prose for operators.\n- Changes within `/v1` are additive. Installed mobile applications cannot upgrade\n  in lockstep with the server.\n- Realtime notifications announce that state changed; they never carry\n  authoritative state. Recovery is always through a canonical GET.\n- Every signed-in user, device and machine key has a budget for each class of\n  request, far above what its own client sends. Past it the answer is\n  `429 rate_limited` (`RateLimited`), checked before the request is routed.\n","license":{"name":"Proprietary"}},"servers":[{"url":"https://api.roundr.com","description":"Production"},{"url":"https://api.roundr.localhost","description":"Development"}],"tags":[{"name":"Authentication","description":"Browser sign-in through the Cognito Hosted UI, and the caller's own session. Served\non the shared API host and on every tenant host; a session is issued by, and only\nvalid on, the host that issued it.\n"},{"name":"Account","description":"What a signed-in person sees of their own playing, across every tournament that\nlinked one of its players to their account. Read by the account, not by a\nprincipal: a person with no organization chosen has matches to see, and a device\nor a machine key has none.\n"},{"name":"Tournaments","description":"The tournament feed: the snapshot a client starts from, the durable change feed it\ncatches up on, and the stream it listens to. HTTP stays authoritative. A\nnotification announces that an aggregate changed and at which revision, never what\nit changed to; the client fetches the canonical projection through a GET, so a\nnotification that goes missing costs a fetch and never a score. Every request here\nreauthorizes the principal's tournament and court scope, and a cursor carries no\nauthority.\n"},{"name":"Draws","description":"Draws, knockout and round robin: a draft listed, seeded, placed and arranged, then\npublished into matches. A knockout draw places its entries on the lines of a\nbracket; a round-robin draw places them into groups, in which every entry plays\nevery other, and ranks each group by its standings. Writes are for an\nadministrator of the organization or the tournament's director, reads for its\nreferee as well; a hall display reads the standings of the draws its projection\nnames, and nothing else here. Every draft edit names the revision it was made\nagainst, and a published draw is not changed structurally again.\n"},{"name":"Live","description":"The anonymous public scoreboard: an organization's page and the tournaments it has\npublished on Live. A customer's host serves its own organization's page and\ntournaments; the platform's site (`roundr.com`, which reads the shared API host)\nserves any organization's published tournament or match by its id, and no list. No credential is read\nand none is needed; a tournament nobody published is not found, exactly as an id that\ndoes not exist. Every answer carries an `ETag`, and a client polls with\n`If-None-Match` rather than holding a stream open: Live's audience is unbounded, and a\nbodiless 304 is the cheapest answer there is. Reads are budgeted per client address,\nan IPv6 address by its /64.\n"},{"name":"Registration","description":"Personal registration for a tournament (DEV-18). A director opens a tournament and\noffers its draft draws as the events that take entries; anyone reads what an open\ntournament offers on its organization's host or the platform's site, as Live is\nread; a signed-in person\nenters an event alone or naming a partner, who confirms from their own account; and\nthe tournament's director or an administrator of the organization accepts a complete\nregistration into the participants or declines it. Only a person registers: a court\ndevice, a display and a machine key have no account.\n"},{"name":"Control","description":"Tournament administration: what Control does to a tournament outside a match's\nscore. An administrator of the organization creates tournaments; an administrator\nor that tournament's director does everything inside one. The participants are\nopen to any principal that may read the tournament but a display, since a court\ndevice may be given any match; a display reads only the sides of the matches its\nprojection shows, from its snapshot. Draws are under Draws. A tournament\noutside the principal's scope is not found, never forbidden, so nothing here maps\none tenant's tournaments for another.\n\nA machine key (`key_`) is an organization's own system reading its tournaments and\nloading their participants and matches; the draws and standings under Draws are\nfor staff alone. Issued `tournaments:read`, it lists the tournaments, reads\nparticipants, snapshots, the change feed, the stream, matches and their events,\nand exports results; issued `tournaments:import`, it imports. It administers\nnothing else and scores nothing, whatever it holds, and one issued for a\ntournament sees that tournament alone. It reads no draw and no display, so the\nfeed announces one to it only as a cursor checkpoint.\n"}],"paths":{"/v1/auth/login":{"get":{"tags":["Authentication"],"operationId":"beginSignIn","summary":"Start a browser sign-in","description":"Reached by navigation, not by `fetch`. Stores a one-time sign-in transaction\n(state, nonce, PKCE verifier), sets a short-lived `roundr_login` cookie bound to\nit, and redirects to the Cognito Hosted UI, which returns to\n`/v1/auth/callback` on the same host.\n\n`return_to` is a path on the page's own origin to land on afterwards. Anything\nthat is not a plain absolute path (protocol-relative, a backslash, whitespace, an\nAPI path) is replaced by `/`, so a bad link still signs the user in.\n\n`intent=signup` opens the Hosted UI on its sign-up page instead of its sign-in\npage, with the same transaction and the same callback: anyone may create an\naccount, which belongs to no organization until one admits it or the person\ncreates one (`POST /v1/organizations`). Any other value is a sign-in.\n\nNot available on hosts that serve no sign-in, or where Cognito is not configured.\n","security":[],"parameters":[{"name":"return_to","in":"query","required":false,"schema":{"type":"string","maxLength":512}},{"name":"intent","in":"query","required":false,"schema":{"type":"string","enum":["signin","signup"]}},{"name":"provider","in":"query","required":false,"description":"A federated provider's name on the user pool, from `/v1/auth/options`, which\nsends the browser straight to that provider rather than to the Hosted UI's own\npage. Any other value opens the Hosted UI.\n","schema":{"type":"string","maxLength":32}}],"responses":{"302":{"description":"To the Cognito Hosted UI. Sets `roundr_login`.","headers":{"Location":{"schema":{"type":"string","format":"uri"}}}},"404":{"description":"Sign-in is not available on this host.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Too many sign-ins started from this IP, or, for a caller already signed in, its\nread budget spent (`RateLimited`). `Retry-After` says when it clears.\n","headers":{"Retry-After":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/auth/callback":{"get":{"tags":["Authentication"],"operationId":"completeSignIn","summary":"Complete a browser sign-in","description":"Where the Hosted UI returns the browser. Requires both the `state` it echoes and\nthe `roundr_login` cookie the same browser was given; either alone is refused,\nwhich is what stops one person's sign-in being completed in another's browser.\nThe transaction is used up whether or not the callback succeeds.\n\nOn success the code is exchanged with PKCE, the ID token is verified (signature,\nissuer, audience, nonce), and the Cognito identity is found by issuer and subject\nor, the first time, registered as a new account. Registration needs a verified\nemail address that no other account holds; accounts are never linked by email.\nA new account belongs to no organization.\n\nThe browser is redirected to its `return_to` with a new `roundr_session` cookie,\nor, when the callback registered the account, first to `/sign-in/welcome` with\nthat `return_to`, where the person confirms the name the provider gave (DEV-713).\nAny session it already had is ended. On failure it is redirected to `/sign-in`\nwith `signin_error` set to one of `failed`, `email_unverified`, `email_in_use`,\n`account_disabled` or `unavailable`; that list is append-only.\n","security":[],"parameters":[{"name":"code","in":"query","required":false,"schema":{"type":"string"}},{"name":"state","in":"query","required":false,"schema":{"type":"string"}}],"responses":{"302":{"description":"To the page, signed in or with `signin_error`. Always clears `roundr_login`.","headers":{"Location":{"schema":{"type":"string","format":"uri"}}}},"404":{"description":"Sign-in is not available on this host.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Too many callbacks from this IP, or, for a caller already signed in, its read\nbudget spent (`RateLimited`). `Retry-After` says when it clears.\n","headers":{"Retry-After":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/auth/session":{"get":{"tags":["Authentication"],"operationId":"getSession","summary":"Who the caller is, and for which organization","description":"Answers for any credential. `principal` is present when the request acts for an\norganization: on a tenant host when the user is a member of it, on the shared API\nhost once one is chosen. `csrf_token` is present for a browser session and must\naccompany every write it makes, as `X-CSRF-Token`.\n","responses":{"200":{"description":"The caller.","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/AuthSession"}}}}}},"401":{"description":"No credential, or one that no longer authenticates.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/auth/organization":{"post":{"tags":["Authentication"],"operationId":"selectOrganization","summary":"Choose the organization a browser session acts for","description":"On the shared API host only; a tenant host acts for its own organization. The\nuser must be an active member. The session is replaced by a new one, because\nits authority changes, so the response carries a new CSRF token and sets a new\n`roundr_session`.\n","security":[{"sessionCookie":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["organization_id"],"properties":{"organization_id":{"type":"string","format":"uuid"}}}}}},"responses":{"200":{"description":"The new session.","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/AuthSession"}}}}}},"401":{"description":"No session.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"`csrf_rejected`: no Roundr page `Origin`, or a wrong `X-CSRF-Token`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not an organization the user is an active member of.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Not a browser session, a tenant host, or no organization id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/auth/logout":{"post":{"tags":["Authentication"],"operationId":"signOut","summary":"End the browser session","description":"Ends the session and clears its cookie. Always succeeds, signed in or not.\n\n`logout_url`, when present, ends the Cognito Hosted UI's own session too. A page\nshould navigate to it: otherwise the next sign-in on that browser completes\nwithout a password, which on a shared venue laptop signs in the previous user.\n","responses":{"200":{"description":"Signed out.","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["logout_url"],"properties":{"logout_url":{"type":["string","null"],"format":"uri"}}}}}}}},"403":{"description":"`csrf_rejected`, for a browser session without its page `Origin` and token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/auth/options":{"get":{"tags":["Authentication"],"operationId":"getSignInOptions","summary":"Which ways in this host offers","description":"What the sign-in page draws: a password form when this instance signs people in\nwith an email address and a password on its own page (`/v1/auth/password/*`), and\na button per federated provider the web app client supports, read from Cognito,\nwhich `/v1/auth/login?provider=` sends the browser straight to. Both are false and empty on a host that offers no\nsign-in, where the page falls back to the Hosted UI link.\n","security":[],"responses":{"200":{"description":"The options.","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/SignInOptions"}}}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/auth/password/sign-in":{"post":{"tags":["Authentication"],"operationId":"signInWithPassword","summary":"Sign in with an email address and a password","description":"Sign-in on Roundr's own page, beside the Hosted UI flow: the server asks Cognito\nwith the password, verifies the tokens it answers with, finds or registers the\naccount exactly as `/v1/auth/callback` does (DEV-11), ends any session the\nbrowser already had and sets a new `roundr_session` cookie. The page then re-reads\n`/v1/auth/session` and goes to `return_to`, reduced to a plain path on the page's\norigin as the Hosted UI's is.\n\nAnonymous and state-changing, so it is accepted only with a Roundr page's `Origin`\n(`csrf_rejected` otherwise) and limited per address. A wrong password and an\nunknown address are one refusal, `invalid_credentials`. An address not yet\nconfirmed is `email_unverified`; an account Cognito wants an authenticator code or\na new password from is `sign_in_challenge`, which the Hosted UI takes.\n","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email","password"],"properties":{"email":{"type":"string","maxLength":254},"password":{"type":"string","minLength":8,"maxLength":256},"return_to":{"type":"string","maxLength":512}}}}}},"responses":{"200":{"description":"Signed in; sets `roundr_session`.","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["return_to","registered"],"properties":{"return_to":{"type":"string","description":"The path to go to, reduced to one on the page's origin."},"registered":{"type":"boolean","description":"Whether this sign-in created the account."}}}}}}}},"401":{"description":"`invalid_credentials`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"`csrf_rejected`, `email_unverified`, `account_disabled`, `password_reset_required` or `sign_in_challenge`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Password sign-in is not offered on this host.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"`email_in_use`: the address belongs to another Roundr account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"`validation_failed`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"description":"`sign_in_unavailable`: Cognito could not be asked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/auth/password/sign-up":{"post":{"tags":["Authentication"],"operationId":"signUpWithPassword","summary":"Create a Cognito user for an email address","description":"Registers the address on the user pool, which sends it a confirmation code;\n`/v1/auth/password/confirm` completes it. The Roundr account itself is created at\nthe first sign-in (DEV-11). Same `Origin` check and per-address limit as sign-in.\n","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email","name","password"],"properties":{"email":{"type":"string","maxLength":254},"name":{"type":"string","maxLength":200},"password":{"type":"string","minLength":8,"maxLength":256}}}}}},"responses":{"200":{"$ref":"#/components/responses/PasswordStepDone"},"403":{"description":"`csrf_rejected`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Password sign-in is not offered on this host.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"`email_in_use`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"`validation_failed` or `password_policy`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"description":"`sign_in_unavailable`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/auth/password/confirm":{"post":{"tags":["Authentication"],"operationId":"confirmSignUp","summary":"Confirm an address with the code Cognito sent it","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailAndCode"}}}},"responses":{"200":{"$ref":"#/components/responses/PasswordStepDone"},"403":{"description":"`csrf_rejected`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Password sign-in is not offered on this host.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"410":{"description":"`code_expired`: ask for another with `/v1/auth/password/resend`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"`validation_failed` or `code_invalid`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"description":"`sign_in_unavailable`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/auth/password/resend":{"post":{"tags":["Authentication"],"operationId":"resendConfirmationCode","summary":"Send the confirmation code again","description":"Answers 200 for any well-formed address, known to the pool or not, so the route\nlists no addresses. Limited more tightly than sign-in: each call costs a message.\n","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailOnly"}}}},"responses":{"200":{"$ref":"#/components/responses/PasswordStepDone"},"403":{"description":"`csrf_rejected`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Password sign-in is not offered on this host.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"`validation_failed`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"description":"`sign_in_unavailable`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/auth/password/forgot":{"post":{"tags":["Authentication"],"operationId":"forgotPassword","summary":"Send a password reset code","description":"Answers 200 for any well-formed address, known to the pool or not;\n`/v1/auth/password/reset` takes the code with the new password. Limited as resend\nis.\n","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailOnly"}}}},"responses":{"200":{"$ref":"#/components/responses/PasswordStepDone"},"403":{"description":"`csrf_rejected`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Password sign-in is not offered on this host.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"`validation_failed`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"description":"`sign_in_unavailable`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/auth/password/reset":{"post":{"tags":["Authentication"],"operationId":"resetPassword","summary":"Set a new password with the reset code","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email","code","password"],"properties":{"email":{"type":"string","maxLength":254},"code":{"type":"string","maxLength":20},"password":{"type":"string","minLength":8,"maxLength":256}}}}}},"responses":{"200":{"$ref":"#/components/responses/PasswordStepDone"},"403":{"description":"`csrf_rejected`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Password sign-in is not offered on this host.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"410":{"description":"`code_expired`: ask for another with `/v1/auth/password/forgot`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"`validation_failed`, `code_invalid` or `password_policy`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"description":"`sign_in_unavailable`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/auth/redeem":{"post":{"tags":["Authentication"],"operationId":"redeemCode","summary":"Turn a one-time court or display code into a credential","description":"What `/join/{code}` and `/display/join/{code}` call. The code came from a QR image\nor was read out as a PIN; it is single-use and short-lived, and redeeming it\nconsumes it in the same transaction that creates the credential, so two scans of\none code yield one device and one `redemption_code_redeemed`.\n\nWith the default `cookie` transport a browser receives a `roundr_session` cookie\nnaming the new credential and never sees its secret; the page then replaces its\nURL with the clean Panel or Display URL. Any session the browser already had is\nended. With `bearer`, for a native client's secure store, the response carries the\n`crt_` or `dsp_` secret exactly once and sets no cookie.\n\nAnonymous and limited per client IP, sized for a crew setting up a hall behind\none address. A code that is found and refused counts against itself and locks\nafter repeated refusals; a guess that finds nothing is only ever counted per IP.\nNot available on hosts that serve no sign-in.\n","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["code","purpose"],"properties":{"code":{"type":"string","maxLength":128},"purpose":{"type":"string","enum":["court","display"],"description":"Which page the code was presented on; a code for the other purpose is unknown."},"transport":{"type":"string","enum":["cookie","bearer"],"default":"cookie"}}}}}},"responses":{"200":{"description":"The device's credential, as a session cookie or as a bearer secret.","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/RedemptionResult"}}}}}},"404":{"description":"`redemption_code_invalid`: no such code, another organization's on this host,\nor the other purpose. Also when redemption is not offered on this host.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"`redemption_code_redeemed`: the code has already become a credential.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"410":{"description":"`redemption_code_expired`: ask for a new code.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Missing or malformed fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"`rate_limited` for too many attempts from this IP, or for a caller already\nsigned in whose write budget is spent (`RateLimited`); `redemption_code_locked`\nfor a code refused too often. `Retry-After` says when any of them clears.\n","headers":{"Retry-After":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/auth/codes/inspect":{"post":{"tags":["Authentication"],"operationId":"inspectCode","summary":"Tell what a typed or scanned code is, without spending it","description":"The join screen asks this first, so one field takes a court code, a display code and\na court setup code alike. A court or display code is named by its purpose and then\nredeemed at `/v1/auth/redeem`; its state is not reported here, so a spent or expired\none is refused at redemption. A setup code comes with its tournament's name and\ncourts, one of which the tablet then joins at `/v1/auth/setup/join`. Limited per\naddress like redemption, and a refused setup code counts towards its lock.\n","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["code"],"properties":{"code":{"type":"string","maxLength":128}}}}}},"responses":{"200":{"description":"What the code is.","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/CodeInspection"}}}}}},"404":{"description":"`redemption_code_invalid`: no such code, or another organization's on this host.\nAlso when redemption is not offered on this host.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"`redemption_code_redeemed`: the setup code has admitted as many tablets as it allows.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"410":{"description":"`redemption_code_expired`: the code expired or was withdrawn; ask for a new one.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Missing or malformed fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"`rate_limited` for too many attempts from this IP, or for a caller already\nsigned in whose write budget is spent (`RateLimited`); `redemption_code_locked`\nfor a code refused too often. `Retry-After` says when any of them clears.\n","headers":{"Retry-After":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/auth/setup/join":{"post":{"tags":["Authentication"],"operationId":"joinWithSetupCode","summary":"Make this device a court's Panel with a court setup code","description":"The tablet chose `court_id` from the setup code's courts. It gets a court credential\nexactly as a court code gives one, created from a court code of its own that names\nthe setup code, and the setup code counts one more device. A browser gets a session\ncookie, a native client (`bearer`) the secret once.\n","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["code","court_id"],"properties":{"code":{"type":"string","maxLength":128},"court_id":{"type":"string","format":"uuid"},"transport":{"type":"string","enum":["cookie","bearer"],"default":"cookie"}}}}}},"responses":{"200":{"description":"The device's credential, as a session cookie or as a bearer secret.","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/RedemptionResult"}}}}}},"404":{"description":"`redemption_code_invalid`: no such code, or another organization's on this host.\nAlso when redemption is not offered on this host.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"`redemption_code_redeemed`: the setup code has admitted as many tablets as it allows.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"410":{"description":"`redemption_code_expired`: the code expired or was withdrawn; ask for a new one.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Missing or malformed fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"`rate_limited` for too many attempts from this IP, or for a caller already\nsigned in whose write budget is spent (`RateLimited`); `redemption_code_locked`\nfor a code refused too often. `Retry-After` says when any of them clears.\n","headers":{"Retry-After":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/tournaments/{tournamentId}/setup-codes":{"get":{"tags":["Authentication"],"operationId":"listSetupCodes","summary":"List a tournament's court setup codes","description":"For a director of the tournament or an administrator of the organization. Newest\nfirst, including codes that expired or were withdrawn in the last day. A code's\nplaintext is never shown again after it was issued.\n","parameters":[{"$ref":"#/components/parameters/tournamentId"}],"responses":{"200":{"description":"The setup codes.","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["setup_codes"],"properties":{"setup_codes":{"type":"array","items":{"$ref":"#/components/schemas/CourtSetupCode"}}}}}}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"$ref":"#/components/responses/ControlForbidden"},"404":{"$ref":"#/components/responses/TournamentNotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}},"post":{"tags":["Authentication"],"operationId":"issueSetupCode","summary":"Issue a court setup code","description":"For a director of the tournament or an administrator of the organization. Whoever\nsets the courts up enters it on each tablet and chooses the tablet's court there. It\nadmits at most `max_devices` tablets, lives `lifetime_seconds` (eight hours unless\ngiven, a day at most) and can be withdrawn. A PIN is eight digits unused by any court\nor display code; `join_url` opens the same choice for a QR code, with the code in the\nfragment: it admits tablet after tablet for hours, and a fragment reaches neither a\nserver log nor a referrer.\n","parameters":[{"$ref":"#/components/parameters/tournamentId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["max_devices"],"properties":{"kind":{"type":"string","enum":["qr","pin"],"default":"pin"},"max_devices":{"type":"integer","minimum":1,"maximum":64},"lifetime_seconds":{"type":"integer","minimum":60,"maximum":86400}}}}}},"responses":{"201":{"description":"The setup code, with its one copy of the plaintext.","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"allOf":[{"$ref":"#/components/schemas/CourtSetupCode"},{"type":"object","required":["code","join_url"],"properties":{"code":{"type":"string"},"join_url":{"type":"string","format":"uri"}}}]}}}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"$ref":"#/components/responses/ControlForbidden"},"404":{"$ref":"#/components/responses/TournamentNotFound"},"409":{"$ref":"#/components/responses/MatchDayRefused"},"422":{"description":"`validation_failed`: a kind other than qr or pin, a cap outside 1 to 64, or a lifetime outside a minute to a day.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/tournaments/{tournamentId}/setup-codes/{setupCodeId}":{"delete":{"tags":["Authentication"],"operationId":"revokeSetupCode","summary":"Withdraw a court setup code","description":"No further tablet joins with it. Tablets it already admitted keep their court; each\nis revoked on its own, as any court device is. Withdrawing a code already withdrawn\nanswers `revoked: false`.\n","parameters":[{"$ref":"#/components/parameters/tournamentId"},{"name":"setupCodeId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Whether a live code was withdrawn.","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["revoked"],"properties":{"revoked":{"type":"boolean"}}}}}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"$ref":"#/components/responses/ControlForbidden"},"404":{"description":"The tournament is outside the principal's scope, or the id is malformed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/tournaments/{tournamentId}/courts/{courtId}/redemption-codes":{"post":{"tags":["Authentication"],"operationId":"issueCourtRedemptionCode","summary":"Issue a one-time code that admits a scoring device to a court","description":"For a tournament director of the tournament or an administrator of the\norganization. The court must belong to the caller's organization and to the\ntournament in the path, checked with the organization from the caller's\nprincipal; a court outside either is not found. `join_url` is the page the QR\nimage should encode, on this host's page origin.\n","parameters":[{"$ref":"#/components/parameters/tournamentId"},{"name":"courtId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"$ref":"#/components/requestBodies/RedemptionCodeRequest"},"responses":{"201":{"$ref":"#/components/responses/IssuedRedemptionCode"},"401":{"$ref":"#/components/responses/RedemptionCodeUnauthorized"},"403":{"$ref":"#/components/responses/RedemptionCodeForbidden"},"404":{"$ref":"#/components/responses/RedemptionCodeTargetNotFound"},"409":{"$ref":"#/components/responses/MatchDayRefused"},"422":{"$ref":"#/components/responses/RedemptionCodeInvalidRequest"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/tournaments/{tournamentId}/displays/{displayId}/redemption-codes":{"post":{"tags":["Authentication"],"operationId":"issueDisplayRedemptionCode","summary":"Issue a one-time code that admits a venue display","description":"As for a court code, for a named display. The redeemed display session reads the\napproved venue projection and cannot score.\n","parameters":[{"$ref":"#/components/parameters/tournamentId"},{"name":"displayId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"$ref":"#/components/requestBodies/RedemptionCodeRequest"},"responses":{"201":{"$ref":"#/components/responses/IssuedRedemptionCode"},"401":{"$ref":"#/components/responses/RedemptionCodeUnauthorized"},"403":{"$ref":"#/components/responses/RedemptionCodeForbidden"},"404":{"$ref":"#/components/responses/RedemptionCodeTargetNotFound"},"409":{"$ref":"#/components/responses/MatchDayRefused"},"422":{"$ref":"#/components/responses/RedemptionCodeInvalidRequest"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/matches/{matchId}":{"get":{"tags":["Matches"],"operationId":"getMatch","summary":"The authoritative state of a match","description":"The canonical read every client recovers through: the pinned ruleset and engine\nversion, the scoring epoch, and the projection with its revision. A client that\ndoes not support the engine version may display the state but cannot score\npredictively. Visible to principals of the match's organization; a court device\nsees only the match on its court, a display only a match its projection shows, as\nits feed does, and anything else is not found.\n","parameters":[{"$ref":"#/components/parameters/matchId"}],"responses":{"200":{"description":"The match.","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/Match"}}}}}},"401":{"$ref":"#/components/responses/MatchUnauthorized"},"403":{"$ref":"#/components/responses/ReadScopeRequired"},"404":{"$ref":"#/components/responses/MatchNotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/matches/{matchId}/events":{"get":{"tags":["Matches"],"operationId":"getMatchEvents","summary":"The canonical events of a match since a revision","description":"The event feed a reconciling client lays its retained local tail beside. Every\nevent with a revision above `after_revision`, in revision then index order, each\nwith the command that produced it, so a device that scored while disconnected can\nsee where its own history and the server's part. Omitting `after_revision` returns\neverything the match has produced. `revision` is the match's current one, so an\nempty feed says the match has not moved rather than that nothing was read.\n\nVisible to the same principals as the match itself, a display excepted, and\nread-only: a client never replaces canonical state from it, it reconciles against\nit and recovers through `GET /v1/matches/{matchId}`. A display shows a match's\nstate and reconciles nothing, and the history names every command's device and\ntiming, so it is refused even a match it may read.\n","parameters":[{"$ref":"#/components/parameters/matchId"},{"name":"after_revision","in":"query","required":false,"description":"The revision the client scored from. Events up to and including it are the client's own.","schema":{"type":"integer","minimum":0,"default":0}}],"responses":{"200":{"description":"The events after the revision, possibly none.","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["match_id","after_revision","revision","events"],"properties":{"match_id":{"type":"string","format":"uuid"},"after_revision":{"type":"integer","description":"The revision the feed starts after, as requested."},"revision":{"type":"integer","description":"The match's current revision."},"events":{"type":"array","items":{"$ref":"#/components/schemas/MatchEvent"}}}}}}}}},"401":{"$ref":"#/components/responses/MatchUnauthorized"},"403":{"description":"`insufficient_scope`: a display credential, which reads a match's state and not\nits history, or a machine key that was not issued `tournaments:read`, refused\nbefore the match is looked up.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"$ref":"#/components/responses/MatchNotFound"},"422":{"description":"`validation_failed`: `after_revision` is not a whole number of zero or more.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/matches/{matchId}/commands":{"post":{"tags":["Matches"],"operationId":"submitMatchCommand","summary":"Submit one match command","description":"The one endpoint every match-changing command goes through. `command_id` is the\nidempotency key and is looked up before anything about the match's revision is\ncompared, so a retry after a lost response replays its stored outcome rather than\ncolliding with a revision that has since moved. The same id with a different\ncommand is `409 idempotency_key_reused`; `Idempotency-Key`, when sent, equals\n`command_id`.\n\nA scoring command needs the caller to hold the match's live scoring session and to\ncarry its epoch; a device fenced by a takeover gets `409 scoring_session_superseded`,\na stale `expected_revision` gets `409 revision_conflict`, and a command the rules\nrefuse gets `422 invalid_command_for_state`. Each of those is a durable outcome,\nrecorded and replayed for the same id, and each carries the current revision and\nepoch in `details`. `correct_score` and `reopen_result` need no session, only a\nreferee or director. `undo_command` reverses the latest command still standing, one\nstep at a time as far back as the umpire presses, over every command the umpire\nenters; never the confirmation, the two repairs or an undo, and never after\nconfirmation. It names that command's `command_id` as `payload.target_command_id`,\na UUID, or is `422 validation_failed` on that field; its `CommandUndone` event names\nthe target by the same UUID.\n\n`record_score_sheet` is the result of a match nobody umpired, entered at the desk\nfrom the players' paper score sheet (`ScoreSheetPayload`): the games in order and\nnothing else. A referee or director sends it, holding the session like any scoring\ncommand, and only while the match is `scheduled`. It is refused with\n`422 invalid_command_for_state` unless every game ends at exactly its score under\nthe match's ruleset and the last game decides the match. The engine derives the\nwinner: the batch is `ScoreSheetRecorded`, carrying each game with the side that\nwon it, and the `MatchEnded` a played match ends with, so the result then waits for\n`confirm_result` like any other.\n\n`record_game` is the same result a game at a time, for a match still being played\nwith no umpire (`GamePayload`): the game in play and its final score, sent as each\ngame ends. A referee or director sends it, holding the session, while the match is\n`scheduled` or `in_progress`. It is refused with `422 invalid_command_for_state`\nfor a game other than the one in play, a score no game ends at under the match's\nruleset, or one below the points already played in that game. The batch opens with\n`GameRecorded`, carrying the side that won the game; the first one also carries\n`MatchStarted`, and the one that decides the match the `MatchEnded` a played match\nends with. Each is one command, so the latest is taken back with `undo_command`.\n\nAn accepted command advances the revision exactly once; the response carries the\nevent batch it produced and the state after it.\n","parameters":[{"$ref":"#/components/parameters/matchId"},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MatchCommandRequest"}}}},"responses":{"200":{"description":"Accepted, now or on an earlier delivery of the same command.","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/MatchCommandAccepted"}}}}}},"401":{"$ref":"#/components/responses/MatchUnauthorized"},"403":{"description":"`insufficient_scope`: the command is outside the principal's authority, or the\ndevice does not hold the match's scoring session and must claim it first.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"$ref":"#/components/responses/MatchNotFound"},"409":{"description":"`revision_conflict`, `scoring_session_superseded` or `idempotency_key_reused`.\nThe first two are durable outcomes with `details.current_revision` and\n`details.current_epoch`; the third records nothing.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"`validation_failed` for a malformed envelope, or `invalid_command_for_state` for\na command the rules refuse, with `details.reason` naming the domain reason.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/matches/{matchId}/scoring-session":{"post":{"tags":["Matches"],"operationId":"openScoringSession","summary":"Claim a match's scoring session, or take it over","description":"A court device claims the match assigned to its court, which opens the one live\nscoring session under the next epoch; claiming again from the same device returns\nit unchanged. A device cannot take a match another device holds. A referee or\ntournament director takes the session over, which fences the former holder: its\nqueued commands are refused as superseded and kept for reconciliation. Every\nscoring command then carries the epoch returned here.\n","parameters":[{"$ref":"#/components/parameters/matchId"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"client_instance_id":{"type":"string","maxLength":36,"description":"The installation claiming, for the audit trail."}}}}}},"responses":{"200":{"description":"The live session.","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/ScoringSessionState"}}}}}},"401":{"$ref":"#/components/responses/MatchUnauthorized"},"403":{"description":"Neither the court's own device nor a referee or director of the tournament.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"$ref":"#/components/responses/MatchNotFound"},"409":{"description":"`scoring_session_held`: another device holds the match. `tournament_closed`: the\ntournament is closed (DEV-25). `plan_limit_reached`: a referee or director\ntaking over in a tournament still being prepared begins its match day, and the\norganization's plan has no room for another running tournament (DEV-47); the\ntournament stays a draft and nothing is taken. A court's device never meets it.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}},"delete":{"tags":["Matches"],"operationId":"releaseScoringSession","summary":"Release a match's scoring session so the court's device can claim it","description":"A referee or tournament director ends the live scoring session without opening\nanother, for a holder that will never score again, such as a tablet that rejoined\nits court under a new credential while its old one still held the match. The\nmatch moves to the next epoch with nobody holding it, so the former holder is\nfenced at once, exactly as by a takeover: its queued commands are refused as\nsuperseded and kept for reconciliation. The court's device then claims the match\nunder a new epoch. Releasing a match nobody holds changes nothing and answers\n`released: false`, so a repeated request does not move the epoch again.\n","parameters":[{"$ref":"#/components/parameters/matchId"}],"responses":{"200":{"description":"The match's scoring session is released, or nobody held it.","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/ScoringSessionRelease"}}}}}},"401":{"$ref":"#/components/responses/MatchUnauthorized"},"403":{"description":"Not a referee or director of the tournament.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"$ref":"#/components/responses/MatchNotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/tournaments/{tournamentId}/courts/{courtId}/open-matches":{"get":{"tags":["Matches"],"operationId":"listCourtOpenMatches","summary":"The matches a court's tablet may take","description":"For a court's tablet whose umpire chooses the match (DEV-58), on a match day with\nno desk putting matches on courts: the tournament's matches that wait for a court,\neach nothing has been played in, on no court and with both sides known, and the\nnames of their sides. `choosing` is the Referee's briefing (`court_chooses_match`);\nwith it off, or the tournament closed, the list is empty, so a tablet learns from\nthis one read whether to offer a list at all.\n\nThe court's own device alone: a court session reads it for the court and the\ntournament it is bound to. No notification says that a waiting match was taken by\nanother court, so a tablet showing the list reads it again while it does.\n","parameters":[{"$ref":"#/components/parameters/tournamentId"},{"$ref":"#/components/parameters/courtId"}],"responses":{"200":{"description":"The matches that wait for a court.","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/CourtOpenMatches"}}}}}},"401":{"$ref":"#/components/responses/MatchUnauthorized"},"403":{"description":"Not a court's device. Staff place a match through the schedule.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"$ref":"#/components/responses/CourtNotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/tournaments/{tournamentId}/courts/{courtId}/matches/{matchId}":{"put":{"tags":["Matches"],"operationId":"takeMatchToCourt","summary":"Take a waiting match to the court","description":"The court's tablet takes one of the matches `open-matches` lists (DEV-58). The\nmatch keeps its start and its place in the schedule and gains the court, announced\nas `match_assignment_changed` like any assignment, so from here it is a match on\nthe court as one the desk placed is: the tablet claims it and scores it. Decided\nunder the match's row lock: of two courts taking one match, the second is answered\n`404`, and reads the list again. Taking a match already on this court changes\nnothing, so a request sent again after a lost answer is safe.\n","parameters":[{"$ref":"#/components/parameters/tournamentId"},{"$ref":"#/components/parameters/courtId"},{"$ref":"#/components/parameters/matchId"}],"responses":{"200":{"description":"The match is on the court.","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/CourtMatchChoice"}}}}}},"401":{"$ref":"#/components/responses/MatchUnauthorized"},"403":{"description":"Not a court's device, or a tournament whose briefing gives its courts their\nmatches from the desk.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No such court for this device, or no such match waiting for a court: not the\ntournament's, begun, on another court, or with a side still to come.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"`tournament_closed`: the tournament is closed (DEV-25).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}},"delete":{"tags":["Matches"],"operationId":"returnMatchFromCourt","summary":"Give a match on the court back before it starts","description":"For a match chosen by mistake: the court's tablet gives back a match on its court\nthat nothing has been played in, which waits for a court again. The tablet's claim\non it is released with the move, as when the desk moves a match. A match that has\nbegun stays on its court and is the desk's to move. Giving back a match that waits\nalready changes nothing.\n","parameters":[{"$ref":"#/components/parameters/tournamentId"},{"$ref":"#/components/parameters/courtId"},{"$ref":"#/components/parameters/matchId"}],"responses":{"200":{"description":"The match waits for a court.","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/CourtMatchChoice"}}}}}},"401":{"$ref":"#/components/responses/MatchUnauthorized"},"403":{"description":"Not a court's device, or a tournament whose briefing gives its courts their\nmatches from the desk.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No such court for this device, or no such match on it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"`tournament_closed`: the tournament is closed (DEV-25).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"`validation_failed` on `match_id`: play has begun in the match.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/tournaments/{tournamentId}/snapshot":{"get":{"tags":["Tournaments"],"operationId":"getTournamentSnapshot","summary":"One consistent read of a tournament, and the cursor it was taken at","description":"Where a client starts, and where it starts again after `410 cursor_expired`. The\ntournament, its courts and its matches are read under `REPEATABLE READ` with an\nexplicit consistent snapshot, so they and `snapshot_cursor` come from one read\nview: any change the snapshot does not show carries a greater cursor. The client\nstores the projection and the cursor and opens the stream, or pages the change\nfeed, after `snapshot_cursor`, and nothing falls between the two.\n\nA match here is the shape `GET /v1/matches/{matchId}` returns. A court device\nreceives only its own court and the matches on it. A display receives what its\nprojection shows, the courts chosen for it and the matches on them, the matches on\nno court while it lists them, and every match of a draw whose standings it shows,\nwith only the participants of those matches; its\nprojection is read in the same view, so a later change of it arrives on the\nstream. Staff and a machine key issued `tournaments:read` see the whole\ntournament. A tournament outside the principal's scope or organization is not\nfound, never forbidden.\n","parameters":[{"$ref":"#/components/parameters/tournamentId"}],"responses":{"200":{"description":"The snapshot.","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/TournamentSnapshot"}}}}}},"401":{"$ref":"#/components/responses/TournamentUnauthorized"},"403":{"$ref":"#/components/responses/ReadScopeRequired"},"404":{"$ref":"#/components/responses/TournamentNotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/tournaments/{tournamentId}/changes":{"get":{"tags":["Tournaments"],"operationId":"getTournamentChanges","summary":"Durable catch-up on a tournament's notifications","description":"The retained outbox rows after a cursor, as the notifications the stream would\nhave delivered, in cursor order and paginated. Rows are read from MySQL regardless\nof whether the relay has published them yet, so a client can poll this while the\nstream is unavailable and a blocked relay cannot hide a change from it.\n\n`next_cursor` is the last row scanned, not the last item returned. A court device,\na display or a machine key may not see every row, and a hidden one is left out of\n`items` rather than replaced, so the cursor advances past what the principal may not see; `items` can\nbe empty while `has_more` is true, and the client keeps paging by `next_cursor`\nuntil `has_more` is false. `next_cursor` is null only when the page scanned\nnothing.\n\nA position that has fallen out of retention is `410 cursor_expired`, and the\nclient takes a new snapshot rather than continuing from a gap.\n","parameters":[{"$ref":"#/components/parameters/tournamentId"},{"name":"after_cursor","in":"query","required":false,"description":"The last cursor the client handled. Absent, the page starts at the beginning\nof the feed, which is `410 cursor_expired` once its earliest rows have been\npruned; a client with no position takes a snapshot instead.\n","schema":{"$ref":"#/components/schemas/StreamCursor"}},{"name":"limit","in":"query","required":false,"description":"Rows scanned per page, hidden ones included.","schema":{"type":"integer","minimum":1,"maximum":500,"default":100}}],"responses":{"200":{"description":"A page of notifications, possibly empty.","content":{"application/json":{"schema":{"type":"object","required":["ok","items","next_cursor","has_more"],"properties":{"ok":{"type":"boolean","const":true},"items":{"type":"array","items":{"$ref":"#/components/schemas/RealtimeNotification"}},"next_cursor":{"description":"The last row scanned, hidden or not. Null when the page scanned nothing.","oneOf":[{"type":"null"},{"$ref":"#/components/schemas/StreamCursor"}]},"has_more":{"type":"boolean","description":"Whether a row follows `next_cursor`."}}}}}},"401":{"$ref":"#/components/responses/TournamentUnauthorized"},"403":{"$ref":"#/components/responses/ReadScopeRequired"},"404":{"$ref":"#/components/responses/TournamentNotFound"},"410":{"$ref":"#/components/responses/CursorExpired"},"422":{"description":"`validation_failed`: `after_cursor` is not a stream cursor, or `limit` is outside 1 to 500.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/tournaments/{tournamentId}/stream":{"get":{"tags":["Tournaments"],"operationId":"openTournamentStream","summary":"Listen to a tournament's notifications","description":"A server-sent event stream of the tournament's notifications, for the browser's\n`EventSource` opened with `withCredentials` and for a native client's own\nstreaming HTTP. Authentication is the session cookie or a bearer credential in\nthe request headers, never a token in the URL: a URL ends up in history,\nreferrers, proxy logs and analytics.\n\nThe stream resumes from the earlier of `Last-Event-ID`, the browser's delivered\nposition, and `after_cursor`, the client's handled position, because replaying a\nhandled notification is harmless and skipping one that was delivered but never\nprocessed loses it. Without either it starts at the current end of the feed, so a\nclient with no position takes a snapshot first and resumes after its cursor. A\nposition behind what the stream store retains but inside outbox retention is\nserved from the durable feed first and then from the stream; one that has fallen\nout of retention is `410 cursor_expired`.\n\nA `retry:` line comes first. Each message carries `id: <stream_cursor>` and one\n`data:` line holding either a `RealtimeNotification` or, for a row the principal\nmay not see, a `StreamCheckpoint` whose only field is that same cursor, so a court\ndevice, a display or a machine key advances past a hidden row without learning it. A display's\nprojection is read when the stream opens and again after each notification of a\nchange to that display, so every row after the change is narrowed by it. A comment line\n`: heartbeat` is written at the configured interval while nothing is new. The\nstream closes after its maximum lifetime, and when the stream store cannot be\nread; the client reconnects with `Last-Event-ID`, which `EventSource` does on\nits own.\n\nEvery error is answered before any frame: a client that has read a frame holds an\nopen stream. `429 rate_limited` means the principal already holds its share of\nopen streams, or has spent its read budget; `503 realtime_unavailable` means\nstreaming is switched off, the stream store is unreachable, or the deployment\nalready holds as many streams as it allows. Both carry\n`Retry-After`, and the client polls the change feed meanwhile. A stream refused\nfor its share, or with a 503, leaves commands and canonical reads unaffected.\n","parameters":[{"$ref":"#/components/parameters/tournamentId"},{"name":"after_cursor","in":"query","required":false,"description":"The last cursor the client handled, from its own storage, persisted only once\na notification has been fully processed.\n","schema":{"$ref":"#/components/schemas/StreamCursor"}},{"name":"Last-Event-ID","in":"header","required":false,"description":"The last message `id` the browser delivered, which `EventSource` sends when it\nreconnects. The server issued it, so a value that is not a stream cursor is\nignored rather than refused.\n","schema":{"type":"string"}}],"responses":{"200":{"description":"An open stream: a `retry:` line, then messages whose `id` is a `StreamCursor`\nand whose `data` is a `RealtimeNotification` or a `StreamCheckpoint`, with\n`: heartbeat` comments between them. Response buffering is off end to end, so\neach frame leaves as it is written.\n","content":{"text/event-stream":{"schema":{"type":"string","description":"Server-sent events. Each message is `id: <stream_cursor>`, a newline and\n`data: <json>`, then a blank line; the JSON is a `RealtimeNotification` or\na `StreamCheckpoint`.\n"}}}},"401":{"$ref":"#/components/responses/TournamentUnauthorized"},"403":{"$ref":"#/components/responses/ReadScopeRequired"},"404":{"$ref":"#/components/responses/TournamentNotFound"},"410":{"$ref":"#/components/responses/CursorExpired"},"422":{"description":"`validation_failed`: `after_cursor` is not a stream cursor.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"`rate_limited`: the principal already holds its share of open streams. They are\ncounted across every instance from a registry each stream refreshes on its\nheartbeat, so a worker that died drops out of the count within a window. Or its\nread budget is spent, and `details.limit_class` is `read` (`RateLimited`).\n`Retry-After` says when to try again.\n","headers":{"Retry-After":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"`realtime_unavailable`: streaming is switched off, the stream store is\nunreachable, or the deployment already holds as many open streams as it allows,\ncounted across every instance, since each holds a server worker for its life.\nPoll the change feed until `Retry-After` has passed, then open the stream again.\nCommands and canonical reads are unaffected.\n","headers":{"Retry-After":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/time-zones":{"get":{"tags":["Control"],"operationId":"listTimeZones","summary":"The venue time zones a tournament accepts","description":"Exactly the IANA zones `POST /v1/tournaments` accepts, as the server's time zone\ndatabase names them, UTC included. A browser's own list keeps names the database\nhas retired (Asia/Calcutta, Europe/Kiev), which the server refuses, so a form that\noffers a zone asks here. Staff only.\n","responses":{"200":{"description":"The zones, in the database's order.","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["time_zones"],"properties":{"time_zones":{"type":"array","items":{"type":"string"}}}}}}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"$ref":"#/components/responses/ControlForbidden"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/tournaments":{"get":{"tags":["Control"],"operationId":"listTournaments","summary":"The organization's tournaments","description":"Every tournament of the caller's organization, newest first: by start date, or by\ncreation for one without, then by name. Staff only. A signed-in member sees the\nwhole list whatever roles they hold, because a director of one tournament still\nhas to find it; the roles decide what may be done inside one. A machine key issued\n`tournaments:read` lists them too, or only its own when it was issued for one\ntournament. A court or display credential is refused: it is scoped to one\ntournament, which it reads through the snapshot.\n","responses":{"200":{"description":"The tournaments, possibly none.","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["tournaments"],"properties":{"tournaments":{"type":"array","items":{"$ref":"#/components/schemas/Tournament"}}}}}}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"description":"`insufficient_scope`: not a staff credential, nor a machine key issued `tournaments:read`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}},"post":{"tags":["Control"],"operationId":"createTournament","summary":"Create a tournament","description":"For an administrator of the organization only: a tournament director's authority\nis a grant on a tournament that already exists, so nobody else can hold it here.\nThe new tournament is a `draft` at revision 1 and is announced on its own feed as\n`tournament_created`. `venue_time_zone` is the IANA zone every venue-local date and\ntime of the tournament is read in, which is why it is required from the start, and\n`ends_on` may not fall before `starts_on`.\n\nA draft is prepared in full, entries, draws, schedule, registration and Live\nincluded, and holds none of the organization's places (DEV-19), so creating one is\nnever refused for the plan. Its match day begins with its first court or display\ncode, its first court setup code or a scoring session taken from Control, which\nstart it (`active`, announced as `tournament_started`) and may be refused as\n`plan_limit_reached` (DEV-47). A started tournament holds its place until it is\nclosed, whatever its dates (DEV-706).\n","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","venue_time_zone"],"properties":{"name":{"type":"string","minLength":1,"maxLength":200},"venue_time_zone":{"type":"string","maxLength":64,"description":"An IANA time zone, such as `Europe/Berlin`."},"starts_on":{"type":["string","null"],"format":"date"},"ends_on":{"type":["string","null"],"format":"date","description":"Not before `starts_on`."}}}}}},"responses":{"201":{"description":"The tournament.","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["tournament"],"properties":{"tournament":{"$ref":"#/components/schemas/Tournament"}}}}}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"description":"`insufficient_scope`: not an administrator of the organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"`validation_failed`, with `details.validation_errors` keyed by field: a missing\nor over-long name, a time zone that is not an IANA identifier, a date that is\nnot `YYYY-MM-DD`, or an end before the start.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/tournaments/{tournamentId}/referee-briefing":{"patch":{"tags":["Control"],"operationId":"updateRefereeBriefing","summary":"Change how every court tablet of a tournament runs its matches","description":"The Referee's briefing (ITTO Section 3.4): what the Referee decides for every\numpire of the tournament before play, applied by each court tablet to the matches\nit scores, and how the tablets look and answer a finger, since a court tablet has\nno settings of its own (DEV-33). The Laws of a match stay in its pinned ruleset\n(DEV-9); nothing here changes the meaning of a command already accepted, so it\nis not versioned the way a ruleset is.\n\nA field left out, or null, keeps its value, so a form and an integration that\nsets one switch both send only what they mean to change. A change moves the\ntournament's `revision` once and is announced on the tournament aggregate as\n`referee_briefing_changed`, which every court device of the tournament receives; a\nbriefing equal to the stored one changes nothing and announces nothing.\n\nIt is the Referee's decision, so a referee of the tournament may set it, as may a\ndirector of the tournament and an administrator of the organization. A court or\ndisplay credential reads it on the snapshot and never sets it.\n","parameters":[{"$ref":"#/components/parameters/tournamentId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","description":"Any of the briefing's fields; each is refused unless it has the type below.","properties":{"call_language":{"$ref":"#/components/schemas/CallLanguage"},"time_clock":{"type":"boolean"},"warm_up_minutes":{"type":"integer","minimum":1,"maximum":3},"referee_on_site":{"type":"boolean"},"rally_start_tracking":{"type":"boolean"},"rally_start_required":{"type":"boolean"},"auto_confirm_result":{"type":"boolean"},"overrule_mark":{"type":"boolean"},"result_extras":{"type":"boolean"},"theme":{"$ref":"#/components/schemas/TabletTheme"},"interface_language":{"$ref":"#/components/schemas/TabletInterfaceLanguage"},"announcements":{"$ref":"#/components/schemas/TabletAnnouncements"},"speak_calls":{"type":"boolean"},"input_delay":{"$ref":"#/components/schemas/TabletInputDelay"},"touch_mode":{"$ref":"#/components/schemas/TabletTouchMode"},"point_vibration":{"type":"boolean"},"point_tone":{"type":"boolean"},"call_guard":{"type":"boolean"},"keyboard_shortcuts":{"type":"boolean"},"court_diagram":{"type":"boolean"},"scoresheet":{"type":"boolean"},"scoresheet_expand":{"type":"boolean"},"court_chooses_match":{"type":"boolean"}}}}}},"responses":{"200":{"description":"The tournament with the briefing as it now stands.","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["tournament"],"properties":{"tournament":{"$ref":"#/components/schemas/Tournament"}}}}}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"description":"`insufficient_scope`: not a referee or director of the tournament, nor an\nadministrator of the organization. A court or display credential is refused on\nits own tournament too.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"$ref":"#/components/responses/TournamentNotFound"},"422":{"description":"`validation_failed`, with `details.validation_errors` keyed by field: a call\nlanguage the panel has no vocabulary for, a warm-up that is not a whole number\nof minutes from 1 to 3, a choice outside its values, an interface language that\nis not `system` or a language tag, or a switch that is not a JSON boolean (a\nstring `\"false\"` or a `0` is refused, not read as one). Nothing is stored.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/tournaments/{tournamentId}/live-publication":{"patch":{"tags":["Control"],"operationId":"updateLivePublication","summary":"Publish a tournament on Live, or take it off","description":"Live is the anonymous public scoreboard under `/v1/public/*`. A tournament is\nprivate until a director of the tournament or an administrator of the organization\npublishes it here; until then Live answers it exactly as it answers an id that does\nnot exist. Taking it off hides it again from the next read.\n\n`show_nationalities`, when sent, says whether Live and the hall displays show the\nplayers' nationalities, for an event such as a junior one where the public needs\nno more than a name and a club; left out, it stays as it was.\n\n`name_style`, when sent, says how Live and the hall displays write a side's name\n(DEV-28): `as_entered`, every tournament's until a director chooses, shows its\n`display_name`, and `bwf` its players as the BWF prints them, given name then the\nfamily name in capitals (\"Alex LANIER\"), or the family name first where it is\nwritten first (\"LIANG Wei Keng\"), a pair's two joined by \" / \". A side with a player\nwhose name nobody split keeps its `display_name`. Only the name a public screen\nalready showed changes: Live sends no players' names. Left out, it stays as it was.\n\n`public_page_qr`, when sent, says whether the printed order of play and the hall\ndisplays offer the tournament's public page as a QR code, for the people in the hall;\noff until a director turns it on, and shown by nothing while the page is not\npublished. Left out, it stays as it was.\n\nA change of any of them moves the tournament's `revision` once and is announced on the\ntournament aggregate as `live_publication_changed`; asking for what is already so\nchanges nothing and announces nothing. A referee of the tournament, a court or\ndisplay credential and a machine key are refused.\n","parameters":[{"$ref":"#/components/parameters/tournamentId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["published"],"properties":{"published":{"type":"boolean"},"show_nationalities":{"type":"boolean","description":"Whether Live and the hall displays show nationalities; left as it was when absent."},"name_style":{"$ref":"#/components/schemas/NameStyle"},"public_page_qr":{"type":"boolean","description":"Whether the order of play and the hall displays offer the public page as a QR code; left as it was when absent."}}}}}},"responses":{"200":{"description":"The tournament with its publication as it now stands.","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["tournament"],"properties":{"tournament":{"$ref":"#/components/schemas/Tournament"}}}}}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"$ref":"#/components/responses/ControlForbidden"},"404":{"$ref":"#/components/responses/TournamentNotFound"},"422":{"description":"`validation_failed`: `published` is missing or not a JSON boolean,\n`show_nationalities` or `public_page_qr` is sent and is not one, or `name_style` is\nsent and is neither `as_entered` nor `bwf`. Nothing is stored.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/tournaments/{tournamentId}/closure":{"patch":{"tags":["Control"],"operationId":"updateTournamentClosure","summary":"Close a tournament, or reopen it","description":"Closing is how a tournament ends (DEV-25). Its `status` becomes `completed`, which\nfrees the place it held in the organization's plan, which nothing else does\n(DEV-706), and it becomes read-only: every write to it (courts, displays,\nparticipants, import, matches, schedule, briefing, venue, draws and lots,\nregistration, codes, match commands and taking a scoring session) is refused as\n`tournament_closed` until it is reopened. Its Live publication and its staff stay\nchangeable, since neither is the tournament's record. Every court tablet and hall\ndisplay it admitted is signed out: their credentials are revoked, the codes nobody\nhas used are spent and its setup codes revoked, and a court device's scoring\nsession is released. Reads, the results export and printing are unaffected.\n\nClosing is refused as `matches_unfinished` while a match is being played or holds a\nresult nobody has confirmed, with `details.count`. Reopening makes it `active` again\nand takes a place in the plan when its dates still hold one, so it is refused as\n`plan_limit_reached` when the plan has no room; the devices closing signed out stay\nsigned out.\n\nEither change moves the tournament's `revision` once and is announced on the\ntournament aggregate as `tournament_closed` or `tournament_reopened`; asking for\nwhat is already so changes nothing and announces nothing. A director of the\ntournament or an administrator of the organization decides it; a referee, a court or\ndisplay credential and a machine key are refused.\n","parameters":[{"$ref":"#/components/parameters/tournamentId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["closed"],"properties":{"closed":{"type":"boolean","description":"True to close the tournament, false to reopen it."}}}}}},"responses":{"200":{"description":"The tournament with its status as it now stands.","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["tournament"],"properties":{"tournament":{"$ref":"#/components/schemas/Tournament"}}}}}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"$ref":"#/components/responses/ControlForbidden"},"404":{"$ref":"#/components/responses/TournamentNotFound"},"409":{"description":"`matches_unfinished`: closing while a match is being played or holds a result\nnobody has confirmed; `details.count` says how many. `plan_limit_reached`:\nreopening a tournament that would hold a place when the plan has no room, with\nthe details `PlanLimitReached` describes. Nothing is changed.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"`validation_failed`: `closed` is missing or not a JSON boolean. Nothing is\nchanged.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/tournaments/{tournamentId}/registration":{"get":{"tags":["Registration"],"operationId":"getRegistrationSettings","summary":"Whether the tournament takes entries, until when, and in which events","description":"For a director of the tournament or an administrator of the organization. A\ntournament whose settings were never saved reads as closed, with no event, at\nrevision 0.\n","parameters":[{"$ref":"#/components/parameters/tournamentId"}],"responses":{"200":{"description":"The settings.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegistrationSettingsResponse"}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"$ref":"#/components/responses/ControlForbidden"},"404":{"$ref":"#/components/responses/TournamentNotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}},"put":{"tags":["Registration"],"operationId":"updateRegistrationSettings","summary":"Open or close the tournament for registration","description":"Replaces the settings whole, as a form holds them, so a retry means the same thing\ntwice. An event is a draft draw of the tournament not fed by the groups of another,\nand a draw that is published later stops taking entries on its own. Opening needs at\nleast one event and a closing time, if there is one, still to come; closing takes\nany. An opening time, when there is one, comes before the closing time, open or not.\n`opens_at`, `withdrawals_close_at` and `public_associations` came later: left out,\neach keeps what is stored, so a client that has never heard of them clears nothing\n(DEV-27). For a director of the tournament or an administrator of the\norganization; a referee, a court or display credential and a machine key are\nrefused. A change moves the settings' revision once, and is logged with who made it.\n","parameters":[{"$ref":"#/components/parameters/tournamentId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["open","closes_at","draw_ids"],"properties":{"open":{"type":"boolean"},"closes_at":{"type":["string","null"],"format":"date-time","description":"An instant with its offset; Control asks for it at the venue's clock."},"opens_at":{"type":["string","null"],"format":"date-time","description":"When entries open, an instant with its offset; null to take them as soon as\nthe tournament is open. Left out, it stays as it is.\n"},"withdrawals_close_at":{"type":["string","null"],"format":"date-time","description":"After this instant a player can no longer withdraw an entry themselves; the\nstaff still can. Null for no deadline. Left out, it stays as it is.\n"},"public_associations":{"type":"boolean","description":"Whether Live's entry list shows each entry's club or association. Left out,\nit stays as it is; off until a director turns it on.\n"},"draw_ids":{"type":"array","items":{"type":"string","format":"uuid"}}}}}}},"responses":{"200":{"description":"The settings as they now stand.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegistrationSettingsResponse"}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"$ref":"#/components/responses/ControlForbidden"},"404":{"$ref":"#/components/responses/TournamentNotFound"},"422":{"description":"`validation_failed`: a field of the wrong shape, or `details.problem`\n`event_unavailable` (on `draw_ids`: not a draft of the tournament taking entries\nof its own), `no_events` (opening with none), `closing_passed` (on\n`closes_at`) or `opening_after_closing` (on `opens_at`). Nothing is stored.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/tournaments/{tournamentId}/registrations":{"get":{"tags":["Registration"],"operationId":"listTournamentRegistrations","summary":"The tournament's registrations","description":"Every registration for the tournament, those waiting for an answer first, then the\nnewest. For a director of the tournament or an administrator of the organization.\nA player is named as they enter, with whether they confirmed; no account or address\nis shown.\n","parameters":[{"$ref":"#/components/parameters/tournamentId"}],"responses":{"200":{"description":"The registrations.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegistrationList"}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"$ref":"#/components/responses/ControlForbidden"},"404":{"$ref":"#/components/responses/TournamentNotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/tournaments/{tournamentId}/registrations/{registrationId}/decision":{"post":{"tags":["Registration"],"operationId":"decideRegistration","summary":"Accept a registration into the participants, decline it, or withdraw an accepted one","description":"Accepting a complete registration finds the tournament's participant of exactly\nits players, or creates one, each player being the tournament's player already\nlinked to that account or a new one linked to it; it enters the participant at the\nend of the event's draw while the draw is a draft, which clears the draft's lines as\nany change to its entries does, and says so in `entered`. Declining records who and\nwhen, and frees the players' places. Withdrawing an accepted entry, for a player who\ntells the organizers they cannot play, takes it out of the event's draft whatever the\nwithdrawal deadline says, and keeps the participant and the decision; a draw being\nplaced or published is refused as `draw_made`, since the staff then take the entry\nout in the draw. The same decision again is answered as it stands. For a director of\nthe tournament or an administrator of the organization, and logged with who decided.\n","parameters":[{"$ref":"#/components/parameters/tournamentId"},{"$ref":"#/components/parameters/registrationId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["decision"],"properties":{"decision":{"type":"string","enum":["accept","decline","withdraw"],"description":"Append-only. `withdraw` is sent by every server that withdraws for a player."}}}}}},"responses":{"200":{"description":"The registration as it now stands.","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["registration","entered"],"properties":{"registration":{"$ref":"#/components/schemas/Registration"},"entered":{"type":["boolean","null"],"description":"On an acceptance, whether the participant is an entry of the\nevent's draw; false once the draw is published. Null on a decline.\n"}}}}}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"$ref":"#/components/responses/ControlForbidden"},"404":{"description":"Not a tournament of the organization, or not a registration for it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"`validation_failed`: `decision` is neither, or `details.problem` is\n`incomplete` (the partner has not confirmed), `not_pending` (withdrawn, or\ndecided the other way), `not_accepted` (a withdrawal of an entry not accepted),\n`draw_made` (a withdrawal from a draw being placed or published), or, from the\ndraw, `entry_count`, `kind_mismatch` or\n`gender_mismatch` on `draw`, the last with `details.participant_ids`. Nothing is\nstored.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/tournaments/{tournamentId}/venue":{"patch":{"tags":["Control"],"operationId":"updateTournamentVenue","summary":"Name where a tournament is played, or clear it","description":"One line of free text, such as a hall and a town, that Control, the hall display\nand Live show beside the tournament's name. Set by a director of the tournament or\nan administrator of the organization. `null`, or a blank name, clears it.\n\nA change moves the tournament's `revision` once and is announced on the tournament\naggregate as `venue_changed`; naming the venue it already has changes nothing and\nannounces nothing. A referee of the tournament, a court or display credential and a\nmachine key are refused.\n","parameters":[{"$ref":"#/components/parameters/tournamentId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["venue_name"],"properties":{"venue_name":{"type":["string","null"],"maxLength":160,"description":"Trimmed; blank or null for none."}}}}}},"responses":{"200":{"description":"The tournament with its venue as it now stands.","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["tournament"],"properties":{"tournament":{"$ref":"#/components/schemas/Tournament"}}}}}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"$ref":"#/components/responses/ControlForbidden"},"404":{"$ref":"#/components/responses/TournamentNotFound"},"422":{"description":"`validation_failed` under `venue_name`: the field is missing, not a string or\nnull, or longer than 160 characters. Nothing is stored.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/tournaments/{tournamentId}/details":{"put":{"tags":["Control"],"operationId":"updateTournamentDetails","summary":"Correct a tournament's name and dates","description":"The name and the first and last day, as `POST /v1/tournaments` took them, all three\nin every body: a date is `null` for none, and a body that leaves one out is refused\nrather than read as a clear. Set by a director of the tournament or an\nadministrator of the organization. The venue's time zone is not among them: every\ntime entered at the venue's clock is stored as the instant it named, so a corrected\nzone would misname each one.\n\nThe dates decide nothing about the organization's plan: a running tournament holds\nits place until it is closed, and a draft or a closed one holds none, whatever their\ndates, so no date is refused for the plan. The first\nday's year is the one an age category counts from, for entries made after the\nchange.\n\nA change moves the tournament's `revision` once and is announced on the tournament\naggregate as `details_changed`; asking for what is already so changes nothing and\nannounces nothing. A closed tournament refuses it as `tournament_closed`. A referee\nof the tournament, a court or display credential and a machine key are refused.\n","parameters":[{"$ref":"#/components/parameters/tournamentId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","starts_on","ends_on"],"properties":{"name":{"type":"string","minLength":1,"maxLength":200,"description":"Trimmed."},"starts_on":{"type":["string","null"],"format":"date"},"ends_on":{"type":["string","null"],"format":"date","description":"Not before `starts_on`."}}}}}},"responses":{"200":{"description":"The tournament with its name and dates as they now stand.","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["tournament"],"properties":{"tournament":{"$ref":"#/components/schemas/Tournament"}}}}}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"$ref":"#/components/responses/ControlForbidden"},"404":{"$ref":"#/components/responses/TournamentNotFound"},"409":{"description":"`tournament_closed`: the tournament is closed. Nothing is changed.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"`validation_failed` under `name`, `starts_on` or `ends_on`: a field is missing, a\nname is blank or longer than 200 characters, a date is not a real day as\nYYYY-MM-DD or null, or the last day is before the first. Nothing is stored.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/tournaments/{tournamentId}/copy":{"post":{"tags":["Control"],"operationId":"copyTournament","summary":"Make a new tournament from a tournament's setup","description":"A new tournament with the name and dates given and the setup of this one\n(DEV-42), for running the same event again. The source may be open or closed and of\nany date; nothing is written to it.\n\nCopied: the Referee's briefing, the venue, the schedule assumptions, and whether\nLive and the hall displays show nationalities, how they write names and whether\nthey offer the public page as a QR code; the courts by name and order, each open;\nthe hall displays with the courts each shows; every draw as an empty draft of the\nsame definition (format, ruleset, options, entry, age and class rules); and the\nregistration settings, closed, offering the copies of the events the source\noffered, with the opening, closing and withdrawal times moved by the days between\nthe two first days at the venue's clock, or cleared when either tournament has no\nfirst day. Not copied: participants, entries, placements, lots, matches, results,\nschedules, registrations, the event history, credentials and codes, scoring\nsessions, staff roles, and Live publication, so the copy is off Live. A\nconsolation draw is made again by its main draw's publication, and a knockout fed\nby a round robin's groups once those are published, which `draws_left_out` names;\na display's group standings go with them.\n\nFor an administrator of the organization only, as creating a tournament is: a\ndirector's authority is a grant on the source and holds nothing on a new\ntournament. The copy is `active` at revision 1 and announces each row it creates\non its own feed, as creating them one by one would. Counted against the\norganization's plan as a new tournament is, and each court and display as though\ncreated one by one, so a source set up under a larger plan is refused rather than\ncopied past the present one. One transaction: a refusal writes nothing.\n","parameters":[{"$ref":"#/components/parameters/tournamentId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","minLength":1,"maxLength":200,"description":"Trimmed."},"venue_time_zone":{"type":["string","null"],"maxLength":64,"description":"An IANA time zone, such as `Europe/Berlin`; absent or `null` keeps the source's."},"starts_on":{"type":["string","null"],"format":"date"},"ends_on":{"type":["string","null"],"format":"date","description":"Not before `starts_on`."}}}}}},"responses":{"201":{"description":"The new tournament, and what of the setup came with it.","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["tournament","copied","draws_left_out"],"properties":{"tournament":{"$ref":"#/components/schemas/Tournament"},"copied":{"type":"object","required":["courts","displays","draws"],"properties":{"courts":{"type":"integer","minimum":0},"displays":{"type":"integer","minimum":0},"draws":{"type":"integer","minimum":0}}},"draws_left_out":{"type":"array","description":"The source's draws not copied, to be made again in the copy.","items":{"type":"object","required":["id","name","code","reason"],"properties":{"id":{"type":"string","format":"uuid","description":"The source's draw."},"name":{"type":"string"},"code":{"type":"string"},"reason":{"type":"string","description":"`fed_by_groups`: a knockout fed by a round robin's groups,\nmade once those groups are published. Values may be added.\n"}}}}}}}}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"description":"`insufficient_scope`: not an administrator of the organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"$ref":"#/components/responses/TournamentNotFound"},"409":{"$ref":"#/components/responses/PlanLimitReached"},"422":{"description":"`validation_failed`, with `details.validation_errors` keyed by field: a missing\nor over-long name, a time zone that is not an IANA identifier, a date that is\nnot `YYYY-MM-DD`, or an end before the start. Nothing is written.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/tournaments/{tournamentId}/schedule-assumptions":{"patch":{"tags":["Control"],"operationId":"updateScheduleAssumptions","summary":"Set the match length and rest Control's schedule check works from","description":"How long a match is assumed to take and how long a player rests between two, in\nwhole minutes: what Control estimates the play left from and warns of a player due\ntwice at once or short of rest by. Assumptions, not rules: nothing is refused for\nthem. Kept with the tournament so every desk working it warns from the same\nnumbers, and read back as `Tournament.schedule_assumptions`. Set by a director of\nthe tournament or an administrator of the organization; a field left out keeps\nits value, applied to the row as it stands under its lock.\n\nA change moves the tournament's `revision` once and is announced on the tournament\naggregate as `schedule_assumptions_changed`; asking for what is already so changes\nnothing and announces nothing. A referee of the tournament, a court or display\ncredential and a machine key are refused.\n","parameters":[{"$ref":"#/components/parameters/tournamentId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","minProperties":1,"properties":{"match_minutes":{"type":"integer","minimum":1,"maximum":600},"rest_minutes":{"type":"integer","minimum":0,"maximum":600}}}}}},"responses":{"200":{"description":"The tournament with its schedule assumptions as they now stand.","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["tournament"],"properties":{"tournament":{"$ref":"#/components/schemas/Tournament"}}}}}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"$ref":"#/components/responses/ControlForbidden"},"404":{"$ref":"#/components/responses/TournamentNotFound"},"422":{"description":"`validation_failed`: neither field is sent, or one is not a whole number in its\nrange (`match_minutes` 1 to 600, `rest_minutes` 0 to 600). Nothing is stored.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/public/tournaments":{"get":{"tags":["Live"],"operationId":"getLiveOrganization","summary":"The organization's public page, and its tournaments on Live","description":"The organization the request's host belongs to, by name, and the tournaments it has\npublished on Live, newest first. Served on a customer host only: the shared API host\nand the platform's own hosts belong to no organization and answer 404, so there is\nno list of every organization's tournaments.\n","security":[],"parameters":[{"$ref":"#/components/parameters/ifNoneMatch"}],"responses":{"200":{"description":"The organization and its published tournaments.","headers":{"ETag":{"$ref":"#/components/headers/LiveETag"},"Cache-Control":{"$ref":"#/components/headers/LiveCacheControl"}},"content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/LiveOrganization"}}}}}},"304":{"$ref":"#/components/responses/LiveNotModified"},"404":{"$ref":"#/components/responses/LiveNotFound"},"429":{"$ref":"#/components/responses/LiveRateLimited"}}}},"/v1/public/tournaments/{tournamentId}":{"get":{"tags":["Live"],"operationId":"getLiveTournament","summary":"A tournament published on Live","description":"The explicitly public projection of one tournament: its name, dates, zone, venue and\norganization's name, its courts, every match with its sides' names, its place in the schedule and in its\ndraw, its status and its score, and every published draw with its bracket or\ngroups and a round-robin draw's standings. Nothing else: no participant's players,\nreference or contact, no staff, device or credential, no Referee's briefing, no\nmatch history and no draft draw.\n\nOn a customer's host, only that organization's tournaments. On the platform's site,\nthe apex and the shared API host its pages read, any organization's published\ntournament. The demo host serves none. A tournament not published on Live, one\noutside the host's reach, and an id that does not exist are one identical 404.\n","security":[],"parameters":[{"$ref":"#/components/parameters/tournamentId"},{"$ref":"#/components/parameters/ifNoneMatch"}],"responses":{"200":{"description":"The tournament's public projection.","headers":{"ETag":{"$ref":"#/components/headers/LiveETag"},"Cache-Control":{"$ref":"#/components/headers/LiveCacheControl"}},"content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/LiveTournamentProjection"}}}}}},"304":{"$ref":"#/components/responses/LiveNotModified"},"404":{"$ref":"#/components/responses/LiveNotFound"},"429":{"$ref":"#/components/responses/LiveRateLimited"}}}},"/v1/public/matches/{matchId}":{"get":{"tags":["Live"],"operationId":"getLiveMatch","summary":"One match of a tournament published on Live","description":"The match as `GET /v1/public/tournaments/{tournamentId}` carries it, with its\ntournament, its court and its two sides' names, so the match's own page reads it in\none request. Served on the hosts that serve its tournament: its organization's own\nand the platform's site. A match of a tournament not published on Live, one outside\nthe host's reach, and an id that does not exist are one identical 404.\n","security":[],"parameters":[{"$ref":"#/components/parameters/matchId"},{"$ref":"#/components/parameters/ifNoneMatch"}],"responses":{"200":{"description":"The match's public projection.","headers":{"ETag":{"$ref":"#/components/headers/LiveETag"},"Cache-Control":{"$ref":"#/components/headers/LiveCacheControl"}},"content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/LiveMatchPage"}}}}}},"304":{"$ref":"#/components/responses/LiveNotModified"},"404":{"$ref":"#/components/responses/LiveNotFound"},"429":{"$ref":"#/components/responses/LiveRateLimited"}}}},"/v1/public/organizations/{slug}":{"get":{"tags":["Live"],"operationId":"getLiveOrganizationPage","summary":"An organization's page on the platform's site","description":"What a customer's host answers at `GET /v1/public/tournaments`, for an organization\nfound by its slug on the platform's site, the apex and the shared API host its pages\nread (DEV-645): its name and the tournaments and leagues it published on Live. The\naddress an organization with no host of its own shares, `roundr.com/o/{slug}`. An\norganization that published nothing has no page; it, a suspended one, a slug that\nnames none and any other host are one identical 404.\n","security":[],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","maxLength":100}},{"$ref":"#/components/parameters/ifNoneMatch"}],"responses":{"200":{"description":"The organization's page.","headers":{"ETag":{"$ref":"#/components/headers/LiveETag"},"Cache-Control":{"$ref":"#/components/headers/LiveCacheControl"}},"content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/LiveOrganization"}}}}}},"304":{"description":"Unchanged since the tag named in `If-None-Match`."},"404":{"description":"`not_found`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/LiveRateLimited"}}}},"/v1/public/leagues/{leagueId}":{"get":{"tags":["Live"],"operationId":"getLiveLeague","summary":"A league published on Live","description":"A league its administrators published (DEV-50): its points and the codes of its\ntie, its match days, and each division's table and ties, read from the started ties'\nmatches as they stand. Teams by name and club; never a roster, a line-up, a\ncaptain, a guest or a team the league has not placed. A rubber names its match\n(`match_id`), and a match day its tournament, only where that tournament is itself\non Live within the host's reach, whose page shows the players. Served on the hosts\nthat serve its organization's tournaments; a league not published, one outside the\nhost's reach, and an id that does not exist are one identical 404. The tag is a\ndigest of the answer.\n","security":[],"parameters":[{"name":"leagueId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"$ref":"#/components/parameters/ifNoneMatch"}],"responses":{"200":{"description":"The league's public projection.","headers":{"ETag":{"$ref":"#/components/headers/LiveETag"},"Cache-Control":{"$ref":"#/components/headers/LiveCacheControl"}},"content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/LiveLeaguePage"}}}}}},"304":{"$ref":"#/components/responses/LiveNotModified"},"404":{"$ref":"#/components/responses/LiveNotFound"},"429":{"$ref":"#/components/responses/LiveRateLimited"}}}},"/v1/public/pages/tournaments/{tournamentId}":{"get":{"tags":["Live"],"operationId":"getLiveTournamentPage","summary":"A Live tournament's page, named for a link preview","description":"The web application's page for a Live address, with the tournament and its organization named in its title and\nlink preview (DEV-646). A chat app or a social network reads a shared link's HTML\nand runs no script, so the page carries the names itself: the static hosts answer\nthe Live address from here. Found by Live's own rules and the host's reach; what\nLive would not show, an id that does not exist, the demo host and a spent read\nbudget are the page unchanged, which is not an error. Not a JSON read, and not for\nan application to call.\n","security":[],"parameters":[{"$ref":"#/components/parameters/tournamentId"}],"responses":{"200":{"description":"The page, as HTML.","content":{"text/html":{"schema":{"type":"string"}}}},"500":{"description":"The web build is not where the server expects it; the vhost then sends the page from disk.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/public/pages/matches/{matchId}":{"get":{"tags":["Live"],"operationId":"getLiveMatchPage","summary":"A Live match's page, named for a link preview","description":"The web application's page for a Live address, with the match's two sides and its tournament named in its title and\nlink preview (DEV-646). A chat app or a social network reads a shared link's HTML\nand runs no script, so the page carries the names itself: the static hosts answer\nthe Live address from here. Found by Live's own rules and the host's reach; what\nLive would not show, an id that does not exist, the demo host and a spent read\nbudget are the page unchanged, which is not an error. Not a JSON read, and not for\nan application to call.\n","security":[],"parameters":[{"$ref":"#/components/parameters/matchId"}],"responses":{"200":{"description":"The page, as HTML.","content":{"text/html":{"schema":{"type":"string"}}}},"500":{"description":"The web build is not where the server expects it; the vhost then sends the page from disk.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/public/pages/leagues/{leagueId}":{"get":{"tags":["Live"],"operationId":"getLiveLeaguePage","summary":"A Live league's page, named for a link preview","description":"The web application's page for a Live address, with the league, its season and its organization named in its title and\nlink preview (DEV-646). A chat app or a social network reads a shared link's HTML\nand runs no script, so the page carries the names itself: the static hosts answer\nthe Live address from here. Found by Live's own rules and the host's reach; what\nLive would not show, an id that does not exist, the demo host and a spent read\nbudget are the page unchanged, which is not an error. Not a JSON read, and not for\nan application to call.\n","security":[],"parameters":[{"name":"leagueId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"The page, as HTML.","content":{"text/html":{"schema":{"type":"string"}}}},"500":{"description":"The web build is not where the server expects it; the vhost then sends the page from disk.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/public/tournaments/{tournamentId}/registration":{"get":{"tags":["Registration"],"operationId":"getRegistrationOffer","summary":"What a tournament offers someone who would enter it","description":"The tournament's name, dates and zone, whether it takes entries now, from and until\nwhen, until when a player may withdraw, and the events that do, each with the kind\nof participant that enters it and its age category. A tournament open for entries\nfrom a time still to come is found, answers `open: false`, and names its window and\nits events, so a link handed out early says when to come back (DEV-27). Served\nby Live's rules on the organization's own host and on the platform's site, which\nfinds a tournament by its id as its Live does (DEV-671): no credential is read, the\nclient address spends Live's budget, and a tournament that is neither published on\nLive nor taking entries, another organization's on a tenant host, and an id that\ndoes not exist are one identical 404. A tournament on Live that takes none answers `open: false` and no\nevents, so no draft's name is shown before it is offered. A closed tournament takes\nnone whatever its settings say, so it is found only on Live. Not cached: the answer\nturns at the opening and closing times.\n","security":[],"parameters":[{"$ref":"#/components/parameters/tournamentId"}],"responses":{"200":{"description":"The offer.","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/RegistrationOffer"}}}}}},"404":{"$ref":"#/components/responses/LiveNotFound"},"429":{"$ref":"#/components/responses/LiveRateLimited"}}}},"/v1/tournaments/{tournamentId}/courts":{"post":{"tags":["Control"],"operationId":"createCourt","summary":"Add a court to a tournament","description":"For a director of the tournament or an administrator of the organization. A\ncourt's name is unique within its tournament, because the import places a match\nby court name and a display labels the court by it; a second `Court 1` is refused\nas a validation error on `name`, so a form shows it on the field like any other\nbad name. `sort_order` is where the court sits in the snapshot and on a display,\nwith ties broken by name. The court is `open` at revision 1 and is announced as\n`court_created`. Counted against the organization's plan, in this tournament.\n","parameters":[{"$ref":"#/components/parameters/tournamentId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","minLength":1,"maxLength":100},"sort_order":{"type":"integer","minimum":0,"maximum":65535,"default":0}}}}}},"responses":{"201":{"description":"The court.","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["court"],"properties":{"court":{"$ref":"#/components/schemas/Court"}}}}}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"$ref":"#/components/responses/ControlForbidden"},"404":{"$ref":"#/components/responses/TournamentNotFound"},"409":{"$ref":"#/components/responses/PlanLimitReached"},"422":{"description":"`validation_failed`: a missing or over-long name, a `sort_order` that is not a\nwhole number from 0 to 65535, or a name the tournament's courts already carry,\nreported under `validation_errors.name`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/tournaments/{tournamentId}/courts/{courtId}":{"patch":{"tags":["Control"],"operationId":"renameCourt","summary":"Correct a court's name","description":"For a director of the tournament or an administrator of the organization, as\ncreating a court is. The name is the one a court's tablet, the hall displays and\nLive show, and the one the import places a match by, so it is held to the rules a\nnew court's is: up to 100 characters, trimmed, and none another court of the\ntournament carries, compared without regard to case; such a name is refused as a\nvalidation error on `name`.\n\nA change moves the court's `revision` once and is announced on the court\naggregate as `court_renamed`, so the court's tablet, every display showing the\ncourt and Live read it again. The name the court already has, to the letter,\nchanges nothing and announces nothing; a change of its case alone is a rename. A\nreferee of the tournament, a court or display credential and a machine key are\nrefused.\n","parameters":[{"$ref":"#/components/parameters/tournamentId"},{"$ref":"#/components/parameters/courtId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","minLength":1,"maxLength":100}}}}}},"responses":{"200":{"description":"The court as it now stands.","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["court"],"properties":{"court":{"$ref":"#/components/schemas/Court"}}}}}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"$ref":"#/components/responses/ControlForbidden"},"404":{"description":"`not_found`: no such tournament in the principal's scope, or no such court in\nthe tournament.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"`tournament_closed`: the tournament is closed (DEV-25). Nothing is changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"`validation_failed` under `name`: the field is missing, not a string, blank or\nlonger than 100 characters, or another court of the tournament carries the\nname. Nothing is stored.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}},"delete":{"tags":["Control"],"operationId":"removeCourt","summary":"Remove a court nothing uses","description":"For a director of the tournament or an administrator of the organization, as\ncreating a court is (DEV-628): a court added by mistake, which would otherwise stay\non the tablets' join screen, every display of all courts, the order of play and\nLive, and hold a place of the plan's courts per tournament.\n\nA court anything uses is refused with `409 court_in_use`, each use in\n`details.uses`: `matches` (a match or a schedule entry is on it, or a match was\nscored on it), `devices` (a tablet was paired to it, revoked since or not),\n`displays` (a display was chosen to show it) or `officials` (a rotation of\nofficials serves it, DEV-113). Codes issued for the court that no\ntablet redeemed go with it. The removal is announced on the court aggregate as\n`court_removed`, at the revision after the court's last, so a display of every\ncourt and Live read the courts again. A referee of the tournament, a court or\ndisplay credential and a machine key are refused.\n","parameters":[{"$ref":"#/components/parameters/tournamentId"},{"$ref":"#/components/parameters/courtId"}],"responses":{"200":{"description":"The court is gone.","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["court_id"],"properties":{"court_id":{"type":"string","format":"uuid"}}}}}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"$ref":"#/components/responses/ControlForbidden"},"404":{"description":"`not_found`: no such tournament in the principal's scope, or no such court in\nthe tournament.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"`court_in_use`, with `details.uses`, a list of `matches`, `devices`,\n`displays` and `officials` in that order; or `tournament_closed` (DEV-25).\nNothing is changed.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/tournaments/{tournamentId}/displays":{"get":{"tags":["Control"],"operationId":"listDisplays","summary":"List a tournament's hall displays","description":"For a director of the tournament or an administrator of the organization, who issue\neach display's join code and choose what it shows. Ordered by name, each with its\nprojection. A display reads the tournament snapshot, not this list.\n","parameters":[{"$ref":"#/components/parameters/tournamentId"}],"responses":{"200":{"description":"The displays.","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["displays"],"properties":{"displays":{"type":"array","items":{"$ref":"#/components/schemas/Display"}}}}}}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"$ref":"#/components/responses/ControlForbidden"},"404":{"$ref":"#/components/responses/TournamentNotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}},"post":{"tags":["Control"],"operationId":"createDisplay","summary":"Add a hall display to a tournament","description":"For a director of the tournament or an administrator of the organization. A\ndisplay's name is unique within its tournament, so staff can tell two screens apart\nwhen issuing codes; a second `Hall` is refused as a validation error on `name`. It\nshows every court of the tournament and the next four matches on no court until its\nprojection is changed, and is announced as `display_created`. Counted against the\norganization's plan, in this tournament.\n","parameters":[{"$ref":"#/components/parameters/tournamentId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","minLength":1,"maxLength":120}}}}}},"responses":{"201":{"description":"The display.","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["display"],"properties":{"display":{"$ref":"#/components/schemas/Display"}}}}}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"$ref":"#/components/responses/ControlForbidden"},"404":{"$ref":"#/components/responses/TournamentNotFound"},"409":{"$ref":"#/components/responses/PlanLimitReached"},"422":{"description":"`validation_failed`: a missing or over-long name, or one the tournament's\ndisplays already carry, under `validation_errors.name`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/tournaments/{tournamentId}/displays/{displayId}":{"patch":{"tags":["Control"],"operationId":"updateDisplayProjection","summary":"Choose what a hall display shows","description":"A display's projection: every court of the tournament or only the courts chosen\nfor it, whether and how many of the matches on no court it lists under them, and\nthe group standings of which published round-robin draws it shows. A court added\nlater is not added to a display showing chosen courts. A display chosen no draw\nshows no standings, which is every display made before draws could be chosen.\n\nThe projection bounds what the display's session may read, not only how its board\nis laid out: a display is an unattended screen in a public hall. So it is set by a\ndirector of the tournament or an administrator of the organization, who issue the\ndisplay's codes, and not by a referee.\n\nA field left out, or null, keeps its value; `court_ids` and `standings_draw_ids`\nare each the whole choice and replace the stored one. The chosen courts are kept\nwhile `court_scope` is `all`, so switching back finds the last choice. The change\nis applied to the projection under the display's row lock, so two directors\nchanging different fields both land.\nA change moves the display's `revision` once and is announced on the display\naggregate as `display_projection_changed`; a projection equal to the stored one\nchanges nothing and announces nothing.\n","parameters":[{"$ref":"#/components/parameters/tournamentId"},{"$ref":"#/components/parameters/displayId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","description":"Any of the projection's fields; each is refused unless it has the type below.","properties":{"court_scope":{"$ref":"#/components/schemas/DisplayCourtScope"},"court_ids":{"type":"array","description":"Courts of this tournament, in any order; a court named twice is shown once.","items":{"type":"string","format":"uuid"}},"show_upcoming":{"type":"boolean"},"upcoming_count":{"type":"integer","minimum":1,"maximum":12},"standings_draw_ids":{"type":"array","description":"Published round-robin draws of this tournament, in any order; a draw\nnamed twice is shown once. The display then reads their standings and\ntheir matches on whatever court they are played.\n","items":{"type":"string","format":"uuid"}},"show_header":{"type":"boolean","description":"Whether the board is headed with its title line: the tournament and its\nvenue, and on a court's scoreboard the court's name before them.\n"},"show_seeds":{"type":"boolean","description":"Whether a side's seed is written beside its name."},"layout":{"$ref":"#/components/schemas/DisplayLayout"}}}}}},"responses":{"200":{"description":"The display with its projection as it now stands.","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["display"],"properties":{"display":{"$ref":"#/components/schemas/Display"}}}}}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"$ref":"#/components/responses/ControlForbidden"},"404":{"description":"`not_found`: no such tournament in the principal's scope, or no such display in\nthe tournament.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"`validation_failed`, with `details.validation_errors` keyed by field: a\n`court_scope` other than `all` or `selected`, a `court_ids` that is not a list of\nthis tournament's courts, a `show_upcoming` that is not a JSON boolean, an\n`upcoming_count` that is not a whole number from 1 to 12, or a\n`standings_draw_ids` that is not a list of this tournament's published\nround-robin draws (a knockout draw has no standings, and a draft is shown to no\ndisplay), a `show_header` or `show_seeds` that is not a JSON boolean, or a\n`layout` other than `cards` or `list`. A change that would leave the display showing chosen courts with none\nchosen, the upcoming list off and no draw's standings is refused under\n`court_ids`, judged on the projection it would leave rather than on the body\nalone. Nothing is stored.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/tournaments/{tournamentId}/staff":{"get":{"tags":["Control"],"operationId":"listTournamentStaff","summary":"List a tournament's directors, referees and umpires","description":"For a director of the tournament or an administrator of the organization. One\nentry per role held, so a person holding several appears once for each; directors\ncome first, then referees, then umpires, each role ordered by name. A\n`tournament_umpire` is a person who watches the courts' panels, read only, and holds\nno match authority; the court tablet's `umpire` is a credential's role and is never\ngranted here. An organization administrator holds no match authority,\nand is listed here only through a tournament role of their own.\n","parameters":[{"$ref":"#/components/parameters/tournamentId"}],"responses":{"200":{"description":"The tournament's staff.","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["staff"],"properties":{"staff":{"type":"array","items":{"$ref":"#/components/schemas/TournamentStaffMember"}}}}}}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"$ref":"#/components/responses/ControlForbidden"},"404":{"$ref":"#/components/responses/TournamentNotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}},"post":{"tags":["Control"],"operationId":"grantTournamentRole","summary":"Give a person a tournament role","description":"An administrator of the organization grants any of the three roles; a director of\nthe tournament grants `referee` and `tournament_umpire`. A person may hold several,\neach granted on its own. The person is named by the verified\naddress their Roundr account registered with, and becomes a member of the\norganization if they were not one. A person who never signed in has no account,\nand a suspended member is not reactivated; both are validation errors on `email`.\nA repeated grant changes nothing and answers 200 with `granted: false`.\n","parameters":[{"$ref":"#/components/parameters/tournamentId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email","role"],"properties":{"email":{"type":"string","maxLength":254},"role":{"type":"string","enum":["tournament_director","referee","tournament_umpire"]}}}}}},"responses":{"200":{"$ref":"#/components/responses/TournamentRoleGranted"},"201":{"$ref":"#/components/responses/TournamentRoleGranted"},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"$ref":"#/components/responses/ControlForbidden"},"404":{"$ref":"#/components/responses/TournamentNotFound"},"422":{"description":"`validation_failed`: a missing address or a role other than the two, under\n`validation_errors.email` and `validation_errors.role`; an address no account\nuses, or a suspended member, under `validation_errors.email`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/tournaments/{tournamentId}/staff/{userId}/{role}":{"delete":{"tags":["Control"],"operationId":"revokeTournamentRole","summary":"Take a tournament role away","description":"The same authority as a grant: an administrator removes any role, a director\nremoves referees and umpires. Removing a role the person does not hold answers 200 with\n`revoked: false`. The person's sessions pick the change up on their next request,\nbecause grants are read on every request.\n","parameters":[{"$ref":"#/components/parameters/tournamentId"},{"name":"userId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"role","in":"path","required":true,"schema":{"type":"string","enum":["tournament_director","referee","tournament_umpire"]}}],"responses":{"200":{"description":"Whether a grant was removed.","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["revoked"],"properties":{"revoked":{"type":"boolean"}}}}}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"$ref":"#/components/responses/ControlForbidden"},"404":{"description":"The tournament is outside the principal's scope, or the user or role is not one this path names.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/tournaments/{tournamentId}/staff-invitations":{"get":{"tags":["Control"],"operationId":"listStaffInvitations","summary":"List a tournament's staff invitations still waiting","description":"For a director of the tournament or an administrator of the organization (DEV-43).\nOnly invitations nobody has redeemed, withdrawn or let expire, newest first. An\ninvitation is listed by what it grants and whom the staff said it was for; its code\nwas shown once, when it was created, and is never listed.\n","parameters":[{"$ref":"#/components/parameters/tournamentId"}],"responses":{"200":{"description":"The waiting invitations.","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["invitations"],"properties":{"invitations":{"type":"array","items":{"$ref":"#/components/schemas/StaffInvitation"}}}}}}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"$ref":"#/components/responses/ControlForbidden"},"404":{"$ref":"#/components/responses/TournamentNotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}},"post":{"tags":["Control"],"operationId":"createStaffInvitation","summary":"Invite someone to a tournament's staff with a one-time link","description":"For a person who may have no Roundr account yet (DEV-43). The answer carries a\nlink, `invitation_url`, which the staff send however they like; whoever opens it,\nsigns up or signs in, and accepts it at `/v1/staff-invitations/redeem` holds\n`roles` in this tournament. Nothing is keyed by an address: accounts are never\nlinked by email (DEV-11), and the link is what decides. It works once and for 14\ndays, and the code in its fragment is shown here and never again; only its keyed\ndigest is kept. An administrator invites to any of the three roles, a director to\n`referee` and `tournament_umpire`. A closed tournament invites nobody\n(`tournament_closed`).\n\nWith `email`, Roundr also sends the link to that address, now and once, as a\nconvenience: the address is not stored and decides nothing. `email_sent` says\nwhether it went; it is false when no mail service is configured, when the sender\nhas had twenty sent within the hour, or when the service refused it, and the link\nis in the answer either way. It is null when no address was given.\n","parameters":[{"$ref":"#/components/parameters/tournamentId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["roles"],"properties":{"roles":{"type":"array","minItems":1,"items":{"type":"string","enum":["tournament_director","referee","tournament_umpire"]}},"label":{"type":["string","null"],"maxLength":200,"description":"Whom it is for, in the staff's own words, for the list of waiting invitations."},"email":{"type":["string","null"],"maxLength":254,"description":"An address Roundr sends the link to. Not stored."}}}}}},"responses":{"201":{"description":"The invitation, with its link, the one time it exists.","headers":{"Cache-Control":{"schema":{"type":"string","const":"no-store"}}},"content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/CreatedStaffInvitation"}}}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"$ref":"#/components/responses/ControlForbidden"},"404":{"$ref":"#/components/responses/TournamentNotFound"},"409":{"description":"`tournament_closed`: the tournament is closed; reopen it to invite anyone.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"`validation_failed`: no role, or one other than the three, under\n`validation_errors.roles`; a label too long under `validation_errors.label`; an\naddress that is not one under `validation_errors.email`; or a tournament already\nholding 100 waiting invitations, under `validation_errors.invitations`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/tournaments/{tournamentId}/staff-invitations/{invitationId}":{"delete":{"tags":["Control"],"operationId":"revokeStaffInvitation","summary":"Withdraw a waiting staff invitation","description":"Its link admits nobody from now. Whoever may invite to every role it carries may\nwithdraw it, so a director withdraws no invitation for a director. A withdrawn\ninvitation is refused as `redemption_code_expired`, as an expired one is. Allowed on\na closed tournament: it gives nobody anything.\n","parameters":[{"$ref":"#/components/parameters/tournamentId"},{"name":"invitationId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Whether a waiting invitation was withdrawn.","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["revoked"],"properties":{"revoked":{"type":"boolean"}}}}}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"$ref":"#/components/responses/ControlForbidden"},"404":{"description":"The tournament is outside the principal's scope, or no invitation of it is waiting under this id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/staff-invitations/inspect":{"post":{"tags":["Account"],"operationId":"inspectStaffInvitation","summary":"Tell what a staff invitation offers, without spending it","description":"Open to whoever holds the code, signed in or not, so the invitation page can say\nwhat signing up is for: the tournament, its organization, the roles and who sent\nit. On a customer's host another organization's invitation is unknown. A spent,\nwithdrawn or expired invitation is refused as redemption would refuse it, and the\nrefusal counts towards its lock; one the signed-in caller redeemed reads as\n`accepted` instead. Invitations looked at or redeemed from one client address are\nlimited to thirty an hour, and refused with `429` while that cannot be checked.\n","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["code"],"properties":{"code":{"type":"string","maxLength":128,"description":"The fragment of the invitation link."}}}}}},"responses":{"200":{"description":"What the invitation offers.","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["invitation"],"properties":{"invitation":{"$ref":"#/components/schemas/StaffInvitationPreview"}}}}}}}},"404":{"description":"`redemption_code_invalid`: no such invitation, or another organization's on this\nhost. Also when invitations are not offered on this host.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"`redemption_code_redeemed`: someone has already accepted it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"410":{"description":"`redemption_code_expired`: it expired or was withdrawn; ask for a new one.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"`validation_failed`: no code.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"`rate_limited` for too many invitations tried from this address;\n`redemption_code_locked` for an invitation refused too often. `Retry-After`\nsays when either clears.\n","headers":{"Retry-After":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/staff-invitations/redeem":{"post":{"tags":["Account"],"operationId":"redeemStaffInvitation","summary":"Accept a staff invitation as the signed-in person","description":"The invitation is found by its keyed digest and consumed, with its row locked, in\nthe transaction that grants its roles, so it works once: two people accepting one\nlink at once yield one grant and one `redemption_code_redeemed`. The person becomes\na member of the organization if they were not one; a role they already held stays\nas it was, and `granted` lists only the new ones. Each grant names whoever sent the\ninvitation as its grantor, and the invitation names who redeemed it. The caller who\nredeemed it asking again is answered as the first time, with nothing granted. A\nclosed tournament is refused as `tournament_closed` and a person suspended in the\norganization with `403`, neither counting against the invitation. Roles are read on\nevery request, so a session on the organization's own host acts on them at once; on\nthe shared host the client then chooses the organization at `/v1/auth/organization`.\n","security":[{"sessionCookie":[]},{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["code"],"properties":{"code":{"type":"string","maxLength":128}}}}}},"responses":{"200":{"description":"The roles are the caller's.","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["redemption"],"properties":{"redemption":{"$ref":"#/components/schemas/StaffInvitationRedemption"}}}}}}}},"401":{"description":"No session or token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"A court device, a display or a machine key, which has no account; or a person\nsuspended in the organization.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"`redemption_code_invalid`: no such invitation, or another organization's on this\nhost; or `junior_id` named, since nobody accepts a role for a junior.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"`redemption_code_redeemed`: someone else accepted it first; or `tournament_closed`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"410":{"description":"`redemption_code_expired`: it expired or was withdrawn.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"`validation_failed`: no code.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"`rate_limited` for too many invitations tried from this address, or a spent\nwrite budget; `redemption_code_locked` for an invitation refused too often.\n","headers":{"Retry-After":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/me":{"delete":{"tags":["Account"],"operationId":"deleteMyAccount","summary":"Delete the signed-in person's account","description":"GDPR Art. 17, by the person's own hand (DEV-37). `confirmation` is the address the\naccount signs in with, as the person typed it (case and surrounding spaces aside),\nwhich the client collects in a dialog saying what is deleted and what stays with\nthe organizers. The sign-in behind the request must be recent, within\n`details.within_seconds` of a `403 reauthentication_required`: a browser session's\nown sign-in (choosing an organization keeps it), or a token's `auth_time`.\n\nIn one transaction the account's row is scrubbed and disabled (its name becomes\n\"Deleted account\" wherever an organization's records still name it), its sign-in\nidentities, memberships, roles, follows and umpired matches are deleted, its\npending registrations withdrawn, and every tournament player and registration\nunlinked from it. Tournament records stay the organizers'. An organization that\nheld nothing but the account goes with it. Afterwards the session cookie is\nexpired, every other session of the account ends on its next request, and the\nidentity provider's user is removed where this deployment administers it.\n","security":[{"sessionCookie":[]},{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["confirmation"],"properties":{"confirmation":{"type":"string","description":"The address the account signs in with."}}}}}},"responses":{"200":{"description":"Deleted. The session cookie is expired in the same answer.","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["deleted"],"properties":{"deleted":{"type":"boolean","const":true}}}}}}}},"401":{"description":"No session or token, or the account was already gone.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"`reauthentication_required`: the sign-in is older than `details.within_seconds`;\nsign in again and ask again. `csrf_rejected` as for any write. Otherwise a court\ndevice, a display or a machine key, which has no account.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"`organization_needs_administrator`: the account is the only administrator of\neach organization in `details.organizations` (`id`, `name`), which holds another\nmember, a tournament, a hostname or a key. Grant another administrator there, or\nhave the organization closed, and ask again.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"`validation_failed` on `confirmation`: not the address the account signs in with.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/me/export":{"get":{"tags":["Account"],"operationId":"exportMyData","summary":"A copy of everything Roundr holds about the signed-in person","description":"GDPR Art. 15 and 20 (DEV-37): a JSON file of what Roundr holds about the person\nacross every organization, which the browser saves. `format` is\n`roundr.personal-data` and `version` 1; `sections` holds one object per area: the\naccount (profile, date of birth, classes, sharing), sign-in identities (issuer and\nsubject), organizations and roles, registrations, the tournaments' players linked\nto the account with their matches, followed players, club memberships with the\nnumbers the club keeps, umpired matches, and a count\nof the organizations' records that name the account as who did something. A\nsection added later is additive; a changed shape moves `version`.\n\nSpends a budget of its own, ten an hour for a person.\n","security":[{"sessionCookie":[]},{"bearerAuth":[]}],"responses":{"200":{"description":"The file, as an attachment named `roundr-personal-data-YYYY-MM-DD.json`.","headers":{"Content-Disposition":{"description":"`attachment; filename=\"roundr-personal-data-2026-09-26.json\"`, dated the day it was produced (UTC).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PersonalDataExport"}}}},"401":{"description":"No session or token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"A court device, a display or a machine key, which has no account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/me/matches":{"get":{"tags":["Account"],"operationId":"myMatches","summary":"The account's own matches","description":"The matches the signed-in person is to play and the results of those they played,\nin every tournament whose staff linked one of its players to their account\n(DEV-15). On a tenant host, that organization's tournaments alone; on the\nshared API host, every organization's. A draft or cancelled tournament's matches\nare not shown, nor a suspended organization's.\n\nBuilt field by field, as Live's projection is: each match carries what the\ntournament's own reads show that player, the sides' display names, the court,\nthe reference, the start and the score, and nothing about any account. Upcoming\nmatches are soonest first, one with no start yet last; results are newest first.\n","security":[{"sessionCookie":[]},{"bearerAuth":[]}],"parameters":[{"$ref":"#/components/parameters/juniorId"}],"responses":{"200":{"description":"The two lists, either possibly empty.","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["upcoming","results"],"properties":{"upcoming":{"type":"array","items":{"$ref":"#/components/schemas/AccountMatch"}},"results":{"type":"array","items":{"$ref":"#/components/schemas/AccountMatch"}}}}}}}}},"401":{"description":"No session or token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"A court device, a display or a machine key, which has no account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"A `junior_id` that is not a junior of the caller's.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/me/teams":{"get":{"tags":["Account"],"operationId":"myTeams","summary":"The league teams the person captains","description":"The teams the signed-in person captains (DEV-49), withdrawn ones left out. On a\ntenant host, those of that organization's leagues, or of that club, alone.\n","security":[{"sessionCookie":[]},{"bearerAuth":[]}],"responses":{"200":{"description":"The teams, possibly none.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LeagueTeamsAnswer"}}}},"401":{"description":"No session or token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"A court device, a display or a machine key, which has no account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/me/teams/{teamId}":{"parameters":[{"name":"teamId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"get":{"tags":["Account"],"operationId":"myTeam","summary":"A team the person captains, with its roster","description":"With `candidates`, the club's active members in the league's sport, whom the roster\nis chosen from.\n","security":[{"sessionCookie":[]},{"bearerAuth":[]}],"responses":{"200":{"description":"The team.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TeamRosterAnswer"}}}},"401":{"description":"No session or token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"A court device, a display or a machine key, which has no account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"`not_found`: no team the person captains.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/me/teams/{teamId}/roster":{"parameters":[{"name":"teamId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"put":{"tags":["Account"],"operationId":"saveMyTeamRoster","summary":"Replace the roster of a team the person captains","description":"As the club's administrators do, under the same rule (DEV-49).","security":[{"sessionCookie":[]},{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RosterRequest"}}}},"responses":{"200":{"description":"The team with its new roster.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TeamRosterAnswer"}}}},"401":{"description":"No session or token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"A court device, a display or a machine key, which has no account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"`not_found`: no team the person captains.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"`league_refused` with `details.reason` `no_such_account`, `too_many_guests`,\n`on_another_team` or `withdrawn`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"`validation_failed` under `members`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/me/teams/{teamId}/season":{"parameters":[{"name":"teamId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"get":{"tags":["Account"],"operationId":"myTeamSeason","summary":"The season of a team the person captains","description":"As the club's staff read it (DEV-49).","security":[{"sessionCookie":[]},{"bearerAuth":[]}],"responses":{"200":{"description":"The season.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TeamSeasonAnswer"}}}},"401":{"description":"No session or token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"A court device, a display or a machine key, which has no account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"`not_found`: no team the person captains.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/me/teams/{teamId}/fixtures/{fixtureId}/lineup":{"parameters":[{"name":"teamId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"fixtureId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"get":{"tags":["Account"],"operationId":"myTeamLineup","summary":"A line-up of a team the person captains","description":"As the club's staff read it.","security":[{"sessionCookie":[]},{"bearerAuth":[]}],"responses":{"200":{"description":"The line-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LineupAnswer"}}}},"401":{"description":"No session or token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"A court device, a display or a machine key, which has no account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"`not_found`: no fixture of a team the person captains.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}},"put":{"tags":["Account"],"operationId":"saveMyTeamLineup","summary":"Give or change a line-up of a team the person captains","description":"As the club's administrators do, under the same rule (DEV-49).","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LineupRequest"}}}},"security":[{"sessionCookie":[]},{"bearerAuth":[]}],"responses":{"200":{"description":"The line-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LineupAnswer"}}}},"401":{"description":"No session or token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"A court device, a display or a machine key, which has no account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"`not_found`: no fixture of a team the person captains.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"`league_refused` with `details.reason` `not_on_roster`, `plays_too_often`, `roster_full` or `tie_started`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"`validation_failed` under `rubbers`: every rubber of the tie once, with one player or a pair as its ruleset says or `not_fielded` and nobody, and one rubber fielded at least. Under `new_players`: a list of names.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/me/clubs":{"get":{"tags":["Account"],"operationId":"myClubs","summary":"The clubs the person is a member of","description":"The signed-in person's memberships of clubs (DEV-39), with those they asked for or\nwere invited to, and the numbers the club keeps for them. Open ones first, newest\nfirst, ended ones kept. On a tenant host, that organization's alone.\n","security":[{"sessionCookie":[]},{"bearerAuth":[]}],"responses":{"200":{"description":"The memberships, possibly none.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClubMembershipsAnswer"}}}},"401":{"description":"No session or token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"A court device, a display or a machine key, which has no account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}},"post":{"tags":["Account"],"operationId":"askToJoinClub","summary":"Ask to join a club","description":"The club's administrators accept or decline. On a tenant host, that organization's club alone.","security":[{"sessionCookie":[]},{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["club_id","sport"],"properties":{"club_id":{"type":"string","format":"uuid"},"sport":{"$ref":"#/components/schemas/SportCode"}}}}}},"responses":{"201":{"description":"The request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClubMembershipAnswer"}}}},"401":{"description":"No session or token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"A court device, a display or a machine key, which has no account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"`not_found`: no such club.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"`membership_refused`, with `details.reason`: `not_a_club` or `already_open`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"`validation_failed` under `club_id` or `sport`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/me/clubs/{membershipId}/accept":{"parameters":[{"name":"membershipId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"post":{"tags":["Account"],"operationId":"acceptClubInvitation","summary":"Accept a club's invitation","security":[{"sessionCookie":[]},{"bearerAuth":[]}],"responses":{"200":{"description":"The membership as it now stands.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClubMembershipAnswer"}}}},"401":{"description":"No session or token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"A court device, a display or a machine key, which has no account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"`not_found`: no such membership of the person.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"`membership_refused`, with `details.reason`: `not_awaiting_you` (the other side answers) or `ended` (propose a new one).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/me/clubs/{membershipId}/end":{"parameters":[{"name":"membershipId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"post":{"tags":["Account"],"operationId":"leaveClub","summary":"Leave a club, or decline or withdraw a proposal","security":[{"sessionCookie":[]},{"bearerAuth":[]}],"responses":{"200":{"description":"The membership, ended.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClubMembershipAnswer"}}}},"401":{"description":"No session or token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"A court device, a display or a machine key, which has no account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"`not_found`: no such membership of the person.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"`membership_refused`, with `details.reason`: `not_awaiting_you` (the other side answers) or `ended` (propose a new one).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/me/club-directory":{"get":{"tags":["Account"],"operationId":"searchClubs","summary":"Find a club by name","description":"Active clubs whose name contains every word of `q`, by name, at most 20. On a tenant\nhost, that organization alone.\n","security":[{"sessionCookie":[]},{"bearerAuth":[]}],"parameters":[{"name":"q","in":"query","required":true,"schema":{"type":"string","minLength":2,"maxLength":100}}],"responses":{"200":{"description":"The clubs found, possibly none.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClubDirectoryAnswer"}}}},"401":{"description":"No session or token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"A court device, a display or a machine key, which has no account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"`validation_failed` on `q` (2 to 100 characters).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/me/players":{"get":{"tags":["Account"],"operationId":"myLinkedPlayers","summary":"The players organizations linked to the account","description":"Each tournament player whose staff linked it to the signed-in person's account, so\nits matches are theirs (DEV-15), newest tournament first: the name the tournament\nhas, the tournament and whose it is. On a tenant host, that organization's alone. A\nlink is announced to the account as `player_linked`, and the person takes a wrong\none back with `DELETE /v1/me/players/{playerId}`.\n","security":[{"sessionCookie":[]},{"bearerAuth":[]}],"parameters":[{"$ref":"#/components/parameters/juniorId"}],"responses":{"200":{"description":"The linked players, possibly none.","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["players"],"properties":{"players":{"type":"array","items":{"$ref":"#/components/schemas/LinkedPlayer"}}}}}}}}},"401":{"description":"No session or token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"A court device, a display or a machine key, which has no account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"A `junior_id` that is not a junior of the caller's.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/me/players/{playerId}":{"delete":{"tags":["Account"],"operationId":"unlinkMyPlayer","summary":"Take back an organization's link between the account and a player","description":"Removes the link only: the player, its name and its matches stay the organization's\nrecord, and the account no longer reads them as its own, as deleting the account\ndoes for every link. Allowed for a closed tournament too, since the link is the\naccount's and not the tournament's record.\n","security":[{"sessionCookie":[]},{"bearerAuth":[]}],"parameters":[{"name":"playerId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"$ref":"#/components/parameters/juniorId"}],"responses":{"200":{"description":"The link is gone.","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["unlinked"],"properties":{"unlinked":{"type":"boolean","const":true}}}}}}}},"401":{"description":"No session or token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"A court device, a display or a machine key, which has no account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No player of this id is linked to the account, or a `junior_id` that is not a junior of the caller's.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/me/profile":{"get":{"tags":["Account"],"operationId":"myProfile","summary":"What the account says of the person","description":"The signed-in person's date of birth, which an event with an age category checks\nwhen they enter it (DEV-27), or null, their class in each discipline, which an\nevent with a class rule checks (DEV-31), and the language they last used Roundr\nin, which their emails are written in (DEV-35), or null. Only a person has a\nprofile.\n","security":[{"sessionCookie":[]},{"bearerAuth":[]}],"responses":{"200":{"description":"The profile.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccountProfileResponse"}}}},"401":{"description":"No session or token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"A court device, a display or a machine key, which has no account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}},"put":{"tags":["Account"],"operationId":"saveMyProfile","summary":"Give, change or take back the account's date of birth and classes","description":"`date_of_birth` is required, a date from 1900 to today, or null to take it back.\nIt leaves the account only when an entry into an age category is accepted, when the\ntournament's staff see it on that player; nothing public ever shows it. The log\nrecords that it changed, never what it is. `classes` is optional: the person's own\nclass in each discipline, which an event with a class rule checks, copied to the\nplayer when an entry is accepted, where the staff may correct it (DEV-31).\n\n`locale` is optional: the language the client shows, one of the locales Roundr\nsupports, which the account's emails are written in (DEV-35). A body with `locale`\nalone changes the locale and nothing else, so a client reports its language without\nthe date it never read; any other body needs `date_of_birth`.\n","security":[{"sessionCookie":[]},{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"date_of_birth":{"type":["string","null"],"format":"date"},"classes":{"$ref":"#/components/schemas/PlayerClassesChange"},"locale":{"$ref":"#/components/schemas/Locale"}}}}}},"responses":{"200":{"description":"The profile as it now stands.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccountProfileResponse"}}}},"401":{"description":"No session or token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"A court device, a display or a machine key, which has no account, or an account no longer active.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"`validation_failed` on `date_of_birth`: missing, not a real date, before 1900 or still to come; on `classes`: not an object of disciplines, or a value that is not a class code; or on `locale`: not a locale Roundr supports.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/me/name":{"put":{"tags":["Account"],"operationId":"renameMyAccount","summary":"Change the name the account is shown under","description":"The signed-in person's name, trimmed, of 1 to 200 characters, as registration takes\nit. A first sign-in through a provider registers under the name the provider gave,\nand the page that follows asks the person to confirm or correct it (DEV-713). Only a\nperson has a name to change; a junior's is their guardian's to change.\n","security":[{"sessionCookie":[]},{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","minLength":1,"maxLength":200}}}}}},"responses":{"200":{"description":"The name as it now stands.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccountNameAnswer"}}}},"401":{"description":"No session or token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"A court device, a display or a machine key, which has no account, a junior's record, or an account no longer active.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"`validation_failed` on `name`: empty once trimmed, or longer than 200 characters.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/me/legal-acceptances":{"post":{"tags":["Account"],"operationId":"acceptLegalTexts","summary":"Accept changed versions of the legal texts","description":"Accept what `legal_pending` on the session names: the current terms of use for the\nperson, and the current data processing agreement for an organization they\nadminister (terms section 17). Only a pending version is accepted: one the server\nhas moved past, one already accepted, or an organization the person does not\nadminister is refused, and nothing of the body is recorded. Not accepting is\nallowed; the previous terms then stay the person's, and the session names the\ntexts again. The answer is what is still pending.\n","security":[{"sessionCookie":[]},{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["acceptances"],"properties":{"acceptances":{"type":"array","minItems":1,"items":{"$ref":"#/components/schemas/LegalAcceptanceItem"}}}}}}},"responses":{"200":{"description":"What the account still has to accept.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LegalAcceptanceResponse"}}}},"401":{"description":"No session or token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"A court device, a display or a machine key, which has no account, or a guardian acting for a junior.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"`validation_failed` on `acceptances`: not a non-empty list, or an item that is not one of the pending versions.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/me/juniors":{"get":{"tags":["Account"],"operationId":"myJuniors","summary":"The juniors the account is a guardian of","description":"Each junior's record (DEV-36): the name, date of birth and classes their guardians\nkeep for them, whether they hold their own account now, whether a handover would be\ntaken today (`may_hand_over`, from `own_account_age`, counted by the date), the\nexpiry of a handover code still outstanding, their guardians, the caller marked\n`you`, and the invitations to another guardian still waiting, which name nobody.\n","security":[{"sessionCookie":[]},{"bearerAuth":[]}],"responses":{"200":{"description":"The juniors, possibly none, by name.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JuniorList"}}}},"401":{"description":"No session or token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"A court device, a display or a machine key, which has no account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}},"post":{"tags":["Account"],"operationId":"addJunior","summary":"Add a junior the account acts for","description":"Writes a record of the junior's own, which nobody signs in to, and makes the caller\nits first guardian. The junior's entries, matches and career are then theirs, and the\nguardian acts for them with `?junior_id=` on the account's routes. An account manages\nat most twelve juniors.\n","security":[{"sessionCookie":[]},{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JuniorChange"}}}},"responses":{"201":{"description":"The junior.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JuniorResponse"}}}},"401":{"description":"No session or token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"A court device, a display or a machine key, which has no account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"`validation_failed` on `display_name`, `date_of_birth` or `classes`, or with\n`details.problem` `not_an_account` or `too_many_juniors`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/me/juniors/{juniorId}":{"put":{"tags":["Account"],"operationId":"saveJunior","summary":"Change what a junior's record says","description":"For any of the junior's guardians. `classes` left out keeps the junior's. Refused as\n`own_account` once the record is handed over: the junior keeps their own profile.\n","security":[{"sessionCookie":[]},{"bearerAuth":[]}],"parameters":[{"$ref":"#/components/parameters/juniorIdPath"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JuniorChange"}}}},"responses":{"200":{"description":"The junior as the record now stands.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JuniorResponse"}}}},"401":{"description":"No session or token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"A court device, a display or a machine key, which has no account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not a junior of the caller's, whether or not it exists.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"`validation_failed` on a field, or `details.problem` `own_account`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/me/juniors/{juniorId}/guardianship":{"delete":{"tags":["Account"],"operationId":"leaveJunior","summary":"Stop being a junior's guardian","description":"The caller's guardianship ends; the junior's other guardians keep theirs. The last\nguardian of a junior who does not sign in as themselves is refused as\n`last_guardian`: the record would be nobody's to act for.\n","security":[{"sessionCookie":[]},{"bearerAuth":[]}],"parameters":[{"$ref":"#/components/parameters/juniorIdPath"}],"responses":{"200":{"description":"The guardianship has ended.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GuardianshipLeft"}}}},"401":{"description":"No session or token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"A court device, a display or a machine key, which has no account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not a junior of the caller's.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"`validation_failed` with `details.problem` `last_guardian`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/me/juniors/{juniorId}/guardian-invitations":{"post":{"tags":["Account"],"operationId":"inviteGuardian","summary":"Invite a second guardian by the address of their account","description":"The address is looked up as a partner's is (DEV-18): the answer says only whether\nan active account holds it, as `account_unknown` or a waiting invitation, and nothing\nof the account is read or kept but its id. The invited person accepts from their own\naccount. Inviting the same account again is the invitation already waiting. The lookup\nspends the partner lookup's two budgets, 20 an hour per account and 100 per client\naddress, and is refused with `429` over either or while either cannot be checked.\n","security":[{"sessionCookie":[]},{"bearerAuth":[]}],"parameters":[{"$ref":"#/components/parameters/juniorIdPath"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email"],"properties":{"email":{"type":"string","maxLength":254}}}}}},"responses":{"201":{"description":"The junior, with the invitation waiting.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JuniorResponse"}}}},"401":{"description":"No session or token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"A court device, a display or a machine key, which has no account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not a junior of the caller's.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"`validation_failed` on `email`, or `details.problem` `account_unknown`,\n`already_guardian` or `own_account`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/me/juniors/{juniorId}/guardian-invitations/{invitationId}":{"delete":{"tags":["Account"],"operationId":"withdrawGuardianInvitation","summary":"Withdraw an invitation to another guardian","security":[{"sessionCookie":[]},{"bearerAuth":[]}],"parameters":[{"$ref":"#/components/parameters/juniorIdPath"},{"$ref":"#/components/parameters/invitationId"}],"responses":{"200":{"description":"The junior without the invitation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JuniorResponse"}}}},"401":{"description":"No session or token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"A court device, a display or a machine key, which has no account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not an invitation waiting for a junior of the caller's.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/me/juniors/{juniorId}/handover":{"post":{"tags":["Account"],"operationId":"issueJuniorHandover","summary":"Give a junior the code that hands their record over","description":"Once the junior has reached `own_account_age` by the date of birth on their record,\na guardian gets a one-time code, shown this once and kept only as its keyed digest.\nThe junior signs up with an account of their own and redeems it\n(`POST /v1/me/handover`), and the record, with its entries and career, becomes that\naccount. The code works for fourteen days; issuing another replaces it.\n","security":[{"sessionCookie":[]},{"bearerAuth":[]}],"parameters":[{"$ref":"#/components/parameters/juniorIdPath"}],"responses":{"201":{"description":"The code and when it expires.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HandoverIssued"}}}},"401":{"description":"No session or token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"A court device, a display or a machine key, which has no account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not a junior of the caller's.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"`validation_failed` with `details.problem` `date_of_birth_required`, `too_young`\nor `own_account`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/me/guardian-invitations":{"get":{"tags":["Account"],"operationId":"myGuardianInvitations","summary":"Invitations to become a junior's guardian","description":"The junior each is for and the guardian who sent it, oldest first.","security":[{"sessionCookie":[]},{"bearerAuth":[]}],"responses":{"200":{"description":"The invitations, possibly none.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GuardianInvitationList"}}}},"401":{"description":"No session or token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"A court device, a display or a machine key, which has no account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/me/guardian-invitations/{invitationId}":{"delete":{"tags":["Account"],"operationId":"declineGuardianInvitation","summary":"Decline an invitation to become a guardian","security":[{"sessionCookie":[]},{"bearerAuth":[]}],"parameters":[{"$ref":"#/components/parameters/invitationId"}],"responses":{"200":{"description":"The invitation is gone.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GuardianInvitationDeclined"}}}},"401":{"description":"No session or token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"A court device, a display or a machine key, which has no account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not an invitation waiting for the caller.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/me/guardian-invitations/{invitationId}/acceptance":{"post":{"tags":["Account"],"operationId":"acceptGuardianInvitation","summary":"Become a junior's guardian","security":[{"sessionCookie":[]},{"bearerAuth":[]}],"parameters":[{"$ref":"#/components/parameters/invitationId"}],"responses":{"200":{"description":"The junior the caller now guards.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JuniorResponse"}}}},"401":{"description":"No session or token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"A court device, a display or a machine key, which has no account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not an invitation waiting for the caller.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"`validation_failed` with `details.problem` `not_an_account` or `too_many_juniors`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/me/handover":{"post":{"tags":["Account"],"operationId":"redeemJuniorHandover","summary":"Take over the record a guardian kept for you","description":"With the account the junior has just made, and the code a guardian gave them. The\ncode is found by its keyed digest and consumed in the transaction that moves the\naccount's sign-in to the junior's record, so it works once. The account's address,\nname, umpired matches and followed players move with it, and the account itself is\ndeleted; one that already plays, staffs or guards is refused as `account_in_use`,\nsince a code does not merge two histories. A browser's session is replaced by one\nnaming the record, written inside the redemption; a bearer token finds the record on\nits next request. `keep_guardians` says whether the guardians may still act for the\njunior; otherwise their guardianship ends here. Codes tried from one client address\nare limited to ten an hour, and refused with `429` while that cannot be checked.\n","security":[{"sessionCookie":[]},{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["code"],"properties":{"code":{"type":"string","maxLength":64,"description":"As given, in any case, with or without its hyphens."},"keep_guardians":{"type":"boolean","default":false}}}}}},"responses":{"200":{"description":"The record is the caller's; a browser's cookie now names it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HandoverRedeemed"}}}},"401":{"description":"No session or token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"A court device, a display or a machine key, which has no account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"`validation_failed` on `code` or `keep_guardians`, or with `details.problem`\n`code_invalid`, `code_redeemed`, `code_expired`, `account_in_use` or\n`not_an_account`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/me/guardians":{"get":{"tags":["Account"],"operationId":"myGuardians","summary":"The guardians still acting for a person who signs in as themselves","description":"The guardians a junior kept when their record was handed over; none for anyone else.","security":[{"sessionCookie":[]},{"bearerAuth":[]}],"responses":{"200":{"description":"The guardians, possibly none.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OwnGuardians"}}}},"401":{"description":"No session or token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"A court device, a display or a machine key, which has no account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}},"delete":{"tags":["Account"],"operationId":"endGuardianship","summary":"End every guardianship over the caller","description":"Nobody acts for the caller any more; their guardians keep nothing of them.","security":[{"sessionCookie":[]},{"bearerAuth":[]}],"responses":{"200":{"description":"No guardians remain.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OwnGuardians"}}}},"401":{"description":"No session or token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"A court device, a display or a machine key, which has no account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/me/career":{"get":{"tags":["Account"],"operationId":"myCareer","summary":"The account's career","description":"Every match the signed-in person's linked players stood on a side of (DEV-32), in\nthe tournaments `GET /v1/me/matches` reads: on a tenant host that organization's, on\nthe shared API host every organization's. Each match is sent as Live sends one, with\nthe side and participant the account stood on and the record it counts in, singles,\ndoubles or mixed, read off its ruleset and its draw's entry rule.\n\n`draws` are the published draws those matches are in, as Live sends a draw, narrowed\nto the account: its own entries, the nodes of its own matches and its own rows of a\ngroup's standings. The places a draw decided for the account (its titles and finals)\nare read from these, and the statistics from the matches, on the client.\n","security":[{"sessionCookie":[]},{"bearerAuth":[]}],"parameters":[{"$ref":"#/components/parameters/juniorId"}],"responses":{"200":{"description":"The career, possibly empty.","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["tournaments","matches","draws"],"properties":{"tournaments":{"type":"array","items":{"$ref":"#/components/schemas/CareerTournament"}},"matches":{"type":"array","items":{"$ref":"#/components/schemas/CareerMatch"}},"draws":{"type":"array","items":{"type":"object","required":["tournament_id","draw"],"properties":{"tournament_id":{"type":"string","format":"uuid"},"draw":{"$ref":"#/components/schemas/LiveDraw"}}}}}}}}}}},"401":{"description":"No session or token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"A court device, a display or a machine key, which has no account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"A `junior_id` that is not a junior of the caller's.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/me/sharing":{"get":{"tags":["Account"],"operationId":"mySharing","summary":"Whether the account shows its results to players it has played","description":"\"Show my results to players I have played\" (DEV-32), off until the person turns it\non. A head-to-head between two accounts is read only while both have it on.\n","security":[{"sessionCookie":[]},{"bearerAuth":[]}],"responses":{"200":{"description":"The answer.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccountSharingResponse"}}}},"401":{"description":"No session or token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"A court device, a display or a machine key, which has no account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}},"put":{"tags":["Account"],"operationId":"saveMySharing","summary":"Show or stop showing the account's results to players it has played","security":[{"sessionCookie":[]},{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["results_to_opponents"],"properties":{"results_to_opponents":{"type":"boolean"}}}}}},"responses":{"200":{"description":"The answer as it now stands.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccountSharingResponse"}}}},"401":{"description":"No session or token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"A court device, a display or a machine key, which has no account, or an account no longer active.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"`validation_failed` on `results_to_opponents`: missing or not a boolean.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/me/head-to-head":{"get":{"tags":["Account"],"operationId":"myHeadToHeadOpponents","summary":"The players the account can compare itself against","description":"The other accounts the signed-in person met on the opposite side of a result, in a\ntournament published on Live, who show their results to players they have played\n(DEV-32); one per account and organization, most meetings first. Nobody while the\ncaller does not show their own. Each is named by their player row at their latest\ntournament on Live in that organization, its id and the name its staff entered, and\nnever by the account. A partner is not an opponent, and nobody is matched by name.\n","security":[{"sessionCookie":[]},{"bearerAuth":[]}],"responses":{"200":{"description":"The caller's own answer and the opponents.","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["sharing","opponents"],"properties":{"sharing":{"$ref":"#/components/schemas/AccountSharing"},"opponents":{"type":"array","items":{"type":"object","required":["player_id","name","organization_name","meetings"],"properties":{"player_id":{"type":"string","format":"uuid"},"name":{"type":"string"},"organization_name":{"type":"string"},"meetings":{"type":"integer","minimum":1}}}}}}}}}}},"401":{"description":"No session or token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"A court device, a display or a machine key, which has no account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/me/head-to-head/{playerId}":{"get":{"tags":["Account"],"operationId":"myHeadToHead","summary":"The account against another it has played","description":"The signed-in person against the account the player row is linked to, in that\nplayer's organization's tournaments on Live (DEV-32): every result between them,\nwhere they stood on opposite sides, and both records there, each result as the\nstatistics count it and with nothing else, no names, tournament or ids.\n\n`404` alike whenever the caller or the other account does not show their results,\nthe player is linked to nobody or to the caller, is another organization's on a\ntenant host, or the two never met on Live, so the answer says nothing of an account.\n","security":[{"sessionCookie":[]},{"bearerAuth":[]}],"parameters":[{"name":"playerId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"The head-to-head.","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["organization_name","opponent","you","them","matches"],"properties":{"organization_name":{"type":"string"},"opponent":{"type":"object","required":["player_id","name"],"properties":{"player_id":{"type":"string","format":"uuid"},"name":{"type":"string"}}},"you":{"$ref":"#/components/schemas/CareerRecord"},"them":{"$ref":"#/components/schemas/CareerRecord"},"matches":{"type":"array","items":{"allOf":[{"$ref":"#/components/schemas/CareerMatch"},{"type":"object","required":["tournament"],"properties":{"tournament":{"type":"object","required":["id","name","venue_time_zone","starts_on"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"venue_time_zone":{"type":"string"},"starts_on":{"type":["string","null"],"format":"date"}}}}}]}}}}}}}}},"401":{"description":"No session or token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"A court device, a display or a machine key, which has no account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No head-to-head with this player, for any of the reasons above.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/me/following":{"get":{"tags":["Account"],"operationId":"myFollowing","summary":"The players the account follows on Live","description":"The players followed on Live that the signed-in person saved to their account\n(DEV-32), by tournament, newest tournament first, each list in the order they were\nfollowed. On a tenant host that organization's tournaments alone. A tournament taken\noff Live stays, with `on_live` false, so a follow is not lost while it is republished.\n","security":[{"sessionCookie":[]},{"bearerAuth":[]}],"responses":{"200":{"description":"The follows, possibly none.","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["tournaments"],"properties":{"tournaments":{"type":"array","items":{"type":"object","required":["tournament","players"],"properties":{"tournament":{"type":"object","required":["id","name","organization_name","starts_on","on_live"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"organization_name":{"type":"string"},"starts_on":{"type":["string","null"],"format":"date"},"on_live":{"type":"boolean"}}},"players":{"type":"array","items":{"type":"object","required":["id","name"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"}}}}}}}}}}}}}},"401":{"description":"No session or token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"A court device, a display or a machine key, which has no account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/me/following/{tournamentId}":{"put":{"tags":["Account"],"operationId":"saveMyFollowing","summary":"Replace the players the account follows at a tournament","description":"The whole list, so a retry means the same thing twice; none to follow nobody there\nany more. Only at a tournament this host's Live shows, and only its own players.\n","security":[{"sessionCookie":[]},{"bearerAuth":[]}],"parameters":[{"name":"tournamentId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["player_ids"],"properties":{"player_ids":{"type":"array","maxItems":100,"uniqueItems":true,"items":{"type":"string","format":"uuid"}}}}}}},"responses":{"200":{"description":"The list as it now stands.","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["tournament_id","player_ids"],"properties":{"tournament_id":{"type":"string","format":"uuid"},"player_ids":{"type":"array","items":{"type":"string","format":"uuid"}}}}}}}}},"401":{"description":"No session or token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"A court device, a display or a machine key, which has no account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No tournament this host's Live shows under that id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"`validation_failed` on `player_ids`: not a list of up to 100 distinct players of the tournament.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/me/notifications":{"get":{"tags":["Account"],"operationId":"myNotifications","summary":"The account's notifications","description":"The signed-in person's inbox (DEV-35), newest first, with how many are unread. On\na tenant host it is that organization's notifications alone; on the shared API host\nevery organization's, as every read scoped by a person is. `next_before` is null on\nthe last page; sent back as `before`, it asks for the page after. The signed-in bar\nasks for `limit=1`, since it wants the count. Only a person has notifications.\n","security":[{"sessionCookie":[]},{"bearerAuth":[]}],"parameters":[{"name":"before","in":"query","required":false,"description":"The `next_before` of the page before, as it was given. Opaque.","schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"description":"How many to send, 1 to 50; 50 when left out.","schema":{"type":"integer","minimum":1,"maximum":50}}],"responses":{"200":{"description":"A page of notifications, possibly none, and the unread count.","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["notifications","unread_count","next_before"],"properties":{"notifications":{"type":"array","maxItems":50,"items":{"$ref":"#/components/schemas/Notification"}},"unread_count":{"type":"integer","minimum":0},"next_before":{"type":["string","null"],"description":"Where the next page starts, or null when this is the last."}}}}}}}},"401":{"description":"No session or token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"A court device, a display or a machine key, which has no account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"`validation_failed` on `before`: not a cursor this list gave out; or on `limit`: not 1 to 50.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/me/notifications/{notificationId}/read":{"parameters":[{"name":"notificationId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"post":{"tags":["Account"],"operationId":"readMyNotification","summary":"Mark one notification read","description":"Marks the notification read, keeping the time it was first read when it already\nwas. A notification of another account, or of another organization on a tenant host,\nis not found.\n","security":[{"sessionCookie":[]},{"bearerAuth":[]}],"responses":{"200":{"description":"The notification and the unread count after it.","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["notification","unread_count"],"properties":{"notification":{"$ref":"#/components/schemas/Notification"},"unread_count":{"type":"integer","minimum":0}}}}}}}},"401":{"description":"No session or token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"A court device, a display or a machine key, which has no account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No such notification of this account here.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/me/notifications/read-all":{"post":{"tags":["Account"],"operationId":"readAllMyNotifications","summary":"Mark every notification read","description":"Marks every unread notification the list on this host shows read.","security":[{"sessionCookie":[]},{"bearerAuth":[]}],"responses":{"200":{"description":"How many were unread, and the unread count after.","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["marked","unread_count"],"properties":{"marked":{"type":"integer","minimum":0},"unread_count":{"type":"integer","minimum":0}}}}}}}},"401":{"description":"No session or token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"A court device, a display or a machine key, which has no account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/me/notification-preferences":{"get":{"tags":["Account"],"operationId":"myNotificationPreferences","summary":"Which notifications come by email and by push","description":"Every notification type, the channels after the inbox it goes by (`channels`), and\nfor each whether the account gets it that way and whether that is on unless turned\noff (DEV-35): email for what keeps, web push to the account's devices for match-day\nnews, a match called to its court and a next match moved (DEV-45). A channel a\ntype does not go by reads as off. The inbox always has every notification. The\npreferences are the account's wherever it signs in. The types told only to an\norganization's administrators, the `membership_*` ones and\n`club_membership_requested` (DEV-39), are left out for an account that administers\nno organization; a choice saved for one is kept.\n\n`push_public_key` is the VAPID public key a browser subscribes with\n(`applicationServerKey`), from the server rather than the build so a new key needs\nno new client; null when this instance sends no web push, and then the client offers\nnone.\n","security":[{"sessionCookie":[]},{"bearerAuth":[]}],"responses":{"200":{"description":"The preferences.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotificationPreferencesResponse"}}}},"401":{"description":"No session or token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"A court device, a display or a machine key, which has no account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}},"put":{"tags":["Account"],"operationId":"saveMyNotificationPreferences","summary":"Turn notification emails and pushes on or off","description":"`preferences` lists the types to change, each with `email`, `push` or both on or\noff. A type left out keeps what it had, and so does a channel left out, so a client\nthat does not know a newer type or channel does not turn it off. An email or a push\nalready waiting to be sent follows the change. A choice for a channel the type does\nnot go by is kept and never used.\n","security":[{"sessionCookie":[]},{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["preferences"],"properties":{"preferences":{"type":"array","items":{"type":"object","description":"At least one of `email` and `push`.","required":["type"],"properties":{"type":{"$ref":"#/components/schemas/NotificationType"},"email":{"type":"boolean"},"push":{"type":"boolean"}}}}}}}}},"responses":{"200":{"description":"The preferences as they now stand.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotificationPreferencesResponse"}}}},"401":{"description":"No session or token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"A court device, a display or a machine key, which has no account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"`validation_failed` on `preferences`: not a list, a type Roundr does not know, an `email` or `push` that is not true or false, or neither of them given.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/me/email-suppression":{"delete":{"tags":["Account"],"operationId":"resumeMyEmail","summary":"Send email to my address again","description":"The person's word that their address takes email again (DEV-111), after SES\nreported a hard bounce or a complaint for it and Roundr stopped writing to it\n(`email_suppression` in the preferences). Clears the stop, whether there was one or\nnot, and answers with the preferences as they now read. A court device, a display\nand a machine key have no address and are refused.\n","security":[{"sessionCookie":[]},{"bearerAuth":[]}],"responses":{"200":{"description":"The preferences, with `email_suppression` null.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotificationPreferencesResponse"}}}},"401":{"description":"No session or token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"A court device, a display or a machine key, which has no account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/me/push-subscriptions":{"get":{"tags":["Account"],"operationId":"myPushSubscriptions","summary":"The devices the account turned web push on in","description":"Each browser the account turned push on in (DEV-45), oldest first, by what it is\nlisted as (its browser and system, read from the user agent when it was turned on)\nand when its push service last took or refused a message. The endpoint and the keys\nare never sent back.\n","security":[{"sessionCookie":[]},{"bearerAuth":[]}],"responses":{"200":{"description":"The devices.","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["subscriptions"],"properties":{"subscriptions":{"type":"array","items":{"$ref":"#/components/schemas/PushSubscription"}}}}}}}}},"401":{"description":"No session or token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"A court device, a display or a machine key, which has no account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}},"put":{"tags":["Account"],"operationId":"saveMyPushSubscription","summary":"Turn web push on for this browser","description":"Keeps the subscription the browser's `PushManager.subscribe()` gave, as\n`PushSubscription.toJSON()` writes it, against the account (DEV-45). Match-day\nnotifications the account wants by push go to it from then on: a match called to\nits court and a next match moved, the player's own and those of players the account\nfollows on Live. On a customer host it is told only that organization's news, since\nits service worker opens that host's pages; on the platform's site, every\norganization's.\n\nSaving the same browser again updates it and keeps its id, which is how a client\nlearns the id of its own device. An endpoint another account held moves to this one:\none browser is one device. Only the push services of the browsers Roundr supports\nare taken (Google's, Mozilla's, Apple's and Microsoft's).\n","security":[{"sessionCookie":[]},{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["endpoint","keys"],"properties":{"endpoint":{"type":"string","format":"uri","maxLength":2048},"keys":{"type":"object","required":["p256dh","auth"],"properties":{"p256dh":{"type":"string","description":"The browser's P-256 public key, base64url."},"auth":{"type":"string","description":"The browser's 16-byte authentication secret, base64url."}}}}}}}},"responses":{"200":{"description":"The device, as the list shows it.","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["subscription"],"properties":{"subscription":{"$ref":"#/components/schemas/PushSubscription"}}}}}}}},"401":{"description":"No session or token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"A court device, a display or a machine key, which has no account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"`validation_failed` on `endpoint` (not https, or not at a browser push service), `keys.p256dh` or `keys.auth`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"description":"`push_unavailable`: this instance sends no web push.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/me/push-subscriptions/{subscriptionId}":{"delete":{"tags":["Account"],"operationId":"removeMyPushSubscription","summary":"Turn web push off for a device","description":"The device is told nothing more. The web app removes its own device when the person\nsigns out there and unsubscribes the browser as well; a device whose browser gave the\nsubscription up is removed by the relay when its push service says so.\n","security":[{"sessionCookie":[]},{"bearerAuth":[]}],"parameters":[{"name":"subscriptionId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"The device is gone.","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["removed"],"properties":{"removed":{"type":"boolean","const":true}}}}}}}},"401":{"description":"No session or token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"A court device, a display or a machine key, which has no account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"The account has no device of this id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/me/umpired-matches":{"get":{"tags":["Account"],"operationId":"myUmpiredMatches","summary":"The matches the account umpired on its own panel","description":"The finished personal matches the signed-in person kept on their account\n(DEV-34), newest ended first, fifty at a time. `next_before` is null on the last\npage; sent back as `before`, it asks for the page after. The host a request arrives\non changes nothing: the matches are the account's wherever it signs in.\n","security":[{"sessionCookie":[]},{"bearerAuth":[]}],"parameters":[{"name":"before","in":"query","required":false,"description":"The `next_before` of the page before, as it was given. Opaque.","schema":{"type":"string"}}],"responses":{"200":{"description":"A page of matches, possibly none.","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["matches","next_before"],"properties":{"matches":{"type":"array","maxItems":50,"items":{"$ref":"#/components/schemas/UmpiredMatchSummary"}},"next_before":{"type":["string","null"],"description":"Where the next page starts, or null when this is the last."}}}}}}}},"401":{"description":"No session or token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"A court device, a display or a machine key, which has no account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"`validation_failed` on `before`: not a cursor this list gave out.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/me/umpired-matches/{matchId}":{"parameters":[{"name":"matchId","in":"path","required":true,"description":"The match id the device generated for the match.","schema":{"type":"string","format":"uuid"}}],"get":{"tags":["Account"],"operationId":"getMyUmpiredMatch","summary":"One of the account's umpired matches, with its history","description":"The match as kept: its summary, the state the server's replay started from and\nthe events it derived, from which a client rebuilds the timeline, the statistics\nand the scoresheet. The stored events are the record, not a fresh replay, so an\nengine that changes later does not change a match kept before it.\n","security":[{"sessionCookie":[]},{"bearerAuth":[]}],"responses":{"200":{"description":"The match.","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/UmpiredMatch"}}}}}},"401":{"description":"No session or token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"A court device, a display or a machine key, which has no account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No match of this account under that id, whether none exists or another account's does.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}},"put":{"tags":["Account"],"operationId":"saveMyUmpiredMatch","summary":"Keep a finished match on the account","description":"Sent once the result is confirmed on the device: the ruleset, the client's own\ndescription of the match and the accepted commands in the order the device\naccepted them. The server replays them through the ruleset's adapter from its\ninitial state, as it decides a tournament match's commands for the device that\nholds its scoring session, undo included, and keeps its own reading: the events,\nthe state they end in and the result read off it (DEV-34).\n\nA command the rules refuse, an unknown type, a command id sent twice,\n`correct_score` or `reopen_result`, or a sequence that does not end in a\nconfirmed result refuses the whole upload as `umpired_match_invalid`.\n\nIdempotent by match id. The same commands again answer 200 with the match as it\nwas kept, `setup` included; different commands under an id already kept, or an id\nanother account holds, are `idempotency_key_reused`, one answer for both.\n`started_at` and `ended_at` are the first and last commands' `authored_at`, the\nupload's time where the last command sent none.\n","security":[{"sessionCookie":[]},{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UmpiredMatchUpload"}}}},"responses":{"200":{"description":"The same commands were kept under this id already; the match as it was kept.","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/UmpiredMatch"}}}}}},"201":{"description":"The match is kept.","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/UmpiredMatch"}}}}}},"401":{"description":"No session or token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"A court device, a display or a machine key, which has no account; or `csrf_rejected`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"`idempotency_key_reused`: the id is kept with other commands, or by another\naccount. `umpired_match_limit_reached`: the account keeps as many matches as it\nmay, `details.limit` of them, until one is deleted; a retry of a match already\nkept is still answered 200.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"`validation_failed`: the body is not an upload, `details.validation_errors`\nnaming the fields, a command's as `commands.{index}.{field}`, the first\nmalformed command only. `ruleset_not_supported`: this deployment does not run\nthe ruleset. `umpired_match_invalid`: the commands do not replay;\n`details.index` names the command, from zero, or is null when every command\nreplayed and the match is not confirmed, and `details.reason` says why: a\nrejection reason (`invalid_command_for_state`, `not_undoable`,\n`match_complete`, `interval_in_progress`, `result_already_confirmed`), or\n`unknown_command_type`, `duplicate_command_id`, `command_not_allowed` or\n`not_confirmed`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}},"delete":{"tags":["Account"],"operationId":"deleteMyUmpiredMatch","summary":"Delete one of the account's umpired matches","description":"Only the person deletes a match; there is nobody else it belongs to.","security":[{"sessionCookie":[]},{"bearerAuth":[]}],"responses":{"200":{"description":"The match is gone.","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["id"],"properties":{"id":{"type":"string","format":"uuid"}}}}}}}},"401":{"description":"No session or token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"A court device, a display or a machine key, which has no account; or `csrf_rejected`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No match of this account under that id, whether none exists or another account's does.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/me/registrations":{"get":{"tags":["Registration"],"operationId":"myRegistrations","summary":"The account's registrations and the invitations it was sent","description":"Every registration naming the signed-in person, as the one who registered or as\nthe partner named, newest first; `you` marks their own player, and an invitation is\ntheir player not yet `confirmed`. An invitation withdrawn or declined before it was\nconfirmed is not listed to the invited account. On a tenant host, that organization's alone; on\nthe shared API host, every active organization's, as `GET /v1/me/matches` reads.\n","security":[{"sessionCookie":[]},{"bearerAuth":[]}],"parameters":[{"$ref":"#/components/parameters/juniorId"}],"responses":{"200":{"description":"The registrations, possibly none.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegistrationList"}}}},"401":{"description":"No session or token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"A court device, a display or a machine key, which has no account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"A `junior_id` that is not a junior of the caller's.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}},"post":{"tags":["Registration"],"operationId":"registerForEvent","summary":"Enter an event of a tournament","description":"On the tournament's own host or the platform's site, where its Live page is, for a\nsigned-in person who needs no membership of the organization. A single enters alone; a pair names the partner by the address their\nRoundr account holds and by `partner_name`, the name the registrant gives them. The\naddress is looked up only once the tournament takes entries in the event and the\nevent is a pair's, and nothing of the account behind it is read: the partner is\ninvited under `partner_name` and confirms from their own account, where their own\nname takes over. Whether the partner already holds a place in the event is told to\nthe partner at confirmation, never here. An account holds one place in an event\nwhile its registration is pending or accepted. A tournament neither on Live nor\ntaking entries is not found; one on Live that takes none is refused as `closed`,\nand one on Live that has been closed as `tournament_closed`.\nA lookup spends two budgets, 20 an hour per account and 100 an hour per client\naddress, and is refused with `429` over either or while either cannot be checked.\n","security":[{"sessionCookie":[]},{"bearerAuth":[]}],"parameters":[{"$ref":"#/components/parameters/juniorId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["tournament_id","draw_id"],"description":"The registrant's name is `full_name`, or its parts, `given_name`,\n`family_name` and `family_name_first`, read as an import reads a player's\n(DEV-28): a string `family_name` makes the name its parts and `full_name`\nis not read. The partner's is `partner_name` or its parts under `partner_`.\n","properties":{"tournament_id":{"type":"string","format":"uuid"},"draw_id":{"type":"string","format":"uuid"},"full_name":{"type":"string","maxLength":200,"description":"The name the registrant enters under, the account's by default. Required without `family_name`."},"given_name":{"type":["string","null"]},"family_name":{"type":["string","null"]},"family_name_first":{"type":"boolean"},"partner_email":{"type":["string","null"],"maxLength":254},"partner_name":{"type":["string","null"],"maxLength":200,"description":"Required with `partner_email`, unless `partner_family_name` is given; how the partner is shown until they confirm."},"partner_given_name":{"type":["string","null"]},"partner_family_name":{"type":["string","null"]},"partner_family_name_first":{"type":"boolean"},"partner_junior_id":{"type":["string","null"],"format":"uuid","description":"Instead of `partner_email`: one of the caller's juniors, confirmed at once,\nsince the caller answers for both (DEV-36). `partner_name` is optional\nbeside it; the junior's record names them by default.\n"},"partner_self":{"type":"boolean","description":"Instead of `partner_email`, with `junior_id`, the caller themselves as the junior's partner, confirmed at once."}}}}}},"responses":{"201":{"description":"The registration, pending.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegistrationResponse"}}}},"401":{"description":"No session or token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"A court device, a display or a machine key, which has no account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not a tournament this host may show: another organization's on a tenant\nhost, one on the demo's host, neither on Live nor taking entries, or none by\nthat id.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"`tournament_closed`: the tournament has been closed (DEV-25).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"`validation_failed`: a field of the wrong shape, `partner_name` missing beside\n`partner_email`, or `details.problem`: `closed` (on `draw_id`: the tournament or\nthe event takes no entries now), `not_open_yet` (on `draw_id`: entries open\nlater), `already_entered`, `date_of_birth_required` (the event has an age\ncategory and the account gives no date of birth, `PUT /v1/me/profile`) or\n`age_ineligible` (the account's date of birth is outside it),\n`player_class_required` (the event admits some classes and the account gives no\nclass in its discipline) or `class_ineligible` (the account's class in the\nevent's discipline is not one it admits), or on `partner_email`\n`partner_required`, `partner_not_wanted`, `partner_unknown` (no active account\nholds the address) or `partner_is_self`, or on `partner_junior_id` a partner the\ncaller does not answer for. The partner's age and class are not checked here\nwhen named by address: they learn them themselves when they confirm. A partner\nthe caller answers for is checked here, since they are confirmed here.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/me/registrations/{registrationId}":{"delete":{"tags":["Registration"],"operationId":"withdrawRegistration","summary":"Withdraw an entry, or decline an invitation","description":"Either player withdraws a pending registration, freeing both places, or an accepted\none while its draw is a draft not yet placed, which takes the entry out of the draw\nand keeps the participant and the decision (DEV-27). Both only until the\ntournament's withdrawal deadline; after it the staff withdraw an entry. The partner\nit named declines the invitation the same way, deadline or not, since they hold no\nplace. A registration withdrawn already is answered as it stands; a declined one is\nrefused.\n","security":[{"sessionCookie":[]},{"bearerAuth":[]}],"parameters":[{"$ref":"#/components/parameters/registrationId"},{"$ref":"#/components/parameters/juniorId"}],"responses":{"200":{"description":"The registration as it now stands.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegistrationResponse"}}}},"401":{"description":"No session or token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"A court device, a display or a machine key, which has no account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not a registration naming the account, or on a tenant host not that organization's.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"`tournament_closed`: the tournament of the registration has been closed (DEV-25).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"`validation_failed` with `details.problem` `not_pending` (the staff declined\nit), `withdrawals_closed` (the withdrawal deadline or the tournament's last day\nhas passed) or `draw_made` (accepted into a draw being placed or published).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/me/registrations/{registrationId}/confirmation":{"post":{"tags":["Registration"],"operationId":"confirmRegistration","summary":"Confirm a place as the partner a registration named","description":"The invited partner confirms under the name given, or their own account's, which\nreplaces the name the registrant gave them, and the pair is complete for the staff\nto accept. The name is `full_name` or its parts, as at registration; a whole name\nthat is exactly the one the registrant gave keeps the parts they entered. Refused as `already_entered` when the account holds a place in the event\nalready: only the partner learns that.\n\nWith `junior_id`, a guardian confirms for their junior (DEV-36): an invitation the\njunior holds, or one addressed to the guardian's own account, which is how another\nfamily names a junior. The place becomes the junior's, their record is checked\nagainst the event's age and class rules, and their record's name is the default.\n","security":[{"sessionCookie":[]},{"bearerAuth":[]}],"parameters":[{"$ref":"#/components/parameters/registrationId"},{"$ref":"#/components/parameters/juniorId"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"full_name":{"type":["string","null"],"maxLength":200},"given_name":{"type":["string","null"]},"family_name":{"type":["string","null"]},"family_name_first":{"type":"boolean"}}}}}},"responses":{"200":{"description":"The registration as it now stands.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegistrationResponse"}}}},"401":{"description":"No session or token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"A court device, a display or a machine key, which has no account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not a registration naming the account, or on a tenant host not that organization's.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"`tournament_closed`: the tournament of the registration has been closed (DEV-25).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"`validation_failed`: `full_name` of the wrong shape, or `details.problem`\n`not_invited` (nothing for this account to confirm), `not_pending`,\n`already_entered`, or, for an event with an age category,\n`date_of_birth_required` or `age_ineligible` by the partner's own date of birth,\nand for an event with a class rule `player_class_required` or `class_ineligible`\nby the partner's own class.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/organizations":{"post":{"tags":["Authentication"],"operationId":"createOrganization","summary":"Create an organization","description":"For any browser session on the shared API host: an account is free to create, and\nso is an organization, which the person then administers (DEV-15). The slug is\nderived from the name and is an operator's handle only; a tenant hostname is a\nseparate provisioning step. An account creates at most 5 organizations\n(`validation_errors.organizations` past that), a bound on what a script can leave\nbehind rather than a security boundary.\n\nAs choosing an organization, the session is replaced by one acting for the new\norganization as `organization_admin`, so the response carries a new CSRF token\nand sets a new `roundr_session`. A tenant host acts for its own organization and\ncreates none.\n","security":[{"sessionCookie":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","minLength":1,"maxLength":200,"description":"The organization's name, as its staff and players will see it."},"kind":{"allOf":[{"$ref":"#/components/schemas/OrganizationKind"}],"default":"club","description":"What the organization is (DEV-39), the person's choice."}}}}}},"responses":{"201":{"description":"The new session, acting for the new organization.","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/AuthSession"}}}}}},"401":{"description":"No session.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"`csrf_rejected`: no Roundr page `Origin`, or a wrong `X-CSRF-Token`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"`validation_failed`: no name or one over 200 characters (`name`), a kind that is\nneither (`kind`), not a browser session or a tenant host (`name`), or the account\nhas created as many organizations as it may (`organizations`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/organization/administrators":{"get":{"tags":["Control"],"operationId":"listOwnOrganizationAdministrators","summary":"The organization's administrators","description":"For an administrator of the organization (DEV-626): every administrator by name, a\nsuspended one marked inactive.\n","responses":{"200":{"description":"The administrators.","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["administrators"],"properties":{"administrators":{"type":"array","items":{"$ref":"#/components/schemas/OrganizationAdministrator"}}}}}}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"description":"`insufficient_scope`: not an administrator of the organization, or a device or a key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}},"post":{"tags":["Control"],"operationId":"admitOwnOrganizationAdministrator","summary":"Make another person an administrator of the organization","description":"For an administrator of the organization (DEV-626). The person is named by the\naddress their Roundr account registered with, as a tournament role is granted;\nsomeone who never signed in has no account, and the address is refused. An address\nfinds an account and links nothing. The person is told in their account, and by\nemail where they asked for it, unless they admitted themselves. Admitting an\nadministrator again changes nothing and answers 200.\n","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email"],"properties":{"email":{"type":"string","maxLength":254}}}}}},"responses":{"200":{"description":"Already an administrator; `granted` is false.","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["administrators"],"properties":{"administrators":{"type":"array","items":{"$ref":"#/components/schemas/OrganizationAdministrator"}}}}}}}}},"201":{"description":"Made an administrator; `granted` is true.","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["administrators"],"properties":{"administrators":{"type":"array","items":{"$ref":"#/components/schemas/OrganizationAdministrator"}}}}}}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"description":"`insufficient_scope`: not an administrator of the organization, or a device or a key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"`validation_failed` under `email`: no address, or no Roundr account uses it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/organization/administrators/{userId}":{"delete":{"tags":["Control"],"operationId":"removeOwnOrganizationAdministrator","summary":"Take an administrator's role away","description":"For an administrator of the organization (DEV-626), themselves included, as long as\nanother active administrator remains: the last one is refused with\n`organization_needs_administrator`, so the organization is never left without one.\nThe person stays a member and keeps any tournament role.\n","parameters":[{"name":"userId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"The administrators that remain.","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["administrators"],"properties":{"administrators":{"type":"array","items":{"$ref":"#/components/schemas/OrganizationAdministrator"}}}}}}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"description":"`insufficient_scope`: not an administrator of the organization, or a device or a key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"`not_found`: not an administrator of this organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"`organization_needs_administrator`: the last active administrator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/organization/domains":{"get":{"tags":["Control"],"operationId":"listOwnOrganizationDomains","summary":"The organization's hostnames","description":"For an administrator of the organization. By name, with where each stands and which\nis primary. No verification token: claiming and proving a hostname is a platform\noperator's.\n","responses":{"200":{"description":"The hostnames, possibly none.","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["domains"],"properties":{"domains":{"type":"array","items":{"$ref":"#/components/schemas/OrganizationHostname"}}}}}}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"$ref":"#/components/responses/OrganizationHostnameForbidden"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/organization/domains/{domainId}/primary":{"post":{"tags":["Control"],"operationId":"makeOwnOrganizationDomainPrimary","summary":"Choose the hostname the organization's links name","description":"For an administrator of the organization. The active hostname whose sign-in is\nregistered becomes primary: the\njoin link and QR code of every court, display and setup code issued from then on,\nand the Live link Control shares, name it, from whichever host they are issued. The\none before it is primary for nobody. Another organization's hostname is not found.\n","parameters":[{"name":"domainId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"The hostname, primary.","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["domain"],"properties":{"domain":{"$ref":"#/components/schemas/OrganizationHostname"}}}}}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"$ref":"#/components/responses/OrganizationHostnameForbidden"},"404":{"description":"`not_found`: not a hostname of this organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"`validation_failed` under `primary`, and nothing changed. `details.reason` is\n`not_verified`, `not_registered` (its sign-in is not registered yet, so it may\nnot serve Roundr), `disabled` or `reserved`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/organization/integration-keys":{"get":{"tags":["Control"],"operationId":"listIntegrationKeys","summary":"The organization's API keys","description":"For an administrator of the organization. Newest first: every key still active,\nand every key revoked or expired in the last 30 days, so what was just ended is\nstill seen. A key's secret is never shown again after it was issued, and its\ndigest never leaves the server.\n","responses":{"200":{"description":"The keys, possibly none.","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["keys"],"properties":{"keys":{"type":"array","items":{"$ref":"#/components/schemas/IntegrationKey"}}}}}}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"$ref":"#/components/responses/IntegrationKeyForbidden"},"429":{"$ref":"#/components/responses/RateLimited"}}},"post":{"tags":["Control"],"operationId":"issueIntegrationKey","summary":"Issue an API key","description":"For an administrator of the organization: a `key_` credential for the\norganization's own system, presented as `X-API-Key` or a bearer token. `scopes`\nsays what it may do, and nothing else is ever granted: `tournaments:read` reads the\ntournaments, their participants, feeds, matches and results, and\n`tournaments:import` loads participants and matches as the CSV import does. A key\nscores nothing and administers nothing else whatever it holds, and it never issues\na key. With `tournament_id` it sees that tournament alone.\n\nEvery key expires, after `expires_in_days` (90 unless given, 366 at most). An\norganization holds at most 25 active keys, and at most what its plan allows inside\nthat (DEV-19). The secret is in this response and\nnowhere else, which is sent `Cache-Control: no-store`: the server keeps only its\ndigest, so a lost secret is revoked and replaced, never read back.\n","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["label","scopes"],"properties":{"label":{"type":"string","minLength":1,"maxLength":120,"description":"What the key is for, as staff will recognize it in the list."},"scopes":{"type":"array","minItems":1,"items":{"$ref":"#/components/schemas/IntegrationScope"}},"tournament_id":{"oneOf":[{"type":"null"},{"type":"string","format":"uuid"}],"description":"A tournament of the organization, or null for all of them."},"expires_in_days":{"type":"integer","minimum":1,"maximum":366,"default":90}}}}}},"responses":{"201":{"description":"The key, with its one copy of the secret.","headers":{"Cache-Control":{"description":"`no-store`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["key","secret"],"properties":{"key":{"$ref":"#/components/schemas/IntegrationKey"},"secret":{"type":"string","description":"The `key_` credential. Shown once; store it in the system that will use it."}}}}}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"$ref":"#/components/responses/IntegrationKeyForbidden"},"409":{"$ref":"#/components/responses/PlanLimitReached"},"422":{"description":"`validation_failed`, and nothing was issued: a missing or over-long `label`, no\nscope or one this deployment does not know, a `tournament_id` that is not one\nof the organization's tournaments, or a lifetime outside 1 to 366 days, each\nunder its field; or 25 active keys already, under `validation_errors.keys`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/organization/integration-keys/{keyId}":{"delete":{"tags":["Control"],"operationId":"revokeIntegrationKey","summary":"Revoke an API key","description":"For an administrator of the organization. The key is refused as\n`credential_revoked` from its next request. Revoking a key already revoked answers\n`revoked: false`. Another organization's key is not found.\n","parameters":[{"name":"keyId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Whether this request revoked the key.","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["revoked"],"properties":{"revoked":{"type":"boolean"}}}}}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"$ref":"#/components/responses/IntegrationKeyForbidden"},"404":{"description":"No such key in the organization, or the id is malformed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/organization/plan":{"get":{"tags":["Control"],"operationId":"getOrganizationPlan","summary":"The organization's plan","description":"For an administrator of the organization: the plan it is on, who set it, and how\nmuch of each allowance it uses (DEV-19), and `billing`, what this deployment lets\nthe administrator do about it (DEV-41). Read only: a platform operator sets the\nplan, or Stripe's webhook does for a subscription; the administrator subscribes\nthrough `startBillingCheckout` and manages it through `openBillingPortal`. A\ndirector, a device and a key are refused.\n","responses":{"200":{"description":"The plan.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrganizationOwnPlanAnswer"}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"description":"`insufficient_scope`: not an administrator of the organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/organization/billing/checkout-session":{"post":{"tags":["Control"],"operationId":"startBillingCheckout","summary":"Subscribe the organization to a paid plan","description":"For an administrator of the organization: open a Stripe-hosted Checkout Session to\nsubscribe the organization to the `event` or `club` plan (DEV-40, DEV-41), and\nreturn its URL for the client to send the admin to. Stripe collects the card, the\nbilling address and, for a business, its VAT number; the plan is written by the\nwebhook when the payment confirms, not by the return from Checkout, which lands on\n`/settings/plan` of the host the request came to, with `checkout=complete`\nor `checkout=cancelled`. The organization is the one the administrator acts for. A\ndirector, a device and a key are refused. Exists only where a payment provider is\nconfigured; otherwise an operator sets the paid plan and this endpoint is absent.\n","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["plan"],"properties":{"plan":{"type":"string","enum":["event","club"],"description":"The plan to subscribe to. Only `event` and `club` are sold."},"locale":{"type":"string","maxLength":35,"description":"The admin's interface locale. Checkout, and the line on which the admin agrees to the terms and asks for the subscription to begin at once, are shown in German for a German locale and in English otherwise, the two languages the terms exist in. Absent is English."}}}}}},"responses":{"200":{"description":"The Checkout Session was opened. Send the admin to `checkout_url`.","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["checkout_url"],"properties":{"checkout_url":{"type":"string","format":"uri","description":"The Stripe-hosted page to redirect the administrator to."}}}}}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"description":"`insufficient_scope`: not an administrator of the organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"`billing_refused`, with `details.reason`: `subscription_active` (the\norganization is billed on a subscription already; change plan in the Portal) or\n`plan_agreed_separately` (an operator set its paid plan).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"No plan, or a plan that is not on sale.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/organization/billing/portal-session":{"post":{"tags":["Control"],"operationId":"openBillingPortal","summary":"Manage the organization's subscription","description":"For an administrator of the organization: open a session of Stripe's Customer\nPortal and return its URL (DEV-41). There the administrator cancels the\nsubscription, switches between the plans, changes the card and reads the invoices;\nwhat they change reaches the plan through the webhook. The Portal sends them back\nto `/settings/plan` of the host the request came to. A director, a device\nand a key are refused. Exists only where a payment provider is configured.\n","responses":{"200":{"description":"The Portal session was opened. Send the admin to `portal_url`.","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["portal_url"],"properties":{"portal_url":{"type":"string","format":"uri","description":"The Stripe-hosted page to redirect the administrator to."}}}}}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"description":"`insufficient_scope`: not an administrator of the organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"`billing_refused`, with `details.reason` `not_a_customer`: the organization has never subscribed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/webhooks/ses":{"post":{"tags":["Account"],"operationId":"receiveSesFeedback","summary":"SES bounces and complaints, by Amazon SNS","description":"Where SES reports a hard bounce or a complaint (DEV-111), delivered by Amazon SNS,\nso Roundr stops writing to the address. Anonymous: SNS presents no credential, so a\nmessage is believed only when its `TopicArn` is one configured in\n`SES_FEEDBACK_TOPIC_ARNS`, its signature verifies against the certificate at its\n`SigningCertURL`, an Amazon SNS host, and its `Timestamp` is within an hour. An\ninstance with no topic configured believes no message.\n\nA `SubscriptionConfirmation` is confirmed by fetching its `SubscribeURL`, an Amazon\nSNS address only. A `Notification` whose message is a permanent bounce stops each\nbounced recipient's account, and a complaint each complaining recipient's; a\ntransient bounce, a delivery and anything else stop nothing. SNS retries a `5xx`\nand takes any other answer as delivered, so the status is the contract: `200` for a\nmessage acted on or with nothing to act on, `403` for one that does not verify,\n`422` for a body that is no SNS message, `500` for a failure a retry may clear.\n","security":[],"requestBody":{"required":true,"content":{"text/plain":{"schema":{"type":"string","description":"An SNS message, JSON, as SNS posts it with `Content-Type` text/plain."}},"application/json":{"schema":{"type":"object","description":"An SNS message. Only the signed fields and the SES message inside are read."}}}},"responses":{"200":{"description":"The message was believed, and acted on where it named a bounce or a complaint.","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["received"],"properties":{"received":{"type":"boolean","const":true}}}}}}}},"403":{"description":"The message does not verify, or names a topic this instance did not subscribe.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"`validation_failed`: the body is empty, too large, or no SNS message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"`internal_error`: the stop could not be stored, or the subscription not confirmed. SNS retries.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/organization/official-rotations":{"get":{"tags":["Control"],"operationId":"listOfficialRotations","summary":"The organization's rotations of officials","description":"Each rotation of umpires and service judges the organization keeps (DEV-113), with\nthe number of its courts and officials and the tournaments its courts belong to. A\nrotation is the organization's, so its courts may be two tournaments' played in one\nhall at once. For an administrator and the directors and referees of its\ntournaments.\n","responses":{"200":{"description":"The rotations, by name.","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["rotations"],"properties":{"rotations":{"type":"array","items":{"type":"object","required":["id","name","courts","officials","tournaments"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"courts":{"type":"integer"},"officials":{"type":"integer"},"tournaments":{"type":"array","items":{"type":"string"}}}}}}}}}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"description":"`insufficient_scope`: not an administrator, nor a director or referee of one of the organization's tournaments; or a device or a key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}},"post":{"tags":["Control"],"operationId":"createOfficialRotation","summary":"Start a rotation of officials","description":"A new rotation, with no courts and no officials yet. The name is unique among the organization's.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","minLength":1,"maxLength":120}}}}}},"responses":{"201":{"description":"The new rotations board.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OfficialRotationBoardResponse"}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"description":"`insufficient_scope`: not an administrator, nor a director or referee of one of the organization's tournaments; or a device or a key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"`validation_failed` on `name`: empty, too long, or another rotations.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/organization/official-rotations/{rotationId}":{"get":{"tags":["Control"],"operationId":"getOfficialRotation","summary":"A rotation's board","description":"The board the desk arranges (DEV-113): each court with its umpire and service judge,\nwhether it takes a service judge, and the match its crew serves now (the one in play,\nor the court's next) with the officials who share a Member with one of its sides; the\ntwo queues head first; the reserve; every official with the matches they have\numpired or judged by discipline; and the courts the caller may add. The board is not\nstreamed: a desk reads it again every few seconds while it is open.\n","parameters":[{"name":"rotationId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"The board.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OfficialRotationBoardResponse"}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"description":"`insufficient_scope`: not an administrator, nor a director or referee of one of the organization's tournaments; or a device or a key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"`not_found`: no such rotation, or one serving courts only of tournaments the caller does not run.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/organization/official-rotations/{rotationId}/courts":{"put":{"tags":["Control"],"operationId":"setOfficialRotationCourts","summary":"Choose the courts a rotation serves","description":"The courts, in the board's order, each of one of the organization's tournaments not\nclosed, with whether it takes a service judge. A court is added only by someone who\nruns its tournament, and a court another rotation serves is refused. An official on a\ncourt left out, or in a service judge's seat closed, goes back to the tail of their\nqueue.\n","parameters":[{"name":"rotationId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["courts"],"properties":{"courts":{"type":"array","maxItems":40,"items":{"type":"object","required":["court_id"],"properties":{"court_id":{"type":"string","format":"uuid"},"has_service_judge":{"type":"boolean","default":true}}}}}}}}},"responses":{"200":{"description":"The board as it now stands.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OfficialRotationBoardResponse"}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"description":"`insufficient_scope`: not an administrator, nor a director or referee of one of the organization's tournaments; or a device or a key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"`not_found`: no such rotation, or one serving courts only of tournaments the caller does not run.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"`validation_failed` under `courts`: not a list, a court not the organizations or of a closed tournament, one named twice, or one in another rotation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/organization/official-rotations/{rotationId}/officials":{"post":{"tags":["Control"],"operationId":"addOfficial","summary":"Add an official to a rotation","description":"An umpire, a service judge or both, off the rotation until the desk places them. An\nofficial is a name the desk types, or the account at `email`, whose name is taken\nunless `name` is given too; an account is in a rotation once, and is told by push\nwhen it is put on a court.\n","parameters":[{"name":"rotationId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["umpires","judges_service"],"properties":{"name":{"type":"string","maxLength":200},"email":{"type":"string"},"nationality":{"type":["string","null"],"pattern":"^[A-Za-z]{3}$"},"umpires":{"type":"boolean"},"judges_service":{"type":"boolean"}}}}}},"responses":{"201":{"description":"The board with the official.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OfficialRotationBoardResponse"}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"description":"`insufficient_scope`: not an administrator, nor a director or referee of one of the organization's tournaments; or a device or a key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"`not_found`: no such rotation, or one serving courts only of tournaments the caller does not run.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"`validation_failed` on `name`, `email` (no such account, or in the rotation already), `nationality` or `umpires` (neither role taken).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/organization/official-rotations/{rotationId}/officials/{officialId}":{"patch":{"tags":["Control"],"operationId":"updateOfficial","summary":"Correct an official","description":"Their name, Member and the roles they take. A role taken away takes them off the rotation where they stood in it.","parameters":[{"name":"rotationId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"officialId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","umpires","judges_service"],"properties":{"name":{"type":"string","maxLength":200},"nationality":{"type":["string","null"]},"umpires":{"type":"boolean"},"judges_service":{"type":"boolean"}}}}}},"responses":{"200":{"description":"The board as it now stands.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OfficialRotationBoardResponse"}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"description":"`insufficient_scope`: not an administrator, nor a director or referee of one of the organization's tournaments; or a device or a key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"`not_found`: no such rotation, or one serving courts only of tournaments the caller does not run.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"`validation_failed` on `name`, `nationality` or `umpires`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/organization/official-rotations/{rotationId}/arrangement":{"put":{"tags":["Control"],"operationId":"arrangeOfficialRotation","summary":"Arrange a rotation's board","description":"The whole board as the desk sees it after a drag (DEV-113): each court's umpire and\nservice judge, the two queues head first, and the reserve. An official it does not\nname is off the rotation. Sent with the `revision` it was read at: a board changed\nsince, by another desk or by the rotator's turn when a match ended, is refused with\n`409 revision_conflict` and `details.current_revision`, and read again. Each court's\ncrew is written on the match it serves at once, so a crew changed on a match in play\nis that match's.\n","parameters":[{"name":"rotationId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["revision","courts","umpires","service_judges","reserve"],"properties":{"revision":{"type":"integer"},"courts":{"type":"array","items":{"type":"object","required":["court_id"],"properties":{"court_id":{"type":"string","format":"uuid"},"umpire_id":{"type":["string","null"],"format":"uuid"},"service_judge_id":{"type":["string","null"],"format":"uuid"}}}},"umpires":{"type":"array","items":{"type":"string","format":"uuid"}},"service_judges":{"type":"array","items":{"type":"string","format":"uuid"}},"reserve":{"type":"array","items":{"type":"object","required":["id","role"],"properties":{"id":{"type":"string","format":"uuid"},"role":{"$ref":"#/components/schemas/OfficialRole"}}}}}}}}},"responses":{"200":{"description":"The board as it now stands.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OfficialRotationBoardResponse"}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"description":"`insufficient_scope`: not an administrator, nor a director or referee of one of the organization's tournaments; or a device or a key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"`not_found`: no such rotation, or one serving courts only of tournaments the caller does not run.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"`revision_conflict`: the board changed since it was read; `details.current_revision` is the one it has.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"`validation_failed` on the field that cannot stand: an official not the rotations or named twice, a role they do not take, a court not the rotations, or a service judge on a court that takes none.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/organization/player-classes":{"get":{"tags":["Control"],"operationId":"getPlayerClassScale","summary":"The organization's player classes","description":"The classes the organization grades players in, strongest first, A to D until an\nadministrator names others (DEV-31). For the organization's staff, who choose a\nplayer's class and a draw's classes from it. A device and a key are refused.\n","responses":{"200":{"description":"The scale.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlayerClassScaleAnswer"}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"description":"`insufficient_scope`: a device or a key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}},"put":{"tags":["Control"],"operationId":"setPlayerClassScale","summary":"Name the organization's player classes","description":"For an administrator of the organization: one to ten different codes, strongest\nfirst. Nothing recorded is rewritten: a class the new scale drops stays on the\nplayers and draws that name it, and still counts where a draw admits it.\n","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["classes"],"properties":{"classes":{"type":"array","minItems":1,"maxItems":10,"items":{"type":"string","maxLength":3}}}}}}},"responses":{"200":{"description":"The scale as it now stands.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlayerClassScaleAnswer"}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"description":"`insufficient_scope`: not an administrator of the organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"`validation_failed` on `classes`: not one to ten different codes of one to three letters or digits.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/organization/name":{"put":{"tags":["Control"],"operationId":"renameOrganization","summary":"Rename the organization","description":"For an administrator of the organization (DEV-674): the name, trimmed, of 1 to 200\ncharacters, as a new organization's. The slug stays, so no host or link changes.\nLive, the league pages, Control and the emails read the new name on their next\nread; a published tournament's poll answers 200 again rather than 304.\n","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","minLength":1,"maxLength":200}}}}}},"responses":{"200":{"description":"The name as it now stands.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrganizationNameAnswer"}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"description":"`insufficient_scope`: not an administrator of the organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"`validation_failed` on `name`: empty once trimmed, or longer than 200 characters.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/organization/memberships":{"get":{"tags":["Control"],"operationId":"listAssociationMemberships","summary":"The organization's memberships of associations, and of its members","description":"Every membership the organization the session acts for is on either side of\n(DEV-39): the associations it belongs to or has asked to join, and, for an\nassociation, the clubs and associations that belong to it. Open ones first, newest\nfirst, and the ended ones kept as the record. For the organization's staff; a\ndevice and a key are refused.\n","responses":{"200":{"description":"The memberships, possibly none.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssociationMembershipsAnswer"}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"description":"`insufficient_scope`: a device or a key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}},"post":{"tags":["Control"],"operationId":"proposeAssociationMembership","summary":"Invite a member, or ask to join an association","description":"For an administrator of the organization, which is one of the two sides: as the\nassociation it invites the member (`invited`), as the member it asks to join\n(`requested`). The other side's administrator accepts. Only the association gives a\nmember number, and the association named must be one.\n","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["association_id","member_id","sport"],"properties":{"association_id":{"type":"string","format":"uuid"},"member_id":{"type":"string","format":"uuid","description":"A club, or an association that belongs to a larger one."},"sport":{"$ref":"#/components/schemas/SportCode"},"member_number":{"type":["string","null"],"maxLength":20,"description":"The association's number for the member. The association alone sends one."}}}}}},"responses":{"201":{"description":"The proposed membership.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssociationMembershipAnswer"}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"description":"`insufficient_scope`: not an administrator of the organization, or a device or a key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"`not_found`: the organization is neither side, or the other side is not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"`membership_refused`, with `details.reason`: `not_an_association`,\n`its_own_member`, `already_open` (a membership in the sport is asked for or\nactive), `circular` (the member is the association's own association in the\nsport) or `only_the_association` (a member sent a number).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"`validation_failed` under `association_id`, `member_id`, `sport` or `member_number`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/organization/memberships/{membershipId}/accept":{"parameters":[{"name":"membershipId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"post":{"tags":["Control"],"operationId":"acceptAssociationMembership","summary":"Accept an invitation or a request","description":"For an administrator of the side the membership waits for: the association\naccepting a request, the member accepting an invitation. The membership is active\nfrom then on.\n","responses":{"200":{"description":"The membership as it now stands.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssociationMembershipAnswer"}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"description":"`insufficient_scope`: not an administrator of the organization, or a device or a key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"`not_found`: no such membership on either side of the organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"`membership_refused`, with `details.reason`: `not_awaiting_you` (the other\nside answers), `ended` (propose a new one), or `only_the_association` (the\nmember does not number itself).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/organization/memberships/{membershipId}/end":{"parameters":[{"name":"membershipId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"post":{"tags":["Control"],"operationId":"endAssociationMembership","summary":"End a membership, or decline or withdraw a proposal","description":"For an administrator of either side, answered or not: a club leaving, an\nassociation removing a member, or a proposal declined or taken back. The membership\nis kept as ended; a new one is proposed afresh.\n","responses":{"200":{"description":"The membership, ended.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssociationMembershipAnswer"}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"description":"`insufficient_scope`: not an administrator of the organization, or a device or a key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"`not_found`: no such membership on either side of the organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"`membership_refused`, with `details.reason`: `not_awaiting_you` (the other\nside answers), `ended` (propose a new one), or `only_the_association` (the\nmember does not number itself).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/organization/memberships/{membershipId}/number":{"parameters":[{"name":"membershipId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"put":{"tags":["Control"],"operationId":"setAssociationMemberNumber","summary":"Number a member","description":"For an administrator of the association: its number for the member, as its\nregister has it, or null to clear it. Surrounding spaces are dropped, and an empty\nnumber clears it too.\n","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["member_number"],"properties":{"member_number":{"type":["string","null"],"maxLength":20}}}}}},"responses":{"200":{"description":"The membership as it now stands.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssociationMembershipAnswer"}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"description":"`insufficient_scope`: not an administrator of the organization, or a device or a key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"`not_found`: no such membership on either side of the organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"`membership_refused`, with `details.reason`: `not_awaiting_you` (the other\nside answers), `ended` (propose a new one), or `only_the_association` (the\nmember does not number itself).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"`validation_failed` on `member_number`: more than 20 characters, or not a string.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/organization/directory":{"get":{"tags":["Control"],"operationId":"searchOrganizations","summary":"Find an organization by name","description":"For an administrator of the organization, to find the other side of a membership\n(DEV-39). Every word of `q` appears in the name, in any order. Only active\norganizations, never the one the session acts for, by name, at most 20. Names are\npublic on Live already, so the answer is nothing a stranger could not read.\n","parameters":[{"name":"q","in":"query","required":true,"schema":{"type":"string","minLength":2,"maxLength":100}},{"name":"kind","in":"query","required":false,"schema":{"$ref":"#/components/schemas/OrganizationKind"}}],"responses":{"200":{"description":"The organizations found, possibly none.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrganizationDirectoryAnswer"}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"description":"`insufficient_scope`: not an administrator of the organization, or a device or a key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"`validation_failed` on `q` (2 to 100 characters) or `kind`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/organization/club-members":{"get":{"tags":["Control"],"operationId":"listClubMembers","summary":"The club's members","description":"The people who are members of the club the session acts for, or asked or were\ninvited to be (DEV-39), with the numbers the club keeps. Open ones first, newest\nfirst, the ended ones kept as the record. For the club's staff; a device and a key\nare refused. An association has no members of this kind and reads none.\n","responses":{"200":{"description":"The memberships, possibly none.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClubMembershipsAnswer"}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"description":"`insufficient_scope`: a device or a key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}},"post":{"tags":["Control"],"operationId":"inviteClubMember","summary":"Invite a person to the club","description":"For an administrator of the club: the person whose Roundr account uses `email` is\ninvited (`invited`) and accepts or declines from their account. Only people with an\naccount for now; one who has none signs in once, which creates it.\n","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email","sport"],"properties":{"email":{"type":"string","maxLength":254},"sport":{"$ref":"#/components/schemas/SportCode"},"member_number":{"type":["string","null"],"maxLength":20,"description":"The club's own number for the member."},"licence_number":{"type":["string","null"],"maxLength":30,"description":"The member's licence number with the federation, which a league checks later."}}}}}},"responses":{"201":{"description":"The invitation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClubMembershipAnswer"}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"description":"`insufficient_scope`: not an administrator of the club, or a device or a key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"`membership_refused`, with `details.reason`: `not_a_club` (the organization is an association) or `already_open`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"`validation_failed` under `email` (none, or no account uses it), `sport`, `member_number` or `licence_number`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/organization/club-members/{membershipId}/accept":{"parameters":[{"name":"membershipId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"post":{"tags":["Control"],"operationId":"acceptClubMember","summary":"Accept a person's request to join","description":"For an administrator of the club, on a request the person made.","responses":{"200":{"description":"The membership as it now stands.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClubMembershipAnswer"}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"description":"`insufficient_scope`: not an administrator of the club, or a device or a key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"`not_found`: no such membership of the club.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"`membership_refused`, with `details.reason`: `not_awaiting_you` (the other side answers) or `ended` (propose a new one).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/organization/club-members/{membershipId}/end":{"parameters":[{"name":"membershipId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"post":{"tags":["Control"],"operationId":"endClubMember","summary":"Remove a member, or decline or withdraw a proposal","description":"For an administrator of the club, answered or not. The membership is kept as ended.","responses":{"200":{"description":"The membership, ended.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClubMembershipAnswer"}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"description":"`insufficient_scope`: not an administrator of the club, or a device or a key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"`not_found`: no such membership of the club.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"`membership_refused`, with `details.reason`: `not_awaiting_you` (the other side answers) or `ended` (propose a new one).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/organization/club-members/{membershipId}/numbers":{"parameters":[{"name":"membershipId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"put":{"tags":["Control"],"operationId":"setClubMemberNumbers","summary":"The club's numbers for a member","description":"For an administrator of the club: both numbers are replaced, each cleared by null\nor an empty one, surrounding spaces dropped. Nobody is told.\n","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"member_number":{"type":["string","null"],"maxLength":20,"description":"The club's own number for the member."},"licence_number":{"type":["string","null"],"maxLength":30,"description":"The member's licence number with the federation, which a league checks later."}}}}}},"responses":{"200":{"description":"The membership as it now stands.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClubMembershipAnswer"}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"description":"`insufficient_scope`: not an administrator of the club, or a device or a key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"`not_found`: no such membership of the club.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"`membership_refused`, with `details.reason`: `not_awaiting_you` (the other side answers) or `ended` (propose a new one).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"`validation_failed` on `member_number` or `licence_number`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/organization/clubs":{"get":{"tags":["Control"],"operationId":"searchClubsForStaff","summary":"Find a club by name, for the staff","description":"For the organization's staff, to name the club a tournament's player stands for\n(DEV-39): every active club whose name contains every word of `q`, by name, at\nmost 20, whichever host the search is made on. A device and a key are refused.\n","parameters":[{"name":"q","in":"query","required":true,"schema":{"type":"string","minLength":2,"maxLength":100}}],"responses":{"200":{"description":"The clubs found, possibly none.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClubDirectoryAnswer"}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"description":"`insufficient_scope`: a device or a key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"`validation_failed` on `q` (2 to 100 characters).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/organization/leagues":{"get":{"tags":["Control"],"operationId":"listLeagues","summary":"The leagues the organization runs","description":"Every league of the organization the session acts for (DEV-49), newest season\nfirst, each with its tie. For the organization's staff; a device and a key are\nrefused.\n","responses":{"200":{"description":"The leagues, possibly none.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LeaguesAnswer"}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"description":"`insufficient_scope`: a device or a key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}},"post":{"tags":["Control"],"operationId":"createLeague","summary":"Make a league","description":"For an administrator of the organization, an association or a club, on a plan that\nruns leagues (Club or larger, DEV-48). Its tie is a `format` preset or `rubbers`\nlisted in score-sheet order, one of the two. Points default to 2 for a tie won, 1\ndrawn and 0 lost, the guest allowance to none, and entries to closed.\n","requestBody":{"required":true,"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/LeagueSettingsRequest"},{"type":"object","required":["name","season","sport"],"properties":{"sport":{"$ref":"#/components/schemas/SportCode"}}}]}}}},"responses":{"201":{"description":"The league, with no division and no team yet.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LeagueAnswer"}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"description":"`insufficient_scope`: not an administrator of the organization, or a device or a key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"`league_refused` with `details.reason` `name_taken` (another league of the\norganization has the name in the season), or `plan_limit_reached` with\n`details.limit` `leagues` (the plan runs no leagues).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"`validation_failed` under `name`, `season`, `sport`, `starts_on`, `ends_on`, `points`, `guest_allowance`, `entries_open`, `format` or `rubbers`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/organization/leagues/{leagueId}":{"parameters":[{"name":"leagueId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"get":{"tags":["Control"],"operationId":"getLeague","summary":"A league with its divisions and teams","description":"For the staff of the organization that runs it. Teams are listed by division in\nthe league's order, entries still to be placed after them, and withdrawn teams last.\n","responses":{"200":{"description":"The league.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LeagueAnswer"}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"description":"`insufficient_scope`: a device or a key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"`not_found`: no such league of the organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}},"put":{"tags":["Control"],"operationId":"updateLeague","summary":"Change a league's settings or its tie","description":"For an administrator of the organization that runs it. A field left out keeps its\nvalue; a `format` or `rubbers` replaces the tie. The sport is the league's from its\ncreation.\n","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LeagueSettingsRequest"}}}},"responses":{"200":{"description":"The league as it now stands.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LeagueAnswer"}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"description":"`insufficient_scope`: not an administrator of the organization, or a device or a key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"`not_found`: no such league of the organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"`league_refused` with `details.reason` `name_taken`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"`validation_failed` under `name`, `season`, `starts_on`, `ends_on`, `points`, `guest_allowance`, `entries_open`, `format` or `rubbers`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}},"delete":{"tags":["Control"],"operationId":"deleteLeague","summary":"Delete a league no team was entered in","description":"For an administrator of the organization that runs it. A league a club has ever\nentered a team in stays, as the record of who entered it.\n","responses":{"200":{"description":"Deleted, with its divisions and tie.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeletedAnswer"}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"description":"`insufficient_scope`: not an administrator of the organization, or a device or a key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"`not_found`: no such league of the organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"`league_refused` with `details.reason` `has_teams`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/organization/leagues/{leagueId}/divisions":{"parameters":[{"name":"leagueId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"post":{"tags":["Control"],"operationId":"addLeagueDivision","summary":"Add a division below the league's others","description":"For an administrator of the organization that runs the league.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LeagueDivisionRequest"}}}},"responses":{"201":{"description":"The division.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LeagueDivisionAnswer"}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"description":"`insufficient_scope`: not an administrator of the organization, or a device or a key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"`not_found`: no such league of the organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"`league_refused` with `details.reason` `name_taken`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"`validation_failed` under `name`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/organization/leagues/{leagueId}/divisions/{divisionId}":{"parameters":[{"name":"leagueId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"divisionId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"put":{"tags":["Control"],"operationId":"renameLeagueDivision","summary":"Rename a division","description":"For an administrator of the organization that runs the league.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LeagueDivisionRequest"}}}},"responses":{"200":{"description":"The division.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LeagueDivisionAnswer"}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"description":"`insufficient_scope`: not an administrator of the organization, or a device or a key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"`not_found`: no such division of the organization's league.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"`league_refused` with `details.reason` `name_taken`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"`validation_failed` under `name`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}},"delete":{"tags":["Control"],"operationId":"deleteLeagueDivision","summary":"Delete a division no team was placed in","description":"For an administrator of the organization that runs the league.","responses":{"200":{"description":"Deleted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeletedAnswer"}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"description":"`insufficient_scope`: not an administrator of the organization, or a device or a key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"`not_found`: no such division of the organization's league.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"`league_refused` with `details.reason` `has_teams`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/organization/leagues/{leagueId}/teams":{"parameters":[{"name":"leagueId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"post":{"tags":["Control"],"operationId":"addLeagueTeam","summary":"Enter a team by name, for a club that is not on Roundr","description":"For an administrator of the organization that runs the league (DEV-51). The team\nhas no club (`club` null), is placed in the division as it is entered, and is kept by\nthe organization: its roster and its line-ups are saved through\n`/v1/organization/teams/{teamId}` as a club's own team's are. It has no captains,\nand it leaves the league through `remove`.\n","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","division_id"],"properties":{"name":{"type":"string","maxLength":60},"division_id":{"type":"string","format":"uuid"}}}}}},"responses":{"201":{"description":"The team, placed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LeagueTeamAnswer"}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"description":"`insufficient_scope`: not an administrator of the organization, or a device or a key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"`not_found`: no such league of the organization, or division of it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"`league_refused` with `details.reason` `name_taken`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"`validation_failed` under `name` or `division_id`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/organization/leagues/{leagueId}/teams/{teamId}/place":{"parameters":[{"name":"leagueId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"teamId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"post":{"tags":["Control"],"operationId":"placeLeagueTeam","summary":"Place an entered team in a division, or move a placed one","description":"For an administrator of the organization that runs the league: accepting a club's\nentry by placing it, or moving a team between divisions.\n","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["division_id"],"properties":{"division_id":{"type":"string","format":"uuid"}}}}}},"responses":{"200":{"description":"The team as it now stands.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LeagueTeamAnswer"}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"description":"`insufficient_scope`: not an administrator of the organization, or a device or a key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"`not_found`: no such team, or division, of the organization's league.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"`league_refused` with `details.reason` `withdrawn`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"`validation_failed` under `division_id`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/organization/leagues/{leagueId}/teams/{teamId}/remove":{"parameters":[{"name":"leagueId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"teamId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"post":{"tags":["Control"],"operationId":"removeLeagueTeam","summary":"Decline an entry, or take a team out of the league","description":"For an administrator of the organization that runs the league. The team is\n`withdrawn` and stays as the record; its name may be entered again.\n","responses":{"200":{"description":"The team as it now stands.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LeagueTeamAnswer"}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"description":"`insufficient_scope`: not an administrator of the organization, or a device or a key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"`not_found`: no such team of the organization's league.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"`league_refused` with `details.reason` `withdrawn`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/organization/leagues/{leagueId}/teams/{teamId}":{"parameters":[{"name":"leagueId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"teamId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"get":{"tags":["Control"],"operationId":"getLeagueTeam","summary":"A team of the league, with its roster and captains","description":"For the staff of the organization that runs the league (DEV-49), who read a roster\nand never write it. A device and a key are refused.\n","responses":{"200":{"description":"The team.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TeamRosterAnswer"}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"description":"`insufficient_scope`: a device or a key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"`not_found`: no such team of the organization's league.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/organization/leagues/{leagueId}/schedule":{"parameters":[{"name":"leagueId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"get":{"tags":["Control"],"operationId":"getLeagueSchedule","summary":"A league's fixtures and match days","description":"For the staff of the organization that runs the league (DEV-49): every fixture by\ndivision, round and place in the round, and the match days by date, each with the\ntournament its ties are played in once it is opened.\n","responses":{"200":{"description":"The schedule.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LeagueScheduleAnswer"}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"description":"`insufficient_scope`: a device or a key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"`not_found`: no such league of the organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/organization/leagues/{leagueId}/divisions/{divisionId}/fixtures":{"parameters":[{"name":"leagueId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"divisionId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"post":{"tags":["Control"],"operationId":"makeLeagueFixtures","summary":"Make a division's fixtures","description":"For an administrator of the organization that runs the league: a double round robin\nof the division's placed teams, every team meeting every other once at each one's\nhome, replacing the fixtures it had. Refused while any is on a match day, and on a\nplan without leagues: a club whose plan lapsed plays the fixtures it has (DEV-675).\n","responses":{"200":{"description":"The schedule as it now stands.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LeagueScheduleAnswer"}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"description":"`insufficient_scope`: not an administrator of the organization, or a device or a key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"`not_found`: no such division of the organization's league.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"`league_refused` with `details.reason` `too_few_teams` or `fixtures_scheduled`; `plan_limit_reached` with `details.limit` `leagues` on a plan without leagues.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}},"delete":{"tags":["Control"],"operationId":"clearLeagueFixtures","summary":"Clear a division's fixtures","description":"For an administrator of the organization that runs the league, while none of the\ndivision's fixtures is on a match day; its teams may then leave or move.\n","responses":{"200":{"description":"The schedule as it now stands.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LeagueScheduleAnswer"}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"description":"`insufficient_scope`: not an administrator of the organization, or a device or a key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"`not_found`: no such division of the organization's league.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"`league_refused` with `details.reason` `fixtures_scheduled`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/organization/leagues/{leagueId}/match-days":{"parameters":[{"name":"leagueId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"post":{"tags":["Control"],"operationId":"addLeagueMatchDay","summary":"Add a match day","description":"For an administrator of the organization that runs the league, on a plan with\nleagues. A club whose plan lapsed opens and plays the match days it has, and adds\nnone (DEV-675): each opens as a tournament that holds no plan place (DEV-49).\n","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","plays_on","venue_time_zone"],"properties":{"name":{"type":"string","minLength":1,"maxLength":80},"plays_on":{"type":"string","format":"date"},"venue_name":{"type":["string","null"],"maxLength":120},"venue_time_zone":{"type":"string","description":"An IANA time zone, which the match day's tournament takes."}}}}}},"responses":{"201":{"description":"The schedule with the match day.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LeagueScheduleAnswer"}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"description":"`insufficient_scope`: not an administrator of the organization, or a device or a key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"`not_found`: no such league of the organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"`plan_limit_reached` with `details.limit` `leagues`: the plan has no leagues.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"`validation_failed` under `name`, `plays_on`, `venue_name` or `venue_time_zone`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/organization/leagues/{leagueId}/match-days/{matchDayId}":{"parameters":[{"name":"leagueId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"matchDayId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"delete":{"tags":["Control"],"operationId":"deleteLeagueMatchDay","summary":"Delete a match day that has not been opened","description":"For an administrator of the organization that runs the league. Its fixtures come off\nit and wait for another.\n","responses":{"200":{"description":"The schedule as it now stands.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LeagueScheduleAnswer"}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"description":"`insufficient_scope`: not an administrator of the organization, or a device or a key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"`not_found`: no such match day of the organization's league.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"`league_refused` with `details.reason` `match_day_open`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/organization/leagues/{leagueId}/match-days/{matchDayId}/open":{"parameters":[{"name":"leagueId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"matchDayId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"post":{"tags":["Control"],"operationId":"openLeagueMatchDay","summary":"Open a match day","description":"For an administrator of the organization that runs the league: makes the draft\ntournament the match day's ties are played in, named for the league and the day, on\nits date and at its venue, where the hall is run as in any tournament. It is marked as\nthe league's match day and takes no place against the plan's running tournaments\n(DEV-49). It opens as the league's last opened match day at the same venue was set\nup (DEV-54): that tournament's settings, its courts and its hall displays, as far\nas the plan has room for them, and never its codes, its devices or its place on Live.\nA match day at another venue, and a league's first, opens with none.\n","responses":{"200":{"description":"The schedule, the match day with its `tournament_id`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LeagueScheduleAnswer"}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"description":"`insufficient_scope`: not an administrator of the organization, or a device or a key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"`not_found`: no such match day of the organization's league.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"`league_refused` with `details.reason` `match_day_open` or `no_fixtures`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/organization/leagues/{leagueId}/fixtures/{fixtureId}/match-day":{"parameters":[{"name":"leagueId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"fixtureId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"put":{"tags":["Control"],"operationId":"assignLeagueFixture","summary":"Put a fixture on a match day, or take it off one","description":"For an administrator of the organization that runs the league. `start_time` says\nwhen the fixture starts on that day, at the venue's clock (DEV-51): left out, the\nfixture keeps the time it has, and null clears it. A fixture on no match day has no\ntime. `match_day_id` is always said: null takes the fixture off its match day, and a\nrequest that leaves it out is refused, so one that meant only the time cannot lose\nthe day with it.\n","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["match_day_id"],"properties":{"match_day_id":{"type":["string","null"],"format":"uuid"},"start_time":{"type":["string","null"],"pattern":"^([01][0-9]|2[0-3]):[0-5][0-9]$","description":"`HH:MM`, 24 hours, at the match day's venue."}}}}}},"responses":{"200":{"description":"The schedule as it now stands.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LeagueScheduleAnswer"}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"description":"`insufficient_scope`: not an administrator of the organization, or a device or a key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"`not_found`: no such fixture or match day of the organization's league.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"`league_refused` with `details.reason` `tie_started`: the tie has started, and its fixture stays on its match day at its time.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"`validation_failed` under `match_day_id` or `start_time`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/organization/leagues/{leagueId}/standings":{"parameters":[{"name":"leagueId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"get":{"tags":["Control"],"operationId":"getLeagueStandings","summary":"Every division's ties and table","description":"For the staff of the organization that runs the league (DEV-49). Read from the\nrubbers' matches each time: a tie is decided once every rubber is confirmed, and the\ntable counts decided ties by the league's points, ordered by points, rubber\ndifference, games difference, the ties between the teams still level, then name.\n","responses":{"200":{"description":"The divisions.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LeagueStandingsAnswer"}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"description":"`insufficient_scope`: a device or a key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"`not_found`: no such league of the organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/organization/leagues/{leagueId}/fixtures/{fixtureId}/tie":{"parameters":[{"name":"leagueId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"fixtureId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"get":{"tags":["Control"],"operationId":"getLeagueTie","summary":"A tie with both line-ups","description":"For the staff of the organization that runs the league.","responses":{"200":{"description":"The tie.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LeagueTieAnswer"}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"description":"`insufficient_scope`: a device or a key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"`not_found`: no such fixture of the organization's league.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/organization/leagues/{leagueId}/fixtures/{fixtureId}/start":{"parameters":[{"name":"leagueId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"fixtureId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"post":{"tags":["Control"],"operationId":"startLeagueTie","summary":"Start a tie","description":"For an administrator of the organization that runs the league, once the fixture's\nmatch day is open and both teams have given their line-up (DEV-49). Each player\nbecomes a player of the match day's tournament, linked to their account and club,\neach side of a rubber a participant, and each rubber a match, coded\n`D<division>R<round>.<place>-<rubber>`, which is scored as every match is. The\nline-ups, and the league's tie and points, stay as they are from then on. A rubber\none side left out of its line-up becomes no match: the tie records which side had\nno player and counts it for the other (DEV-53). A tie no rubber of which both\nteams field is refused as `nothing_to_play`. A match day whose tournament has been\nclosed takes no tie until it is reopened (`tournament_closed`, DEV-25), and\nnothing is written; the teams' line-ups are the league's and are still given.\n","responses":{"200":{"description":"The tie, started.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LeagueTieAnswer"}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"description":"`insufficient_scope`: not an administrator of the organization, or a device or a key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"`not_found`: no such fixture of the organization's league.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"`league_refused` with `details.reason` `match_day_not_open`, `lineups_missing`,\n`nothing_to_play` or `tie_started`; or `tournament_closed`: the match day's\ntournament has been closed (DEV-25).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/organization/teams":{"get":{"tags":["Control"],"operationId":"listClubTeams","summary":"The club's teams in leagues","description":"Every team the organization the session acts for has entered in a league, its own\nleagues' and others' (DEV-49), open ones first. For the organization's staff; a\ndevice and a key are refused.\n","responses":{"200":{"description":"The teams, possibly none.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LeagueTeamsAnswer"}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"description":"`insufficient_scope`: a device or a key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}},"post":{"tags":["Control"],"operationId":"enterLeagueTeam","summary":"Enter a team in a league","description":"For an administrator of a club on a plan that takes part in leagues (Club or larger,\nDEV-48), in a league whose entries are open: any club's, or an association's the\nclub is an active member of in the league's sport. The team waits for the league's\nadministrators to place it in a division.\n","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["league_id","name"],"properties":{"league_id":{"type":"string","format":"uuid"},"name":{"type":"string","minLength":1,"maxLength":60,"description":"The team's name, such as `VRC 2`, once among the league's open teams."}}}}}},"responses":{"201":{"description":"The team, `entered`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LeagueTeamAnswer"}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"description":"`insufficient_scope`: not an administrator of the organization, or a device or a key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"`not_found`: no such league.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"`league_refused` with `details.reason` `not_a_club`, `entries_closed`,\n`not_a_member` or `name_taken`, or `plan_limit_reached` with `details.limit`\n`league_teams` (the club's plan takes part in no leagues).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"`validation_failed` under `league_id` or `name`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/organization/teams/{teamId}/withdraw":{"parameters":[{"name":"teamId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"post":{"tags":["Control"],"operationId":"withdrawLeagueTeam","summary":"Withdraw the club's team","description":"For an administrator of the club whose team it is, entered or placed. The team stays\nas the record; its name may be entered again.\n","responses":{"200":{"description":"The team, `withdrawn`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LeagueTeamAnswer"}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"description":"`insufficient_scope`: not an administrator of the organization, or a device or a key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"`not_found`: no such team of the club.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"`league_refused` with `details.reason` `withdrawn`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/organization/teams/{teamId}":{"parameters":[{"name":"teamId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"get":{"tags":["Control"],"operationId":"getClubTeam","summary":"One of the club's teams, with its roster and captains","description":"For the club's staff (DEV-49), with `candidates`: the club's active members in the\nleague's sport, whom the roster and the captains are chosen from. A team the\norganization's own league entered by name is read here too (DEV-51): it has no\nclub, so `candidates` is empty, and the organization keeps its roster and line-ups\nthrough this path and those below it as a club keeps its own team's.\n","responses":{"200":{"description":"The team.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TeamRosterAnswer"}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"description":"`insufficient_scope`: a device or a key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"`not_found`: no such team of the club.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/organization/teams/{teamId}/roster":{"parameters":[{"name":"teamId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"put":{"tags":["Control"],"operationId":"saveClubTeamRoster","summary":"Replace a team's roster","description":"For an administrator of the club whose team it is (DEV-49). The roster is checked\nagainst the league's rule as it is saved: a member of the club in the league's sport is\nthe club's own, anyone else a guest, up to the league's `guest_allowance`, and a person\nplays for one team per league. A refusal keeps the roster as it was. On a team\nwithout a club (DEV-51), kept by the organization that runs its league, nobody is\na guest.\n","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RosterRequest"}}}},"responses":{"200":{"description":"The team with its new roster.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TeamRosterAnswer"}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"description":"`insufficient_scope`: not an administrator of the organization, or a device or a key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"`not_found`: no such team of the club.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"`league_refused` with `details.reason` `no_such_account` (no account holds a\nguest's address, or a member's account is gone), `too_many_guests`,\n`on_another_team` or `withdrawn`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"`validation_failed` under `members`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/organization/teams/{teamId}/captains":{"parameters":[{"name":"teamId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"put":{"tags":["Control"],"operationId":"nameClubTeamCaptains","summary":"Name a team's captains","description":"For an administrator of the club whose team it is (DEV-49): up to three of the\nclub's active members in the league's sport, replacing those named before. A captain\nkeeps the team's roster from their own account (`/v1/me/teams`).\n","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["user_ids"],"properties":{"user_ids":{"type":"array","maxItems":3,"items":{"type":"string","format":"uuid"}}}}}}},"responses":{"200":{"description":"The team with its captains.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TeamRosterAnswer"}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"description":"`insufficient_scope`: not an administrator of the organization, or a device or a key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"`not_found`: no such team of the club.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"`league_refused` with `details.reason` `not_a_club_member` or `withdrawn`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"`validation_failed` under `user_ids`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/organization/teams/{teamId}/season":{"parameters":[{"name":"teamId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"get":{"tags":["Control"],"operationId":"getClubTeamSeason","summary":"A club team's season","description":"For the club's staff (DEV-49): the team's league, its match days, and its\ndivision's ties and table once it is placed.\n","responses":{"200":{"description":"The season.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TeamSeasonAnswer"}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"description":"`insufficient_scope`: a device or a key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"`not_found`: no such team of the club.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/organization/teams/{teamId}/fixtures/{fixtureId}/lineup":{"parameters":[{"name":"teamId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"fixtureId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"get":{"tags":["Control"],"operationId":"getClubTeamLineup","summary":"A club team's line-up for one of its fixtures","description":"For the club's staff: the tie, its rubbers, the roster, the line-up given, and the\nother side's line-up once the tie has started (`opponent`, null before). Until then\n`previous` is the line-up the team gave last, for another fixture, to fill this one\nin from.\n","responses":{"200":{"description":"The line-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LineupAnswer"}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"description":"`insufficient_scope`: a device or a key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"`not_found`: no such fixture of the club's team.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}},"put":{"tags":["Control"],"operationId":"saveClubTeamLineup","summary":"Give or change a club team's line-up","description":"For an administrator of the club, until the tie starts: a player or a pair from the\nroster for every rubber, each player in two rubbers at most and once in any one. A\nrubber the team has nobody for is marked `not_fielded` (DEV-53).\n`new_players` are put on the roster by name as the line-up is saved, so a visiting\nteam's sheet is typed in once; a refused line-up leaves the roster as it was.\n","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LineupRequest"}}}},"responses":{"200":{"description":"The line-up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LineupAnswer"}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"description":"`insufficient_scope`: not an administrator of the organization, or a device or a key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"`not_found`: no such fixture of the club's team.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"`league_refused` with `details.reason` `not_on_roster`, `plays_too_often`, `roster_full` or `tie_started`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"`validation_failed` under `rubbers`: every rubber of the tie once, with one player or a pair as its ruleset says or `not_fielded` and nobody, and one rubber fielded at least. Under `new_players`: a list of names.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/organization/open-leagues":{"get":{"tags":["Control"],"operationId":"listOpenLeagues","summary":"Leagues the club may enter a team in now","description":"Leagues whose entries are open and that would take the club's: any club's, the\nclub's own among them, and an association's the club is an active member of in the\nleague's sport. For the organization's staff; a device and a key are refused.\n","responses":{"200":{"description":"The leagues, possibly none.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LeaguesAnswer"}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"description":"`insufficient_scope`: a device or a key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/operator/organizations/{organizationId}/plan":{"parameters":[{"name":"organizationId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}]},"/v1/operator/organizations/{organizationId}/status":{"parameters":[{"name":"organizationId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}]},"/v1/operator/organizations/{organizationId}/administrators":{"parameters":[{"name":"organizationId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}]},"/v1/operator/organizations/{organizationId}/domains":{"parameters":[{"name":"organizationId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}]},"/v1/tournaments/{tournamentId}/participants":{"get":{"tags":["Control"],"operationId":"listParticipants","summary":"The participants of a tournament, with their players","description":"Every participant of the tournament by display name, each with its players in\nposition order. Open to staff, a court device and a machine key issued\n`tournaments:read`, because a court may be given any match. Not to a display: its\nsnapshot carries only the sides of the matches its projection shows, and every\nplayer's name here would undo that. The snapshot carries the same participants\nwithout their players; this is the read for a screen that needs the players, or\nfor a client that holds no snapshot.\n","parameters":[{"$ref":"#/components/parameters/tournamentId"}],"responses":{"200":{"description":"The participants, possibly none.","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["participants"],"properties":{"participants":{"type":"array","items":{"$ref":"#/components/schemas/Participant"}}}}}}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"description":"`insufficient_scope`: a machine key that was not issued `tournaments:read`,\nrefused before the tournament is looked up, or a display of this tournament.\nA display of another tournament is answered 404 like anyone outside it.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"$ref":"#/components/responses/TournamentNotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/tournaments/{tournamentId}/players/{playerId}/account":{"put":{"tags":["Control"],"operationId":"linkPlayerAccount","summary":"Link a player to a Roundr account","description":"For an administrator of the organization or a director of the tournament: the\nplayer becomes the account registered with the address, replacing any account it\nnamed before, as a tournament role is granted by an address. The link authorizes\nnothing; it is what the account's own view of its results and upcoming matches\nwill join through (DEV-15). One account is one player per tournament, so an\naddress already linked to another player of it is refused. The person must hold\nan account already, and the answer says when the address holds none.\n","parameters":[{"$ref":"#/components/parameters/tournamentId"},{"$ref":"#/components/parameters/playerId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email"],"properties":{"email":{"type":"string","maxLength":254},"junior_name":{"type":["string","null"],"maxLength":200,"description":"With a guardian's address, the name that guardian keeps for the junior\nto link (DEV-36), compared without case, accents or extra spaces. Each\nsuch try spends the partner lookup's budgets.\n"}}}}}},"responses":{"200":{"description":"The participant the player belongs to, as staff now see it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ParticipantResponse"}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"$ref":"#/components/responses/ControlForbidden"},"404":{"description":"Not a tournament of the organization, or not a player of it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"`validation_failed` on `email`: not an address, no account uses it, or the\naccount is already another player of the tournament; or on `junior_name`: no\njunior of that guardian's is kept under that name.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}},"delete":{"tags":["Control"],"operationId":"unlinkPlayerAccount","summary":"Unlink a player from its Roundr account","description":"For an administrator of the organization or a director of the tournament. The\nplayer stays; only the account it named is removed. A player that named none is\nanswered the same way.\n","parameters":[{"$ref":"#/components/parameters/tournamentId"},{"$ref":"#/components/parameters/playerId"}],"responses":{"200":{"description":"The participant the player belongs to, as staff now see it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ParticipantResponse"}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"$ref":"#/components/responses/ControlForbidden"},"404":{"description":"Not a tournament of the organization, or not a player of it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/tournaments/{tournamentId}/players/{playerId}/classes":{"put":{"tags":["Control"],"operationId":"setPlayerClasses","summary":"Record a player's classes","description":"For an administrator of the organization or a director of the tournament: the\nplayer's class in each discipline, replacing what was recorded, the import's or the\naccount's (DEV-31). The way to correct a player a registration created, whom the\nimport cannot name again. Every discipline is required, a code or null.\n","parameters":[{"$ref":"#/components/parameters/tournamentId"},{"$ref":"#/components/parameters/playerId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["singles","doubles","mixed"],"properties":{"singles":{"type":["string","null"],"maxLength":3},"doubles":{"type":["string","null"],"maxLength":3},"mixed":{"type":["string","null"],"maxLength":3}}}}}},"responses":{"200":{"description":"The participant the player belongs to, as staff now see it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ParticipantResponse"}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"$ref":"#/components/responses/ControlForbidden"},"404":{"description":"Not a tournament of the organization, or not a player of it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"`tournament_closed`: the tournament has been closed (DEV-25).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"`validation_failed` on `classes`: a discipline missing or unknown, or a value that is not a class code.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/tournaments/{tournamentId}/players/{playerId}/club":{"put":{"tags":["Control"],"operationId":"setPlayerClub","summary":"Name the club a player stands for","description":"For an administrator of the organization or a director of the tournament: the club\non Roundr the player stands for (DEV-39), or null for none. A club named fills the\nplayer's `association` where it has none, since draw separation and Live read that\ntext; one already written stays. The way to name the club of a player a registration\ncreated, whom the import cannot name again. An accepted entry, or an account linked\nto the player, names the account's own club by itself when it belongs to exactly one.\n","parameters":[{"$ref":"#/components/parameters/tournamentId"},{"$ref":"#/components/parameters/playerId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["club_id"],"properties":{"club_id":{"type":["string","null"],"format":"uuid"}}}}}},"responses":{"200":{"description":"The participant the player belongs to, as staff now see it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ParticipantResponse"}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"$ref":"#/components/responses/ControlForbidden"},"404":{"description":"Not a tournament of the organization, or not a player of it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"`tournament_closed`: the tournament has been closed (DEV-25).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"`validation_failed` on `club_id`: not an id, or not an active club on Roundr.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/tournaments/{tournamentId}/check-ins":{"get":{"tags":["Control"],"operationId":"listCheckIns","summary":"Read who has checked in at the desk","description":"Every player of the tournament the desk has checked in, taken a check-in back from,\nor marked away (DEV-44); a player not listed has not checked in. Per player, not\nper entry: a player in singles and doubles reports once.\n\nFor the desk: an administrator of the organization, or a director or the referee of\nthe tournament. Any other person, a court device, a hall display and a machine key\nare refused once the tournament is found. Live and the hall displays never show\ncheck-in, and the tournament feed announces a change as a `player_check_in`\nnotification to the staff alone; a device, a display and a key see a checkpoint.\n","parameters":[{"$ref":"#/components/parameters/tournamentId"}],"responses":{"200":{"description":"The check-ins the desk has recorded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckInListResponse"}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"$ref":"#/components/responses/ControlForbidden"},"404":{"description":"Not a tournament of the organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/tournaments/{tournamentId}/players/{playerId}/check-in":{"patch":{"tags":["Control"],"operationId":"changeCheckIn","summary":"Check a player in, take it back, or mark them away","description":"The desk checks a player in (`present: true`) or takes it back (`present: false`),\nand sets the time before which the player is not to be called (`away_until`, \"back\nat 14:30\" or \"arriving at 11:00\"), or clears it with null (DEV-44). A part left out\nstays as it is, and the change is applied under the player's lock, so two desk\nlaptops changing different parts both land. Checking in a player already present\nkeeps who took the first check-in and when; taking it back clears both.\n\nA change moves the check-in's own `revision` and is announced on the tournament feed\nas `player_check_in_changed` on the `player_check_in` aggregate; one that changes\nnothing writes and announces nothing. It is no match command and never moves\n`match_revision`. Nothing is refused for it: Control flags a match whose players\nhave not all checked in, and the desk decides.\n\nFor the desk: an administrator of the organization, or a director or the referee of\nthe tournament.\n","parameters":[{"$ref":"#/components/parameters/tournamentId"},{"$ref":"#/components/parameters/playerId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","minProperties":1,"properties":{"present":{"type":"boolean"},"away_until":{"type":["string","null"],"format":"date-time","description":"An instant with its zone; null for not away."}}}}}},"responses":{"200":{"description":"The player's check-in as it now stands.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckInResponse"}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"$ref":"#/components/responses/ControlForbidden"},"404":{"description":"Not a tournament of the organization, or not a player of it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"`tournament_closed`: the tournament has been closed (DEV-25).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"`validation_failed` on `present` (not a boolean, or neither part named) or `away_until` (not an instant with its zone).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/tournaments/{tournamentId}/import":{"post":{"tags":["Control"],"operationId":"importTournamentData","summary":"Load participants and matches from a draw file","description":"Takes the rows of an entry or draw file the client has already parsed, so the\nserver accepts one JSON shape whatever spreadsheet produced it. Every row is keyed\nby the external reference the file gives it: a participant, a player or a match\nwhose reference the tournament already holds is updated in place, so a corrected\nfile loads again over the first one instead of beside it. Nothing is deleted; a\nrow missing from a later file stays. An entry already in the tournament, and a\nplayer, may be named by `id` instead, as Control corrects an entry a registration\nmade, which has no reference.\n\nParticipants are written first, so a match may name a participant from the same\nfile. A match's sides are participant references, and its `court` is a court name,\nmatched without regard to case. A match that has been scored keeps its sides,\nbecause its history would otherwise name players who never played it; its court\nand slot still move. A row naming the reference of a knockout draw's match is\nrefused: the draw sets that match's sides.\n\nThe import is one transaction, all or nothing. Rows are validated before anything\nis written, and a reference that names no participant or court, or an id the\ntournament does not have, is found inside the transaction and rolls the whole\nimport back, so a bad row leaves nothing half\napplied. Either refusal names its rows under `validation_errors`, keyed\n`participants[3]` or `matches[0]`. At most 2000 rows in one request, participants\nand matches together.\n\nFor an administrator of the organization, a director of the tournament, or a\nmachine key issued `tournaments:import`, which is how an organization's own system\nloads its participants and matches without a person exporting a file.\n","parameters":[{"$ref":"#/components/parameters/tournamentId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TournamentImport"}}}},"responses":{"200":{"description":"Everything was written.","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/ImportSummary"}}}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"description":"`insufficient_scope`: not an administrator of the organization, a director of\nthe tournament, nor a machine key issued `tournaments:import`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"$ref":"#/components/responses/TournamentNotFound"},"422":{"description":"`validation_failed`, and nothing was imported. `participants` or `matches` is\nnot a list; more than 2000 rows, under `validation_errors.rows`; or a row that\nis malformed or refers to a participant or court that does not exist, under\nits row key.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/tournaments/{tournamentId}/matches":{"post":{"tags":["Control"],"operationId":"createMatch","summary":"Create one match","description":"The single-match form of the import, for a match added by hand. The ruleset and\nits version are pinned at creation, together with the engine version the ruleset\nnames, and never change afterwards; a ruleset this deployment does not carry is a\nvalidation error. The sides and the court are optional and must belong to the\ntournament, so a match can exist before the draw is known.\n\nWith an `external_ref` the request is an upsert: a match already holding that\nreference is answered `200` with `created: false`, its sides corrected unless it\nhas been scored and its court and slot moved, which is what lets a client retry\nafter a lost response without making a second match. Without one, every request\ncreates a match. A new match is announced as `match_created` at revision 0. The\nreference of a knockout draw's match is refused on `external_ref`: the draw sets\nthat match's sides.\n","parameters":[{"$ref":"#/components/parameters/tournamentId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["ruleset_id","ruleset_version"],"properties":{"ruleset_id":{"type":"string","description":"A ruleset this deployment carries, such as `badminton.bwf.21x3`."},"ruleset_version":{"type":"string","description":"The ruleset's version, such as `v1`."},"home_participant_id":{"type":["string","null"],"format":"uuid","description":"A participant of this tournament."},"away_participant_id":{"type":["string","null"],"format":"uuid","description":"A participant of this tournament."},"court_id":{"type":["string","null"],"format":"uuid","description":"A court of this tournament."},"external_ref":{"type":["string","null"],"maxLength":100,"description":"The organizer's own key for the match; makes the request an upsert."},"scheduled_start":{"type":["string","null"],"format":"date-time"},"not_before":{"type":["string","null"],"format":"date-time","description":"The earliest the match may start, as an instant, or null for none. Left\nout, an upsert of an existing match keeps the one it has.\n"}}}}}},"responses":{"200":{"description":"A match already held `external_ref` and was updated; none was created.","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/MatchCreated"}}}}}},"201":{"description":"The match was created.","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/MatchCreated"}}}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"$ref":"#/components/responses/ControlForbidden"},"404":{"$ref":"#/components/responses/TournamentNotFound"},"422":{"description":"`validation_failed`: a ruleset or version this deployment does not carry, under\n`ruleset_id`; an id that is not a UUID; a participant or court that is not this\ntournament's; or a `scheduled_start` or `not_before` that is not an ISO 8601\ninstant.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/tournaments/{tournamentId}/results":{"get":{"tags":["Control"],"operationId":"exportResults","summary":"The tournament's results as CSV","description":"One row per match of the tournament, in creation order, for the organizer's own\nspreadsheet or a federation upload: the export is how results leave Roundr, so it\nis a file rather than JSON. For an administrator of the organization, a director\nor referee of the tournament, or a machine key issued `tournaments:read`. Every match is included whatever its state; `status`\nsays which have a result.\n\nThe columns are `match_id`, `external_ref`, `court`, `home`, `away`, `status`,\n`games`, `winner`, `confirmed_at`, `draw`, `round`, `group` and `places`. `games`\nis each game's score as home-away, space separated, such as `21-19 18-21 21-15`;\n`winner` is `home` or `away` once there is a result; `confirmed_at` is UTC, ISO\n8601; `status` is a match status as `GET /v1/matches/{matchId}` reports it.\n`external_ref` is the reference the import gave the match, so the file joins back\nonto the organizer's own. `draw` is the name of the draw the match is in, `round`\nits round from 1, `group` a group match's group as a letter from `A`, and `places`\nthe places a play-off match decides between, such as `5-6`; all four are empty for\na match in no draw. New columns are only ever added after the last, so a reader\nthat takes the columns by position keeps working. A cell without a value is\nempty. Lines end in CRLF, and a cell holding a comma, a quote or a line\nbreak is quoted.\n\nA cell that begins with `=`, `+`, `-`, `@`, a tab or a carriage return and is not a\nnumber is prefixed with an apostrophe, because a participant can be entered under\na name that a spreadsheet opening the file would otherwise evaluate as a formula.\n","parameters":[{"$ref":"#/components/parameters/tournamentId"}],"responses":{"200":{"description":"The CSV, as an attachment named `results.csv`.","headers":{"Content-Disposition":{"description":"`attachment; filename=\"results.csv\"`","schema":{"type":"string"}}},"content":{"text/csv":{"schema":{"type":"string","description":"UTF-8, CRLF line endings, a header row, then one row per match with the\ncolumns `match_id`, `external_ref`, `court`, `home`, `away`, `status`,\n`games`, `winner`, `confirmed_at`, `draw`, `round`, `group` and `places`.\n"}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"description":"`insufficient_scope`: not an administrator of the organization, nor a director\nor referee of the tournament, nor a machine key issued `tournaments:read`. A\nkey without it is refused before the tournament is looked up, so its answer is\nthe same for every tournament.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"$ref":"#/components/responses/TournamentNotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/tournaments/{tournamentId}/panels":{"get":{"tags":["Control"],"operationId":"listPanelsInUse","summary":"The umpire panels scoring the tournament's matches now","description":"One entry per live scoring session: which match is being scored, from which court,\nby whom, since when, and when the last command arrived. Control draws each beside\nthe match's own state from the snapshot, so a director or referee sees every panel\nin the hall without walking to it. A released or superseded session is not in use\nand is not listed.\n\nOrdered as the courts are, by `sort_order` then name, with a match on no court\nlast. For an administrator of the organization, or a director or referee of the\ntournament; a court or display device reads its own match, not this.\n","parameters":[{"$ref":"#/components/parameters/tournamentId"}],"responses":{"200":{"description":"The panels in use.","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["panels"],"properties":{"panels":{"type":"array","items":{"$ref":"#/components/schemas/PanelInUse"}}}}}}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"description":"`insufficient_scope`: not an administrator of the organization, nor a director\nor referee of the tournament.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"$ref":"#/components/responses/TournamentNotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/matches/{matchId}/assignment":{"patch":{"tags":["Control"],"operationId":"assignMatch","summary":"Put a match on a court and in a slot, or take it off","description":"Moves the match's schedule entry: its court, its scheduled start, its sequence\namong the matches on that court and the earliest it may start. The entry's revision moves and\n`match_assignment_changed` is announced on the `schedule_entry` aggregate;\n`match_revision` never moves, because that revision counts accepted scoring\ncommands and the undo replay checks the event history against it. A court device\nlearns of a reassignment from the feed, not from a match revision it would\notherwise take for a scored command it missed.\n\nThe body is the whole assignment, not a set of changed fields: `court_id` absent\nor null takes the match off its court, and an absent `scheduled_start`,\n`not_before` or `sequence` clears it. The court must belong to the match's tournament. For a\ndirector of that tournament or an administrator of the organization; a match in a\ntournament outside the principal's scope is not found, never forbidden.\n","parameters":[{"$ref":"#/components/parameters/matchId"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"court_id":{"type":["string","null"],"format":"uuid","description":"A court of the match's tournament, or null to take the match off its court."},"scheduled_start":{"type":["string","null"],"format":"date-time"},"sequence":{"type":"integer","minimum":0,"default":0,"description":"The match's place among those queued on the court, from 0."},"not_before":{"type":["string","null"],"format":"date-time","description":"The earliest the match may start, as an instant, or null for none."}}}}}},"responses":{"200":{"description":"The assignment as written.","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/MatchAssignment"}}}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"$ref":"#/components/responses/ControlForbidden"},"404":{"description":"No such match in the principal's organization, or one in a tournament outside\nthe principal's scope.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"`validation_failed`: a `court_id` that is not a UUID or not a court of this\ntournament, a `scheduled_start` or `not_before` that is not an ISO 8601\ninstant, or a `sequence` that is not a whole number of zero or more.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/matches/{matchId}/schedule":{"patch":{"tags":["Control"],"operationId":"changeMatchSchedule","summary":"Move a match in the schedule by the parts named","description":"Changes the parts of the match's schedule entry the body names, and keeps the\nrest: `court_id`, `scheduled_start` and `not_before` are cleared by an explicit\nnull, and a part left out is not touched. That is the difference from the assignment, which takes\nthe whole placement and clears what it leaves out: the change is applied to the\nplacement as it stands under the match's row lock, so two directors moving\ndifferent parts at once both land.\n\nA change moves the entry's revision and announces `match_assignment_changed` on\nthe `schedule_entry` aggregate, as an assignment does; one that leaves the\nplacement as it was writes and announces nothing. `match_revision` never moves.\nFor a director of the match's tournament or an administrator of the\norganization; a match in a tournament outside the principal's scope is not found,\nnever forbidden.\n","parameters":[{"$ref":"#/components/parameters/matchId"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"court_id":{"type":["string","null"],"format":"uuid","description":"A court of the match's tournament, or null to take the match off its court."},"scheduled_start":{"type":["string","null"],"format":"date-time","description":"The start as an instant, or null to clear it."},"sequence":{"type":"integer","minimum":0,"description":"The match's place among those queued on the court, from 0."},"not_before":{"type":["string","null"],"format":"date-time","description":"The earliest the match may start however its court's queue runs, as an\ninstant, or null to clear it. Stored and announced as the start is.\n"}}}}}},"responses":{"200":{"description":"The placement as it now stands, every part of it.","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/MatchAssignment"}}}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"$ref":"#/components/responses/ControlForbidden"},"404":{"description":"No such match in the principal's organization, or one in a tournament outside\nthe principal's scope.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"`validation_failed`: a `court_id` that is not a UUID or not a court of this\ntournament, a `scheduled_start` or `not_before` that is not an ISO 8601\ninstant, or a `sequence` that is null or not a whole number of zero or more.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/tournaments/{tournamentId}/schedule":{"patch":{"tags":["Control"],"operationId":"changeTournamentSchedule","summary":"Move many of the tournament's matches in the schedule at once","description":"Each change moves one match of the tournament by the parts it names, exactly as\n`PATCH /v1/matches/{matchId}/schedule` moves one: a part left out is kept, and\n`court_id`, `scheduled_start` and `not_before` are cleared by an explicit null.\nWhat Control sends when the desk fills its courts from a start time.\n\nOne transaction: every match is locked before anything is written, and a change\ngiven wrong, or a match that is not the tournament's, leaves every placement as it\nwas. Each change that moves its placement moves that match's schedule entry and\nannounces `match_assignment_changed` on its `schedule_entry` aggregate; one that\nleaves it as it was writes and announces nothing. `match_revision` never moves.\nFor a director of the tournament or an administrator of the organization.\n","parameters":[{"$ref":"#/components/parameters/tournamentId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["changes"],"properties":{"changes":{"type":"array","minItems":1,"maxItems":1000,"description":"One change per match; a match may be named once.","items":{"type":"object","required":["match_id"],"properties":{"match_id":{"type":"string","format":"uuid"},"court_id":{"type":["string","null"],"format":"uuid","description":"A court of the tournament, or null to take the match off its court."},"scheduled_start":{"type":["string","null"],"format":"date-time","description":"The start as an instant, or null to clear it."},"sequence":{"type":"integer","minimum":0,"description":"The match's place among those queued on the court, from 0."},"not_before":{"type":["string","null"],"format":"date-time","description":"The earliest the match may start, as an instant, or null to clear it."}}}}}}}}},"responses":{"200":{"description":"Each match's placement as it now stands, every part of it, in the order of the changes.","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["placements"],"properties":{"placements":{"type":"array","items":{"$ref":"#/components/schemas/MatchAssignment"}}}}}}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"$ref":"#/components/responses/ControlForbidden"},"404":{"$ref":"#/components/responses/TournamentNotFound"},"409":{"description":"`tournament_closed`: the tournament has been closed (DEV-25).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"`validation_failed`: `changes` is not a list of 1 to 1000, or a change names no\nmatch id, names a match twice, or gives a part as the single change refuses it,\neach named as `changes[i].field`; or a match is not the tournament's, named as\n`changes`. Nothing is stored.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/tournaments/{tournamentId}/draws":{"get":{"tags":["Draws"],"operationId":"listDraws","summary":"The tournament's draws, drafts and published","description":"Every draw of the tournament, ordered by name, each whole: its entries and, once\npublished, its matches. For the tournament's staff: an administrator of the\norganization, and the tournament's director and referee.\n","parameters":[{"$ref":"#/components/parameters/tournamentId"}],"responses":{"200":{"description":"The draws.","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["draws"],"properties":{"draws":{"type":"array","items":{"$ref":"#/components/schemas/Draw"}}}}}}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"$ref":"#/components/responses/DrawReadForbidden"},"404":{"$ref":"#/components/responses/TournamentNotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}},"post":{"tags":["Draws"],"operationId":"createDraw","summary":"Make a draw, as an empty draft","description":"A draft with no entries, pinned to the ruleset its matches will be played under.\n`code` is the prefix of the references publication gives the matches (`MS` makes\n`MS-1`, `MS-2` and on in a knockout, `MS-A1` in group A of a round robin); it is\nstored upper-case, and both it and the name are unique in the tournament. `format`\nis `knockout` when not given; a `round_robin` draw records the rules its standings\nare ranked by, `bwf-gcr-16.2`, which the draw keeps. A knockout draw may be made\nplaying for third place or with a consolation, or playing off every place, which\ntakes neither of the other two (`/options` changes all three while it is a draft);\na round-robin draw with any of them is refused. A knockout draw may instead be\nfed by the groups of a published round robin of the tournament, `source_draw_id`\nwith `qualifiers_per_group`: it takes no entries, its bracket is laid out at once\n(`size` is set) with the group winners where seeds 1 onwards would stand and each\nrunner-up in the half its group's winner is not in, and its first sides are\n`group_rank` sources that the server fills as each group completes. It may play for\nthird place and have a consolation, whose losers are those of its own first round\nand which is refused at once when its bracket cannot play one, or play off every\nplace; one round robin feeds one draw. Any draw may be made with an entry rule,\n`entry_gender` (`/entry-rule` changes it while it is a draft). Announced as\n`draw_created` on the `draw` aggregate.\n","parameters":[{"$ref":"#/components/parameters/tournamentId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","code","ruleset_id","ruleset_version"],"properties":{"name":{"type":"string","maxLength":120},"code":{"type":"string","pattern":"^[A-Za-z0-9]{1,16}$"},"ruleset_id":{"type":"string"},"ruleset_version":{"type":"string"},"format":{"type":"string","enum":["knockout","round_robin"],"default":"knockout"},"third_place":{"type":"boolean","default":false,"description":"A knockout draw's match between the losers of its semi-finals."},"consolation":{"type":"boolean","default":false,"description":"A knockout draw's consolation, created with it at publication; its code is\nthe draw's with a `C`, so the draw's has at most 15 characters.\n"},"all_places":{"type":"boolean","default":false,"description":"A knockout draw whose losers play on in placement groups of its own\nbracket, so that every place is decided; refused with `third_place` or\n`consolation`, on this field.\n"},"source_draw_id":{"type":"string","format":"uuid","description":"The published round robin of the tournament whose groups feed this\nknockout draw; sent with `qualifiers_per_group` or not at all.\n"},"qualifiers_per_group":{"type":"integer","minimum":1,"description":"How many of each group go on, from its winner down: at most as many as\nthe smallest group holds, and 2 to 256 in all.\n"},"qualifiers":{"type":["integer","null"],"minimum":1,"maximum":128,"description":"Makes a knockout draw a qualifying draw of this many qualifiers: its\nlines are cut into that many sections, it plays until each section has a\nwinner, and those winners are its qualifiers, which another draw may be\nfed by (`PUT /v1/draws/{drawId}/feeds`). Refused on this field with any\nother option, a source, or a round-robin format. Sent by every server\nthat serves `feeds`.\n"},"entry_gender":{"$ref":"#/components/schemas/EntryGender"},"age_rule":{"$ref":"#/components/schemas/AgeRule"},"class_rule":{"$ref":"#/components/schemas/ClassRule"}}}}}},"responses":{"201":{"$ref":"#/components/responses/DrawBody"},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"$ref":"#/components/responses/ControlForbidden"},"404":{"$ref":"#/components/responses/TournamentNotFound"},"422":{"description":"`validation_failed`: a missing or overlong name, a code that is not letters and\ndigits, an unknown ruleset, a format that is neither, an option that is not a\nboolean or that a round-robin draw asks for, `all_places` asked with either\nother option, a consolation asked of a code of 16, or a name or code the\ntournament already has. A draw fed by groups is\nrefused with `details.problem` as a draft edit is: `source_unusable` on\n`source_draw_id` (not a published round robin of the tournament),\n`source_taken` on `source_draw_id` (another draw takes its qualifiers),\n`qualifier_count` on `qualifiers_per_group`, `kind_mismatch` on `ruleset_id`\n(a ruleset played by another kind of participant than the groups), or\n`consolation_unplayable` on `consolation`; a source without qualifiers, or\nthe reverse, is refused on the one missing. A mixed `entry_gender` on a\nruleset played by singles is refused as `mixed_needs_pairs` on `entry_gender`,\nand a value of no rule on `entry_gender`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/draws/{drawId}":{"get":{"tags":["Draws"],"operationId":"readDraw","summary":"One draw, with its entries and its matches","parameters":[{"$ref":"#/components/parameters/drawId"}],"responses":{"200":{"$ref":"#/components/responses/DrawBody"},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"$ref":"#/components/responses/DrawReadForbidden"},"404":{"$ref":"#/components/responses/DrawNotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}},"delete":{"tags":["Draws"],"operationId":"deleteDraw","summary":"Delete a draft","description":"A draft only: a published draw has matches and is refused as `draw_locked`.\nAnnounced as `draw_deleted` on the `draw` aggregate.\n","parameters":[{"$ref":"#/components/parameters/drawId"}],"responses":{"200":{"description":"The draft is gone.","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["draw_id","deleted"],"properties":{"draw_id":{"type":"string","format":"uuid"},"deleted":{"type":"boolean","const":true}}}}}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"$ref":"#/components/responses/ControlForbidden"},"404":{"$ref":"#/components/responses/DrawNotFound"},"409":{"$ref":"#/components/responses/DrawConflict"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/draws/{drawId}/entries":{"put":{"tags":["Draws"],"operationId":"replaceDrawEntries","summary":"List the draft's entries, with their seeds","description":"Replaces the entries whole, in the order given, which is the order a placement in\nentry order fills the bracket or the groups with. Every line and every group is\ncleared: a new list is placed again. Each entry is a participant of the tournament\nof the kind the ruleset is played by (a pair for a doubles ruleset), once; a seed is\none of 1 to the entry count, held once. A knockout draw takes at most 256 entries,\nthe largest bracket, and a round-robin draw 416, 26 groups of 16. Announced as\n`draw_changed`.\n","parameters":[{"$ref":"#/components/parameters/drawId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["expected_revision","entries"],"properties":{"expected_revision":{"type":"integer","minimum":1},"entries":{"type":"array","maxItems":416,"items":{"type":"object","required":["participant_id"],"properties":{"participant_id":{"type":"string","format":"uuid"},"seed":{"type":["integer","null"],"minimum":1}}}}}}}}},"responses":{"200":{"$ref":"#/components/responses/DrawBody"},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"$ref":"#/components/responses/ControlForbidden"},"404":{"$ref":"#/components/responses/DrawNotFound"},"409":{"$ref":"#/components/responses/DrawConflict"},"422":{"$ref":"#/components/responses/DrawInvalid"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/draws/{drawId}/placement":{"post":{"tags":["Draws"],"operationId":"placeDraw","summary":"Place every entry in the bracket or into groups","description":"A knockout draw: the bracket is the smallest power of two that takes every entry.\nSeed i stands on rank i's line of the standard order, the byes go to the top ranks\nso that two never meet, and the unseeded entries fill the remaining lines top to\nbottom. A round-robin draw, into `group_count` groups, which it requires and a\nknockout draw refuses as `format_mismatch`: the seeds in seed order one to each\ngroup, 1 to G, then snaking back, G to 1; the unseeded each to the group with the\nfewest entries, the lowest on a tie, so no two groups differ by more than one. A\ngroup plays with 2 to 16 entries, and its seeds stand first by seed number, then\nthe unseeded as they arrived. Either way the unseeded go `entry_order` in the order\nlisted, `lot` in the order of a lot the server draws once and records on the draw.\n`gcr_draw`, a knockout draw's only, is the draw of BWF General Competition\nRegulations Regulation 11 instead, from the same recorded lot: seed 1 at the top\nline and seed 2 at the bottom, seeds 3-4, 5-8, 9-16 and 17-32 drawn by lot into\nthe quarters, eighths, sixteenths and thirty-seconds no earlier seed holds, at the\ntop line of a section in the top half and the bottom line in the bottom half; the\nbyes on the lines Section 5.3.8's Tables 1 to 7 give the entry count, whichever\nseed the lot put beside them; one Member's entries kept apart (GCR 11.12), its\nfirst and second ranked in opposite halves and, wherever possible, none of its\nentries meeting in the first round, a Member being the players' `association`, a\npair's only when both share it; and the rest drawn into the lines left. A Member's\nentries rank by seed, then by ranking position, then by the lot. It takes seeds\nnumbered 1 upwards without a gap, at most 2 below 16 entries, 4 below 32, 8 below\n64, 16 below 128 and 32 from 128 (GCR 11.8.1), and refuses any other seeding as\n`regulation_seeding`. `bec_championships_draw` is the same draw with the seeds\nBadminton Europe's European Championships allow (Appendix I 7.2): 8 from 17\nentries, and GCR 11.8.1's at 16 or fewer. Each entry of a draw by the\nregulations answers the `association` and `ranking_position` the draw read, so\nit is made again from its record. A round-robin draw refuses both as\n`format_mismatch` on `method`. Placing again replaces the lines or the groups.\nAnnounced as `draw_changed`.\n","parameters":[{"$ref":"#/components/parameters/drawId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["expected_revision","method"],"properties":{"expected_revision":{"type":"integer","minimum":1},"method":{"type":"string","enum":["entry_order","lot","gcr_draw","bec_championships_draw"]},"group_count":{"type":["integer","null"],"minimum":1,"maximum":26,"description":"A round-robin draw's groups, named A onwards; none for a knockout draw."}}}}}},"responses":{"200":{"$ref":"#/components/responses/DrawBody"},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"$ref":"#/components/responses/ControlForbidden"},"404":{"$ref":"#/components/responses/DrawNotFound"},"409":{"$ref":"#/components/responses/DrawConflict"},"422":{"$ref":"#/components/responses/DrawInvalid"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/draws/{drawId}/seeds-from-ranking":{"post":{"tags":["Draws"],"operationId":"seedDrawFromRanking","summary":"Number the seeds from the entries' ranking","description":"Numbers the draft's seeds from the ranking positions its entries carry\n(`Participant.ranking`), as BWF GCR 11.6 numbers them from the World Ranking: the\nhighest ranked seed 1, the next seed 2, for `count` seeds, or as many as GCR\n11.8.1 allows for the entry count when it is left out. An entry without a ranking\nposition is not seeded; every seed the draft held before is replaced, and the\nplacement is cleared, as a new entry list clears it. Entries at one position are\nordered by a lot the server draws (GCR 11.6.1) and does not keep, since the seeds\nit gave are kept; `seeds_drawn_by_lot` names them, for the director to change by\nlisting the entries again. Roundr computes no ranking: it reads the one the\ndirector entered or imported, and positions from more than one ranking, told apart\nby name and date, are refused as `ranking_mixed` on `ranking`. No entry with a\nranking position is `ranking_missing` on `ranking`, and a count outside 1 to the\nentry count `seed_out_of_range` on `count`. A draw fed by groups refuses it as\n`format_mismatch`. Announced as `draw_changed`.\n","parameters":[{"$ref":"#/components/parameters/drawId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["expected_revision"],"properties":{"expected_revision":{"type":"integer","minimum":1},"count":{"type":["integer","null"],"minimum":1,"description":"How many seeds; left out or null, as many as GCR 11.8.1 allows."}}}}}},"responses":{"200":{"description":"The draft as seeded, and the seeds a lot decided.","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["draw","seeds_drawn_by_lot"],"properties":{"draw":{"$ref":"#/components/schemas/Draw"},"seeds_drawn_by_lot":{"type":"array","items":{"type":"integer"},"description":"The seeds whose holders shared a ranking position with another entry, in seed order."}}}}}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"$ref":"#/components/responses/ControlForbidden"},"404":{"$ref":"#/components/responses/DrawNotFound"},"409":{"$ref":"#/components/responses/DrawConflict"},"422":{"$ref":"#/components/responses/DrawInvalid"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/draws/{drawId}/positions":{"put":{"tags":["Draws"],"operationId":"arrangeDraw","summary":"Stand the entries on the lines given","description":"The director's swaps, sent as the whole arrangement so that a retry means the same\nthing twice: every entry and every fed place, once, on a line of the placed\nbracket, with never two byes against each other, and in a qualifying draw an entry\nin each half of every section. The lot on record stays. A knockout draw's only: a\nround-robin draw is arranged by `/groups`, and refused here as `format_mismatch`.\nAnnounced as `draw_changed`.\n","parameters":[{"$ref":"#/components/parameters/drawId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["expected_revision","positions"],"properties":{"expected_revision":{"type":"integer","minimum":1},"positions":{"type":"array","items":{"type":"object","required":["position"],"description":"An entry by `participant_id`, or a fed place by `feed`, never both.","properties":{"participant_id":{"type":"string","format":"uuid"},"feed":{"type":"integer","minimum":1,"description":"A fed place's number (`Draw.feeds[].feed`)."},"position":{"type":"integer","minimum":1}}}}}}}}},"responses":{"200":{"$ref":"#/components/responses/DrawBody"},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"$ref":"#/components/responses/ControlForbidden"},"404":{"$ref":"#/components/responses/DrawNotFound"},"409":{"$ref":"#/components/responses/DrawConflict"},"422":{"$ref":"#/components/responses/DrawInvalid"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/draws/{drawId}/publish":{"post":{"tags":["Draws"],"operationId":"publishDraw","summary":"Publish the placed draft into its matches","description":"One transaction. Every match is created as an ordinary match, pinned to the draw's\nruleset. A knockout draw's are referenced `{code}-{n}` round by round and top to\nbottom, skipping the first-round pairings a bye leaves unplayed; a side a line\nfills starts with its entry, and a side fed by a winner starts empty. A round-robin\ndraw's are every group's, in the order of play of BWF GCR 16.4 for groups of 3 to\n7 and of the circle method for the rest, referenced `{code}-{letter}{n}` (`MS-A1`)\nby group and number in the group's order of play, with both sides their entries;\na round's slots run on from one group to the next. Each match is announced as\n`match_created` at revision 0, and then the draw as `draw_published`. From then on\nthe draw is not changed structurally; a late withdrawal is a walkover.\n\nA knockout draw that plays for third place gets one more match, the final round's\nsecond slot, numbered last, whose sides are `loser_of` its semi-finals. One with a\nconsolation creates it in the same transaction: a second knockout draw, published,\nnamed after the draw with its code in brackets and coded with the draw's code and a\n`C` (`MS` makes `MSC`, its matches `MSC-1` on), its matches announced after the main\ndraw's and it as `draw_published` after the main draw. Its bracket is half the main\none's, laid out from it with no lot: its line p takes the loser of the main draw's\nfirst-round pairing p, which for a pairing that is a bye is the loser of the\nsecond-round match the bye leads to, and only when that loser is the entry the bye\nsent through, since a consolation takes an entry who lost the first match they\nplayed. Where two byes stand side by side, the upper line takes their second-round\nmatch's loser and the lower is a bye. A draw fed by groups lays out its consolation\nthe same way from its lines of ranks; a qualifier's first match is its first in the\nknockout, and the loser of one whose rank was void is nobody, so that line is void.\n\nA knockout draw that plays off every place gets its placement groups' matches in\nevery round after the first, numbered round by round with the main bracket's, top\nto bottom within a round, so all of round 2 is numbered before round 3. Round r's\nslots are split into groups of size / 2^r; group g plays for the places from\ng × size / 2^(r − 1) + 1, group 0 is the main bracket, and round r + 1's group 2g\ntakes the winners of round r's group g in order and group 2g + 1 their losers, as\n`loser_of` sides of the same draw. The match for third is round log2(size)'s slot 2.\nA pairing with one side absent is no match: its side goes through keeping its\nsource, which may name a match two rounds back or more, and a pairing with none\nsends nobody on. Each placement match carries `places`, the places it decides\nbetween, counted over the entrants who can reach it, so a bye takes no place.\n\nRefused while any entry has no line or no group (`not_placed`, on `positions` or\n`groups`); with `third_place_unplayable` when the bracket has no two semi-finals to\nplay for third (two or three entries); with `consolation_unplayable` for a bracket\nof two, or when the draw also plays for third place and a semi-final can be an\nentrant's first match, whose loser both would take (a bracket of four, or of eight\nwith a bye); and on the code, or the name, when a match of the tournament already\nholds one of the references the draw would give or a draw holds the consolation's\ncode or name.\n","parameters":[{"$ref":"#/components/parameters/drawId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["expected_revision"],"properties":{"expected_revision":{"type":"integer","minimum":1}}}}}},"responses":{"200":{"$ref":"#/components/responses/DrawBody"},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"$ref":"#/components/responses/ControlForbidden"},"404":{"$ref":"#/components/responses/DrawNotFound"},"409":{"$ref":"#/components/responses/DrawConflict"},"422":{"$ref":"#/components/responses/DrawInvalid"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/draws/{drawId}/options":{"put":{"tags":["Draws"],"operationId":"setDrawOptions","summary":"Set whether a knockout draft plays for third place, has a consolation and plays off every place","description":"The options, sent whole so that a retry means the same thing twice; `all_places`\nleft out is false, as a client older than it sends the other two. Whether the\nbracket can play them is asked at publication, since the entries may change until\nthen (`/publish`). A round-robin draw asked for any is refused as\n`format_mismatch`, and a consolation of a draw coded with 16 characters as\n`consolation_unplayable`, since its code adds a `C`; so is a consolation a draw fed\nby groups cannot play, since its bracket is laid out when it is made. `all_places`\nwith either other option is refused on `all_places`: every place includes the\nmatch for third, and every loser already plays on. Announced as `draw_changed`.\n","parameters":[{"$ref":"#/components/parameters/drawId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["expected_revision","third_place","consolation"],"properties":{"expected_revision":{"type":"integer","minimum":1},"third_place":{"type":"boolean"},"consolation":{"type":"boolean"},"all_places":{"type":"boolean","default":false}}}}}},"responses":{"200":{"$ref":"#/components/responses/DrawBody"},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"$ref":"#/components/responses/ControlForbidden"},"404":{"$ref":"#/components/responses/DrawNotFound"},"409":{"$ref":"#/components/responses/DrawConflict"},"422":{"$ref":"#/components/responses/DrawInvalid"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/draws/{drawId}/entry-rule":{"put":{"tags":["Draws"],"operationId":"setDrawEntryRule","summary":"Set who may enter a draft by gender","description":"The draft's entry rule, sent whole so that a retry means the same thing twice:\n`entry_gender` is required, and null lifts the rule. A rule the entries already\nlisted contradict is refused as `gender_mismatch` on `entry_gender`, with\n`details.participant_ids` naming them; a mixed rule on a ruleset played by singles\nas `mixed_needs_pairs`. Announced as `draw_changed`.\n","parameters":[{"$ref":"#/components/parameters/drawId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["expected_revision","entry_gender"],"properties":{"expected_revision":{"type":"integer","minimum":1},"entry_gender":{"$ref":"#/components/schemas/EntryGender"}}}}}},"responses":{"200":{"$ref":"#/components/responses/DrawBody"},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"$ref":"#/components/responses/ControlForbidden"},"404":{"$ref":"#/components/responses/DrawNotFound"},"409":{"$ref":"#/components/responses/DrawConflict"},"422":{"$ref":"#/components/responses/DrawInvalid"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/draws/{drawId}/age-rule":{"put":{"tags":["Draws"],"operationId":"setDrawAgeRule","summary":"Set a draft's age category","description":"The draft's age category, sent whole so that a retry means the same thing twice:\n`age_rule` is required, and null lifts it. Nothing already entered is refused: a\nplayer entering themselves is checked at registration, and Control warns about an\nentry the staff make (DEV-27). Announced as `draw_changed`.\n","parameters":[{"$ref":"#/components/parameters/drawId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["expected_revision","age_rule"],"properties":{"expected_revision":{"type":"integer","minimum":1},"age_rule":{"$ref":"#/components/schemas/AgeRule"}}}}}},"responses":{"200":{"$ref":"#/components/responses/DrawBody"},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"$ref":"#/components/responses/ControlForbidden"},"404":{"$ref":"#/components/responses/DrawNotFound"},"409":{"$ref":"#/components/responses/DrawConflict"},"422":{"$ref":"#/components/responses/DrawInvalid"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/draws/{drawId}/class-rule":{"put":{"tags":["Draws"],"operationId":"setDrawClassRule","summary":"Set a draft's class rule","description":"The player classes the draft admits in its discipline, sent whole so that a retry\nmeans the same thing twice: `class_rule` is required, and null lifts it. Nothing\nalready entered is refused: a player entering themselves is checked at\nregistration, and Control warns about an entry the staff make (DEV-31).\nAnnounced as `draw_changed`.\n","parameters":[{"$ref":"#/components/parameters/drawId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["expected_revision","class_rule"],"properties":{"expected_revision":{"type":"integer","minimum":1},"class_rule":{"$ref":"#/components/schemas/ClassRule"}}}}}},"responses":{"200":{"$ref":"#/components/responses/DrawBody"},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"$ref":"#/components/responses/ControlForbidden"},"404":{"$ref":"#/components/responses/DrawNotFound"},"409":{"$ref":"#/components/responses/DrawConflict"},"422":{"$ref":"#/components/responses/DrawInvalid"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/draws/{drawId}/groups":{"put":{"tags":["Draws"],"operationId":"arrangeDrawGroups","summary":"Stand the entries in the groups given","description":"The director's moves between groups and changes of order, sent as the whole\narrangement so that a retry means the same thing twice. Group i is the i-th list,\nnamed by the i-th letter, and an entry's place in its group is its order in the\nlist, which is the position the order of play is read by. Every entry stands in one\ngroup, once; 1 to 26 groups, each of 2 to 16 entries. A draft need not be placed\nfirst. The lot on record stays. A round-robin draw's only: a knockout draw is\narranged by `/positions`, and refused here as `format_mismatch`. Every problem is\ngiven on `groups`. Announced as `draw_changed`.\n","parameters":[{"$ref":"#/components/parameters/drawId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["expected_revision","groups"],"properties":{"expected_revision":{"type":"integer","minimum":1},"groups":{"type":"array","maxItems":26,"items":{"type":"array","maxItems":16,"description":"An entry by its participant id, or a fed place as `{\"feed\": n}`.","items":{"oneOf":[{"type":"string","format":"uuid"},{"type":"object","required":["feed"],"properties":{"feed":{"type":"integer","minimum":1}}}],"format":"uuid"}}}}}}}},"responses":{"200":{"$ref":"#/components/responses/DrawBody"},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"$ref":"#/components/responses/ControlForbidden"},"404":{"$ref":"#/components/responses/DrawNotFound"},"409":{"$ref":"#/components/responses/DrawConflict"},"422":{"$ref":"#/components/responses/DrawInvalid"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/draws/{drawId}/standings":{"get":{"tags":["Draws"],"operationId":"readDrawStandings","summary":"A round-robin draw's standings, group by group","description":"Computed on every read from the group matches as they stand, never stored: a\nconfirmation or a reopening moves the match's revision and announces it, which is\nwhen to read again. Only a confirmed match counts; a result nobody has confirmed\nmay still be corrected. Each group is ranked by the rules the draw records,\n`bwf-gcr-16.2`: matches won; two level by the match between them; three or more by\ngame difference, then point difference, going to the match between two whenever a\nstep leaves exactly two level; three or more still level share a rank, `lots`,\nuntil the group is complete and a lot is drawn for the tie (`/lots`), which ranks\nthem from that rank in the order it drew, `lot_drawn`. A lot ranks only the tie it\nwas drawn for, the same entries level at the same rank; a correction that leaves\nanother tie leaves it out of force. An entry that did not complete its group matches (it\nretired, gave a walkover, was disqualified or was ended out by the referee, or was\neither side of a double walkover) has all its results deleted, its opponents'\nincluded, and is listed last, `withdrawn`. Until the group is `complete` the\nranking is provisional: where the match between two has not been played, the\ndifferences decide in its place. For the tournament's staff, as the draws are, and\nfor a hall display whose projection names the draw (`standings_draw_ids`), read\nagain on every request; a display is refused every other draw as it is refused\nthe draws themselves.\n","parameters":[{"$ref":"#/components/parameters/drawId"}],"responses":{"200":{"description":"The standings.","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/DrawStandings"}}}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"$ref":"#/components/responses/DrawReadForbidden"},"404":{"description":"No such draw in the principal's organization, one in a tournament outside the\nprincipal's scope, a knockout draw, or a draft, which has no matches to rank.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/draws/{drawId}/feeds":{"put":{"tags":["Draws"],"operationId":"feedDraw","summary":"Feed a draft from other draws of its tournament","description":"Replaces whatever fed the draft (DEV-30). Each source gives the draft one fed\nplace for each match of a round of a published knockout of the same tournament:\nits winner, from the round that gives each section of a qualifying draw its winner\n(the default for `winners`), or its loser, from any round. The places are numbered\nin the order asked, each source's matches top to bottom, and placed with the\nentries as unseeded entries not yet known; like a new entry list, this clears the\nplacement. A match sends its winner and its loser to one place each. Refused with\n`details.problem` on `feeds`: `source_unusable` (not a published knockout of the\ntournament, a round it does not play, or winners of any round but a qualifying\ndraw's last), `source_taken` (the source sends those losers on already, or another\ndraw is fed by them), `kind_mismatch`, `entry_repeated` (one round asked twice),\n`entry_count`, or `format_mismatch` for a draw fed by groups. Announced as\n`draw_changed`.\n","parameters":[{"$ref":"#/components/parameters/drawId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["expected_revision","feeds"],"properties":{"expected_revision":{"type":"integer","minimum":1},"feeds":{"type":"array","items":{"type":"object","required":["source_draw_id","takes"],"properties":{"source_draw_id":{"type":"string","format":"uuid"},"takes":{"type":"string","enum":["winners","losers"]},"round":{"type":["integer","null"],"minimum":1,"description":"The round whose matches feed; required for `losers`."}}}}}}}}},"responses":{"200":{"$ref":"#/components/responses/DrawBody"},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"$ref":"#/components/responses/ControlForbidden"},"404":{"$ref":"#/components/responses/DrawNotFound"},"409":{"$ref":"#/components/responses/DrawConflict"},"422":{"$ref":"#/components/responses/DrawInvalid"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/draws/{drawId}/seed-withdrawal":{"post":{"tags":["Draws"],"operationId":"withdrawSeeds","summary":"Move the seeds of a published knockout draw after a seed withdraws","description":"BWF GCR 11.3, the Seed Withdrawal Movement, for seeds who withdraw after the draw\nand before play. A withdrawn seed's line is taken by a remaining seed of the next\nseeding group (1-2, 3-4, 5-8, 9-16, 17-32), the one in that section of the draw;\neach seed who moves leaves a line filled the same way, and the lines the lowest\nseeds leave are taken by unseeded entries: the best ranked in the director's\nranking, a tie and the unranked by a lot the server draws, onto lines the lot\ndraws. The lines those entries leave are taken by `reserves`, in order (Regulation\n13); a line nobody takes stays empty, its side `void`, and the entrant opposite goes\nthrough by walkover, which an official records. A seed keeps its number where it\nmoves. The seeds who withdraw leave the draw's entries, a reserve promoted becomes\none, and each line that changes is its match's own `SidePlaced` batch naming the\nmovement, announced as `match_sides_changed`; the draw is announced last as\n`draw_seeds_moved`. The tournament's director or referee moves them, once per draw\n(11.3.1), and only while no match a line feeds has begun.\n","parameters":[{"$ref":"#/components/parameters/drawId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["withdrawn"],"properties":{"withdrawn":{"type":"array","minItems":1,"items":{"type":"string","format":"uuid"},"description":"The seeds who withdraw, each once, in any order."},"reserves":{"type":"array","items":{"type":"string","format":"uuid"},"description":"Participants of the tournament not in the draw, promoted in this order into\nthe lines the unseeded entries leave, in line order. At most one for each\nsuch line; none leaves them empty.\n"}}}}}},"responses":{"201":{"description":"What the movement did, in the order it did it, and the draw as it now stands.","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["movement","draw"],"properties":{"movement":{"$ref":"#/components/schemas/SeedMovement"},"draw":{"$ref":"#/components/schemas/Draw"}}}}}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"description":"Not the tournament's director or referee: an administrator of the organization\nwithout a tournament role, a device, a display or a key.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No such draw in the principal's organization or scope, or not a published\nknockout draw of entries: a draft, a round robin, a consolation or a knockout\nfed by groups.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"`validation_failed`, with `details.problem` where the draw refuses it:\n`not_a_seed` (a participant named is not a seed of the draw), `seeds_moved`\n(the draw's seeds have been moved once already), `draw_under_way` (a match a\nline feeds has begun; a withdrawal now is a walkover) or `reserve_unusable` (a\nreserve in the draw already, or more reserves than lines to fill), all on\n`withdrawn` or `reserves`; `unknown_participant`, `kind_mismatch` and\n`gender_mismatch` on `reserves`. Without a problem: an empty `withdrawn`, or a\nparticipant id twice.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/draws/{drawId}/lots":{"post":{"tags":["Draws"],"operationId":"drawGroupLot","summary":"Draw the lot for a tie in a complete group","description":"BWF GCR 16.2.4.2 ranks three or more entries still level after points by drawing\nlots. The server draws sixteen random bytes, orders the tied entries by them and\nrecords the lot with the group, the rank, the entries in order, who drew it and\nwhen, so the order can be drawn again from the record. Nothing draws a lot on its\nown: the tournament's director or referee asks for it, as the regulation's drawing\nis an official's act. `participant_ids` names the entries the official saw tied at\n`rank`, the rank they share, so a lot is never drawn on a tie a correction made\nmeanwhile. From then on the standings rank the tie by the lot (`lot_drawn`), and\nthe knockout draw the group feeds takes the ranks it settled, each as that match's\nown `SidePlaced` batch naming the lot, as a confirmation places them. A lot ranks\nonly the tie it was drawn for; a correction that leaves another tie leaves it on\nrecord and out of force, and the new tie is drawn afresh. A tie a lot ranks is\nnever drawn again. Announced as `draw_lot_drawn` on the round robin's `draw`\naggregate, which a hall display showing its standings is told of too.\n","parameters":[{"$ref":"#/components/parameters/drawId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["group","rank","participant_ids"],"properties":{"group":{"type":"integer","minimum":1,"description":"The group, from 1 for A."},"rank":{"type":"integer","minimum":1,"description":"The rank the tied entries share, the first of the places they tie for."},"participant_ids":{"type":"array","minItems":2,"items":{"type":"string","format":"uuid"},"description":"The entries tied at `rank`, in any order."}}}}}},"responses":{"201":{"description":"The lot, and the draw's standings ranked by it.","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"allOf":[{"$ref":"#/components/schemas/DrawStandings"},{"type":"object","required":["lot"],"properties":{"lot":{"$ref":"#/components/schemas/GroupLot"}}}]}}}}}},"401":{"$ref":"#/components/responses/ControlUnauthorized"},"403":{"description":"Not the tournament's director or referee: an administrator of the organization\nwithout a tournament role, a device, a display or a key.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No such draw in the principal's organization or scope, a knockout draw, or a\ndraft, which has no groups to draw in.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"`lot_refused`, with `details.reason`: `group_incomplete` (a match of the group is\nstill to be confirmed), `no_tie` (no tie that only lots can rank begins at\n`rank`), `lot_drawn` (a lot ranks that tie already) or `tie_changed` (the tie at\n`rank` is not the entries named). Read the standings again.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"`validation_failed`: a group the draw does not have, a rank below 1, or fewer\nthan two participant ids, or one twice.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}}},"components":{"parameters":{"juniorId":{"name":"junior_id","in":"query","required":false,"description":"A junior the caller is a guardian of, to read or act for them rather than for the\ncaller (DEV-36). The caller is recorded as who did it; the junior's is what it is\nabout. A junior that is not the caller's is `404`.\n","schema":{"type":"string","format":"uuid"}},"juniorIdPath":{"name":"juniorId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},"invitationId":{"name":"invitationId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},"tournamentId":{"name":"tournamentId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},"matchId":{"name":"matchId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},"courtId":{"name":"courtId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},"drawId":{"name":"drawId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},"playerId":{"name":"playerId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},"displayId":{"name":"displayId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},"registrationId":{"name":"registrationId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},"ifNoneMatch":{"name":"If-None-Match","in":"header","required":false,"description":"The `ETag` of the projection the client last read. When it still describes the\nprojection the answer is a bodiless 304; compared weakly, so a `W/` a compressing\nproxy added still matches.\n","schema":{"type":"string"}}},"headers":{"LiveETag":{"description":"Names the projection the answer describes, the same for every reader of it: a\ntournament's names its version, and changes whenever anything shown on it may have.\nSent back as `If-None-Match` on the next poll.\n","schema":{"type":"string"}},"LiveCacheControl":{"description":"`public, max-age=5`: a shared cache in front of Live may answer for a few seconds,\nless than a page's polling interval. The browser's own polling sends\n`If-None-Match` and so always reaches the server or that cache.\n","schema":{"type":"string"}}},"requestBodies":{"RedemptionCodeRequest":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"kind":{"type":"string","enum":["qr","pin"],"default":"qr","description":"A QR code is a 256-bit secret; a PIN is eight digits a referee can read out, with a shorter life."},"lifetime_seconds":{"type":"integer","minimum":60,"maximum":3600,"description":"Defaults to fifteen minutes for a QR code and ten for a PIN; clamped to a minute and an hour."}}}}}}},"responses":{"PasswordStepDone":{"description":"Done. Nothing to show but the next step.","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object"}}}}}},"LiveNotModified":{"description":"The projection is the one `If-None-Match` named. No body; the tag is sent again so a\nclient may keep it.\n","headers":{"ETag":{"$ref":"#/components/headers/LiveETag"},"Cache-Control":{"$ref":"#/components/headers/LiveCacheControl"}}},"LiveNotFound":{"description":"`not_found`. For a tournament or match: none by that id, one of a tournament not\npublished on Live, or another organization's, all answered alike so the answer says\nnothing about a tournament that is not public. For the organization's page: a host\nthat belongs to no organization, which is every host but a customer's.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"LiveRateLimited":{"description":"`rate_limited`: the client's address has spent Live's budget of reads, which is\nshared by every page behind that address and generous for it. `Retry-After`, at\nleast a second, says when to try again; a page keeps what it last read meanwhile.\nWhile the budget cannot be checked, the read goes through.\n","headers":{"Retry-After":{"schema":{"type":"integer","minimum":1}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"PlanLimitReached":{"description":"`plan_limit_reached`: the organization's plan has no room for another, and nothing\nwas written (DEV-19). `details.limit` names which allowance\n(`active_tournaments`, `courts_per_tournament`, `displays_per_tournament`,\n`hostnames`, `machine_keys`, `leagues` or `league_teams`), with `details.plan` and\n`details.allowance`;\n`details.reason` repeats the limit. It stays refused until something is removed, a\nrunning tournament is closed or its dates pass, or the plan changes. Never an answer\nto a match command.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"MatchDayRefused":{"description":"`tournament_closed`: the tournament is closed, and admits no device (DEV-25).\n`plan_limit_reached`: the code would begin the match day of a tournament still being\nprepared, and the organization's plan has no room for another running tournament\n(DEV-47); `details.limit` is `active_tournaments`. Nothing is issued, and the\ntournament stays a draft.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"RateLimited":{"description":"`rate_limited`: the signed-in user, device or machine key has spent its budget\nfor this class of request, which `details.limit_class` names: `command` (a\nscoring command, or claiming or releasing the scoring session), `import` (an\nimport of more than one row, or any from a machine key; one row from staff, an\nentry added by hand, is a `write`), `write` or `read`. Each user, each device credential and each machine key has its\nown, so an integration's imports never share a person's. `Retry-After`, at least\na second, says when to try again.\n\nThe budget is checked before the request is routed, so a refused command was\nnever received and holds no receipt: send it again under the same `command_id`.\nWhile the budget cannot be checked, a command, a write or a read goes through\nand an import is refused with `Retry-After: 30`.\n","headers":{"Retry-After":{"schema":{"type":"integer","minimum":1}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"MatchUnauthorized":{"description":"No credential.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"MatchNotFound":{"description":"No such match in the principal's organization, or not on this device's court.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"CourtNotFound":{"description":"No such court of that tournament for this device: a court session is bound to one\ncourt of one tournament, and the path names another.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"TournamentUnauthorized":{"description":"No credential.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"TournamentNotFound":{"description":"No such tournament in the principal's organization, or one outside the principal's\nscope. Not found rather than forbidden, so the answer reveals nothing about a\ntournament the caller may not read.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"ReadScopeRequired":{"description":"`insufficient_scope`: a machine key that was not issued `tournaments:read`. It is\nrefused before the path is looked up, so the answer is the same for every\ntournament and match.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"CursorExpired":{"description":"`cursor_expired`: the position has fallen out of retained history, or is ahead of\nthe feed, which only a restored database produces. Take a new snapshot and resume\nafter its `snapshot_cursor`; continuing from a gap would silently skip changes.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"IssuedRedemptionCode":{"description":"The code, shown once, and the page URL that redeems it.","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/IssuedRedemptionCode"}}}}}},"RedemptionCodeUnauthorized":{"description":"No credential.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"RedemptionCodeForbidden":{"description":"The caller is not a director of this tournament or an administrator of the organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"RedemptionCodeTargetNotFound":{"description":"No such court or display under this organization and tournament.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"RedemptionCodeInvalidRequest":{"description":"A `kind` that is not `qr` or `pin`, or a lifetime that is not a whole number.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"DrawBody":{"description":"The draw as it now stands.","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["draw"],"properties":{"draw":{"$ref":"#/components/schemas/Draw"}}}}}}}},"DrawReadForbidden":{"description":"`insufficient_scope`: the caller is none of the tournament's staff. An\nadministrator of the organization and the tournament's director and referee read\nits draws; a court or display credential, or a machine key, does not. A display\nreads the standings of the draws its projection names and is refused any other\ndraw's with this answer.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"DrawNotFound":{"description":"No such draw in the principal's organization, or one in a tournament outside the\nprincipal's scope.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"DrawConflict":{"description":"`draw_locked`: the draw is published, and a published draw is not changed\nstructurally at any revision; a late withdrawal is a walkover. `revision_conflict`:\nanother edit moved the draft past `expected_revision`, which `details.current_revision`\ngives; read the draw again and redo the change on it.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"DrawInvalid":{"description":"`validation_failed`: a malformed body, or a draw that cannot be played as asked,\nwith `details.problem` saying which: `entry_count` (fewer than two entries to\nplace, more than 256 in a knockout draw or 416 in a round robin, or more than 16\nin one group), `entry_repeated`, `seed_out_of_range`, `position_out_of_range`,\n`two_byes_meet`, `not_placed` (an entry without a line or a group),\n`kind_mismatch` (a pair in a singles draw, or the reverse), `unknown_participant`,\n`group_count` (a round-robin placement with no group count, one outside 1 to 26,\nor too few groups of 16 for the entries), `group_too_small` (a group of fewer than\ntwo), `format_mismatch` (an edit of the other format: lines for a round robin,\ngroups or a group count for a knockout, a draw by the regulations or an option\nfor a round robin), `regulation_seeding` (a `gcr_draw` or\n`bec_championships_draw` of seeds numbered with a gap, or of more than GCR\n11.8.1, or Badminton Europe's Appendix I 7.2, allows for the entry count),\n`ranking_missing` and `ranking_mixed` (seeds numbered from a ranking no entry has\na position in, or from positions in more than one), `third_place_unplayable` (a\nthird-place match asked of a bracket without two semi-finals) or\n`consolation_unplayable` (a consolation asked of a bracket of two, of a code of\n16, or where a semi-final's loser would be owed to both the third-place match and\nthe consolation, a draw fed by groups included) or `all_places_exclusive` (every\nplace asked with a third-place match or a consolation). A draw fed by groups refuses\nentries, a placement and lines as `format_mismatch`; making one may also be\nrefused as `qualifier_count`, `source_unusable` or `source_taken`. An entry whose\nplayers' recorded gender contradicts the draw's entry rule is `gender_mismatch`,\non listing the entries, setting the rule and publishing, since a gender may be\nrecorded after the entry; `details.participant_ids` names every such entry. A mixed\nrule on a ruleset played by singles is `mixed_needs_pairs`. The field in `details.validation_errors` is the request's:\n`group_count` from `/placement`, `method` for a draw by the regulations of a\nround robin, `ranking` or `count` from `/seeds-from-ranking`,\n`groups` from `/groups`, `third_place`, `consolation` or `all_places` for the\noptions. The\nproblem values are append-only. At publication, a\nreference a match of the tournament already holds is refused on `code`, in\n`details.validation_errors`, with no problem value.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"ControlUnauthorized":{"description":"No credential.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"TournamentRoleGranted":{"description":"The role as granted; `granted` is false when the person already held it.","content":{"application/json":{"schema":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["member","granted"],"properties":{"member":{"$ref":"#/components/schemas/TournamentStaffMember"},"granted":{"type":"boolean"}}}}}}}},"OperatorUnauthorized":{"description":"No signed-in session.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"OperatorForbidden":{"description":"`insufficient_scope`: not a platform operator, or not on the shared API host. An\nadministrator of any organization, a device, a display, a machine key and an\naccess token are all refused.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"OperatorNotFound":{"description":"No such organization, or no such hostname in it; or a malformed id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"IntegrationKeyForbidden":{"description":"`insufficient_scope`: not an administrator of the organization. A director, a\ndevice and a key itself are all refused: credentials are the administrator's, and\na key never mints another.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"OrganizationHostnameForbidden":{"description":"`insufficient_scope`: not an administrator of the organization. The primary host\nnames the organization's public address, so a director, a device and a key are\nrefused.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"ControlForbidden":{"description":"`insufficient_scope`: the caller is not a director of this tournament or an\nadministrator of the organization. A court or display credential is refused even\non its own tournament, and so is a referee: administering the draw is not scoring\nit.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"securitySchemes":{"sessionCookie":{"type":"apiKey","in":"cookie","name":"roundr_session","description":"First-party browser session, issued by the page's API origin (`api.roundr.com`\nfor a Roundr page, the tenant host itself for a customer's) and scoped to `/v1`,\nhost-only. `SameSite=Lax` is correct here and looks wrong at a glance:\n`roundr.com` and `api.roundr.com` share a registrable domain, so requests between\nthem are cross-origin but same-site, and Lax cookies are sent on credentialed\n`fetch` and on `EventSource` with `withCredentials`; a customer host calls its\nown origin. Every write needs the session's `X-CSRF-Token` and a Roundr page\n`Origin`, or it is refused with `403 csrf_rejected`.\n"},"apiKey":{"type":"apiKey","in":"header","name":"X-API-Key","description":"A `key_` machine integration key. Any other credential shape here is rejected."},"bearerAuth":{"type":"http","scheme":"bearer","description":"Routed by shape, never by guessing. Exactly three dot-separated segments is a\nCognito access token. A `crt_`, `dsp_` or `key_` prefix is an opaque court\nsession, display session or integration key, verified by digest lookup.\nAnything else is rejected. Shape detection only selects a verifier; it\nauthenticates nothing.\n"}},"schemas":{"HealthStatus":{"type":"object","description":"Not wrapped in the standard `{ok, data}` envelope. Adminware's readiness probe\nreads these keys at the top level, which makes the shape a cross-repository\ncontract rather than a Roundr preference.\n","required":["component","status","healthy","message","build_id","timestamp","components"],"properties":{"component":{"type":"string","const":"application"},"status":{"type":"string","enum":["healthy","degraded","unknown","unhealthy"]},"healthy":{"type":"boolean","description":"True when the HTTP status is 200. Degraded still counts as serving."},"message":{"type":"string"},"build_id":{"type":"string","description":"The version actually running here, as opposed to the latest successfully\nbuilt version Adminware reports from its build records. `dev` on an\nunstamped local checkout.\n"},"timestamp":{"type":"string","format":"date-time"},"components":{"type":"object","description":"Component name to level. Names and levels only, no diagnostic detail.","additionalProperties":{"type":"string","enum":["healthy","degraded","unknown","unhealthy"]}}}},"OperationalStatus":{"type":"object","required":["application_id","build_id","status","degraded_components","unmeasured_components","thresholds","timestamp"],"properties":{"application_id":{"type":"string","const":"roundr","description":"Must match the `application_id` on centralized log entries."},"build_id":{"type":"string"},"status":{"type":"string","enum":["ok","degraded","unknown"]},"degraded_components":{"type":"array","description":"`outbox_relay` for a blocked row or lag past the warning threshold;\n`realtime_observability` when the registry behind the realtime gauges cannot\nbe read; `realtime_capacity` when the open streams have reached\n`realtime_open_connections_warn`.\n","items":{"type":"string"}},"unmeasured_components":{"type":"array","description":"Always empty now that the stream is served and every gauge is measured. Kept so\na poller written against the earlier shape keeps working; a gauge that cannot\nbe read is reported as null and degrades `status` instead.\n","items":{"type":"string"}},"metrics":{"description":"Null when the gauges could not be read. Null is not zero: \"no backlog\" and\n\"cannot see the backlog\" call for opposite operator responses.\n","oneOf":[{"type":"null"},{"$ref":"#/components/schemas/OutboxMetrics"}]},"thresholds":{"type":"object","description":"Travels with the gauges so the alert rule lives in one place, rather than\nAdminware keeping a second copy that drifts from Roundr's configuration.\n","required":["outbox_lag_warn_seconds","outbox_lag_critical_seconds","outbox_retention_days","realtime_open_connections_warn","realtime_open_connections_critical"],"properties":{"outbox_lag_warn_seconds":{"type":"integer"},"outbox_lag_critical_seconds":{"type":"integer"},"outbox_retention_days":{"type":"integer"},"realtime_open_connections_warn":{"type":"integer","description":"Open streams at which `status` reads degraded, three quarters of the cap.\n"},"realtime_open_connections_critical":{"type":"integer","description":"The cap on open streams: at it, the next stream is refused with\n`503 realtime_unavailable`.\n"}}},"timestamp":{"type":"string","format":"date-time"}}},"OutboxMetrics":{"type":"object","required":["outbox_unpublished_count","outbox_blocked_count","outbox_publish_failure_count","realtime_open_connections","realtime_cursor_resets"],"properties":{"outbox_unpublished_count":{"type":"integer","description":"Rows committed to MySQL but not yet published to a stream."},"outbox_oldest_unpublished_age_seconds":{"type":["integer","null"],"description":"Null when there is no backlog."},"outbox_blocked_count":{"type":"integer","description":"Rows that exhausted retries. A blocked row is an explicit unmet delivery\nobligation: it is never dropped and never marked published, and it head-blocks\nits own tournament's feed until an operator repairs and replays it. Other\ntournaments continue.\n"},"outbox_publish_failure_count":{"type":"integer"},"outbox_relay_last_success_timestamp":{"type":["string","null"],"format":"date-time"},"realtime_open_connections":{"type":["integer","null"],"description":"Streams open across every instance, from a registry each stream refreshes on\nits heartbeat. Null only when the registry cannot be read, which lists\n`realtime_observability` under `degraded_components` and degrades `status`:\nthe registry is the same Valkey the streams read from.\n"},"realtime_cursor_resets":{"type":["integer","null"],"description":"Clients told to reload because their cursor fell outside retained history; a\nrising count is clients reloading whole tournaments. Null only when the\nregistry cannot be read, which degrades `status`.\n"}}},"ClientErrorReport":{"type":"object","required":["errors"],"properties":{"errors":{"type":"array","minItems":1,"maxItems":25,"items":{"$ref":"#/components/schemas/ClientError"}}}},"ClientError":{"type":"object","description":"One uncaught error, as the browser reported it. Strings over the server's\nper-field caps are cut, not refused; an entry without a message is skipped.\n","required":["message","type","url","userAgent","timestamp","buildId"],"properties":{"message":{"type":"string"},"type":{"type":"string","enum":["error","unhandledrejection","react"],"description":"Which hook caught it: the window `error` event, the `unhandledrejection`\nevent, or React's root error callbacks, which are the only route for a\nrender error.\n"},"source":{"type":"string","description":"The script URL from the `error` event, where there was one."},"lineno":{"type":"integer"},"colno":{"type":"integer"},"stack":{"type":"string"},"componentStack":{"type":"string","description":"React's trace of the component that threw, for `type = react`."},"url":{"type":"string","description":"The page the error happened on."},"userAgent":{"type":"string"},"timestamp":{"type":"string","format":"date-time","description":"When the client caught it, which can be well before it was sent."},"buildId":{"type":"string","description":"The client build that threw. Per entry rather than per report, because the\nqueue survives a reload and can be flushed by the build after the one that\nproduced the error.\n"}}},"Draw":{"type":"object","required":["id","tournament_id","name","code","format","standings_rules","third_place","consolation","main_draw_id","consolation_draw_id","ruleset","status","size","group_count","revision","lot_drawn","placement_method","published_at","entries","matches"],"properties":{"id":{"type":"string","format":"uuid"},"tournament_id":{"type":"string","format":"uuid"},"name":{"type":"string"},"code":{"type":"string","description":"The prefix of the matches' references, `MS` for `MS-1` and `MS-A1`."},"format":{"type":"string","enum":["knockout","round_robin"],"description":"Append-only."},"standings_rules":{"type":["string","null"],"description":"The rules a round-robin draw's standings are ranked by, recorded when it was\nmade, `bwf-gcr-16.2`; a later rule set is a new value. Null for a knockout draw.\n"},"third_place":{"type":"boolean","description":"Whether the losers of the semi-finals play for third: the final round's second\nslot, numbered after the final. Always false for a round robin and a consolation.\n"},"consolation":{"type":"boolean","description":"Whether the draw has a consolation, which its publication creates. Always false\nfor a round robin and for a consolation itself.\n"},"all_places":{"type":"boolean","description":"Whether the draw plays off every place: its losers play on in placement groups\nof its own bracket, each such match naming its `places`. Never with\n`third_place` or `consolation`, and always false for a round robin and a\nconsolation. Sent by every server that plays off every place.\n"},"main_draw_id":{"type":["string","null"],"format":"uuid","description":"For a consolation, the draw it consoles, whose losers its sides take; null for\nany other draw. A consolation is published from the start, has no entries of its\nown, and is changed as its main draw is: never.\n"},"consolation_draw_id":{"type":["string","null"],"format":"uuid","description":"The draw's consolation, once publication has created it; otherwise null."},"source_draw_id":{"type":["string","null"],"format":"uuid","description":"For a knockout draw fed by groups, the round robin whose groups feed it; null for\nany other draw. Such a draw has no entries: its first sides are `group_rank`\nsources. Sent by every server that serves `group_rank` sides.\n"},"qualifiers_per_group":{"type":["integer","null"],"description":"How many of each group go on to a draw fed by groups; null for any other draw."},"qualifiers":{"type":["integer","null"],"description":"How many qualifiers a qualifying knockout produces, its sections; null for any\nother draw. Its `size` is the qualifiers times the lines of a section, and it\nplays until each section has a winner. Sent by every server that serves `feeds`.\n"},"seed_bands":{"type":"boolean","description":"Whether the seeds were drawn in bands, 3 and 4 between two lines, 5 to 8 between\nfour and so on, as the regulations draw them: a client prints seed 3 as \"3/4\".\n"},"feeds":{"type":"array","description":"The draw's places another draw's results fill, in feed order; empty for a draw\nfed by none. Sent by every server that serves them.\n","items":{"$ref":"#/components/schemas/DrawFeed"}},"entry_gender":{"$ref":"#/components/schemas/EntryGender"},"age_rule":{"$ref":"#/components/schemas/AgeRule"},"class_rule":{"$ref":"#/components/schemas/ClassRule"},"ruleset":{"type":"object","required":["id","version","engine_version"],"properties":{"id":{"type":"string"},"version":{"type":"string"},"engine_version":{"type":"string"}}},"status":{"type":"string","enum":["draft","published"]},"size":{"type":["integer","null"],"description":"The bracket's lines, a power of two, or for a qualifying draw its qualifiers\ntimes a power of two; null until the entries are placed, and always null for a\nround-robin draw.\n"},"group_count":{"type":["integer","null"],"description":"How many groups a round-robin draw's entries stand in, named A onwards; null\nuntil they stand in any, and always null for a knockout draw.\n"},"revision":{"type":"integer","description":"Moves once per change of the draw. Sent back as `expected_revision`."},"lot_drawn":{"type":"boolean","description":"Whether a lot the server drew placed the unseeded entries."},"seeds_moved":{"type":"boolean","description":"Whether a seed's withdrawal after publication moved the draw's seeds\n(`POST /v1/draws/{drawId}/seed-withdrawal`), which GCR 11.3.1 allows once. Sent\nby every server that moves seeds.\n"},"placement_method":{"type":["string","null"],"enum":["entry_order","lot","gcr_draw","bec_championships_draw",null],"description":"How the entries were last placed, kept when the director arranges them by hand\nand cleared with a new entry list: null until they are placed, and for a draw\nplaced in entry order before the method was recorded. Append-only.\n"},"published_at":{"type":["string","null"],"format":"date-time"},"entries":{"type":"array","description":"In the order they were listed.","items":{"$ref":"#/components/schemas/DrawEntry"}},"matches":{"type":"array","description":"The published draw's matches, round by round and top to bottom, a round of a\nround robin running from group A on; empty for a draft.\n","items":{"$ref":"#/components/schemas/DrawMatch"}}}},"DrawFeed":{"type":"object","required":["feed","source_draw_id","source_match_id","external_ref","takes","position","group","group_place","participant_id","void","seed","seed_banded"],"description":"A place of a draw another draw's result fills (DEV-30): the winner of a match of a\nqualifying draw, or the loser of a match of another knockout. Placed with the\nentries, on a line or at a place of a group; it holds whom that result sent once\nthe match is confirmed.\n","properties":{"feed":{"type":"integer","description":"The place's number in its draw, from 1, which an arrangement names it by."},"source_draw_id":{"type":"string","format":"uuid"},"source_draw_name":{"type":"string","description":"The name of the draw the feeding match is in. Staff reads only."},"source_match_id":{"type":"string","format":"uuid"},"external_ref":{"type":"string","description":"The feeding match's reference, for \"Winner of Q-7\"."},"takes":{"type":"string","enum":["winner_of","loser_of"]},"position":{"type":["integer","null"]},"group":{"type":["integer","null"]},"group_place":{"type":["integer","null"]},"participant_id":{"type":["string","null"],"format":"uuid","description":"Whom the confirmed feeding match sent; null before, and when it sent nobody."},"void":{"type":"boolean","description":"The feeding match is confirmed and sent nobody, a double walkover."},"seed":{"type":["integer","null"],"description":"The seed its holder carries from the draw they came from, printed with \"Q\" (\"[1, Q]\")."},"seed_banded":{"type":"boolean","description":"Whether that seed was drawn in a band where it was given."}}},"DrawEntry":{"type":"object","required":["participant_id","seed","position","group","group_place","association","ranking_position"],"properties":{"participant_id":{"type":"string","format":"uuid"},"seed":{"type":["integer","null"]},"position":{"type":["integer","null"],"description":"The entry's line of the bracket, from 1 at the top; null until placed, and in a round robin. A line no entry holds is a bye."},"group":{"type":["integer","null"],"description":"A round-robin entry's group, from 1 for A; null until placed, and in a knockout."},"group_place":{"type":["integer","null"],"description":"Its place in the group, from 1: the position the group's order of play is read\nby, seeds first by seed number. Null whenever `group` is.\n"},"association":{"type":["string","null"],"description":"The Member a draw by the regulations kept this entry apart from the rest of by\n(GCR 11.12), as it read the players' association when it placed the entries;\nnull for any other placement, and for an entry of no Member.\n"},"ranking_position":{"type":["integer","null"],"description":"The ranking position a draw by the regulations read of the entry when it placed\nthe entries, which ranks a Member's entries; null for any other placement.\n"}}},"DrawMatch":{"type":"object","required":["match_id","round","slot","group","home_source","away_source"],"properties":{"match_id":{"type":"string","format":"uuid"},"round":{"type":"integer","description":"From 1; a knockout's final is round log2(size), slot 1, and its third-place\nmatch, if it plays one, the same round's slot 2.\n"},"slot":{"type":"integer","description":"The match's place in its round, from 1 at the top; in a round robin, running on\nfrom one group to the next.\n"},"group":{"type":["integer","null"],"description":"A group match's group, from 1 for A; null in a knockout."},"places":{"description":"For a placement match of a draw that plays off every place, the places it\ndecides between: its winner goes on towards `from`, its loser towards `to`, and\nthe last match of a group decides both. Counted over the entrants who can reach\nit, so a bye takes no place. Null for any other match. Sent by every server that\nplays off every place.\n","oneOf":[{"type":"null"},{"$ref":"#/components/schemas/DrawMatchPlaces"}]},"home_source":{"$ref":"#/components/schemas/DrawSideSource"},"away_source":{"$ref":"#/components/schemas/DrawSideSource"}}},"DrawMatchPlaces":{"type":"object","required":["from","to"],"properties":{"from":{"type":"integer","minimum":1},"to":{"type":"integer","minimum":2}}},"DrawSideSource":{"type":"object","required":["kind","position","match_id","external_ref","void","participant_id"],"description":"Where a side of a draw match comes from: the entry at a line of the bracket, the\nwinner of an earlier match of the draw, in a group the entry itself, or the loser of\na match of the main draw: a semi-final's, on a side of the third-place match, or,\non a side of a consolation, the loser of the first match they played there; or, in\na knockout draw fed by groups, the entry finishing at a rank of a group (\"Winner of\nGroup A\"). `kind` is append-only. A side a fed place plays (DEV-30) is sent as\n`winner_of` or `loser_of` of the match that fills the place, which may be another\ndraw's, with `feed`, `source_draw_id` and `source_draw_name` set: \"Winner of Q-7,\nQualifying 2\".\n","properties":{"kind":{"type":"string","enum":["position","winner_of","entry","loser_of","group_rank"]},"position":{"type":["integer","null"],"description":"The line, for `position`."},"match_id":{"type":["string","null"],"format":"uuid","description":"The feeding match, for `winner_of` and `loser_of`."},"external_ref":{"type":["string","null"],"description":"The feeding match's reference, for \"Winner of MS-3\" and \"Loser of MS-5\"."},"void":{"type":"boolean","description":"For `winner_of` and `loser_of`: the feeding match is confirmed and sends nobody\nhere, so nobody stands on this side while that result stands, and it reads \"No\nopponent\". A double walkover has neither a winner nor a loser, and a\nconsolation's side is void when the loser had won a match before: the\nfirst-round winner who then lost to the entry a bye sent through. It is not\nfinal: reopening the feeding match, admitted only while this match is still\nscheduled and has accepted no command, makes it false until the feeding match\nis confirmed again. Sent because a client cannot always tell for itself: a\ndisplay is sent only the matches it shows, and the feeding match may be on a\ncourt it does not. Always false for `position` and `entry`. For `group_rank`: the\ngroup is complete and the rank's holder withdrew (GCR 16.2.5), so nobody goes on\nfrom it while the group's results stand.\n"},"participant_id":{"type":["string","null"],"format":"uuid","description":"The entry, for `entry`; null for any other kind."},"group":{"type":["integer","null"],"description":"For `group_rank`, the group of the draw's round robin, from 1 for A; null for any\nother kind. Sent by every server that serves `group_rank`.\n"},"rank":{"type":["integer","null"],"description":"For `group_rank`, the rank of that group, from 1 for its winner; null for any other kind."},"tied":{"type":"boolean","description":"For `group_rank`: the group is complete and the rank is shared by entries GCR\n16.2.4.2 separates by drawing lots, and no lot has been drawn for the tie yet\n(`/v1/draws/{drawId}/lots`), so nobody stands on the side. Always false for any\nother kind.\n"},"feed":{"type":["integer","null"],"description":"The fed place of the side's own draw this side plays; null for any other side."},"source_draw_id":{"type":["string","null"],"format":"uuid","description":"For a side a fed place plays, the draw its feeding match is in; null otherwise."},"source_draw_name":{"type":["string","null"],"description":"That draw's name, for \"Winner of Q-7, Qualifying 2\"; null otherwise."}}},"DrawStandings":{"type":"object","required":["draw_id","name","code","standings_rules","groups"],"properties":{"draw_id":{"type":"string","format":"uuid"},"name":{"type":"string","description":"The draw's name, which a hall display heads the standings with."},"code":{"type":"string","description":"The prefix of its match references, `MS`."},"standings_rules":{"type":"string","description":"The rules the groups are ranked by, `bwf-gcr-16.2`."},"groups":{"type":"array","description":"Every group, from A.","items":{"$ref":"#/components/schemas/GroupStanding"}}}},"GroupStanding":{"type":"object","required":["group","name","matches_confirmed","matches_total","complete","rows"],"properties":{"group":{"type":"integer","description":"From 1."},"name":{"type":"string","description":"Its letter, `A` for group 1."},"matches_confirmed":{"type":"integer"},"matches_total":{"type":"integer"},"complete":{"type":"boolean","description":"Every match of the group is confirmed, so the ranking is final."},"rows":{"type":"array","description":"In rank order, the withdrawn last in place order.","items":{"$ref":"#/components/schemas/StandingRow"}}}},"StandingRow":{"type":"object","required":["participant_id","rank","tied","withdrawn","played","won","lost","games_won","games_lost","points_won","points_lost","decided_by"],"properties":{"participant_id":{"type":"string","format":"uuid"},"rank":{"type":"integer","description":"From 1. Entries still level share one, and the next skips past them (1, 2, 2, 4)."},"tied":{"type":"boolean","description":"Another entry shares this rank."},"withdrawn":{"type":"boolean","description":"The entry did not complete its group matches, so its results are deleted and\nit is listed after every other entry, with its tallies at zero.\n"},"played":{"type":"integer","description":"Counted matches only, as are the tallies after it."},"won":{"type":"integer"},"lost":{"type":"integer"},"games_won":{"type":"integer"},"games_lost":{"type":"integer"},"points_won":{"type":"integer"},"points_lost":{"type":"integer"},"decided_by":{"description":"What settled the entry's place among those level with it on matches won:\n`matches_won` when none was, `lots` while still level after points, and\n`lot_drawn` once the lot drawn for that tie ranks it (`/lots`). Null for a\nwithdrawn entry. Append-only.\n","oneOf":[{"type":"null"},{"type":"string","enum":["matches_won","head_to_head","game_difference","point_difference","lots","lot_drawn"]}]}}},"SeedMovement":{"type":"object","description":"What a seed's withdrawal did to a knockout draw (GCR 11.3), in the order it did it.","required":["withdrawn","moves","promoted","vacant"],"properties":{"withdrawn":{"type":"array","items":{"type":"string","format":"uuid"},"description":"The seeds who withdrew and left the draw."},"moves":{"type":"array","description":"Seeds group by group, then the unseeded entries the lot drew.","items":{"type":"object","required":["participant_id","from","to"],"properties":{"participant_id":{"type":"string","format":"uuid"},"from":{"type":"integer","minimum":1,"description":"The line the entry stood on."},"to":{"type":"integer","minimum":1,"description":"The line it stands on now."}}}},"promoted":{"type":"array","description":"The reserves promoted, each onto a line an unseeded entry left.","items":{"type":"object","required":["participant_id","to"],"properties":{"participant_id":{"type":"string","format":"uuid"},"to":{"type":"integer","minimum":1}}}},"vacant":{"type":"array","items":{"type":"integer","minimum":1},"description":"Lines nobody took, in order; the entrant opposite goes through by walkover."}}},"GroupLot":{"type":"object","required":["id","group","rank","order","drawn_at"],"properties":{"id":{"type":"string","format":"uuid"},"group":{"type":"integer","description":"From 1 for A."},"rank":{"type":"integer","description":"The rank the tie shared; the lot ranks the entries from it."},"order":{"type":"array","description":"The tied entries in the order the lot gave them, the first ranked highest.","items":{"type":"string","format":"uuid"}},"drawn_at":{"type":"string","format":"date-time"}}},"ErrorResponse":{"type":"object","required":["ok","error"],"properties":{"ok":{"type":"boolean","const":false},"error":{"type":"object","required":["code","message","error_code"],"properties":{"code":{"type":"integer","description":"Mirrors the HTTP status."},"message":{"type":"string","description":"Human-readable and localizable. Not stable; do not branch on it."},"error_code":{"$ref":"#/components/schemas/ErrorCode"},"details":{"type":"object","description":"Machine-readable context for recovery: the current revision for a\n`revision_conflict`, the current epoch for a\n`scoring_session_superseded`, field errors for a `validation_failed`, the\nspent budget's `limit_class` for a `rate_limited` from a principal's budget.\n","additionalProperties":true}}}}},"ErrorCode":{"type":"string","description":"Stable and append-only. Never renamed, never repurposed, and the HTTP status a\nclient already reacts to never changes, because installed applications cannot\nupgrade with the server.\n\n`tournament_closed` (409) answers any write to a tournament that has been closed\n(`PATCH /v1/tournaments/{tournamentId}/closure`), on every route that writes to one,\na match command included, where nothing is recorded against the `command_id`.\n","enum":["authentication_required","invalid_token","credential_expired","credential_revoked","insufficient_scope","conflicting_credentials","csrf_rejected","redemption_code_invalid","redemption_code_expired","redemption_code_redeemed","redemption_code_locked","revision_conflict","scoring_session_superseded","scoring_session_held","idempotency_key_reused","reconciliation_required","ruleset_not_supported","invalid_command_for_state","cursor_expired","validation_failed","not_found","method_not_allowed","request_too_large","unsupported_api_version","rate_limited","internal_error","realtime_unavailable","draw_locked","plan_limit_reached","lot_refused","invalid_credentials","email_unverified","email_in_use","account_disabled","code_invalid","code_expired","password_policy","password_reset_required","sign_in_challenge","sign_in_unavailable","tournament_closed","matches_unfinished","tournament_running","umpired_match_invalid","umpired_match_limit_reached","reauthentication_required","organization_needs_administrator","organization_has_hostnames","membership_refused","league_refused","billing_refused","push_unavailable","court_in_use"]},"PersonalDataExport":{"type":"object","description":"Not wrapped in the envelope: the body is the file. Identifiers are the public UUIDs\nthe API uses, instants ISO 8601 in UTC.\n","required":["format","version","produced_at","account_id","sections"],"properties":{"format":{"type":"string","const":"roundr.personal-data"},"version":{"type":"integer","const":1},"produced_at":{"type":"string","format":"date-time"},"account_id":{"type":"string","format":"uuid"},"sections":{"type":"object","description":"One entry per area of personal data, keyed by the area's name.","additionalProperties":true}}},"OfficialRole":{"type":"string","enum":["umpire","service_judge"]},"OfficialRotationBoardResponse":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["rotation","courts","umpires","service_judges","reserve","officials","courts_to_add"],"properties":{"rotation":{"type":"object","required":["id","name","revision"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"revision":{"type":"integer"}}},"courts":{"type":"array","items":{"type":"object","required":["court_id","court_name","tournament_id","tournament_name","has_service_judge","umpire_id","service_judge_id","match"],"properties":{"court_id":{"type":"string","format":"uuid"},"court_name":{"type":"string"},"tournament_id":{"type":"string","format":"uuid"},"tournament_name":{"type":"string"},"has_service_judge":{"type":"boolean"},"umpire_id":{"type":["string","null"],"format":"uuid"},"service_judge_id":{"type":["string","null"],"format":"uuid"},"match":{"description":"The match the court's crew serves now, the one in play or the court's next; null for none.","oneOf":[{"type":"null"},{"type":"object","required":["id","reference","home_name","away_name","status","serving","same_nationality"],"properties":{"id":{"type":"string","format":"uuid"},"reference":{"type":["string","null"]},"home_name":{"type":"string"},"away_name":{"type":"string"},"status":{"type":"string","enum":["scheduled","in_progress","suspended"]},"serving":{"type":"boolean","description":"Whether the court's crew is written on this match already."},"same_nationality":{"type":"array","description":"The court's officials who share a Member with a player of the match.","items":{"type":"string","format":"uuid"}}}}]}}}},"umpires":{"type":"array","description":"The umpires' queue, head first.","items":{"type":"string","format":"uuid"}},"service_judges":{"type":"array","description":"The service judges' queue, head first.","items":{"type":"string","format":"uuid"}},"reserve":{"type":"array","items":{"type":"object","required":["id","role"],"properties":{"id":{"type":"string","format":"uuid"},"role":{"$ref":"#/components/schemas/OfficialRole"}}}},"officials":{"type":"array","items":{"type":"object","required":["id","name","nationality","umpires","judges_service","account","counts"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"nationality":{"type":["string","null"]},"umpires":{"type":"boolean"},"judges_service":{"type":"boolean"},"account":{"type":"boolean","description":"Whether the official is a Roundr account, told by push when put on a court."},"counts":{"type":"object","description":"Matches umpired or judged that have ended, by discipline (MS, WS, MD, WD, XD; S or D where the draw names no gender).","additionalProperties":{"type":"integer"}}}}},"courts_to_add":{"type":"array","description":"The courts of the organization's tournaments not closed that no rotation serves and the caller may add.","items":{"type":"object","required":["court_id","court_name","tournament_id","tournament_name"],"properties":{"court_id":{"type":"string","format":"uuid"},"court_name":{"type":"string"},"tournament_id":{"type":"string","format":"uuid"},"tournament_name":{"type":"string"}}}}}}}},"MatchOfficial":{"type":"object","required":["name","nationality"],"properties":{"name":{"type":"string"},"nationality":{"type":["string","null"],"description":"The Member's three letters the desk wrote, or null."}}},"Match":{"type":"object","required":["id","tournament_id","court_id","ruleset","participants","external_ref","scheduled_start","sequence","draw","scoring_session_epoch","state"],"properties":{"id":{"type":"string","format":"uuid"},"tournament_id":{"type":"string","format":"uuid"},"court_id":{"type":["string","null"],"format":"uuid"},"ruleset":{"type":"object","required":["id","version","engine_version"],"properties":{"id":{"type":"string"},"version":{"type":"string"},"engine_version":{"type":"string","description":"The adapter a client needs to score this match predictively."}}},"participants":{"type":"object","required":["home","away"],"properties":{"home":{"type":["string","null"],"format":"uuid"},"away":{"type":["string","null"],"format":"uuid"}}},"external_ref":{"type":["string","null"],"description":"The organizer's reference for the match, as the draw file or the create form\ngave it (`MS-1`); what staff call the match. Null for a match made without one.\n"},"scheduled_start":{"type":["string","null"],"format":"date-time","description":"When the match is scheduled to start, in UTC, or null."},"sequence":{"type":"integer","minimum":0,"description":"The match's place in its court's queue; 0 when none was given."},"not_before":{"type":["string","null"],"format":"date-time","description":"The earliest the match may start, in UTC, however its court's queue runs: the\n\"Not before 11:30\" of an order of play. Null for none. Always sent by this\nserver; a client reading an older one takes its absence for null.\n"},"started_at":{"type":["string","null"],"format":"date-time","description":"When the match started, in UTC: when the server accepted the command that began\nplay. Null before, for a match taken back to scheduled, and for a result entered\nfrom a score sheet or given as a walkover, which nobody played. Always sent by\nthis server.\n"},"ended_at":{"type":["string","null"],"format":"date-time","description":"When the match got its result, in UTC: when the server accepted the command\nthat ended it. Null while it has none. Always sent by this server.\n"},"confirmed_at":{"type":["string","null"],"format":"date-time","description":"When the match's result was confirmed, in UTC to the second: when the server\naccepted the confirming command. Null while the result is unconfirmed, and again\nafter a reopening; a later confirmation carries its own time, so a desk\nreconciling another program with the results confirmed since a time sees a\ncorrected result again. Always sent by this server; a client reading an older\none takes its absence for null.\n"},"officials":{"description":"The match's umpire and service judge (DEV-113), as the schedule names them: a\nrotation of officials writes its court's crew on the match the crew serves, and\nthe desk changes it on its board. Each is null for a match that names nobody in\nthat role, as a court without a service judge does. Sent to the staff and to the\ncourt's own tablet; null for a hall display and a machine key. A change moves the\nschedule entry's revision with `match_officials_changed`, never the match's.\n","oneOf":[{"type":"null"},{"type":"object","required":["umpire","service_judge"],"properties":{"umpire":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/MatchOfficial"}]},"service_judge":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/MatchOfficial"}]}}}]},"draw":{"description":"The match's place in a knockout draw and where each side comes from, or null for\na match in no draw. A side whose participant is null and whose source is\n`winner_of` or `loser_of` is still to be decided, \"Winner of MS-3\" or \"Loser of\nMS-5\", unless the source is `void`: that match sends nobody on, and nobody\nstands on the side. When that match's result is confirmed its winner or loser\nis written into the side, or the side becomes void, and the match's revision\nmoves with a `match_sides_changed` notification; a reopening moves it back the\nsame way.\n","oneOf":[{"type":"null"},{"$ref":"#/components/schemas/MatchDrawPlacement"}]},"scoring_session_epoch":{"type":"integer","description":"Sent back on every scoring command; a takeover moves it."},"state":{"$ref":"#/components/schemas/MatchState"}}},"MatchDrawPlacement":{"type":"object","required":["draw_id","round","slot","group","home_source","away_source"],"properties":{"draw_id":{"type":"string","format":"uuid"},"round":{"type":"integer","description":"From 1; a knockout's final is the last round."},"slot":{"type":"integer","description":"The match's place in its round, from 1 at the top."},"group":{"type":["integer","null"],"description":"A group match's group, from 1 for A; null in a knockout."},"home_source":{"$ref":"#/components/schemas/DrawSideSource"},"away_source":{"$ref":"#/components/schemas/DrawSideSource"},"home_seed":{"type":["integer","null"],"description":"The seed in this draw of whoever stands on the home side, printed beside the name; null for an unseeded entry and while nobody stands on the side. A seed belongs to the match's own draw: a consolation's side reads the consolation's entry. Sent by this server; a client reading an older one takes its absence for none."},"away_seed":{"type":["integer","null"],"description":"The same for the away side."},"home_seed_banded":{"type":"boolean","description":"Whether the home side's seed was drawn in a band, printed \"3/4\" rather than \"3\". Sent by every server that serves `feeds`."},"away_seed_banded":{"type":"boolean","description":"The same for the away side."},"home_qualifier":{"type":"boolean","description":"Whether whoever stands on the home side came into the draw as a qualifier of another, printed \"Q\" beside the seed. Sent by every server that serves `feeds`."},"away_qualifier":{"type":"boolean","description":"The same for the away side."}}},"MatchState":{"type":"object","required":["revision","status","data"],"properties":{"revision":{"type":"integer","description":"Increments exactly once per accepted command, or per draw placement batch when a knockout draw writes a confirmed winner into this match's side or takes it back out. Opaque apart from ordering; sent back as `expected_revision`."},"status":{"type":"string","enum":["scheduled","in_progress","suspended","completed","confirmed","walkover","retired","disqualified","ended_by_referee"]},"data":{"type":"object","description":"The adapter's projection; its shape is part of the engine version's contract.","additionalProperties":true}}},"MatchCommandRequest":{"type":"object","required":["command_id","client_instance_id","scoring_session_epoch","type"],"properties":{"command_id":{"type":"string","format":"uuid","description":"Client-generated UUIDv7; the idempotency key."},"client_instance_id":{"type":"string","maxLength":36,"description":"The installation's stable identifier, kept across reauthentication."},"expected_revision":{"type":["integer","null"],"description":"The revision the client scored from. Required for a scoring command; null only for `correct_score` and `reopen_result`."},"scoring_session_epoch":{"type":"integer"},"type":{"type":"string","description":"A `MatchCommandType` value."},"payload":{"type":"object","additionalProperties":true,"description":"The command's own fields; for `record_score_sheet`, a `ScoreSheetPayload`, and\nfor `record_game`, a `GamePayload`.\n"},"client_timing":{"type":["object","null"],"description":"When the device says the command happened, for the scoresheet and its\nstatistics: `authored_at`, and for a rally the press that started it and, for a\nrally entered again after an undo, when it originally ended; for a command\nthat settles a hold, when play was held. Stored with the\nreceipt in UTC with milliseconds and returned with the command's events. Never\nread by the rules and not part of the fingerprint, so a retry stamped again is\nstill the same command.\n","required":["authored_at"],"additionalProperties":false,"properties":{"authored_at":{"type":"string","format":"date-time"},"rally_started_at":{"type":["string","null"],"format":"date-time"},"rally_ended_at":{"type":["string","null"],"format":"date-time"},"held_from":{"type":["string","null"],"format":"date-time","description":"When play was held for what this command settles, such as a review\nanswered or a service-court correction recorded, so the scoresheet can\nsay how long play was held.\n"}}}}},"ScoreSheetPayload":{"type":"object","description":"The payload of `record_score_sheet`: a match's games as its score sheet has them, in\nthe order they were played. Each game is its two scores and nothing else; the winner\nis the engine's to derive and is refused if sent.\n","required":["games"],"additionalProperties":false,"properties":{"games":{"type":"array","minItems":1,"items":{"type":"object","required":["home","away"],"additionalProperties":false,"properties":{"home":{"type":"integer","minimum":0},"away":{"type":"integer","minimum":0}}}}}},"GamePayload":{"type":"object","description":"The payload of `record_game`: one game's final score, stated from the desk as the\ngame ends. It names the game, which must be the one in play, so the same statement\nsent twice is refused rather than written into the next game. The winner is the\nengine's to derive.\n","required":["game","home","away"],"additionalProperties":false,"properties":{"game":{"type":"integer","minimum":1,"description":"The game's number in the match, from 1."},"home":{"type":"integer","minimum":0},"away":{"type":"integer","minimum":0}}},"MatchCommandAccepted":{"type":"object","required":["command_id","outcome","replayed","revision","events","state","scoring_session_epoch"],"properties":{"command_id":{"type":"string","format":"uuid"},"outcome":{"type":"string","enum":["accepted"]},"replayed":{"type":"boolean","description":"True when this id had already been decided and the stored outcome is returned."},"revision":{"type":"integer"},"events":{"type":"array","items":{"type":"object","required":["type","payload","index"],"properties":{"type":{"type":"string"},"payload":{"type":"object","additionalProperties":true},"index":{"type":"integer"}}}},"state":{"$ref":"#/components/schemas/MatchState"},"scoring_session_epoch":{"type":"integer"}}},"MatchEvent":{"type":"object","description":"One event of a match, with the revision it belongs to. An accepted command produces\na batch under one revision, numbered by `index`; revision then index orders the\nfeed.\n","required":["revision","index","type","payload","command_id","client_timing"],"properties":{"revision":{"type":"integer","description":"The match revision the event's command advanced the match to."},"index":{"type":"integer","description":"The event's position within its command's batch, from zero."},"type":{"type":"string","description":"The adapter's event type; its vocabulary is part of the engine version's contract."},"payload":{"type":"object","additionalProperties":true},"command_id":{"type":["string","null"],"format":"uuid","description":"The command that produced the event, or null where none did."},"client_timing":{"type":["object","null"],"description":"The producing command's `client_timing`, or null where it sent none.","additionalProperties":{"type":"string","format":"date-time"}},"actor":{"description":"Who sent the producing command: a person by the name on their account, or a\ncourt's tablet by its court's name. Null where no command produced the event (a\ndraw's placement). Sent only to a person on the tournament's staff, for the audit\ntrail; a court device and a machine key are not told it.\n","oneOf":[{"type":"null"},{"type":"object","required":["type","name"],"properties":{"type":{"type":"string","enum":["person","court_tablet"]},"name":{"type":["string","null"]}}}]},"recorded_at":{"type":["string","null"],"format":"date-time","description":"When the server accepted the producing command. Sent with `actor`."}}},"UmpiredMatchSummary":{"type":"object","description":"A match the account umpired on its own panel (DEV-34), as its list shows it.\n","required":["id","ruleset","setup","status","result","started_at","ended_at","saved_at"],"properties":{"id":{"type":"string","format":"uuid"},"ruleset":{"type":"object","required":["id","version","engine_version"],"properties":{"id":{"type":"string"},"version":{"type":"string"},"engine_version":{"type":"string","description":"The adapter the match was replayed with, pinned as a tournament match pins it."}}},"setup":{"type":"object","additionalProperties":true,"description":"The client's own description of the match, as it was uploaded; read by nothing on the server."},"status":{"type":"string","description":"The status the replay ended in, which is always `confirmed`.","enum":["confirmed"]},"result":{"type":"object","required":["winner","outcome","games"],"properties":{"winner":{"type":["string","null"],"enum":["home","away",null],"description":"Null only after a double walkover."},"outcome":{"type":["string","null"],"enum":["completed","walkover","retired","disqualified","ended_by_referee",null],"description":"How the match ended; `completed` for a match played out."},"games":{"type":"array","description":"The games played, in order: each that ended, and the one a retirement or a\ndisqualification stopped when a point had been scored in it.\n","items":{"type":"object","required":["home","away"],"properties":{"home":{"type":"integer"},"away":{"type":"integer"}}}}}},"started_at":{"type":["string","null"],"format":"date-time","description":"The first command's `authored_at`, UTC to the millisecond, or null when it sent none."},"ended_at":{"type":"string","format":"date-time","description":"The last command's `authored_at`, or when the match was kept where it sent none."},"saved_at":{"type":"string","format":"date-time","description":"When the account kept the match."}}},"UmpiredMatch":{"description":"A kept match with the history the server's replay derived.","allOf":[{"$ref":"#/components/schemas/UmpiredMatchSummary"},{"type":"object","required":["initial_state","events"],"properties":{"initial_state":{"$ref":"#/components/schemas/MatchState"},"events":{"type":"array","description":"Every event of the replay, by revision and index, in the shape\n`GET /v1/matches/{matchId}/events` sends: an undo names what it reversed by\nthe UUID it was uploaded under, and each event carries its command's id and\n`client_timing`.\n","items":{"$ref":"#/components/schemas/MatchEvent"}}}}]},"UmpiredMatchUpload":{"type":"object","required":["ruleset","setup","commands"],"properties":{"ruleset":{"type":"object","required":["id","version"],"properties":{"id":{"type":"string"},"version":{"type":"string"}}},"setup":{"type":"object","additionalProperties":true,"description":"The client's description of the match, at most 16 KiB encoded; kept as sent."},"commands":{"type":"array","maxItems":5000,"description":"The accepted commands, in the order the device accepted them.","items":{"type":"object","required":["command_id","type"],"properties":{"command_id":{"type":"string","format":"uuid"},"type":{"type":"string","description":"A `MatchCommandType` value; `correct_score` and `reopen_result` are refused."},"payload":{"type":"object","additionalProperties":true,"description":"The command's own fields, as for `POST /v1/matches/{matchId}/commands`; an undo names its target by the UUID it was sent under."},"client_timing":{"type":["object","null"],"required":["authored_at"],"additionalProperties":false,"properties":{"authored_at":{"type":"string","format":"date-time"},"rally_started_at":{"type":["string","null"],"format":"date-time"},"rally_ended_at":{"type":["string","null"],"format":"date-time"},"held_from":{"type":["string","null"],"format":"date-time"}}}}}}}},"ScoringSessionState":{"type":"object","required":["scoring_session_id","scoring_session_epoch","held_by"],"properties":{"scoring_session_id":{"type":"string","format":"uuid"},"scoring_session_epoch":{"type":"integer"},"held_by":{"type":"string","enum":["court_session","user"]}}},"PanelInUse":{"type":"object","description":"A live scoring session, as Control lists it.","required":["match_id","court_id","scoring_session_epoch","held_by","holder_label","claimed_at","last_command_at"],"properties":{"match_id":{"type":"string","format":"uuid"},"court_id":{"type":["string","null"],"format":"uuid","description":"The court the session was claimed for, or null for one a referee holds away from a court."},"scoring_session_epoch":{"type":"integer"},"held_by":{"type":"string","enum":["court_session","user"]},"holder_label":{"type":["string","null"],"description":"Who holds it, as staff know them: a court device's credential label, or a\nuser's display name. Null when neither is on record.\n"},"claimed_at":{"type":"string","format":"date-time","description":"When the session was opened, in UTC."},"last_command_at":{"type":["string","null"],"format":"date-time","description":"When the latest command under this session's epoch was received, in UTC, or\nnull before the first. A panel that has gone quiet in a match in play is the\nquestion staff walk over to answer.\n"}}},"ScoringSessionRelease":{"type":"object","required":["match_id","released","scoring_session_epoch"],"properties":{"match_id":{"type":"string","format":"uuid"},"released":{"type":"boolean","description":"Whether a live session was ended; false when nobody held the match."},"scoring_session_epoch":{"type":"integer","description":"The match's epoch afterwards; the next claim opens a session above it."}}},"StreamCursor":{"type":"string","pattern":"^\\d+-0$","description":"A position in one tournament's feed: a commit-ordered, gapless sequence within the\ntournament, followed by `-0`, such as `418-0`. It is the `id` of every stream\nmessage and what the browser sends back as `Last-Event-ID`, and it is neither a\nsecret nor authority: every request reauthorizes the principal, and a cursor only\nsays where to resume. Distinct from a match's revision, which orders one match's\ncommands; a tournament stream multiplexes many matches, so one position cannot\nstand for their revisions.\n"},"RealtimeNotification":{"type":"object","description":"Announces that an aggregate changed, and at which revision. It carries no state:\nthe client deduplicates by `event_id`, compares `aggregate_revision` with what it\nholds, ignores a stale one, and fetches the canonical projection when a revision\nis missing or unexpected. The same object is a `data` frame on the stream and an\nitem of the change feed.\n","required":["event_id","tournament_id","stream_cursor","aggregate_type","aggregate_id","aggregate_revision","type","occurred_at"],"properties":{"event_id":{"type":"string","format":"uuid","description":"What a client deduplicates by; a conservative resume delivers the same row again."},"tournament_id":{"type":"string","format":"uuid"},"stream_cursor":{"$ref":"#/components/schemas/StreamCursor"},"aggregate_type":{"type":"string","enum":["match","court","tournament","schedule_entry","display","draw","player_check_in"],"description":"`player_check_in` names a player (`aggregate_id` is the player's id) whose check-in\nat the desk changed (DEV-44), and reaches the staff alone: a court device, a hall\ndisplay and a machine key see a checkpoint in its place.\n"},"aggregate_id":{"type":"string","format":"uuid"},"aggregate_revision":{"type":"integer","description":"The aggregate's revision after the change. For a match, its `revision`."},"type":{"type":"string","description":"The event type, as the aggregate's own feed names it."},"occurred_at":{"type":"string","format":"date-time"}}},"StreamCheckpoint":{"type":"object","description":"What a filtered client receives in place of a notification it may not see: the\ncursor alone, agreeing with the message's `id`, so it persists a handled position\nand advances past the hidden row without learning its payload. A run of hidden\nrows cannot trap a reconnection behind the same invisible events.\n","required":["stream_cursor"],"properties":{"stream_cursor":{"$ref":"#/components/schemas/StreamCursor"}}},"TournamentSnapshot":{"type":"object","description":"One consistent read of a tournament, with the feed's cursor from the same read\nview. `snapshot_cursor` is where the client opens the stream or pages the change\nfeed from; a change the snapshot does not show carries a greater cursor.\n","required":["tournament","courts","participants","matches","snapshot_cursor"],"properties":{"tournament":{"$ref":"#/components/schemas/Tournament"},"courts":{"type":"array","description":"In `sort_order`, then name. A court device receives only its own, a display\nonly the courts its projection shows.\n","items":{"$ref":"#/components/schemas/Court"}},"participants":{"type":"array","description":"Participants by display name, with their players' names in position order, so\na panel and a display can name a match's sides without a second read. Not\nfiltered by court for a court device, which receives them all because the sides\nof the match on its court may be any of them. A display receives only the sides\nof the matches it is sent.\n","items":{"$ref":"#/components/schemas/ParticipantSummary"}},"matches":{"type":"array","description":"Each as `GET /v1/matches/{matchId}` returns it, so a snapshot entry and a\ncanonical read are one shape. A court device receives only the matches on its\ncourt; a display the matches on the courts it shows, those on no court while it\nlists them, and every match of a draw whose standings it shows.\n","items":{"$ref":"#/components/schemas/Match"}},"snapshot_cursor":{"$ref":"#/components/schemas/StreamCursor"},"display":{"$ref":"#/components/schemas/SnapshotDisplay"},"draws":{"type":"array","description":"Sent to a display session only: the published draws of the matches it is sent,\nby name, so a board names a match's event and round (\"MS · QF\"). Each lists the\nplaces in it of those matches and no other's. Sent by this server; a client\nreading an older one takes its absence for none.\n","items":{"$ref":"#/components/schemas/SnapshotDraw"}}}},"SnapshotDraw":{"type":"object","description":"A draw as a hall display names a match by it: the fields of `Draw` that name a\nround, and the places in it of the matches the display is sent.\n","required":["id","name","code","format","size","third_place","matches"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"code":{"type":"string","description":"The prefix of the matches' references, `MS` for `MS-1` and `MS-A1`."},"format":{"type":"string","enum":["knockout","round_robin"],"description":"Append-only."},"size":{"type":["integer","null"],"description":"The bracket's lines, from which a round is counted back from the final; null for a round robin."},"third_place":{"type":"boolean","description":"Whether the losers of the semi-finals play for third: the final round's second\nslot, numbered after the final.\n"},"matches":{"type":"array","description":"The places in the draw of the matches the display is sent, as `Draw` lists them.","items":{"$ref":"#/components/schemas/DrawMatch"}}}},"CourtOpenMatches":{"type":"object","description":"What a court's tablet may choose from (DEV-58): the tournament's matches that\nwait for a court, and the names of their sides.\n","required":["choosing","matches","participants"],"properties":{"choosing":{"type":"boolean","description":"The Referee's briefing, `court_chooses_match`, false for a closed tournament.\nWith it false both lists are empty, and the tablet waits for the desk.\n"},"matches":{"type":"array","description":"Each as `GET /v1/matches/{matchId}` returns it, in the snapshot's order: nothing\nplayed, on no court, both sides known.\n","items":{"$ref":"#/components/schemas/Match"}},"participants":{"type":"array","description":"The sides of the matches listed and nobody else's, as the snapshot names them.","items":{"$ref":"#/components/schemas/ParticipantSummary"}}}},"CourtMatchChoice":{"type":"object","description":"Where a match stands after a court's tablet took it or gave it back.","required":["match_id","court_id"],"properties":{"match_id":{"type":"string","format":"uuid"},"court_id":{"type":["string","null"],"format":"uuid","description":"The court the match is on now; null once it waits for a court again."}}},"SnapshotDisplay":{"type":"object","description":"Sent to a display session only: the display the snapshot was read for, with the\nparts of its projection it lays out, from the same read view. Its courts are the\nsnapshot's `courts`, already narrowed; `court_scope` tells a board with none\nwhether it shows only the upcoming list or has no court to show yet.\n","required":["id","name","revision","court_scope","show_upcoming","upcoming_count","standings_draw_ids"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"revision":{"type":"integer"},"court_scope":{"$ref":"#/components/schemas/DisplayCourtScope"},"show_upcoming":{"type":"boolean","description":"Whether the matches on no court are listed under the courts; when false, none is sent."},"upcoming_count":{"type":"integer","minimum":1,"maximum":12,"description":"How many of them the board lists."},"standings_draw_ids":{"type":"array","description":"The round-robin draws whose group standings the board shows, each read from\n`GET /v1/draws/{drawId}/standings`, which answers this display for these draws\nalone. Their matches are among the snapshot's `matches`, and a confirmation of\none of them is when to read the standings again.\n","items":{"type":"string","format":"uuid"}},"show_header":{"type":"boolean","description":"Whether the board draws its title line: the tournament and its venue, and on a\ncourt's scoreboard the court's name before them. A server from before the switch\nsends none, and the board draws it.\n"},"show_seeds":{"type":"boolean","description":"Whether the board writes a side's seed beside its name. A server from before the\nswitch sends none, and the board writes them.\n"},"layout":{"$ref":"#/components/schemas/DisplayLayout"}}},"LiveOrganization":{"type":"object","required":["organization","tournaments","leagues"],"properties":{"organization":{"type":"object","required":["name"],"properties":{"name":{"type":"string"}}},"tournaments":{"type":"array","description":"Published on Live, newest first.","items":{"$ref":"#/components/schemas/LiveTournament"}},"leagues":{"type":"array","description":"Leagues published on Live (DEV-50), newest season first.","items":{"$ref":"#/components/schemas/LiveLeague"}}}},"LiveLeague":{"type":"object","required":["id","name","season","organization_name","starts_on","ends_on"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"season":{"type":"string"},"organization_name":{"type":"string"},"starts_on":{"type":["string","null"],"format":"date"},"ends_on":{"type":["string","null"],"format":"date"}}},"LiveLeagueTeam":{"type":"object","required":["id","name","club_name"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"club_name":{"type":["string","null"],"description":"Null for a team the league entered by name, which is no club's (DEV-51)."}}},"LiveLeaguePage":{"type":"object","required":["league","match_days","divisions"],"properties":{"league":{"allOf":[{"$ref":"#/components/schemas/LiveLeague"},{"type":"object","required":["points","rubbers"],"properties":{"points":{"type":"object","required":["win","draw","loss"],"properties":{"win":{"type":"integer"},"draw":{"type":"integer"},"loss":{"type":"integer"}}},"rubbers":{"type":"array","description":"The codes of the league's tie, in score-sheet order.","items":{"type":"string"}}}}]},"match_days":{"type":"array","items":{"type":"object","required":["id","name","plays_on","venue_name","tournament_id"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"plays_on":{"type":"string","format":"date"},"venue_name":{"type":["string","null"]},"venue_time_zone":{"type":"string","description":"The IANA zone `plays_on` is a date in, so a page can tell whether the match\nday is today at its venue. Sent by every server that knows a team's page.\n"},"tournament_id":{"type":["string","null"],"format":"uuid","description":"The match day's tournament, where it is on Live within the host's reach."}}}},"divisions":{"type":"array","items":{"type":"object","required":["id","name","standings","ties"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"standings":{"type":"array","items":{"type":"object","required":["team","played","won","drawn","lost","rubbers_for","rubbers_against","games_for","games_against","points"],"properties":{"team":{"$ref":"#/components/schemas/LiveLeagueTeam"},"played":{"type":"integer"},"won":{"type":"integer"},"drawn":{"type":"integer"},"lost":{"type":"integer"},"rubbers_for":{"type":"integer"},"rubbers_against":{"type":"integer"},"games_for":{"type":"integer"},"games_against":{"type":"integer"},"points":{"type":"integer"}}}},"ties":{"type":"array","items":{"type":"object","required":["id","round","home","away","match_day_id","started","decided","home_rubbers","away_rubbers","rubbers"],"properties":{"id":{"type":"string","format":"uuid"},"round":{"type":"integer"},"home":{"$ref":"#/components/schemas/LiveLeagueTeam"},"away":{"$ref":"#/components/schemas/LiveLeagueTeam"},"match_day_id":{"type":["string","null"],"format":"uuid"},"start_time":{"type":["string","null"],"description":"`HH:MM` at the match day's venue, when the league has said it. Sent\nby every server that knows fixture times (DEV-51).\n"},"started":{"type":"boolean"},"decided":{"type":"boolean"},"home_rubbers":{"type":"integer"},"away_rubbers":{"type":"integer"},"rubbers":{"type":"array","items":{"type":"object","required":["code","status","winner","home_games","away_games","match_id"],"properties":{"code":{"type":"string"},"status":{"type":"string"},"winner":{"type":["string","null"],"enum":["home","away",null]},"home_games":{"type":"integer"},"away_games":{"type":"integer"},"match_id":{"type":["string","null"],"format":"uuid"},"not_fielded_by":{"type":["string","null"],"enum":["home","away","both",null],"description":"Which side named no player for the rubber when the tie\nstarted (DEV-53): it has no match, its `status` is\n`not_fielded`, and `winner` is the other side. Sent by every\nserver that knows such rubbers.\n"}}}}}}}}}}}},"LiveTournament":{"type":"object","description":"A tournament as Live names it. Only a tournament published on Live is ever sent.","required":["id","name","organization_name","venue_name","venue_time_zone","starts_on","ends_on","status"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"organization_name":{"type":"string","description":"Whose tournament it is, which the platform's site, serving every organization, says."},"venue_name":{"type":["string","null"],"description":"Where it is played, as a director wrote it; null when nobody has."},"venue_time_zone":{"type":"string","description":"An IANA time zone; scheduled starts are shown at the venue's clock."},"starts_on":{"type":["string","null"],"format":"date"},"ends_on":{"type":["string","null"],"format":"date"},"status":{"type":"string","enum":["draft","active","completed","cancelled"]},"match_minutes":{"type":"integer","minimum":1,"maximum":600,"description":"How long the tournament's desk assumes a match takes (`ScheduleAssumptions`),\nwhich a spectator's page estimates each court's queue from. Always sent by this\nserver; a client reading an older one takes its absence for 30.\n"},"league_id":{"type":["string","null"],"format":"uuid","description":"The league the tournament is a match day of, where that league is on Live too, so\nits table can be linked (DEV-52); null for any other tournament and for a league\noff Live. Sent by every server that knows leagues.\n"}}},"LiveCourt":{"type":"object","required":["id","name","sort_order"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"sort_order":{"type":"integer"}}},"LiveParticipant":{"type":"object","description":"A side as the public reads it: its display name, its players' nationalities and its players by id and name, and nothing else. Gender, contact, account and references are not sent; the draw's name says whom it is for.","required":["id","display_name","nationalities"],"properties":{"id":{"type":"string","format":"uuid"},"display_name":{"type":"string"},"nationalities":{"type":"array","description":"The players' nationalities as the federation writes them (AUT), each code once, in the players' order: one for a pair from one country, two for a pair from two, and empty when nobody recorded one.","items":{"type":"string","pattern":"^[A-Z]{3}$"}},"players":{"type":"array","description":"The side's players in position order, the order a pair's display name writes them in: one for a single, two for a pair. A player's id is the tournament's own, the same on every side the player is entered on at this tournament and on no other tournament, so Live can gather one player's entries on a page of its own (DEV-29). Sent by this server; a client reading an older one takes its absence for none.","items":{"type":"object","required":["id","name"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string","description":"The player's name as the director entered it for this tournament."}}}}}},"LiveScore":{"type":"object","required":["games","games_won","serving","interval","winner"],"properties":{"games":{"type":"array","description":"Every game begun, in order, the game in play last. Empty before the match starts.","items":{"type":"object","required":["home","away","complete"],"properties":{"home":{"type":"integer"},"away":{"type":"integer"},"complete":{"type":"boolean"}}}},"games_won":{"type":"object","required":["home","away"],"properties":{"home":{"type":"integer"},"away":{"type":"integer"}}},"serving":{"description":"Who serves the next rally; null outside play, through an interval and on a suspended match.","oneOf":[{"type":"null"},{"type":"string","enum":["home","away"]}]},"interval":{"type":"boolean","description":"An interval is running in the match in play."},"winner":{"description":"The side that won, once the match has a result; null before, and for an ending nobody won.","oneOf":[{"type":"null"},{"type":"string","enum":["home","away"]}]},"outcome":{"description":"How the match ended, once it has a result: `completed` for a match played out,\nor `walkover`, `retired`, `disqualified` or `ended_by_referee`; null before. The\nmatch's `status` says the same only until the result is confirmed and is\n`confirmed` from then on, whatever ended the match, so this is what still says\na walkover, a retirement or a disqualification, which was on the side `winner`\ndoes not name and on both sides where it names none. Append-only. A server from\nbefore it leaves the field out, and the ending is then the `status`'s to say.\n","oneOf":[{"type":"null"},{"type":"string","enum":["completed","walkover","retired","disqualified","ended_by_referee"]}]}}},"LiveMatch":{"type":"object","required":["id","court_id","participants","external_ref","scheduled_start","sequence","draw","status","score"],"properties":{"id":{"type":"string","format":"uuid"},"court_id":{"type":["string","null"],"format":"uuid"},"participants":{"type":"object","description":"Each side's participant, or null while a draw has still to decide it.","required":["home","away"],"properties":{"home":{"type":["string","null"],"format":"uuid"},"away":{"type":["string","null"],"format":"uuid"}}},"external_ref":{"type":["string","null"],"description":"The match's reference in its draw file, `MS-1`."},"scheduled_start":{"type":["string","null"],"format":"date-time"},"sequence":{"type":"integer","description":"Its place in its court's order of play."},"not_before":{"type":["string","null"],"format":"date-time","description":"The earliest it may start, in UTC, or null. Always sent by this server."},"started_at":{"type":["string","null"],"format":"date-time","description":"When it started, in UTC, or null: before it has, and for a result nobody played,\na walkover or a score sheet entered afterwards. Always sent by this server.\n"},"ended_at":{"type":["string","null"],"format":"date-time","description":"When it got its result, in UTC, or null. Always sent by this server."},"draw":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/MatchDrawPlacement"}]},"status":{"type":"string","enum":["scheduled","in_progress","suspended","completed","confirmed","walkover","retired","disqualified","ended_by_referee"]},"score":{"$ref":"#/components/schemas/LiveScore"}}},"LiveDraw":{"type":"object","description":"A published draw. A draft is the director's working copy and is never sent.","required":["id","name","code","format","size","group_count","third_place","main_draw_id","entries","matches","standings"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"code":{"type":"string"},"age_rule":{"$ref":"#/components/schemas/AgeRule"},"class_rule":{"$ref":"#/components/schemas/ClassRule"},"format":{"type":"string","enum":["knockout","round_robin"],"description":"Append-only."},"size":{"type":["integer","null"],"description":"A knockout bracket's lines; null for a round robin."},"group_count":{"type":["integer","null"]},"third_place":{"type":"boolean","description":"A knockout draw that plays for third place; its match is the final round's second slot, between the semi-finals' losers."},"all_places":{"type":"boolean","description":"A knockout draw that plays off every place; each placement match names its `places`. Sent by every server that plays off every place."},"main_draw_id":{"type":["string","null"],"format":"uuid","description":"For a consolation, the draw it consoles, whose losers are its sides; null for any other draw."},"qualifiers":{"type":["integer","null"],"description":"How many qualifiers a qualifying knockout produces; null for any other draw."},"seed_bands":{"type":"boolean","description":"Whether the seeds were drawn in bands, printed \"3/4\"."},"feeds":{"type":"array","description":"The draw's places other draws fill, each naming the draw and the match that\nfill it, by which a client links an event's draws in stage order and ranks\nplaces across them. `source_draw_name` is left out: the draw is on Live too.\n","items":{"$ref":"#/components/schemas/DrawFeed"}},"entries":{"type":"array","items":{"$ref":"#/components/schemas/DrawEntry"}},"matches":{"type":"array","items":{"$ref":"#/components/schemas/DrawMatch"}},"standings":{"description":"A round-robin draw's groups, ranked on read from confirmed results as `GET /v1/draws/{drawId}/standings` ranks them; null for a knockout draw.","oneOf":[{"type":"null"},{"type":"array","items":{"$ref":"#/components/schemas/GroupStanding"}}]}}},"LiveTournamentProjection":{"type":"object","required":["tournament","courts","participants","matches","draws"],"properties":{"tournament":{"$ref":"#/components/schemas/LiveTournament"},"courts":{"type":"array","description":"In `sort_order`, then name.","items":{"$ref":"#/components/schemas/LiveCourt"}},"participants":{"type":"array","description":"The sides a match, a published draw or an entry list names, and no one else.","items":{"$ref":"#/components/schemas/LiveParticipant"}},"matches":{"type":"array","items":{"$ref":"#/components/schemas/LiveMatch"}},"draws":{"type":"array","description":"The published draws, by name.","items":{"$ref":"#/components/schemas/LiveDraw"}},"entry_lists":{"type":"array","description":"The events open for entries or drawn, by name, each with who entered it (DEV-27).\nSent by every server that lists entries; a client reading an older one takes its\nabsence for none.\n","items":{"$ref":"#/components/schemas/LiveEntryList"}},"ties":{"type":"array","description":"The ties started in a tournament that is a league's match day (DEV-52), in the\norder they are played; empty for any other tournament. Sent by every server that\nknows leagues; a client reading an older one takes its absence for none.\n","items":{"$ref":"#/components/schemas/LiveTie"}}}},"LiveTie":{"type":"object","description":"A tie between two teams, played as matches of this tournament (DEV-52): its score\nin rubbers and its rubbers in score-sheet order. Each rubber names its match, which\nis among the projection's `matches` with its sides and score. A rubber counts\ntowards the tie once its result is confirmed. A rubber a side did not field\n(DEV-53) names no match and counts for the other side from the start.\n","required":["id","round","home","away","start_time","decided","home_rubbers","away_rubbers","rubbers"],"properties":{"id":{"type":"string","format":"uuid","description":"The league's fixture."},"round":{"type":"integer"},"home":{"$ref":"#/components/schemas/LiveTieTeam"},"away":{"$ref":"#/components/schemas/LiveTieTeam"},"start_time":{"type":["string","null"],"description":"`HH:MM` at the venue, when the league has said it."},"decided":{"type":"boolean","description":"Every rubber's result is confirmed."},"home_rubbers":{"type":"integer"},"away_rubbers":{"type":"integer"},"rubbers":{"type":"array","items":{"type":"object","required":["code","match_id"],"properties":{"code":{"type":"string"},"match_id":{"type":["string","null"],"format":"uuid","description":"Null for a rubber a side did not field."},"not_fielded_by":{"type":["string","null"],"enum":["home","away","both",null],"description":"Which side named no player for the rubber when the tie started; null for a\nrubber that is played. Sent by every server that knows such rubbers\n(DEV-53).\n"}}}}}},"LiveTieTeam":{"type":"object","required":["id","name"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"}}},"LiveEntryList":{"type":"object","description":"An event's public entry list: each draft the tournament offers for entries while it\nis open for them, and each published draw with entries of its own. An entry is here\nonce the staff took it in; a registration waiting for an answer, or declined, never\nis. Nothing of a draft's seeds or lines is sent.\n","required":["draw_id","name","code","age_rule","drawn","entry_count","entries"],"properties":{"draw_id":{"type":"string","format":"uuid"},"name":{"type":"string"},"code":{"type":"string"},"age_rule":{"$ref":"#/components/schemas/AgeRule"},"class_rule":{"$ref":"#/components/schemas/ClassRule"},"drawn":{"type":"boolean","description":"Published; its draw is in `draws`."},"entry_count":{"type":"integer","minimum":0},"entries":{"type":"array","description":"In the order the entries were taken in; each names a `participants` item.","items":{"type":"object","required":["participant_id","associations"],"properties":{"participant_id":{"type":"string","format":"uuid"},"associations":{"type":"array","description":"The entry's club or association, each once in its players' order, when the\ntournament's director shows them (`public_associations`); empty otherwise.\n","items":{"type":"string"}}}}}}},"LiveMatchPage":{"type":"object","required":["tournament","court","participants","match"],"properties":{"tournament":{"$ref":"#/components/schemas/LiveTournament"},"court":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/LiveCourt"}]},"participants":{"type":"array","description":"The match's sides, those it has.","items":{"$ref":"#/components/schemas/LiveParticipant"}},"match":{"$ref":"#/components/schemas/LiveMatch"}}},"Tournament":{"type":"object","required":["id","name","venue_time_zone","starts_on","ends_on","status","revision","referee_briefing"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"venue_time_zone":{"type":"string","description":"An IANA time zone. Venue-local reasoning derives from it."},"venue_name":{"type":["string","null"],"description":"Where it is played, as a director wrote it through\n`PATCH /v1/tournaments/{tournamentId}/venue`; null when nobody has. Always sent\nby this server; a client reading an older one takes its absence for none.\n"},"starts_on":{"type":["string","null"],"format":"date"},"ends_on":{"type":["string","null"],"format":"date"},"status":{"type":"string","enum":["draft","active","completed","cancelled"],"description":"`draft` until its match day begins, when its first court or display code, court\nsetup code or a scoring session taken from Control starts it; only an `active`\ntournament holds a place in the plan (DEV-47). `completed` is closed (DEV-25).\n"},"revision":{"type":"integer"},"referee_briefing":{"$ref":"#/components/schemas/RefereeBriefing"},"live_published":{"type":"boolean","description":"Whether the tournament is published on Live, the anonymous public scoreboard,\nthrough `PATCH /v1/tournaments/{tournamentId}/live-publication`. Always sent by\nthis server; a client reading an older one takes its absence for false, which is\nhow every tournament starts.\n"},"show_nationalities":{"type":"boolean","description":"Whether Live and the hall displays show the players' nationalities, set with the\npublication. When false, Live sends every side with no `nationalities` and a\ndisplay's snapshot every player with a null `nationality`; staff and a court\ndevice still read them. Always sent by this server; a client reading an older\none takes its absence for true, which is how every tournament starts.\n"},"public_page_qr":{"type":"boolean","description":"Whether the printed order of play and the hall displays offer the public page as\na QR code, set with the publication; they show it only while `live_published`.\nAlways sent by this server; a client reading an older one takes its absence for\nfalse, which is how every tournament starts.\n"},"name_style":{"allOf":[{"$ref":"#/components/schemas/NameStyle"}],"description":"How Live and the hall displays write a side's name, set with the publication. A\ndisplay's snapshot carries each `display_name` already written in it; staff and a\ncourt device read the name as entered and the players' parts. Always sent by\nthis server; a client reading an older one takes its absence for `as_entered`.\n"},"schedule_assumptions":{"$ref":"#/components/schemas/ScheduleAssumptions"}}},"ScheduleAssumptions":{"type":"object","description":"The match length and rest Control's schedule check works from, in minutes, set\nthrough `PATCH /v1/tournaments/{tournamentId}/schedule-assumptions`. Always sent by\nthis server; a client reading an older one takes its absence for 30 and 20, which\nis how every tournament starts.\n","required":["match_minutes","rest_minutes"],"properties":{"match_minutes":{"type":"integer","minimum":1,"maximum":600},"rest_minutes":{"type":"integer","minimum":0,"maximum":600}}},"RefereeBriefing":{"type":"object","description":"How every court tablet of the tournament runs its matches: what the Referee briefs\nthe umpires before play (ITTO Section 3.4), and how the tablets look and answer a\nfinger, since a court tablet has no settings of its own (DEV-33). Set in Control\nthrough `PATCH /v1/tournaments/{tournamentId}/referee-briefing` and read by a tablet\noff the snapshot. A tournament nobody has briefed carries the defaults below, which\nare how a tablet runs without a briefing. The instant review system is not part of\nit: the tablet offers none. Nor is full screen, which a browser grants only on a tap.\n","required":["call_language","time_clock","warm_up_minutes","referee_on_site","rally_start_tracking","rally_start_required","auto_confirm_result","overrule_mark","result_extras","theme","interface_language","announcements","speak_calls","input_delay","touch_mode","point_vibration","point_tone","call_guard","keyboard_shortcuts","court_diagram","scoresheet","scoresheet_expand"],"properties":{"call_language":{"$ref":"#/components/schemas/CallLanguage"},"time_clock":{"type":"boolean","default":false,"description":"Whether the between-rallies Time Clock is in operation (ITTO 5.10.8). Its 25\nseconds are fixed by 5.10.8.2 and are not a setting.\n"},"warm_up_minutes":{"type":"integer","enum":[1,2,3],"default":2,"description":"The warm-up's length; two minutes unless the Referee instructs otherwise (ITTO 5.2.2.1)."},"referee_on_site":{"type":"boolean","default":true,"description":"Whether a Referee is on site to be called. With one, a red card holds play until\nthe Referee is informed (Law 16.7.2), and the situations only a Referee brings\nare offered.\n"},"rally_start_tracking":{"type":"boolean","default":true,"description":"Whether the tablet offers Start rally, recording when each rally begins."},"rally_start_required":{"type":"boolean","default":false,"description":"Whether a point is refused until its rally was started. Read only while\n`rally_start_tracking` is on.\n"},"auto_confirm_result":{"type":"boolean","default":false,"description":"Whether a completed result confirms itself after its review period, rather than when the umpire confirms it."},"overrule_mark":{"type":"boolean","default":true,"description":"Whether an overrule is written into the scoresheet's score rows as an O, beside the note it always gets."},"result_extras":{"type":"boolean","default":true,"description":"Whether a completed result offers the scoresheet and its statistics."},"theme":{"$ref":"#/components/schemas/TabletTheme"},"interface_language":{"$ref":"#/components/schemas/TabletInterfaceLanguage"},"announcements":{"$ref":"#/components/schemas/TabletAnnouncements"},"speak_calls":{"type":"boolean","default":false,"description":"Whether each call is read aloud in the call language, where the device has a voice for it."},"input_delay":{"$ref":"#/components/schemas/TabletInputDelay"},"touch_mode":{"$ref":"#/components/schemas/TabletTouchMode"},"point_vibration":{"type":"boolean","default":true,"description":"Whether a point vibrates the tablet, where it can."},"point_tone":{"type":"boolean","default":false,"description":"Whether a point sounds a tone."},"call_guard":{"type":"boolean","default":false,"description":"Whether a call refuses a touch for its first second on screen, for a thumb still bouncing off the key that ended a game."},"keyboard_shortcuts":{"type":"boolean","default":true,"description":"Whether a keyboard or a presenter remote scores and undoes."},"court_diagram":{"type":"boolean","default":true,"description":"Whether the court diagram is shown."},"scoresheet":{"type":"boolean","default":true,"description":"Whether the rally-by-rally card sits under the court."},"scoresheet_expand":{"type":"boolean","default":false,"description":"Whether that card carries a key that opens the full scoresheet during the match."},"court_chooses_match":{"type":"boolean","description":"Whether a court's tablet with no match offers the matches that wait for a court,\nfor its umpire to choose one, and a way to give a match back before it starts\n(DEV-58). Off unless someone turns it on: a tablet then waits for the desk to\nput a match on its court. Sent by every server that knows the setting, and read\nas off where it is absent, so it is not among the required fields and states no\ndefault a generated client would take for a promise that it is always there.\n"}}},"TabletTheme":{"type":"string","enum":["system","light","dark"],"default":"system","description":"The palette court tablets paint in; `system` follows each tablet's own light or dark mode."},"TabletInterfaceLanguage":{"type":"string","pattern":"^(system|[a-z]{2,3}(-[A-Za-z]{2,4})?)$","default":"system","description":"The language of the tablets' interface, which is not the call vocabulary:\n`system` for each tablet's own, or a language tag as the client's catalogs are\nnamed (`de`, `pt-BR`, `zh-Hans`). The server checks only the shape; a tablet given a\nlanguage it has no catalog for runs in its own.\n"},"TabletAnnouncements":{"type":"string","enum":["all","except-start","none"],"default":"all","description":"How much of a pause's announcement a tablet shows unasked. The call that ends the\npause is always shown; `except-start` keeps only the introduction of the players.\n"},"TabletInputDelay":{"type":"string","enum":["off","standard","long"],"default":"standard","description":"How long the rally keys ignore a second tap after a point, 0, 400 or 800 ms."},"TabletTouchMode":{"type":"string","enum":["native","fast"],"default":"native","description":"Whether a rally key scores when the finger lifts (`native`) or when it lands (`fast`)."},"CallLanguage":{"type":"string","enum":["en","de-DE","de-AT"],"default":"en","description":"The published vocabulary the umpire's calls are quoted from, which is not the\ntablet's interface language: `en` is the BWF's (Vocabulary, Section 4.1.5), `de-DE`\nthe DBV's and `de-AT` the ÖBV's. A vocabulary may be added; a client meeting one it\ndoes not know uses English.\n"},"CodeInspection":{"type":"object","description":"What a presented code is. `tournament_name` and `courts` are set for a setup code only.\n`tournament_id`, and `court_id` for a court code, let a browser signed in as staff\nwatch that court's panel instead of spending the code; an id grants nothing, and the\ntournament is read with the staff member's own authority. Both are absent from a\nserver older than them.\n","required":["kind","tournament_name","courts"],"properties":{"kind":{"type":"string","enum":["court","display","setup"]},"tournament_id":{"type":"string","format":"uuid"},"court_id":{"type":["string","null"],"format":"uuid"},"tournament_name":{"type":["string","null"]},"courts":{"type":"array","items":{"type":"object","required":["id","name"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"}}}}}},"CourtSetupCode":{"type":"object","description":"A court setup code as staff see it after issuing; never its plaintext.","required":["id","kind","expires_at","max_devices","devices_joined","revoked"],"properties":{"id":{"type":"string","format":"uuid"},"kind":{"type":"string","enum":["qr","pin"]},"expires_at":{"type":"string","format":"date-time"},"max_devices":{"type":"integer"},"devices_joined":{"type":"integer"},"revoked":{"type":"boolean"}}},"OperatorOrganization":{"type":"object","required":["id","name","slug","active","created_at","plan"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"slug":{"type":"string"},"kind":{"allOf":[{"$ref":"#/components/schemas/OrganizationKind"}],"description":"Absent from a server older than DEV-39, when every organization was a club."},"active":{"type":"boolean","description":"False for a suspended or closed organization, whose hostnames do not resolve."},"status":{"type":"string","enum":["active","suspended","closed"],"description":"Absent from a server older than DEV-717, which knew active and suspended only."},"deletion_due_at":{"type":["string","null"],"format":"date-time","description":"When a closed organization's data is deleted; null for any other."},"created_at":{"type":"string","format":"date-time"},"plan":{"oneOf":[{"$ref":"#/components/schemas/PlanCode"},{"type":"null"}],"description":"Null only for an organization some path created without one, which none does."}}},"OrganizationKind":{"type":"string","description":"What an organization is (DEV-39). An association has clubs and other associations\nas members and runs leagues; a club has players as members and enters teams. Both run\ntournaments. Append-only.\n","enum":["association","club"]},"SportCode":{"type":"string","description":"A sport Roundr runs. Append-only.","enum":["badminton"]},"OrganizationListing":{"type":"object","description":"An organization as another organization sees it.","required":["id","name","kind"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"kind":{"$ref":"#/components/schemas/OrganizationKind"}}},"AssociationMembership":{"type":"object","description":"One organization's membership of an association in one sport (DEV-39). `side` is\nthe end the organization the session acts for is on, and `awaiting` the side whose\nadministrator answers next, null once answered or ended.\n","required":["id","association","member","sport","member_number","status","side","awaiting","proposed_at","answered_at","ended_at"],"properties":{"id":{"type":"string","format":"uuid"},"association":{"$ref":"#/components/schemas/OrganizationListing"},"member":{"$ref":"#/components/schemas/OrganizationListing"},"sport":{"$ref":"#/components/schemas/SportCode"},"member_number":{"type":["string","null"]},"status":{"type":"string","description":"Append-only.","enum":["requested","invited","active","ended"]},"side":{"type":"string","enum":["association","member"]},"awaiting":{"type":["string","null"],"enum":["association","member",null]},"proposed_at":{"type":"string","format":"date-time"},"answered_at":{"type":["string","null"],"format":"date-time"},"ended_at":{"type":["string","null"],"format":"date-time"}}},"AssociationMembershipAnswer":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["membership"],"properties":{"membership":{"$ref":"#/components/schemas/AssociationMembership"}}}}},"AssociationMembershipsAnswer":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["memberships"],"properties":{"memberships":{"type":"array","items":{"$ref":"#/components/schemas/AssociationMembership"}}}}}},"LeagueRubber":{"type":"object","description":"One rubber of a league's tie (DEV-49), in score-sheet order: its code, the\nruleset its match is pinned to, and who plays it, or null when anyone may.\n","required":["code","ruleset","entry_gender"],"properties":{"code":{"type":"string","pattern":"^[A-Z][A-Z0-9]{0,7}$"},"ruleset":{"type":"object","required":["id","version","engine_version"],"properties":{"id":{"type":"string"},"version":{"type":"string"},"engine_version":{"type":"string"}}},"entry_gender":{"type":["string","null"],"enum":["men","women","mixed",null]}}},"League":{"type":"object","description":"A league its owner runs, one season of one sport (DEV-49), with the tie its\nfixtures are played as and the points a tie won, drawn and lost is worth.\n","required":["id","owner","name","sport","season","starts_on","ends_on","points","guest_allowance","entries_open","live_published","rubbers","created_at"],"properties":{"id":{"type":"string","format":"uuid"},"owner":{"$ref":"#/components/schemas/OrganizationListing"},"name":{"type":"string"},"sport":{"$ref":"#/components/schemas/SportCode"},"season":{"type":"string"},"starts_on":{"type":["string","null"],"format":"date"},"ends_on":{"type":["string","null"],"format":"date"},"points":{"type":"object","required":["win","draw","loss"],"properties":{"win":{"type":"integer"},"draw":{"type":"integer"},"loss":{"type":"integer"}}},"guest_allowance":{"type":"integer","description":"How many players from other clubs a team's roster may hold."},"entries_open":{"type":"boolean"},"live_published":{"type":"boolean","description":"Whether the league's page is on Live (DEV-50)."},"rubbers":{"type":"array","items":{"$ref":"#/components/schemas/LeagueRubber"}},"created_at":{"type":"string","format":"date-time"}}},"LeagueSettingsRequest":{"type":"object","description":"A league's settings and tie. The tie is a `format` preset or `rubbers`, never both:\n`obv_8` is MD1, MD2, WD, MS1, MS2, MS3, WS and XD, `hobby_4` is MS, WS, MD and XD,\nall 21×3.\n","properties":{"name":{"type":"string","minLength":1,"maxLength":120,"description":"Once per organization and season."},"season":{"type":"string","minLength":1,"maxLength":20,"description":"Such as `2026/27`."},"starts_on":{"type":["string","null"],"format":"date"},"ends_on":{"type":["string","null"],"format":"date","description":"Not before `starts_on`."},"points":{"type":"object","description":"Whole numbers from 0 to 10, win above loss and draw between them.","required":["win","draw","loss"],"properties":{"win":{"type":"integer"},"draw":{"type":"integer"},"loss":{"type":"integer"}}},"guest_allowance":{"type":"integer","minimum":0,"maximum":20},"entries_open":{"type":"boolean"},"live_published":{"type":"boolean","description":"Publish the league's page on Live (DEV-50); off until it is."},"format":{"type":"string","enum":["obv_8","hobby_4"]},"rubbers":{"type":"array","minItems":1,"maxItems":16,"items":{"type":"object","required":["code","ruleset_id","ruleset_version"],"properties":{"code":{"type":"string","pattern":"^[A-Z][A-Z0-9]{0,7}$","description":"Once in the tie."},"ruleset_id":{"type":"string","description":"A ruleset of the league's sport this deployment can run."},"ruleset_version":{"type":"string"},"entry_gender":{"type":["string","null"],"enum":["men","women","mixed",null],"description":"`mixed` only under a ruleset played by pairs."}}}}}},"LeagueDivision":{"type":"object","description":"A division of a league, in the league's order, its top division first.","required":["id","name","position"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"position":{"type":"integer"}}},"LeagueDivisionRequest":{"type":"object","required":["name"],"properties":{"name":{"type":"string","minLength":1,"maxLength":80,"description":"Once in the league."}}},"LeagueTeam":{"type":"object","description":"A team in a league (DEV-49). `entered` waits for the league to place it, `placed`\nis in `division`, and `withdrawn` stays as the record. `club` is null for a team the\nleague's administrators entered by name (DEV-51), which the organization that runs\nthe league keeps.\n","required":["id","league","club","name","status","division","entered_at","answered_at","withdrawn_at"],"properties":{"id":{"type":"string","format":"uuid"},"league":{"type":"object","required":["id","name","season","owner"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"season":{"type":"string"},"owner":{"$ref":"#/components/schemas/OrganizationListing"}}},"club":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/OrganizationListing"}]},"name":{"type":"string"},"status":{"type":"string","enum":["entered","placed","withdrawn"]},"division":{"type":["object","null"],"required":["id","name"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"}}},"entered_at":{"type":"string","format":"date-time"},"answered_at":{"type":["string","null"],"format":"date-time"},"withdrawn_at":{"type":["string","null"],"format":"date-time"}}},"LeagueAnswer":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["league","divisions","teams"],"properties":{"league":{"$ref":"#/components/schemas/League"},"divisions":{"type":"array","items":{"$ref":"#/components/schemas/LeagueDivision"}},"teams":{"type":"array","items":{"$ref":"#/components/schemas/LeagueTeam"}}}}}},"LeaguesAnswer":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["leagues"],"properties":{"leagues":{"type":"array","items":{"$ref":"#/components/schemas/League"}}}}}},"LeagueDivisionAnswer":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["division"],"properties":{"division":{"$ref":"#/components/schemas/LeagueDivision"}}}}},"LeagueTeamAnswer":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["team"],"properties":{"team":{"$ref":"#/components/schemas/LeagueTeam"}}}}},"LeagueTeamsAnswer":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["teams"],"properties":{"teams":{"type":"array","items":{"$ref":"#/components/schemas/LeagueTeam"}}}}}},"LeaguePerson":{"type":"object","description":"A person on a team's roster or captaining it, by the name their account shows. A\nplayer entered by name has no account (DEV-51): `user_id` is null and `name` is\nwhat was typed. A captain and a candidate always have one.\n","required":["user_id","name"],"properties":{"user_id":{"type":["string","null"],"format":"uuid"},"name":{"type":"string"}}},"RosterRequest":{"type":"object","description":"A team's roster in the order it is to read (DEV-49): `members` by account, from the\nclub's members, `guests` by the address of their account, then `names`, players who\nhave no account (DEV-51). Forty players at most; a person named twice is named\nonce. A name is checked against no membership and counts against no guest allowance.\n","properties":{"members":{"type":"array","items":{"type":"string","format":"uuid"}},"guests":{"type":"array","items":{"type":"string","format":"email"}},"names":{"type":"array","items":{"type":"string","minLength":1,"maxLength":80}}}},"TeamRosterAnswer":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["team","players","captains"],"properties":{"team":{"$ref":"#/components/schemas/LeagueTeam"},"players":{"type":"array","items":{"allOf":[{"$ref":"#/components/schemas/LeaguePerson"},{"type":"object","required":["guest"],"properties":{"guest":{"type":"boolean","description":"Not a member of the team's club in the league's sport when the roster was saved."}}}]}},"captains":{"type":"array","items":{"$ref":"#/components/schemas/LeaguePerson"}},"candidates":{"type":"array","description":"The club's active members in the league's sport; for the club and its captains only.","items":{"$ref":"#/components/schemas/LeaguePerson"}}}}}},"LeagueFixture":{"type":"object","description":"A tie between two teams of a division (DEV-49), by the round of the division's\ndouble round robin and its place in that round, and the match day it is on, if any,\nwith the time it starts there.\n","required":["id","division","round","position","home","away","match_day_id"],"properties":{"id":{"type":"string","format":"uuid"},"division":{"type":"object","required":["id","name"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"}}},"round":{"type":"integer"},"position":{"type":"integer"},"home":{"$ref":"#/components/schemas/LeagueFixtureSide"},"away":{"$ref":"#/components/schemas/LeagueFixtureSide"},"match_day_id":{"type":["string","null"],"format":"uuid"},"start_time":{"type":["string","null"],"description":"`HH:MM` at the match day's venue, or null when the league has said no time or the\nfixture is on no match day. Sent by every server that knows fixture times\n(DEV-51).\n"}}},"LeagueFixtureSide":{"type":"object","required":["id","name","club"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"club":{"description":"Null for a team the league entered by name (DEV-51).","oneOf":[{"type":"null"},{"$ref":"#/components/schemas/OrganizationListing"}]}}},"LeagueMatchDay":{"type":"object","description":"A date and venue a league's fixtures are played on, and its tournament once opened.","required":["id","name","plays_on","venue_name","venue_time_zone","tournament_id"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"plays_on":{"type":"string","format":"date"},"venue_name":{"type":["string","null"]},"venue_time_zone":{"type":"string"},"tournament_id":{"type":["string","null"],"format":"uuid"}}},"LeagueScheduleAnswer":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["fixtures","match_days"],"properties":{"fixtures":{"type":"array","items":{"$ref":"#/components/schemas/LeagueFixture"}},"match_days":{"type":"array","items":{"$ref":"#/components/schemas/LeagueMatchDay"}}}}}},"LeagueTie":{"type":"object","description":"A fixture's tie (DEV-49): whether each side has given its line-up, and once it has\nstarted, its rubbers as their matches stand. `decided` once every rubber is\nconfirmed; `home_rubbers` and `away_rubbers` count confirmed rubbers won. A rubber a\nside did not field (DEV-53) is played by nobody and has no match: its `match_id`\nis null, its `status` is `not_fielded`, and it counts for the other side from the\nstart, with no games.\n","required":["fixture","home_lineup_given","away_lineup_given","started","decided","home_rubbers","away_rubbers","rubbers"],"properties":{"fixture":{"$ref":"#/components/schemas/LeagueFixture"},"home_lineup_given":{"type":"boolean"},"away_lineup_given":{"type":"boolean"},"started":{"type":"boolean"},"decided":{"type":"boolean"},"home_rubbers":{"type":"integer"},"away_rubbers":{"type":"integer"},"rubbers":{"type":"array","items":{"type":"object","required":["code","tournament_id","match_id","status","winner","home_games","away_games"],"properties":{"code":{"type":"string"},"tournament_id":{"type":"string","format":"uuid"},"match_id":{"type":["string","null"],"format":"uuid","description":"Null for a rubber a side did not field."},"status":{"type":"string","description":"The match's status, or `not_fielded` where there is no match."},"winner":{"type":["string","null"],"enum":["home","away",null]},"home_games":{"type":"integer"},"away_games":{"type":"integer"},"not_fielded_by":{"type":["string","null"],"enum":["home","away","both",null],"description":"Which side named no player for the rubber when the tie started (DEV-53);\nnull for a rubber that is played. Sent by every server that knows such\nrubbers.\n"}}}}}},"LeagueLineupRubber":{"type":"object","required":["code","players"],"properties":{"code":{"type":"string"},"players":{"type":"array","items":{"$ref":"#/components/schemas/LeaguePerson"}},"not_fielded":{"type":"boolean","description":"The team left the rubber out (DEV-53), which is why it has no players. False\nfor a rubber of a line-up not given yet. Sent by every server that knows such\nrubbers.\n"}}},"LeagueDivisionTable":{"type":"object","required":["division","standings","ties"],"properties":{"division":{"$ref":"#/components/schemas/LeagueDivision"},"standings":{"type":"array","items":{"type":"object","required":["team","played","won","drawn","lost","rubbers_for","rubbers_against","games_for","games_against","points"],"properties":{"team":{"$ref":"#/components/schemas/LeagueFixtureSide"},"played":{"type":"integer"},"won":{"type":"integer"},"drawn":{"type":"integer"},"lost":{"type":"integer"},"rubbers_for":{"type":"integer"},"rubbers_against":{"type":"integer"},"games_for":{"type":"integer"},"games_against":{"type":"integer"},"points":{"type":"integer"}}}},"ties":{"type":"array","items":{"$ref":"#/components/schemas/LeagueTie"}}}},"LeagueStandingsAnswer":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["divisions"],"properties":{"divisions":{"type":"array","items":{"$ref":"#/components/schemas/LeagueDivisionTable"}}}}}},"LeagueTieAnswer":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["tie","home_lineup","away_lineup"],"properties":{"tie":{"$ref":"#/components/schemas/LeagueTie"},"home_lineup":{"type":"array","items":{"$ref":"#/components/schemas/LeagueLineupRubber"}},"away_lineup":{"type":"array","items":{"$ref":"#/components/schemas/LeagueLineupRubber"}}}}}},"TeamSeasonAnswer":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["team","league","match_days","table"],"properties":{"team":{"$ref":"#/components/schemas/LeagueTeam"},"league":{"$ref":"#/components/schemas/League"},"match_days":{"type":"array","items":{"$ref":"#/components/schemas/LeagueMatchDay"}},"table":{"oneOf":[{"$ref":"#/components/schemas/LeagueDivisionTable"},{"type":"null"}]}}}}},"LineupRequest":{"type":"object","required":["rubbers"],"properties":{"new_players":{"type":"array","maxItems":40,"description":"Players without an account to put on the roster by name before the line-up is\nread (DEV-51), after everyone on it. A name the roster holds already changes\nnothing. Refused as `roster_full` when the roster would pass forty players.\n","items":{"type":"string","maxLength":80}},"rubbers":{"type":"array","description":"Every rubber of the league's tie once, one of them fielded at least.","items":{"type":"object","required":["code","players"],"properties":{"code":{"type":"string"},"not_fielded":{"type":"boolean","description":"True for a rubber the team has nobody for (DEV-53), with no `players`.\nIt counts for the other team when the tie starts. No players without it\nis refused, so a request that lost its players concedes nothing.\n"},"players":{"type":"array","maxItems":2,"description":"One player, or a pair: the account id of a player on the roster, or for\none on the roster by name (DEV-51), an object with that `name`. Empty\nonly with `not_fielded`.\n","items":{"oneOf":[{"type":"string","format":"uuid"},{"type":"object","required":["name"],"properties":{"name":{"type":"string","maxLength":80}}}]}}}}}}},"LineupAnswer":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["tie","roster","lineup","opponent","rubbers"],"properties":{"tie":{"$ref":"#/components/schemas/LeagueTie"},"roster":{"type":"array","items":{"allOf":[{"$ref":"#/components/schemas/LeaguePerson"},{"type":"object","required":["guest"],"properties":{"guest":{"type":"boolean"}}}]}},"lineup":{"type":"array","items":{"$ref":"#/components/schemas/LeagueLineupRubber"}},"opponent":{"type":["array","null"],"items":{"$ref":"#/components/schemas/LeagueLineupRubber"}},"previous":{"type":["array","null"],"description":"The line-up the team gave most recently for another fixture, by this tie's\nrubbers. Null once the tie has started, and for a team's first line-up. Sent\nby every server that offers the last line-up.\n","items":{"$ref":"#/components/schemas/LeagueLineupRubber"}},"rubbers":{"type":"array","items":{"$ref":"#/components/schemas/LeagueRubber"}}}}}},"DeletedAnswer":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["deleted"],"properties":{"deleted":{"type":"boolean","const":true}}}}},"OrganizationDirectoryAnswer":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["organizations"],"properties":{"organizations":{"type":"array","items":{"$ref":"#/components/schemas/OrganizationListing"}}}}}},"ClubMembership":{"type":"object","description":"A person's membership of a club in one sport (DEV-39). `person` is filled for the\nclub's view and null in the person's own. `side` is the viewer's end, `awaiting` the\nside whose answer is next, null once answered or ended.\n","required":["id","club","person","sport","member_number","licence_number","status","side","awaiting","proposed_at","answered_at","ended_at"],"properties":{"id":{"type":"string","format":"uuid"},"club":{"type":"object","required":["id","name"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"}}},"person":{"oneOf":[{"type":"object","required":["id","name","email"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"email":{"type":["string","null"]}}},{"type":"null"}]},"sport":{"$ref":"#/components/schemas/SportCode"},"member_number":{"type":["string","null"]},"licence_number":{"type":["string","null"]},"status":{"type":"string","description":"Append-only.","enum":["requested","invited","active","ended"]},"side":{"type":"string","enum":["club","person"]},"awaiting":{"type":["string","null"],"enum":["club","person",null]},"proposed_at":{"type":"string","format":"date-time"},"answered_at":{"type":["string","null"],"format":"date-time"},"ended_at":{"type":["string","null"],"format":"date-time"}}},"ClubMembershipAnswer":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["membership"],"properties":{"membership":{"$ref":"#/components/schemas/ClubMembership"}}}}},"ClubMembershipsAnswer":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["memberships"],"properties":{"memberships":{"type":"array","items":{"$ref":"#/components/schemas/ClubMembership"}}}}}},"ClubDirectoryAnswer":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["clubs"],"properties":{"clubs":{"type":"array","items":{"type":"object","required":["id","name"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"}}}}}}}},"PlanCode":{"type":"string","description":"A plan of the catalogue (DEV-19). Append-only.","enum":["free","event","club","federation"]},"PlanLimitName":{"type":"string","description":"What a plan bounds. Append-only. `active_tournaments` counts the tournaments holding\na place; `courts_per_tournament` and `displays_per_tournament` count in one\ntournament; `hostnames` counts those not taken out of service; `machine_keys` the\nAPI keys neither revoked nor expired; `leagues` the leagues the organization runs,\nand `league_teams` the teams it has entered in leagues and not withdrawn. A plan\nallows leagues and league teams without a bound or not at all (DEV-48).\n","enum":["active_tournaments","courts_per_tournament","displays_per_tournament","hostnames","machine_keys","leagues","league_teams"]},"OrganizationPlan":{"type":"object","required":["plan","source","changed_at","limits"],"properties":{"plan":{"$ref":"#/components/schemas/PlanCode"},"source":{"type":"string","enum":["default","operator","subscription"],"description":"`default` is the free plan an organization an account created starts on;\n`operator` is a platform operator's choice; `subscription` is the plan a Stripe\nsubscription buys, written by Stripe's webhook (DEV-41). Append-only.\n"},"changed_at":{"type":"string","format":"date-time"},"limits":{"type":"array","description":"Every limit, in the catalogue's order.","items":{"type":"object","required":["limit","allowance","used"],"properties":{"limit":{"$ref":"#/components/schemas/PlanLimitName"},"allowance":{"type":["integer","null"],"minimum":0,"description":"Null when the plan bounds nothing here."},"used":{"type":"integer","minimum":0,"description":"How many the organization holds now; for a per-tournament limit, the\nfullest tournament still holding a place. May exceed the allowance after a\nmove to a smaller plan.\n"}}}}}},"OrganizationPlanAnswer":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["plan"],"properties":{"plan":{"$ref":"#/components/schemas/OrganizationPlan"}}}}},"OrganizationOwnPlanAnswer":{"type":"object","required":["ok","data"],"description":"The plan as the organization's administrator reads it, with what billing offers. A\nplatform operator's read of the same plan (`OrganizationPlanAnswer`) carries no\n`billing`: the operator sets the plan and never buys one.\n","properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["plan","billing"],"properties":{"plan":{"$ref":"#/components/schemas/OrganizationPlan"},"billing":{"$ref":"#/components/schemas/OrganizationBilling"}}}}},"OrganizationBilling":{"type":"object","required":["available","plans_on_sale","customer","subscription"],"description":"What an administrator may do about the plan on this deployment (DEV-41). The plan\nitself is `OrganizationPlan`'s answer, never this: a subscription moves it only when\nStripe's webhook has confirmed it, so a client that has just come back from Checkout\nreads the plan again rather than inferring it from here.\n","properties":{"available":{"type":"boolean","description":"Whether `startBillingCheckout` and `openBillingPortal` exist here. False where no\npayment provider is configured; an operator then sets every paid plan.\n"},"plans_on_sale":{"type":"array","description":"The plans a Checkout can be opened for, smallest first. Empty where billing is\nnot available; a plan with no price configured is not on sale. `free` and\n`federation` are never sold from the account.\n","items":{"type":"string","enum":["event","club"]}},"customer":{"type":"boolean","description":"Whether a Stripe customer exists for the organization, made before its first Checkout."},"subscription":{"description":"The subscription the organization is or was billed on, as Stripe last reported\nit; null when it has never completed a Checkout. The Portal shows its invoices\neven once it has ended.\n","oneOf":[{"type":"null"},{"type":"object","required":["status","live","current_period_end"],"properties":{"status":{"type":["string","null"],"description":"Stripe's subscription status verbatim (`active`, `past_due`, `canceled`, ...)."},"live":{"type":"boolean","description":"Whether the organization holds its paid plan on it now (`active`,\n`trialing` or `past_due`). While it is, a second Checkout is refused\nwith `billing_refused` `subscription_active`.\n"},"current_period_end":{"type":["string","null"],"format":"date-time","description":"When the period paid for ends."}}}]}}},"OperatorOrganizationCreated":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["organization","created"],"properties":{"organization":{"$ref":"#/components/schemas/OperatorOrganization"},"created":{"type":"boolean"}}}}},"OrganizationAdministrator":{"type":"object","required":["user_id","display_name","email","active"],"properties":{"user_id":{"type":"string","format":"uuid"},"display_name":{"type":"string"},"email":{"type":["string","null"]},"active":{"type":"boolean","description":"False for a suspended member or a disabled account, which holds the role and authorizes nothing."}}},"OrganizationAdministratorsAdmitted":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["administrators","granted"],"properties":{"administrators":{"type":"array","items":{"$ref":"#/components/schemas/OrganizationAdministrator"}},"granted":{"type":"boolean"}}}}},"OrganizationDomain":{"type":"object","required":["id","hostname","kind","status","verification","verified_at","sign_in_registered_at","primary"],"properties":{"id":{"type":"string","format":"uuid"},"hostname":{"type":"string"},"kind":{"type":"string","enum":["platform_subdomain","custom"]},"status":{"type":"string","enum":["pending_verification","active","disabled"],"description":"Only `active` resolves to the organization."},"verification":{"description":"The DNS record to publish, while the claim waits for it; null otherwise.","oneOf":[{"type":"null"},{"type":"object","required":["record_type","record_name","record_value"],"properties":{"record_type":{"type":"string","const":"TXT"},"record_name":{"type":"string","description":"`_roundr-verify.{hostname}`"},"record_value":{"type":"string"}}}]},"verified_at":{"type":["string","null"],"format":"date-time"},"sign_in_registered_at":{"type":["string","null"],"format":"date-time","description":"When Roundr last added the hostname's sign-in URLs to the web app client, and\nnull again once a retraction has removed them; a disabled hostname that still\nhas one was retracted without its URLs being removed. A record of those\nwrites, not a reading of Cognito.\n"},"primary":{"type":"boolean","description":"Whether the organization's links are built on it. One hostname at most, and\nonly an active one whose sign-in is registered: a court code's join link carries\nthe code in its path, and a host verified but not yet routed to Roundr would\nhand it to whatever server answers the name.\n"}}},"OrganizationHostname":{"type":"object","description":"A hostname as its own organization sees it.","required":["id","hostname","kind","status","sign_in_registered_at","primary"],"properties":{"id":{"type":"string","format":"uuid"},"hostname":{"type":"string"},"kind":{"type":"string","enum":["platform_subdomain","custom"]},"status":{"type":"string","enum":["pending_verification","active","disabled"],"description":"Only `active` resolves to the organization."},"sign_in_registered_at":{"type":["string","null"],"format":"date-time","description":"When its sign-in was registered, the last provisioning step, after its\ncertificate and route; null before. Only an active hostname with one can be\nprimary.\n"},"primary":{"type":"boolean","description":"Whether the organization's links are built on it."}}},"OrganizationDomainAnswer":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["domain"],"properties":{"domain":{"$ref":"#/components/schemas/OrganizationDomain"}}}}},"IntegrationScope":{"type":"string","enum":["tournaments:read","tournaments:import"],"description":"What an API key may do. `tournaments:read` reads the organization's tournaments,\ntheir participants, snapshots, change feeds, streams, matches, match events and\nresults; `tournaments:import` loads participants and matches into a tournament.\n"},"IntegrationKey":{"type":"object","description":"An API key as the organization's administrator sees it; never its secret.","required":["id","label","scopes","tournament_id","status","created_at","expires_at","revoked_at","last_used_at"],"properties":{"id":{"type":"string","format":"uuid"},"label":{"type":"string"},"scopes":{"type":"array","description":"Empty only for a key that grants nothing.","items":{"$ref":"#/components/schemas/IntegrationScope"}},"tournament_id":{"description":"The one tournament the key sees, or null for every tournament of the organization.","oneOf":[{"type":"null"},{"type":"string","format":"uuid"}]},"status":{"type":"string","enum":["active","revoked","expired"]},"created_at":{"type":"string","format":"date-time"},"expires_at":{"type":"string","format":"date-time"},"revoked_at":{"oneOf":[{"type":"null"},{"type":"string","format":"date-time"}]},"last_used_at":{"description":"Recorded at most every five minutes, for deciding what to revoke.","oneOf":[{"type":"null"},{"type":"string","format":"date-time"}]}}},"Display":{"type":"object","description":"A hall display of a tournament and what it shows.","required":["id","name","revision","court_scope","court_ids","show_upcoming","upcoming_count","standings_draw_ids"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"revision":{"type":"integer"},"court_scope":{"$ref":"#/components/schemas/DisplayCourtScope"},"court_ids":{"type":"array","description":"The courts shown while `court_scope` is `selected`, in id order. Kept while it is\n`all`, when every court is shown.\n","items":{"type":"string","format":"uuid"}},"show_upcoming":{"type":"boolean","description":"Whether the matches on no court are listed under the courts."},"upcoming_count":{"type":"integer","minimum":1,"maximum":12,"description":"How many of them; kept while the list is off."},"standings_draw_ids":{"type":"array","description":"The published round-robin draws whose group standings it shows, in id order.","items":{"type":"string","format":"uuid"}},"show_header":{"type":"boolean","description":"Whether the board is headed with its title line: the tournament and its venue,\nand on a court's scoreboard the court's name before them. On for a display\nnobody switched it off for.\n"},"show_seeds":{"type":"boolean","description":"Whether a side's seed is written beside its name. On unless switched off."},"layout":{"$ref":"#/components/schemas/DisplayLayout"}}},"DisplayLayout":{"type":"string","enum":["cards","list"],"description":"How a board of several courts is laid out (DEV-654): `cards`, a card a court in a\ngrid, or `list`, a band of three lines a court with every game's score in a row and\nno title line, as a big tournament's screen lists its courts. A board of one court is\nthat court's scoreboard either way. `cards` unless chosen; a server from before the\nchoice sends none, and the board draws cards.\n"},"DisplayCourtScope":{"type":"string","enum":["all","selected"],"description":"`all` shows every court of the tournament, one added later included; `selected`\nshows only the courts chosen for the display.\n"},"TournamentStaffMember":{"type":"object","description":"One tournament role held by one person.","required":["user_id","display_name","email","role"],"properties":{"user_id":{"type":"string","format":"uuid"},"display_name":{"type":"string"},"email":{"type":["string","null"]},"role":{"type":"string","enum":["tournament_director","referee","tournament_umpire"]}}},"StaffInvitation":{"type":"object","description":"A staff invitation still waiting, listed by what it grants and never by its code.","required":["id","roles","label","created_by_name","created_at","expires_at"],"properties":{"id":{"type":"string","format":"uuid"},"roles":{"type":"array","minItems":1,"items":{"type":"string","enum":["tournament_director","referee","tournament_umpire"]}},"label":{"type":["string","null"],"description":"Whom the staff said it was for. It decides nothing."},"created_by_name":{"type":["string","null"]},"created_at":{"type":"string","format":"date-time"},"expires_at":{"type":"string","format":"date-time"}}},"CreatedStaffInvitation":{"type":"object","required":["invitation","code","invitation_url","email_sent"],"properties":{"invitation":{"$ref":"#/components/schemas/StaffInvitation"},"code":{"type":"string","description":"The invitation's code, shown this once."},"invitation_url":{"type":"string","format":"uri","description":"The page on the organization's primary host, or the host the request came to,\nwith the code in its fragment, which no server log or referrer sees.\n"},"email_sent":{"type":["boolean","null"],"description":"Whether Roundr sent the link to `email`; null when no address was given."}}},"StaffInvitationPreview":{"type":"object","required":["tournament_id","tournament_name","organization_id","organization_name","roles","invited_by_name","expires_at","tournament_closed","accepted"],"properties":{"tournament_id":{"type":"string","format":"uuid"},"tournament_name":{"type":"string"},"organization_id":{"type":"string","format":"uuid"},"organization_name":{"type":"string"},"roles":{"type":"array","minItems":1,"items":{"type":"string","enum":["tournament_director","referee","tournament_umpire"]}},"invited_by_name":{"type":["string","null"]},"expires_at":{"type":"string","format":"date-time"},"tournament_closed":{"type":"boolean","description":"Redeeming it is refused while this is true."},"accepted":{"type":"boolean","description":"The signed-in caller has already redeemed it."}}},"StaffInvitationRedemption":{"type":"object","required":["tournament_id","organization_id","roles","granted"],"properties":{"tournament_id":{"type":"string","format":"uuid"},"organization_id":{"type":"string","format":"uuid"},"roles":{"type":"array","items":{"type":"string","enum":["tournament_director","referee","tournament_umpire"]}},"granted":{"type":"array","description":"The roles that were new to the person; the others they held already.","items":{"type":"string","enum":["tournament_director","referee","tournament_umpire"]}}}},"Court":{"type":"object","required":["id","name","sort_order","status","revision"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string","description":"Unique within the tournament; the import places a match by it."},"sort_order":{"type":"integer"},"status":{"type":"string","enum":["open","closed","maintenance"]},"revision":{"type":"integer"}}},"ParticipantSummary":{"type":"object","description":"A participant as a match's side is named: enough for a panel or a display. The\nplayers are their names and nationalities, so a panel can name each player of a pair\nin the service court they stand in and call them by their family name, and a display\ncan show where they are from; the players' identities are on the participants read.\nA hall display is sent each `display_name` written in the tournament's `name_style`.\n","required":["id","kind","display_name","external_ref","players"],"properties":{"id":{"type":"string","format":"uuid"},"kind":{"type":"string","enum":["single","pair"]},"display_name":{"type":"string"},"external_ref":{"type":["string","null"],"description":"The reference the import keyed the participant by, or null."},"players":{"type":"array","description":"In position order; one for a `single`, two for a `pair`.","items":{"type":"object","required":["full_name","position","nationality"],"properties":{"full_name":{"type":"string","description":"The whole name as written; a split name's parts in written order."},"given_name":{"type":["string","null"],"description":"The given name or names, where whoever entered the name said which part is\nthe family name; null for a mononym and for a name nobody split (DEV-28).\nAbsent from a server that predates the parts, read as null.\n"},"family_name":{"type":["string","null"],"description":"The family name, the one an umpire calls, or null where nobody said which\npart it is: then nothing on the wire names it and `full_name` is used whole.\nAbsent from a server that predates the parts, read as null.\n"},"family_name_first":{"type":"boolean","description":"Whether the family name is written first, as in \"Liang Wei Keng\"; false for a\nname nobody split. Absent from a server that predates the parts, read as false.\n"},"position":{"type":"integer","minimum":1,"maximum":2},"nationality":{"type":["string","null"],"pattern":"^[A-Z]{3}$","description":"The federation's three-letter code, or null when nobody recorded one."}}}}}},"Participant":{"type":"object","required":["id","kind","display_name","external_ref","players","ranking"],"properties":{"id":{"type":"string","format":"uuid"},"kind":{"type":"string","enum":["single","pair"]},"display_name":{"type":"string"},"external_ref":{"type":["string","null"],"description":"The reference the import keyed the participant by, or null."},"players":{"type":"array","description":"In position order; one for a `single`, two for a `pair`.","items":{"type":"object","required":["id","full_name","position","external_ref","account","association","nationality","gender"],"properties":{"id":{"type":"string","format":"uuid"},"full_name":{"type":"string","description":"The whole name as written; a split name's parts in written order."},"given_name":{"type":["string","null"],"description":"The given name or names, where whoever entered the name said which part is\nthe family name; null for a mononym and for a name nobody split (DEV-28).\nAbsent from a server that predates the parts, read as null.\n"},"family_name":{"type":["string","null"],"description":"The family name, the one an umpire calls, or null where nobody said which\npart it is: then nothing on the wire names it and `full_name` is used whole.\nAbsent from a server that predates the parts, read as null.\n"},"family_name_first":{"type":"boolean","description":"Whether the family name is written first, as in \"Liang Wei Keng\"; false for a\nname nobody split. Absent from a server that predates the parts, read as false.\n"},"position":{"type":"integer","minimum":1,"maximum":2},"external_ref":{"type":["string","null"],"description":"The reference the import keyed the player by, so a new entry can name a\nperson already in the tournament rather than create them twice.\n"},"account":{"description":"The Roundr account the tournament's staff linked the player to, or null.\nAnswered to staff only: a court device and a machine key are given null\nfor every player, whether or not one is linked.\n","oneOf":[{"$ref":"#/components/schemas/PlayerAccount"},{"type":"null"}]},"association":{"type":["string","null"],"description":"The player's club, association or country, as entered or imported: the\nMember GCR 11.12 keeps apart in a draw by the regulations. A pair comes\nfrom its players' when both share one, compared regardless of case and\nspacing, and from none otherwise (GCR 11.13).\n"},"club":{"description":"The club on Roundr the player stands for (DEV-39), or null. Absent from a\nserver older than it.\n","oneOf":[{"type":"object","required":["id","name"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"}}},{"type":"null"}]},"nationality":{"type":["string","null"],"pattern":"^[A-Z]{3}$","description":"The three-letter code the federation writes for the player's country\n(AUT, GER, SLO), in capitals, or null. Roundr keeps no list of countries\nand checks only the shape.\n"},"gender":{"type":["string","null"],"enum":["female","male",null],"description":"As a draw's entry rule reads it (`Draw.entry_gender`); null where nobody\nrecorded it, which no rule refuses.\n"},"date_of_birth":{"type":["string","null"],"format":"date","description":"As the staff recorded it, or as the player's account gave it when an entry\ninto an age category was accepted. Personal data, answered to staff only: a\ncourt device and a machine key are given null for every player (DEV-27).\n"},"classes":{"$ref":"#/components/schemas/PlayerClasses"}}}},"ranking":{"description":"The entry's place in a ranking the director supplies, from which a draw's seeds\nmay be numbered (`/v1/draws/{drawId}/seeds-from-ranking`); null when it has none.\nRoundr computes no ranking.\n","oneOf":[{"$ref":"#/components/schemas/ParticipantRanking"},{"type":"null"}]}}},"NameStyle":{"type":"string","enum":["as_entered","bwf"],"description":"How Live and the hall displays write a side's name (DEV-28): as entered, or the\nBWF's way, \"Alex LANIER\" and \"LIANG Wei Keng\", where every player of the side has a\nfamily name.\n"},"EntryGender":{"type":["string","null"],"enum":["men","women","mixed",null],"description":"A draw's entry rule: `men` takes male players, `women` female ones, `mixed` a pair\nof one of each and only on a ruleset played by pairs, and null anyone. An entry is\nrefused only where a player's recorded gender contradicts it, never for a gender\nnobody recorded.\n"},"ParticipantRanking":{"type":"object","required":["position","name","date"],"properties":{"position":{"type":"integer","minimum":1},"name":{"type":["string","null"],"maxLength":100,"description":"The ranking's name; positions are compared only within one name and date."},"date":{"type":["string","null"],"format":"date","description":"The date the ranking was published."}}},"CheckIn":{"type":"object","description":"A player's check-in at the desk (DEV-44). `checked_in_at` and `checked_in_by` say\nwho took the check-in and when, while the player stays present; `changed_at` and\n`changed_by` the last change of any kind. A staff account that has been deleted is\nnamed by nobody. A player the desk never touched answers at revision 0.\n","required":["player_id","present","checked_in_at","checked_in_by","away_until","changed_at","changed_by","revision"],"properties":{"player_id":{"type":"string","format":"uuid"},"present":{"type":"boolean"},"checked_in_at":{"type":["string","null"],"format":"date-time"},"checked_in_by":{"oneOf":[{"$ref":"#/components/schemas/CheckInStaff"},{"type":"null"}]},"away_until":{"type":["string","null"],"format":"date-time","description":"The time before which the player is not to be called, whether or not they are present."},"changed_at":{"type":["string","null"],"format":"date-time"},"changed_by":{"oneOf":[{"$ref":"#/components/schemas/CheckInStaff"},{"type":"null"}]},"revision":{"type":"integer","minimum":0,"description":"The `aggregate_revision` of the `player_check_in` notification that announced this state."}}},"CheckInStaff":{"type":"object","required":["display_name"],"properties":{"display_name":{"type":"string"}}},"CheckInListResponse":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["check_ins"],"properties":{"check_ins":{"type":"array","items":{"$ref":"#/components/schemas/CheckIn"}}}}}},"CheckInResponse":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["check_in"],"properties":{"check_in":{"$ref":"#/components/schemas/CheckIn"}}}}},"ParticipantResponse":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["participant"],"properties":{"participant":{"$ref":"#/components/schemas/Participant"}}}}},"AccountMatch":{"type":"object","description":"One match of the account's, as its tournament would show that player.","required":["id","tournament","side","sides","court_name","external_ref","scheduled_start","status","score"],"properties":{"id":{"type":"string","format":"uuid"},"tournament":{"type":"object","required":["id","name","venue_time_zone","organization_name","on_live"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"venue_time_zone":{"type":"string"},"organization_name":{"type":"string"},"on_live":{"type":"boolean","description":"Whether the tournament is published on Live. When it is, the match has a page at `/m/{id}` on the host the account was read on."}}},"side":{"type":"string","enum":["home","away"],"description":"The side the account is on."},"sides":{"type":"object","description":"Each side's display name, or null while a draw has still to decide it.","required":["home","away"],"properties":{"home":{"type":["string","null"]},"away":{"type":["string","null"]}}},"court_name":{"type":["string","null"]},"external_ref":{"type":["string","null"],"description":"The match's reference in its draw file, `MS-1`."},"scheduled_start":{"type":["string","null"],"format":"date-time"},"status":{"type":"string","enum":["scheduled","in_progress","suspended","completed","confirmed","walkover","retired","disqualified","ended_by_referee"]},"score":{"$ref":"#/components/schemas/LiveScore"}}},"CareerTournament":{"type":"object","description":"A tournament of the account's career.","required":["id","name","organization_name","venue_time_zone","starts_on","ends_on"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"organization_name":{"type":"string"},"venue_time_zone":{"type":"string"},"starts_on":{"type":["string","null"],"format":"date"},"ends_on":{"type":["string","null"],"format":"date"}}},"CareerDiscipline":{"type":"string","enum":["singles","doubles","mixed"],"description":"The record a match counts in: singles unless its ruleset is played by pairs, and\nthen mixed when its draw's entry rule is mixed, otherwise doubles.\n"},"CareerMatch":{"type":"object","description":"One match of the account's: the match as Live sends it, with its place in its draw\nwhen the draw is sent beside it, and the side and participant the account stood on.\n","required":["tournament_id","participant_id","side","discipline","draw_name","sides","court_name","match"],"properties":{"tournament_id":{"type":"string","format":"uuid"},"participant_id":{"type":"string","format":"uuid","description":"The side the account stood on, a single or the pair it was part of."},"side":{"type":"string","enum":["home","away"]},"discipline":{"$ref":"#/components/schemas/CareerDiscipline"},"draw_name":{"type":["string","null"],"description":"The event, the name of the draw the match is in."},"sides":{"type":"object","description":"Each side's display name, or null while a draw has still to decide it.","required":["home","away"],"properties":{"home":{"type":["string","null"]},"away":{"type":["string","null"]}}},"court_name":{"type":["string","null"]},"match":{"$ref":"#/components/schemas/LiveMatch"}}},"CareerRecord":{"type":"object","description":"One account's results in a head-to-head's organization, on Live: each as the\nstatistics count it, with no names, tournament or ids.\n","required":["results"],"properties":{"results":{"type":"array","items":{"type":"object","required":["discipline","side","venue_time_zone","starts_on","status","score","scheduled_start","started_at","ended_at"],"properties":{"discipline":{"$ref":"#/components/schemas/CareerDiscipline"},"side":{"type":"string","enum":["home","away"]},"venue_time_zone":{"type":"string"},"starts_on":{"type":["string","null"],"format":"date"},"status":{"type":"string","enum":["completed","confirmed","walkover","retired","disqualified","ended_by_referee"]},"score":{"$ref":"#/components/schemas/LiveScore"},"scheduled_start":{"type":["string","null"],"format":"date-time"},"started_at":{"type":["string","null"],"format":"date-time"},"ended_at":{"type":["string","null"],"format":"date-time"}}}}}},"AccountSharing":{"type":"object","required":["results_to_opponents"],"properties":{"results_to_opponents":{"type":"boolean","description":"\"Show my results to players I have played\", off until the person turns it on."}}},"AccountSharingResponse":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["sharing"],"properties":{"sharing":{"$ref":"#/components/schemas/AccountSharing"}}}}},"AgeRule":{"description":"A draw's age category, or null for any age. `under` 15 is U15, a player who does not\nturn 15 in the calendar year of the tournament's start; `over` 35 is 35+, one who\nturns 35 or more that year (DEV-27). Sent by every server that keeps age\ncategories; a client reading an older one takes its absence for any age.\n","oneOf":[{"type":"null"},{"type":"object","required":["bound","age"],"properties":{"bound":{"type":"string","enum":["under","over"]},"age":{"type":"integer","minimum":1,"maximum":99}}}]},"ClassRule":{"description":"A draw's class rule, or null for every class: the player classes it admits in its\ndiscipline (`Discipline`), in the order of the organization's scale. A pair enters\nwhen both players hold an admitted class (DEV-31). Sent by every server that keeps\nclasses; a client reading an older one takes its absence for every class.\n","oneOf":[{"type":"null"},{"type":"object","required":["classes"],"properties":{"classes":{"type":"array","minItems":1,"maxItems":10,"uniqueItems":true,"items":{"$ref":"#/components/schemas/PlayerClassCode"}}}}]},"PlayerClassCode":{"type":"string","pattern":"^[A-Z0-9]{1,3}$","description":"A player class as the organization's scale names it, A to D by default. A request\nmay write it in any case; it is stored and answered in capitals.\n"},"PlayerClasses":{"type":"object","description":"A player's class in each discipline, each null where none is recorded (DEV-31).\nNever public. Sent by every server that keeps classes.\n","required":["singles","doubles","mixed"],"properties":{"singles":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/PlayerClassCode"}]},"doubles":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/PlayerClassCode"}]},"mixed":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/PlayerClassCode"}]}}},"PlayerClassesChange":{"type":"object","description":"Classes by discipline, each a code in any case, or null or blank to clear it. A\ndiscipline left out keeps its class.\n","properties":{"singles":{"type":["string","null"],"maxLength":3},"doubles":{"type":["string","null"],"maxLength":3},"mixed":{"type":["string","null"],"maxLength":3}}},"Discipline":{"type":"string","enum":["singles","doubles","mixed"],"description":"Which of a player's classes an event reads: singles for a single, mixed for a pair\nunder a mixed entry rule, doubles for any other pair. Append-only.\n"},"RegistrationEvent":{"type":"object","description":"An event that takes entries, a draft draw the tournament offers.","required":["draw_id","name","code","kind"],"properties":{"draw_id":{"type":"string","format":"uuid"},"name":{"type":"string"},"code":{"type":"string"},"kind":{"type":"string","enum":["single","pair"],"description":"Who enters it; a pair names a partner."},"age_rule":{"$ref":"#/components/schemas/AgeRule"},"class_rule":{"$ref":"#/components/schemas/ClassRule"},"discipline":{"$ref":"#/components/schemas/Discipline"}}},"RegistrationOffer":{"type":"object","required":["tournament","open","closes_at","events"],"properties":{"tournament":{"type":"object","required":["id","name","venue_time_zone","starts_on","ends_on"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"venue_time_zone":{"type":"string"},"starts_on":{"type":["string","null"],"format":"date"},"ends_on":{"type":["string","null"],"format":"date"}}},"open":{"type":"boolean","description":"Taking entries now."},"opens_at":{"type":["string","null"],"format":"date-time","description":"While open now or later, when entries open; null for no opening time."},"closes_at":{"type":["string","null"],"format":"date-time","description":"While open now or later, when entries close; null for no closing time."},"withdrawals_close_at":{"type":["string","null"],"format":"date-time","description":"While open now or later, until when a player may withdraw an entry themselves; null for no deadline."},"events":{"type":"array","description":"Empty unless open now or later.","items":{"$ref":"#/components/schemas/RegistrationEvent"}}}},"RegistrationSettings":{"type":"object","required":["open","closes_at","draw_ids","revision"],"properties":{"open":{"type":"boolean"},"closes_at":{"type":["string","null"],"format":"date-time"},"opens_at":{"type":["string","null"],"format":"date-time"},"withdrawals_close_at":{"type":["string","null"],"format":"date-time"},"public_associations":{"type":"boolean"},"draw_ids":{"type":"array","description":"The events offered, as the director chose them; one published since takes no entries.","items":{"type":"string","format":"uuid"}},"revision":{"type":"integer","minimum":0}}},"RegistrationSettingsResponse":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["registration"],"properties":{"registration":{"$ref":"#/components/schemas/RegistrationSettings"}}}}},"Registration":{"type":"object","description":"An account's request to enter an event, alone or with a partner, and what became of\nit. Its players carry the names they enter under and whether they confirmed, never\nan account or an address.\n","required":["id","tournament","event","kind","status","display_name","complete","players","participant_id","registered_at","decided_at","withdrawn_at","revision"],"properties":{"id":{"type":"string","format":"uuid"},"tournament":{"type":"object","required":["id","name","organization_name","venue_time_zone"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"organization_name":{"type":"string"},"venue_time_zone":{"type":"string"},"starts_on":{"type":["string","null"],"format":"date","description":"The tournament's first day. Sent by every server that keeps it."},"ends_on":{"type":["string","null"],"format":"date","description":"The tournament's last day. After it has passed at the venue a player's own\nwithdrawal is refused as `withdrawals_closed`, deadline or not. Sent by every\nserver that keeps it.\n"}}},"event":{"type":"object","required":["draw_id","name","code"],"properties":{"draw_id":{"type":"string","format":"uuid"},"name":{"type":"string"},"code":{"type":"string"},"age_rule":{"$ref":"#/components/schemas/AgeRule"},"class_rule":{"$ref":"#/components/schemas/ClassRule"}}},"entry_window":{"type":"object","description":"The tournament's entry window and withdrawal deadline as they stand, each null\nwhen not set. Sent by every server that keeps them.\n","required":["opens_at","closes_at","withdrawals_close_at"],"properties":{"opens_at":{"type":["string","null"],"format":"date-time"},"closes_at":{"type":["string","null"],"format":"date-time"},"withdrawals_close_at":{"type":["string","null"],"format":"date-time"}}},"kind":{"type":"string","enum":["single","pair"]},"status":{"type":"string","enum":["pending","accepted","declined","withdrawn"],"description":"`withdrawn` also after acceptance, when `participant_id` and `decided_at` stay\nas the acceptance set them.\n"},"display_name":{"type":"string","description":"The name the entry is shown under, both surnames for a pair: each player's family\nname where they gave one, and otherwise the last word of their name.\n"},"complete":{"type":"boolean","description":"Every player has confirmed; only then may the staff accept it."},"players":{"type":"array","items":{"type":"object","required":["position","full_name","confirmed","you"],"properties":{"position":{"type":"integer","enum":[1,2],"description":"1 registered, 2 is the partner named."},"full_name":{"type":"string"},"given_name":{"type":["string","null"],"description":"The given name or names, where whoever entered the name said which part is\nthe family name; null for a mononym and for a name nobody split (DEV-28).\nAbsent from a server that predates the parts, read as null.\n"},"family_name":{"type":["string","null"],"description":"The family name, the one an umpire calls, or null where nobody said which\npart it is: then nothing on the wire names it and `full_name` is used whole.\nAbsent from a server that predates the parts, read as null.\n"},"family_name_first":{"type":"boolean","description":"Whether the family name is written first, as in \"Liang Wei Keng\"; false for a\nname nobody split. Absent from a server that predates the parts, read as false.\n"},"confirmed":{"type":"boolean"},"you":{"type":"boolean","description":"The caller's own player; always false on the staff's list."}}}},"participant_id":{"type":["string","null"],"format":"uuid"},"registered_at":{"type":"string","format":"date-time"},"decided_at":{"type":["string","null"],"format":"date-time"},"withdrawn_at":{"type":["string","null"],"format":"date-time"},"revision":{"type":"integer","minimum":1}}},"RegistrationResponse":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["registration"],"properties":{"registration":{"$ref":"#/components/schemas/Registration"}}}}},"RegistrationList":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["registrations"],"properties":{"registrations":{"type":"array","items":{"$ref":"#/components/schemas/Registration"}}}}}},"JuniorChange":{"type":"object","description":"What a junior's record says of them (DEV-36). `date_of_birth` is required, a date\nor null, so a body that forgot it does not clear it; `classes` left out keeps the\njunior's.\n","required":["display_name","date_of_birth"],"properties":{"display_name":{"type":"string","minLength":1,"maxLength":200},"date_of_birth":{"type":["string","null"],"format":"date"},"classes":{"$ref":"#/components/schemas/PlayerClassesChange"}}},"Junior":{"type":"object","required":["id","display_name","date_of_birth","classes","own_account","may_hand_over","handover_expires_at","guardians","invitations"],"properties":{"id":{"type":"string","format":"uuid"},"display_name":{"type":"string"},"date_of_birth":{"type":["string","null"],"format":"date"},"classes":{"$ref":"#/components/schemas/PlayerClasses"},"own_account":{"type":"boolean","description":"The junior signs in as themselves now; guardians kept no longer edit the profile."},"may_hand_over":{"type":"boolean"},"handover_expires_at":{"type":["string","null"],"format":"date-time"},"guardians":{"type":"array","items":{"type":"object","required":["display_name","since","you"],"properties":{"display_name":{"type":"string"},"since":{"type":"string","format":"date-time"},"you":{"type":"boolean"}}}},"invitations":{"type":"array","description":"Invitations to another guardian still waiting. They name nobody.","items":{"type":"object","required":["id","created_at"],"properties":{"id":{"type":"string","format":"uuid"},"created_at":{"type":"string","format":"date-time"}}}}}},"JuniorResponse":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["junior"],"properties":{"junior":{"$ref":"#/components/schemas/Junior"}}}}},"JuniorList":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["juniors","own_account_age"],"properties":{"juniors":{"type":"array","items":{"$ref":"#/components/schemas/Junior"}},"own_account_age":{"type":"integer","description":"The age, by the date, from which a record may be handed over."}}}}},"GuardianshipLeft":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["left"],"properties":{"left":{"type":"boolean","const":true}}}}},"GuardianInvitationList":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["invitations"],"properties":{"invitations":{"type":"array","items":{"type":"object","required":["id","junior_name","invited_by_name","created_at"],"properties":{"id":{"type":"string","format":"uuid"},"junior_name":{"type":"string"},"invited_by_name":{"type":["string","null"]},"created_at":{"type":"string","format":"date-time"}}}}}}}},"GuardianInvitationDeclined":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["declined"],"properties":{"declined":{"type":"boolean","const":true}}}}},"HandoverIssued":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["handover"],"properties":{"handover":{"type":"object","required":["code","expires_at"],"properties":{"code":{"type":"string","description":"Twenty characters of Crockford's base 32 in four groups of five, shown this once."},"expires_at":{"type":"string","format":"date-time"}}}}}}},"HandoverRedeemed":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["handover"],"properties":{"handover":{"type":"object","required":["guardians_kept"],"properties":{"guardians_kept":{"type":"boolean"}}}}}}},"OwnGuardians":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["guardians"],"properties":{"guardians":{"type":"array","items":{"type":"object","required":["display_name","since"],"properties":{"display_name":{"type":"string"},"since":{"type":"string","format":"date-time"}}}}}}}},"LinkedPlayer":{"type":"object","required":["id","name","tournament","organization_name"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string","description":"The player's name as the tournament has it."},"tournament":{"type":"object","required":["id","name","starts_on"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"starts_on":{"type":["string","null"],"format":"date"}}},"organization_name":{"type":"string"}}},"LegalAcceptanceItem":{"type":"object","description":"A version of a legal text, for the person or for one organization they administer.","required":["document","version","organization_id"],"properties":{"document":{"type":"string","enum":["terms","dpa"],"description":"The terms of use, or an organization's data processing agreement."},"version":{"type":"string","description":"The version the server named, the date of the text that asked for consent."},"organization_id":{"type":["string","null"],"format":"uuid","description":"The organization a data processing agreement is accepted for; null for the terms."}}},"LegalPendingItem":{"allOf":[{"$ref":"#/components/schemas/LegalAcceptanceItem"},{"type":"object","required":["organization_name"],"properties":{"organization_name":{"type":["string","null"],"description":"The organization's name, to say whose agreement it is; null for the terms."}}}]},"LegalAcceptanceResponse":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["pending"],"properties":{"pending":{"type":"array","items":{"$ref":"#/components/schemas/LegalPendingItem"}}}}}},"AccountProfileResponse":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["profile"],"properties":{"profile":{"type":"object","required":["date_of_birth"],"properties":{"date_of_birth":{"type":["string","null"],"format":"date"},"classes":{"$ref":"#/components/schemas/PlayerClasses"},"locale":{"type":["string","null"],"description":"The language the account last used Roundr in, or null when no client has reported one."}}}}}}},"Locale":{"type":"string","description":"A locale Roundr supports, as `SUPPORTED_LOCALES` in `packages/i18n/src/locale.ts` lists them.","enum":["af","ar","bg","bn","bs","ca","cs","da","de","el","en","en-AU","en-CA","en-GB","es","es-MX","et","fa","fi","fil","fr","fr-CA","he","hi","hr","hu","id","is","it","ja","ko","lt","lv","ms","nl","no","pl","pt","pt-BR","ro","ru","sk","sl","sq","sr","sr-Latn","sv","sw","th","tr","uk","ur","vi","zh-Hans","zh-Hant"]},"NotificationType":{"type":"string","description":"What the notification tells (DEV-35). Append-only: a client shows a type it does not\nknow as a plain notice that opens `path`.\n","enum":["partner_invited","partner_confirmed","entry_accepted","entry_declined","entry_withdrawn","role_granted","player_linked","membership_invited","membership_requested","membership_accepted","membership_ended","club_invited","club_membership_requested","club_membership_accepted","club_membership_ended","match_called","match_rescheduled","subscription_confirmed","subscription_ended","official_seated"]},"NotificationChannel":{"type":"string","description":"A way a notification reaches a person after the inbox (DEV-35, DEV-45). Append-only:\na client that does not know a channel offers no switch for it.\n","enum":["email","push"]},"Notification":{"type":"object","required":["id","type","payload","organization","path","created_at","read_at"],"properties":{"id":{"type":"string","format":"uuid"},"type":{"$ref":"#/components/schemas/NotificationType"},"payload":{"type":"object","description":"The ids and names the notification is written from, as they stood when it was\nwritten. Registration types carry `tournament_id`, `tournament_name`,\n`registration_id`, `draw_id` and `event_name`, and `player_name`, the other\nplayer, for `partner_invited` and `partner_confirmed`, and `junior_id` on a\nguardian's copy of what is addressed to their junior; `role_granted` carries\n`tournament_id`, `tournament_name` and `role`. The membership types (DEV-39)\ncarry `membership_id`, `association_id`, `association_name`, `member_id`,\n`member_name` and `sport`, and `membership_ended` also `previous_status`\n(`requested`, `invited` or `active`) and `ended_by` (`association` or `member`);\nthey are about the recipient's own organization, whose settings `path` opens.\nThe `club_*` types carry `membership_id`, `club_id`, `club_name`, `person_id`,\n`person_name`, `sport` and `told_as` (`person` or `club`, the side told, whose\npage `path` opens), and `club_membership_ended` `previous_status` and `ended_by`\n(`club` or `person`).\nThe match-day types (DEV-45), `match_called` and `match_rescheduled`, carry\n`tournament_id`, `tournament_name`, `match_id`, `court_id`, `court_name`,\n`scheduled_start` (UTC, or null), `venue_time_zone`, `home_name`, `away_name`,\n`player_name` (the player the recipient is, or follows), `told_as` (`player` or\n`follower`) and `on_live` (`1` while the tournament is on Live, when `path` opens\nthe match there); `match_rescheduled` also `previous_court_name` and\n`previous_scheduled_start`, what the recipient last heard.\n`subscription_confirmed` (DEV-247) confirms the contract for a subscription the\nrecipient's organization bought, whose plan page `path` opens: `plan`, `amount`\n(what was charged, in the currency's minor unit) and `currency`, `period_start`\nand `period_end` (Unix seconds), `language` (`de` or `en`, the order's) and\n`terms_version`. Its email is always sent, so the preferences list no switch\nfor it. `subscription_ended` (DEV-629) says the subscription that paid for the\nrecipient's organization's plan has ended and the organization is on the free\nplan now: `plan` is the plan it lost. It is told to the organization's\nadministrators, whose preferences alone list it, and `path` opens the plan page.\nIds are UUIDs.\n","additionalProperties":{"type":["string","null"]}},"organization":{"description":"The organization it is about, by its name as it stands now; null for one about the account alone.","oneOf":[{"type":"null"},{"type":"object","required":["id","name"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":["string","null"]}}}]},"path":{"type":"string","description":"The page it opens, a path on the web app, such as `/account/entries/{tournamentId}`."},"created_at":{"type":"string","format":"date-time"},"read_at":{"type":["string","null"],"format":"date-time"}}},"PushSubscription":{"type":"object","required":["id","label","created_at","last_success_at","last_failure_at"],"properties":{"id":{"type":"string","format":"uuid"},"label":{"type":"string","description":"The browser and system, such as `Chrome · Android`."},"created_at":{"type":"string","format":"date-time"},"last_success_at":{"type":["string","null"],"format":"date-time","description":"When its push service last took a message."},"last_failure_at":{"type":["string","null"],"format":"date-time","description":"When its push service last refused one for a reason that may pass."}}},"NotificationPreferencesResponse":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["preferences","push_public_key","email_suppression"],"properties":{"preferences":{"type":"array","description":"Every type, the channels it goes by, and for each whether it comes that way now and whether it does unless turned off. The inbox always has it.","items":{"type":"object","required":["type","channels","email","email_by_default","push","push_by_default"],"properties":{"type":{"$ref":"#/components/schemas/NotificationType"},"channels":{"type":"array","items":{"$ref":"#/components/schemas/NotificationChannel"}},"email":{"type":"boolean"},"email_by_default":{"type":"boolean"},"push":{"type":"boolean"},"push_by_default":{"type":"boolean"}}}},"push_public_key":{"type":["string","null"],"description":"The VAPID public key, base64url, a browser subscribes with; null when this instance sends no web push."},"email_suppression":{"description":"Why Roundr writes no email to the account's address (DEV-111), as SES reported\nit, and since when; null while it writes. `bounce`: the address took no mail,\nand no email goes to it. `complaint`: its owner reported a message as spam, and\nonly an email the organization is owed, a contract's confirmation, still goes.\n`DELETE /v1/me/email-suppression` sends to it again.\n","oneOf":[{"type":"null"},{"type":"object","required":["reason","since"],"properties":{"reason":{"type":"string","enum":["bounce","complaint"]},"since":{"type":"string","format":"date-time"}}}]}}}}},"AccountNameAnswer":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["name"],"properties":{"name":{"type":"string"}}}}},"OrganizationNameAnswer":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["name"],"properties":{"name":{"type":"string"}}}}},"PlayerClassScaleAnswer":{"type":"object","required":["ok","data"],"properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","required":["classes"],"properties":{"classes":{"type":"array","minItems":1,"maxItems":10,"items":{"$ref":"#/components/schemas/PlayerClassCode"}}}}}},"PlayerAccount":{"type":"object","required":["display_name"],"properties":{"display_name":{"type":"string","description":"The account's name, so staff can see they linked the right person."}}},"TournamentImport":{"type":"object","description":"The rows of an entry or draw file, keyed by the references the file uses. Both\nlists default to empty, so a file of entries can be loaded before the draw exists.\n","properties":{"participants":{"type":"array","default":[],"items":{"$ref":"#/components/schemas/ImportParticipant"}},"matches":{"type":"array","default":[],"items":{"$ref":"#/components/schemas/ImportMatch"}}}},"ImportParticipant":{"type":"object","required":["kind","display_name","players"],"description":"Named by `external_ref`, or, for an entry already in the tournament, by its `id`\ninstead: an entry a registration made has no reference, and Control corrects it by\nits id. Exactly one of the two. An entry named by id keeps whatever reference it\nhas, and an id the tournament does not have refuses the row, never makes an entry.\n","properties":{"id":{"type":"string","format":"uuid","description":"The id of a participant already in the tournament, given instead of\n`external_ref`.\n"},"external_ref":{"type":"string","minLength":1,"maxLength":100,"description":"The file's key for the participant; a later import with the same reference updates it."},"kind":{"type":"string","enum":["single","pair"]},"display_name":{"type":"string","minLength":1,"maxLength":200},"players":{"type":"array","description":"In position order: exactly one for a `single`, two for a `pair`. Rewritten\nwhole on re-import, because a corrected pair is a different pair.\n","minItems":1,"maxItems":2,"items":{"type":"object","description":"A player is named by `external_ref`, or by the `id` of a player already in the\ntournament instead, never both; one of an entry named by its `id` may be named\nby neither, a partner added to it, and is a new player without a reference.\nAn id the tournament does not have refuses the row.\n\nA player needs `full_name`, or a `family_name` its whole name is made from\n(DEV-28). A string `family_name` says which part is the family name: the\nplayer's name is then its parts, the stored `full_name` is derived from them in\nwritten order, and a `full_name` sent beside them is not read. A null or blank\n`family_name` says nobody knows it, and the name is `full_name`, unsplit. Left\nout, the name is `full_name`, and parts recorded for exactly that whole name\nstay, so a file without the columns and a client that has never heard of them\nwipe nothing; parts recorded for another whole name are forgotten, since they\nwould misname the corrected one.\n","properties":{"id":{"type":"string","format":"uuid","description":"The id of a player already in the tournament, given instead of `external_ref`."},"external_ref":{"type":"string","minLength":1,"maxLength":100,"description":"The file's key for the person, within the tournament, so one person can\nplay a single and in a pair without being two players.\n"},"full_name":{"type":"string","minLength":1,"maxLength":200},"given_name":{"type":["string","null"],"description":"The given name or names, with a `family_name`; null or blank for a mononym."},"family_name":{"type":["string","null"],"description":"The family name, the one an umpire calls. The whole name it makes with the\ngiven name is at most 200 characters.\n"},"family_name_first":{"type":"boolean","description":"Whether the family name is written first, as in \"Liang Wei Keng\"; false when absent."},"association":{"type":["string","null"],"maxLength":100,"description":"The player's club, association or country. Left out, the player's\nassociation stays as it is, so a file without the column wipes nothing;\nnull or blank clears it.\n"},"nationality":{"type":["string","null"],"description":"The federation's three-letter code for the player's country, AUT or GER:\nthree letters A to Z, stored in capitals whatever case the file writes.\nLeft out, it stays as it is; null or blank clears it.\n"},"gender":{"type":["string","null"],"enum":["female","male",null],"description":"What a draw's entry rule reads. Left out, it stays as it is; null clears it.\n"},"date_of_birth":{"type":["string","null"],"format":"date","description":"What a draw's age category is checked by, from 1900 to today. Left out, it\nstays as it is; null or blank clears it.\n"},"classes":{"$ref":"#/components/schemas/PlayerClassesChange"}}}},"ranking":{"description":"The entry's place in the director's ranking. Left out, it stays as it is; null\nclears it.\n","oneOf":[{"$ref":"#/components/schemas/ParticipantRanking"},{"type":"null"}]}}},"ImportMatch":{"type":"object","required":["external_ref","ruleset_id"],"properties":{"external_ref":{"type":"string","minLength":1,"maxLength":100,"description":"The file's key for the match; a later import with the same reference updates it rather than adding a second."},"ruleset_id":{"type":"string","description":"A ruleset this deployment carries, such as `badminton.bwf.21x3`."},"ruleset_version":{"type":"string","default":"v1"},"home_ref":{"type":["string","null"],"maxLength":100,"description":"A participant's `external_ref`, from this file or an earlier import."},"away_ref":{"type":["string","null"],"maxLength":100,"description":"A participant's `external_ref`, from this file or an earlier import."},"court":{"type":["string","null"],"maxLength":100,"description":"A court's name, matched without regard to case."},"scheduled_start":{"type":["string","null"],"format":"date-time"},"not_before":{"type":["string","null"],"format":"date-time","description":"The earliest the match may start. A null clears it; a row without the key, a\nfile without the column, keeps what the match has, which a director may have\nset in Control since the file was made.\n"}}},"ImportSummary":{"type":"object","required":["participants_imported","matches_created","matches_updated"],"properties":{"participants_imported":{"type":"integer","description":"Participants written, new or updated."},"matches_created":{"type":"integer"},"matches_updated":{"type":"integer","description":"Matches that already held their reference."}}},"MatchCreated":{"type":"object","required":["match_id","created"],"properties":{"match_id":{"type":"string","format":"uuid"},"created":{"type":"boolean","description":"False when `external_ref` already named a match, which was updated instead."}}},"MatchAssignment":{"type":"object","required":["match_id","court_id","scheduled_start","sequence"],"properties":{"match_id":{"type":"string","format":"uuid"},"court_id":{"type":["string","null"],"format":"uuid"},"scheduled_start":{"type":["string","null"],"format":"date-time"},"not_before":{"type":["string","null"],"format":"date-time","description":"The earliest the match may start. Always sent by this server."},"sequence":{"type":"integer"}}},"RedemptionResult":{"type":"object","required":["credential","principal","expires_at","csrf_token"],"properties":{"credential":{"type":["string","null"],"description":"The `crt_` or `dsp_` secret, only for the `bearer` transport, and only this once."},"principal":{"$ref":"#/components/schemas/AuthPrincipal"},"expires_at":{"type":"string","format":"date-time","description":"When the session (cookie) or the credential (bearer) ends."},"csrf_token":{"type":["string","null"],"description":"For the cookie transport, the token every write from this device sends as `X-CSRF-Token`."}}},"IssuedRedemptionCode":{"type":"object","required":["id","code","kind","purpose","expires_at","join_url"],"properties":{"id":{"type":"string","format":"uuid"},"code":{"type":"string","description":"Shown once. The server keeps a digest."},"kind":{"type":"string","enum":["qr","pin"]},"purpose":{"type":"string","enum":["court","display"]},"expires_at":{"type":"string","format":"date-time"},"join_url":{"type":"string","format":"uri","description":"The page that redeems the code, on this host's page origin. A QR image encodes this."}}},"SignInOptions":{"type":"object","required":["password","providers"],"properties":{"password":{"type":"boolean","description":"Whether this host signs people in with an email address and a password."},"providers":{"type":"array","description":"Federated providers to offer, by the name `/v1/auth/login?provider=` takes.","items":{"type":"string"}}}},"EmailOnly":{"type":"object","required":["email"],"properties":{"email":{"type":"string","maxLength":254}}},"EmailAndCode":{"type":"object","required":["email","code"],"properties":{"email":{"type":"string","maxLength":254},"code":{"type":"string","maxLength":20}}},"AuthSession":{"type":"object","required":["user","principal","organizations","active_organization_id","csrf_token"],"properties":{"user":{"description":"The signed-in person; null for a court, display or integration credential.","oneOf":[{"$ref":"#/components/schemas/AuthUser"},{"type":"null"}]},"principal":{"description":"What the request may do, and for which organization; null when it acts for none.","oneOf":[{"$ref":"#/components/schemas/AuthPrincipal"},{"type":"null"}]},"organizations":{"type":"array","description":"Organizations the user is an active member of. Empty for a device credential.","items":{"$ref":"#/components/schemas/AuthOrganization"}},"active_organization_id":{"type":["string","null"],"format":"uuid","description":"The organization a browser session acts for. Null before one is chosen, and null again once the chosen one no longer admits the person: closed, suspended, or their membership withdrawn. The page then offers the organizations they still belong to."},"csrf_token":{"type":["string","null"],"description":"Send as `X-CSRF-Token` on every write from a browser session. Null otherwise."},"operator":{"type":"boolean","description":"Whether the person is a platform operator on this request, which only a browser\nsession on the shared API host can be. It tells a page whether to offer the\nOperator screen; every Operator route decides again. Absent from a server older\nthan the field, which a client reads as false.\n"},"public_origin":{"type":["string","null"],"description":"The origin, `https://{host}`, Roundr builds the acting organization's links on,\nas Control builds its Live link: the organization's primary hostname, or without\none this host when it is the organization's, or the platform's site. Null when\nthe request acts for no organization. Absent from a server older than the field.\n"},"legal_pending":{"type":"array","description":"The versions of the legal texts the person has yet to accept, which a page asks\nthem for (terms section 17): the terms of use, and the data processing agreement\nof each organization they administer. Empty for a device credential and once\neverything is accepted. Absent from a server older than the field, which a client\nreads as empty.\n","items":{"$ref":"#/components/schemas/LegalPendingItem"}}}},"AuthUser":{"type":"object","required":["id","display_name","email"],"properties":{"id":{"type":"string","format":"uuid"},"display_name":{"type":"string"},"email":{"type":["string","null"]}}},"AuthPrincipal":{"type":"object","required":["type","organization_id","roles","tournament_roles","tournament_id","court_id"],"properties":{"type":{"type":"string","enum":["user","court_session","display_session","integration"]},"organization_id":{"type":"string","format":"uuid"},"roles":{"type":"array","items":{"type":"string","enum":["organization_admin","tournament_director","referee","umpire","display","integration"]}},"tournament_roles":{"type":"array","items":{"type":"object","required":["tournament_id","role"],"properties":{"tournament_id":{"type":"string","format":"uuid"},"role":{"type":"string","enum":["tournament_director","referee","tournament_umpire"]}}}},"tournament_id":{"type":["string","null"],"format":"uuid"},"court_id":{"type":["string","null"],"format":"uuid"}}},"AuthOrganization":{"type":"object","required":["id","name","slug"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"slug":{"type":"string"},"kind":{"allOf":[{"$ref":"#/components/schemas/OrganizationKind"}],"description":"Absent from a server older than DEV-39, when every organization was a club."}}}}},"security":[{"sessionCookie":[]},{"bearerAuth":[]},{"apiKey":[]}]}
