openapi: 3.1.1
info:
  title: Scacelith serverio HTTPS API
  version: "1.1.0"
  summary: Kiekvieno Scacelith serverio – ir oficialiojo, ir bendruomenės serverių – HTTPS API.
  description: |-
    Į šios HTTPS API užklausas atsako kiekvienas Scacelith serveris: ir oficialusis `caissa.scacelith.com`, ir bet kuris bendruomenės serveris. Žaidimas ją naudoja viskam, kas vyksta ne pačioje partijoje: registracijai ir prisijungimui (įskaitant dviejų veiksnių autentifikavimą ir Google), paskyros puslapiui, partijų istorijai, PGN atsisiuntimams ir animuotiems partijų GIF, prisijungusiems įrenginiams, duomenų atsisiuntimui, paskyros ištrynimui ir pranešimams.

    Žaidimas realiuoju laiku (varžovų parinkimas, iššūkiai, ėjimai, laikrodžiai) vyksta per to paties serverio WebSocket ryšį (`wss://<host>/ws`), atidaromą su šios API seanso žetonu; jis aprašytas faile `PROTOCOL.md`, o ne čia.

    Toliau nurodytos numatytosios reikšmės galioja serveriui, kurio konfigūracija nekeista; bendruomenės serveris gali jas pakeisti (visi nustatymai išvardyti faile `CONFIG.md`).

    ## Bazinis URL, prievadas ir transportas

    - Oficialus serveris: `https://caissa.scacelith.com/api/v1`, TCP prievadas 443.
    - Bendruomenės serveris: `https://<SERVER_PUBLIC_HOST>[:<port>]/api/v1`. `API_PORT` pagal numatytuosius nustatymus yra 443; už NAT ar tarpinio serverio žaidėjų naudojamas prievadas yra `PUBLIC_API_PORT`.
    - Žaidimo WebSocket ryšys pagal numatytuosius nustatymus naudoja tą patį prievadą (`WS_PORT` jį perkelia; `GET /info` nurodo klientui, kur jis yra).
    - HTTP/1.1 per TLS. Kai `TLS_MODE=proxy`, TLS ryšį priešais serverį užbaigia atvirkštinis tarpinis serveris. `TLS_MODE=off` (paprastas HTTP) skirtas tik vietiniam kūrimui.
    - HTML puslapiai, atidaromi iš el. laiškų nuorodų (`/verify-email`, `/reset-password`, `/confirm-email-change`), yra serverio šaknyje, už `/api/v1` ribų. Būklės galiniai punktai atsako ir šaknyje, ir po `/api/v1`. Prisijungimas su Google serveryje neturi puslapio: Google grąžina naršyklę į patį žaidimą, adresu `127.0.0.1`, arba į svetainės, kuri prisijungimą pradėjo, puslapį (`GOOGLE_WEB_REDIRECT_URI`).
    - Galinis pasvirasis brūkšnys nepaisomas (`/api/v1/info/` yra tas pats kaip `/api/v1/info`), o kelio parametrai dekoduojami iš URL kodavimo (į parametrą, kuris nėra tinkamai URL užkoduotas, atsakoma 400 `invalid_request`).

    ## Užklausos

    - **JSON turinys.** `POST`, `PUT` ir `DELETE` užklausos perduoda JSON objektą su `Content-Type: application/json`. Kitokia nei UTF-8 `charset` reikšmė atmetama, kaip ir bet kuris kitas turinio tipas (415 `unsupported_media_type`). Tuščias turinys laikomas `{}` – būtent jį priima galiniai punktai be parametrų (`POST /auth/logout`, `POST /auth/logout-all`, `DELETE /auth/sessions/{id}`).
    - **Dydis ir laikas.** Turinys ribojamas iki `HTTP_BODY_LIMIT` baitų (pagal numatytuosius nustatymus 16 384; `POST /gif` turi savo 135 168 baitų ribą): 413 `payload_too_large`. Jis turi ateiti per 10 sekundžių: 408 `request_timeout`. Po bet kurios iš šių klaidų serveris uždaro ryšį.
    - **Griežtos schemos.** Galiniam punktui nežinomas laukas atmetamas, laukas, nepažymėtas kaip neprivalomas, yra privalomas, o tipai ir ilgiai tikrinami. Į bet kurią iš šių klaidų atsakoma 400 `invalid_request`, o `field` nurodo lauką (įdėtojo lauko vardas rašomas su taškais, pvz., `pow.nonce`). Eilutėse negali būti valdymo simbolių. Ilgis skaičiuojamas UTF-16 kodo vienetais, todėl simbolis už pagrindinės daugiakalbės plokštumos ribų (jaustukas) skaičiuojamas du kartus. Į turinį, kuris nėra JSON, atsakoma 400 `invalid_json`. `POST /gif` ir `POST /reports` savo turinį tikrina patys (žr. kiekvieną galinį punktą).
    - **Užklausos eilutės.** Galioja pirmasis parametro pasikartojimas, nežinomi parametrai nepaisomi, o `+` dekoduojamas kaip tarpas: galiniai punktai, priimantys laiko kontrolės kategoriją, priima ir `3%2B2`, ir `3+2`.
    - **Metodai.** `HEAD` yra `GET` be turinio. `OPTIONS` esamam keliui atsako 204 su antrašte `Allow` (ir išankstinės CORS užklausos antraštėmis, žr. toliau). Į bet kurį kitą metodą, kurio kelias neturi, atsakoma 405 `method_not_allowed` su `Allow`.
    - **Užklausos tikslas.** Jis negali viršyti 4096 simbolių (414 `uri_too_long`). Užklausa, kurios visiškai neįmanoma išanalizuoti arba kurios antraštinė dalis per didelė ar ateina per lėtai, gauna tuščią atsakymą 400, 431 arba 408, ir ryšys uždaromas.
    - **CORS: leidžiamųjų sąrašas, pagal numatytuosius nustatymus išjungtas.** API aptarnauja žaidimą, kuris nesiunčia `Origin`. Kai `CORS_ORIGINS` tuščias (numatytoji reikšmė), jokia `Access-Control-*` antraštė niekada nesiunčiama, todėl joks tinklalapis negali perskaityti atsakymo. `CORS_ORIGINS` išvardija žiniatinklio kilmes (origins), kurių puslapiams leidžiama kreiptis į API iš naršyklės, kiekvieną tiksliai taip, kaip naršyklės ją siunčia antraštėje `Origin`: `https://host` arba `https://host:port`, mažosiomis raidėmis, be kelio, galinio pasvirojo brūkšnio ir numatytojo prievado; `http://` – tik `localhost`, `127.0.0.1` ir `[::1]` (vietiniam kūrimui); be pakaitos simbolių. Oficialus serveris savo svetainei nustato `CORS_ORIGINS=https://scacelith.com,https://www.scacelith.com`. Tada:
      - Kiekviename atsakyme keliui po `/api/v1` (išskyrus būklės galinius punktus), nesvarbu, ar tai sėkmingas atsakymas, ar klaida (įskaitant adresų lygmens 429), yra `Vary: Origin`. Kai užklausos `Origin` baitas į baitą sutampa su viena iš išvardytų kilmių, jame taip pat yra `Access-Control-Allow-Origin: <that origin>` ir, išskyrus atsakymą į išankstinę užklausą, `Access-Control-Expose-Headers: Retry-After, Content-Disposition`, kad puslapis galėtų perskaityti klaidų kodus, `Retry-After` ir atsisiunčiamų PGN bei GIF failų vardus.
      - Į išankstinę užklausą (preflight: `OPTIONS` su `Access-Control-Request-Method`) iš išvardytos kilmės esamam keliui atsakoma 204, be turinio, su `Allow`, `Access-Control-Allow-Origin`, `Access-Control-Allow-Methods` (`Allow` metodai), `Access-Control-Allow-Headers: Authorization, Content-Type`, `Access-Control-Max-Age: 600` ir `Vary: Origin`. Jai nereikia žetono ir ji neįskaičiuojama nei į paskyros biudžetą, nei į jokią galinio punkto ribą; kaip ir bet kuri užklausa, ji paima adresų lygmens žetoną. Naršyklė pakartotinai naudoja išankstinės užklausos rezultatą 10 minučių, bet tik lygiai tam pačiam URL, įskaitant užklausos eilutę. Užklausai be `Authorization` ir be JSON turinio, pvz., `GET /info` ar `GET /leaderboard`, išankstinės užklausos nereikia.
      - Bet koks kitas atsakymas į išankstinę užklausą reiškia nepavykusią išankstinę užklausą, net jei jame yra CORS antraštės: 404 neegzistuojančiam keliui arba adresų lygmens 429. Tada naršyklė užklausos nesiunčia, o puslapis mato tinklo klaidą – be atsakymo ir be jo `Retry-After`.
      - `Access-Control-Allow-Credentials` niekada nesiunčiama, kaip ir `Access-Control-Allow-Origin: *`: puslapis siunčia savo seanso žetoną antraštėje `Authorization: Bearer`, kaip tai daro žaidimas, o API nenustato jokių slapukų. HTML puslapiai, būklės galiniai punktai ir tušti atsakymai į neperskaitomas užklausas nesiunčia jokios CORS antraštės. WebSocket nenaudoja CORS: `WS_ALLOWED_ORIGINS` išvardija puslapius, kuriems leidžiama jį atidaryti.
      - Neišvardytos kilmės puslapis nieko neperskaito, o jo tarpsvetainės rašymo užklausos niekada neišsiunčiamos: JSON turiniui ir `Authorization` reikia išankstinės užklausos, kurios atsakyme tai kilmei nėra `Access-Control-Allow-Origin`. `Cross-Origin-Resource-Policy: same-origin` lieka kiekviename atsakyme: ji taikoma tik `no-cors` užklausoms (kai kita svetainė įterpia atsakymą), niekada – išvardyto puslapio `cors` užklausoms.

    ## Atsakymai ir klaidos

    - Atsakymai pateikiami JSON formatu UTF-8 koduote, išskyrus PGN atsisiuntimą (`application/x-chess-pgn`), animuotus GIF (`image/gif`) ir HTML puslapius. Laikas nurodomas milisekundėmis nuo 1970-01-01 UTC. ID yra sveikieji skaičiai. Partijų ID turi iki 16 skaitmenų, bet lieka mažesni už 2^53, todėl JSON skaičius (double) juos išsaugo tiksliai.
    - Kiekviename API atsakyme yra `Cache-Control: no-store`, `X-Content-Type-Options: nosniff`, `Referrer-Policy: no-referrer`, `X-Frame-Options: DENY`, `Cross-Origin-Resource-Policy: same-origin` ir `Content-Security-Policy: default-src 'none'; frame-ancestors 'none'` (HTML puslapiai turi savo politiką). Kai `TLS_MODE=native`, jame taip pat yra `Strict-Transport-Security: max-age=31536000`. Atsakymuose, kuriuos klausytuvas parašo pats, dar prieš API (būklės galinių punktų `GET` ir `HEAD`, adresų lygmens 429 ir 500 `internal_error`, kai API visiškai sutrinka), yra tos pačios antraštės, išskyrus `Cross-Origin-Resource-Policy`, o jų `Content-Type` yra `application/json` be koduotės (charset).
    - Atsakymas turi būti perskaitytas per 60 sekundžių nuo tada, kai serveris jį paruošia; tai svarbu tik dideliems atsakymams (GIF, duomenų eksportui, ilgam PGN). Laikas, per kurį serveris paruošia atsakymą, neįskaičiuojamas nei į šias 60 sekundžių, nei į 30 sekundžių be jokio gauto ar išsiųsto baito, po kurių ryšys uždaromas.

    Klaidos turi vieną formą (schema `Error`):

    ```json
    { "error": "snake_case_code", "message": "An English sentence.", "retryAfter": 30 }
    ```

    `message` skirtas žurnalams ir kaip atsarginis variantas; ką rodyti, klientas pasirenka pagal `error`. `retryAfter` (sekundėmis) būna tik atmetimuose, kurie baigiasi praėjus laikui, ir tada atsakyme taip pat yra antraštė `Retry-After` su ta pačia reikšme; vienintelė išimtis – istorijos, partijų, žaidėjų ir lyderių lentelės skaitymo 503 `busy`, kuriame yra tik laukas. Kai kurios klaidos prideda laukų: `field` (netinkami įvesties duomenys), `reason` (`weak_password`, `pow_required`), `pow` (`pow_required`), `until` (`banned`), `line` ir `column` (`invalid_pgn`).

    Klaidos, kurias gali grąžinti bet kuris galinis punktas:

    | Būsena | `error` | Kada |
    |---|---|---|
    | 400 | `invalid_request` | Turinys pažeidžia galinio punkto schemą (`field` nurodo vietą), užklausos tikslas arba `Content-Length` netinkamo formato, kelio parametras nėra tinkamai URL užkoduotas arba turinys nutrūko. |
    | 400 | `invalid_json` | Turinys nėra JSON. |
    | 401 | `unauthorized` | Užklausoje galiniam punktui, kuriam reikia seanso, nėra antraštės `Authorization`. |
    | 401 | `invalid_token` | Žetonas netinkamo formato, nebegalioja, atšauktas arba priklauso ištrintai paskyrai; taip pat galiniuose punktuose, kuriuose seansas neprivalomas. |
    | 404 | `not_found` | Tokio galinio punkto nėra. Kai kurie galiniai punktai šią klaidą taip pat naudoja: nėra tokios partijos, žaidėjo ar seanso. |
    | 405 | `method_not_allowed` | Kelias egzistuoja kitiems metodams (žr. `Allow`). |
    | 408 | `request_timeout` | Turinys neatėjo per 10 sekundžių. |
    | 413 | `payload_too_large` | Turinys viršija `HTTP_BODY_LIMIT` (`POST /gif` – 135 168 baitų). |
    | 414 | `uri_too_long` | Užklausos tikslas viršija 4096 simbolius. |
    | 415 | `unsupported_media_type` | Turinys nėra `application/json` arba jo koduotė (charset) nėra UTF-8. |
    | 429 | `rate_limited` | Užklausų dažnio riba (žr. toliau): `retryAfter` ir antraštė `Retry-After`. |
    | 500 | `internal_error` | Netikėta klaida. Serveris ją įrašo į žurnalą. |
    | 503 | `timeout` | Serveris neatsakė per 30 sekundžių (eksportui – per 60 sekundžių, GIF su numatytaisiais nustatymais – per 45 sekundes). |
    | 503 | `server_busy` | Ieškant seanso arba keičiant paskyrą duomenų bazė buvo užrakinta (`retryAfter` 1) arba slaptažodžių maišos eilė pilna (žr. toliau). Taip pat, su `retryAfter` nuo 2 iki 5, kai serveris labai atsilieka: `POST`, `PUT`, `PATCH` ar `DELETE` atveju duomenų bazėje laukia per daug rašymo operacijų; bet kurios užklausos atveju per daug ankstesnių užklausų vis dar vykdomos ilgiau už savo skirtąjį laiką arba seanso žetono nepavyko patikrinti per 5 sekundes. Užklausa nebuvo vykdyta, o jos užklausų dažnio ribos nieko nepaėmė. |

    Skaitymo galiniai punktai (partijų istorija, partijos, žaidėjai, lyderių lentelė) ir eksportas atsako 503 `busy` su `retryAfter: 1`, kai duomenų bazė liko užrakinta.

    Galiniai punktai, kurie tikrina slaptažodį arba skaičiuoja jo maišą, taip pat gali atsakyti viena iš šių klaidų, abiem atvejais su atsitiktine `retryAfter` reikšme nuo 5 iki 15 sekundžių:

    - 503 `server_busy`: serverio slaptažodžių maišos eilė (`PASSWORD_HASH_QUEUE_MAX`) pilna arba baigėsi laukimo laikas.
    - 429 `rate_limited`: kai eilė užpildyta perpus, šis klientas (IPv4 adresas arba IPv6 /48) jau turi `PASSWORD_HASH_WAITERS_PER_SOURCE` laukiančių maišos skaičiavimų. Šis 429 grąžina užklausų dažnio ribų žetonus, kuriuos užklausa paėmė.

    Niekas nebuvo pakeista ir joks nesėkmingas bandymas nebuvo įskaičiuotas (išskyrus `POST /auth/sso/google/link`, kurio bilieto bandymas ir paskyros nesėkmė įskaičiuojami prieš maišos skaičiavimą), o slaptažodžio nustatymo iš naujo nuoroda lieka galiojanti.

    HTML puslapiai savo klaidas grąžina kaip HTML puslapius su tais pačiais būsenos kodais.

    ## Autentifikavimas

    Galiniai punktai, kuriems reikia seanso, priima nešėjo žetoną (bearer token) antraštėje `Authorization` (schema `bearerAuth`):

    ```
    Authorization: Bearer sct_L_8GDd7uzfQ3QQWtqrsWXDTsFWzRwIvJcwIGHhjWPS8
    ```

    - **Žetono gavimas.** Žetoną (`sct_` ir 43 base64url simboliai) išduoda `POST /auth/login`, po to `POST /auth/login/mfa`, kai įjungtas dviejų veiksnių autentifikavimas, `POST /auth/sso/google/finish` ir `POST /auth/sso/google/link` (prisijungimas su Google) bei `POST /auth/sso/complete`. Kiekvienas iš jų atsako `{ token, expiresAt, user }`. Serveris saugo tik žetono SHA-256 maišą.
    - **Privalomas seansas.** Kai antraštės nėra, atsakoma 401 `unauthorized` su `WWW-Authenticate: Bearer realm="scacelith"`. Į netinkamą žetoną atsakoma 401 `invalid_token` su `WWW-Authenticate: Bearer realm="scacelith", error="invalid_token"`. Gavęs `invalid_token`, klientas turėtų pamiršti žetoną ir prisijungti iš naujo.
    - **Neprivalomas seansas.** Galiniuose punktuose `GET /games/{id}`, `GET /games/{id}/pgn`, `GET /players/{username}` ir `GET /players/{username}/games` seansas neprivalomas. Be antraštės jie grąžina viešąjį vaizdą; jei antraštė siunčiama, jos žetonas turi galioti.
    - **Užimta saugykla.** Kai tikrinant žetoną duomenų bazė lieka užrakinta, galinis punktas, kuriam reikia seanso arba kuris jį priima, atsako 503 `server_busy` su `retryAfter: 1` (ir `Retry-After`); kai tikrinimas trunka ilgiau nei 5 sekundes, jis atsako 503 `server_busy` su `retryAfter` nuo 2 iki 5. Abiem atvejais žetono galiojimas nebuvo įvertintas: pasilikite jį ir siųskite užklausą dar kartą.
    - **Galiojimo trukmė.** Seansas baigiasi, kai praeina `SESSION_MAX_DAYS` (90) dienų nuo prisijungimo (tai `expiresAt`) arba `SESSION_IDLE_DAYS` (30) dienų be naudojimo – žiūrint, kas įvyksta anksčiau. Kiekvienas naudojimas atitolina neveiklumo ribą; naują reikšmę serveris įrašo ne dažniau kaip kas 5 minutes, fone. Kol jo duomenų bazėje laukia daug rašymo operacijų, jis tą įrašymą atideda vėlesnei užklausai, išskyrus paskutinę valandą prieš neveiklumo ribą, todėl naudojamo seanso galiojimas nesibaigia. Paskyra turi ne daugiau kaip `MAX_SESSIONS_PER_USER` (10) seansų: naujas prisijungimas atšaukia seniausius, viršijančius šį skaičių.
    - **Atšaukimas.** Atsijungimas (`POST /auth/logout`, `POST /auth/logout-all`, `DELETE /auth/sessions/{id}`) atšaukia seansus; taip pat juos atšaukia slaptažodžio pakeitimas (kitus seansus), slaptažodžio nustatymas iš naujo ir paskyros ištrynimas (visus seansus) bei administratorius. Atšaukimas per API įsigalioja iš karto, o su atšauktu seansu atidarytas WebSocket ryšys uždaromas. Slaptažodžio pakeitimas ar nustatymas iš naujo ir paskyros ištrynimas atšaukia seansus toje pačioje transakcijoje kaip ir pats pakeitimas: jei kuris nors iš jų nepavyksta, joks seansas nebuvo atšauktas ir niekas kita taip pat nepasikeitė. Serveris seansų paieškos rezultatus laiko talpykloje 30 sekundžių, todėl atšaukimas administravimo komanda, kuri veikia kaip atskiras procesas, įsigalioja per 30 sekundžių.
    - **Aprėptis.** Žetonas priklauso vienam serveriui ir taip pat atidaro jo WebSocket ryšį. Niekada nesiųskite jo kitam serveriui.

    ## Užklausų dažnio ribos ir kitas stabdymas

    Ribos taikomos vienai iš dviejų aprėpčių. **Klientas:** IPv4 adresas arba IPv6 /64 (tarpinio serverio režimu adresas imamas iš `X-Forwarded-For`, kurį atsiuntė `TRUSTED_PROXIES` adresas); kai kurios ribos taip pat skaičiuoja kiekvieną IPv6 /48 kaip visumą, be kiekvieno iš jo /64 tinklų. **Žaidėjas:** prisijungusi paskyra, nepriklausomai nuo jos adreso; pagal žaidėją skaičiuojama riba galiniame punkte, kuriame seansas neprivalomas, užklausai be žetono skaičiuojama pagal klientą.

    Kiekviena riba yra žetonų kibiras (token bucket), talpinantis `limit` užklausų ir nuolat pildomas greičiu `limit / window`; `retryAfter` – laikas iki kito žetono. Ribos, pažymėtos *bendra*, taip pat skaičiuojamos tokio pat ilgio slenkančiame lange, kad jokiame lange nebūtų gerokai daugiau nei `limit` užklausų. Kiekviena riba skaičiuojama visam serveriui. Kai viena iš galinio punkto ribų atmeta užklausą, žetonai, kuriuos tai užklausai paėmė kitos jo ribos, grąžinami. Į atmetimą atsakoma 429 `rate_limited` su `retryAfter` ir `Retry-After`.

    - **Adresų lygmuo.** Kiekviena užklausa (bet kuris kelias ir metodas, įskaitant būklės galinius punktus ir WebSocket ryšio perjungimo užklausas) pirmiausia paima vieną žetoną iš savo kliento biudžeto: `HTTP_RATE_PER_IP` (600) per minutę su pusės minutės užklausų pliūpsniu ir `HTTP_RATE_PER_PREFIX` (4 x `HTTP_RATE_PER_IP`) IPv6 /48 tinklui. Klientas taip pat gali turėti ne daugiau kaip `IP_MAX_INFLIGHT` (32 x `WORKERS`) vykdomų užklausų (viršijus – `retryAfter` 1). Klientas, kuris tęsia po atmetimų, blokuojamas: `ABUSE_BLOCK_REFUSALS_PER_MIN` (600) atmetimų per vieną minutę jį užblokuoja 1 minutei, o kiekvieną naują kartą per 6 valandas – 4, 16 ir 60 minučių. Ribų `auth`, `auth_*` ir `reauth` atmetimas skaičiuojamas kaip 5; pagal žaidėją skaičiuojamų ribų atmetimai niekada neįskaičiuojami. Adresai, įrašyti į `ABUSE_EXEMPT`, šį lygmenį praleidžia, bet ne paskyros biudžetą ar galinių punktų ribas.
    - **Paskyros biudžetas.** Kiekviena užklausa su galiojančiu seanso žetonu taip pat įskaičiuojama į savo paskyros biudžetą: `USER_RATE_PER_MIN` (120) per minutę visiems galiniams punktams kartu, nepriklausomai nuo adreso, su pusės minutės užklausų pliūpsniu.
    - **Galinių punktų ribos:**

    | Riba | Numatytoji reikšmė | Skaičiuojama pagal | Galiniai punktai |
    |---|---|---|---|
    | `auth` | `AUTH_RATE_PER_IP` (20) / 10 min, bendra | klientą, taip pat `AUTH_RATE_PER_PREFIX` (5 x `AUTH_RATE_PER_IP`) IPv6 /48 tinklui | `POST /auth/register`, `/auth/login`, `/auth/login/mfa`, `/auth/verify-email/resend`, `/auth/password/forgot`, `/auth/password/reset`, `/auth/sso/google/link`, `/auth/sso/complete`; `POST /verify-email`, `/reset-password`, `/confirm-email-change` |
    | `auth_register` | `AUTH_REGISTER_PER_HOUR` (10) / val., bendra | klientą, IPv6 /48 tinklui – 3 kartus daugiau | `POST /auth/register` |
    | `auth_mail` | `AUTH_MAIL_PER_HOUR` (10) / val., bendra | klientą, IPv6 /48 tinklui – 3 kartus daugiau | `POST /auth/verify-email/resend` |
    | `auth_forgot` | `AUTH_FORGOT_PER_HOUR` (3) / val., bendra | klientą, IPv6 /48 tinklui – 3 kartus daugiau | `POST /auth/password/forgot` |
    | `auth_forgot_day` | `AUTH_FORGOT_PER_DAY` (10) / 24 val., bendra | klientą, IPv6 /48 tinklui – 3 kartus daugiau | `POST /auth/password/forgot` |
    | `auth_reset` | `AUTH_RESET_PER_HOUR` (10) / val., bendra | klientą, IPv6 /48 tinklui – 3 kartus daugiau | `POST /auth/password/reset`, `POST /reset-password` |
    | `reauth` | tie patys skaičiai kaip `auth`, atskiras kibiras, bendra | klientą ir IPv6 /48 | `POST /account/password`, `/account/mfa/totp/setup`, `/account/mfa/totp/enable`, `/account/mfa/totp/disable`, `/account/mfa/recovery-codes`, `/account/email`, `/account/export`, `/account/delete` |
    | `reauth_user` | `AUTH_REAUTH_PER_USER` (10) / 10 min, bendra | žaidėją | tie patys galiniai punktai kaip `reauth` |
    | `account` | 60 / min | žaidėją | `GET /account/me`, `PUT /account/preferences` |
    | `account_games` | 60 / min | žaidėją | `GET /account/games` |
    | `account_export` | 5 / val., bendra | žaidėją | `POST /account/export` (tikrinama prieš `reauth`; įskaičiuojamas kiekvienas bandymas) |
    | `sessions` | 60 / min | žaidėją | `POST /auth/logout`, `/auth/logout-all`, `GET /auth/sessions`, `DELETE /auth/sessions/{id}` |
    | `public_read` | 60 / min | žaidėją (klientą, jei užklausa be žetono) | `GET /players/{username}`, `/players/{username}/games`, `/games/{id}`, `/games/{id}/pgn` (vienas kibiras visiems keturiems) |
    | `gif` | 30 / min | žaidėją | `GET /games/{id}/gif`, `POST /gif` (vienas kibiras abiem) |
    | `gif_user_min`, `gif_user_hour` | `GIF_USER_RENDERS_PER_MIN` (4) / min ir `GIF_USER_RENDERS_PER_HOUR` (30) / val., bendra | žaidėją | tie patys du, tik kai GIF reikia sugeneruoti (ne iš talpyklos) |
    | `gif_ip_min`, `gif_ip_hour` | `GIF_IP_RENDERS_PER_MIN` (12) / min ir `GIF_IP_RENDERS_PER_HOUR` (120) / val., bendra | klientą (visas jo paskyras kartu), IPv6 /48 tinklui – 3 kartus daugiau | tie patys, kaip nurodyta aukščiau |
    | `reports` | 30 / val. | žaidėją | `POST /reports` |
    | `sso_start` | 30 / 10 min, bendra | klientą, taip pat 90 IPv6 /48 tinklui | `POST /auth/sso/google/start`, `POST /auth/sso/google/web/start` (kartu) |
    | `sso_finish` | 30 / min | klientą | `POST /auth/sso/google/finish` |
    | `page` | 60 / min | klientą | `GET /verify-email`, `/reset-password`, `/confirm-email-change` |

    `GET /info` ir `GET /leaderboard` neturi savo ribos: taikomas tik adresų lygmuo. Galinis punktas, turintis kelias ribas, jas tikrina savo aprašyme nurodyta tvarka.

    Serveris užklausą apdoroja tokia tvarka: adresų lygmuo, tada būklės galiniai punktai; galinio punkto nustatymas; patikros, ar serveris labai neatsilieka (503 `server_busy`): ar per daug ankstesnių užklausų vis dar vykdomos ilgiau už savo skirtąjį laiką ir, `POST`, `PUT`, `PATCH` ar `DELETE` atveju, duomenų bazės laukiančių rašymo operacijų sankaupa; autentifikavimas per 5 sekundes (viršijus – 503 `server_busy`); paskyros biudžetas, kai užklausoje yra seansas; galinio punkto ribos; turinys; pats galinis punktas (GIF galiniai punktai savo generavimo ribas paima tik tada, kai turi sugeneruoti GIF). Todėl dėl žetono atmesta užklausa neišeikvoja jokių galinio punkto žetonų, o užklausa su netinkamu turiniu juos išeikvoja.

    Kiti stabdymo atvejai, į kuriuos atsako patys galiniai punktai:

    - **Nesėkmingi prisijungimai vienu prisijungimo vardu** (naudotojo vardu arba el. pašto adresu): nuo `AUTH_FAILURES_PER_ACCOUNT` (5) nesėkmių kiekvienas bandymas turi laukti dvigubai ilgiau nei ankstesnis (2 s, 4 s ir t. t., iki 15 minučių): 429 `too_many_attempts` su `retryAfter`. Skaitiklis išvalomas po valandos be nesėkmių.
    - **Nesėkmingi antrieji veiksniai prisijungiant:** ta pati taisyklė nuo 5-ojo neteisingo paskyros kodo. Vienas prisijungimo žingsnis priima ne daugiau kaip 5 neteisingus kodus.
    - **Vienos paskyros antrojo veiksnio kodai:** ne daugiau kaip `AUTH_MFA_PER_ACCOUNT` (10) kodų (autentifikavimo programėlės ar atkūrimo kodų, teisingų ar neteisingų) per 15 minučių iš bet kurio adreso, prisijungiant ir pakartotinai autentifikuojantis; viršijus – 429 `too_many_attempts` dar prieš patikrinant kodą, todėl atkūrimo kodas nesunaudojamas.
    - **Nesėkmingi paskyros pakartotiniai autentifikavimai** (neteisingas slaptažodis arba kodas): ta pati taisyklė (429 `too_many_attempts`), bendra visiems galiniams punktams, kurie atlieka pakartotinį autentifikavimą.
    - **El. laiškai:** vienas patvirtinimo arba slaptažodžio nustatymo iš naujo laiškas vienam adresui kas 5 minutes (atsakymas lieka toks pat); vienas pranešimas „kažkas bandė naudoti jūsų adresą“ vienam adresui per valandą.
    - **Pranešimai:** `REPORTS_PER_DAY` (5) vienam žaidėjui per 24 valandas: 429 `report_limit`.

    ## Darbo įrodymas

    `POST /auth/register` visada reikalauja darbo įrodymo, kai `POW_REGISTER_BITS` didesnis už 0 (pagal numatytuosius nustatymus 18; `GET /info` jį pateikia kaip `pow.register`). `POST /auth/login` ir `POST /auth/sso/google/link` (abiem ta pati iššūkio rūšis) jo reikalauja tik 5 minutes po to, kai serveris pastebi nesėkmingų prisijungimų bangą (`POW_LOGIN_TRIGGER_PER_MIN`, tada `POW_LOGIN_BITS`); klientas tai sužino iš atsakymo.

    1. Į užklausą be įrodymo (arba su atmestu įrodymu) atsakoma 428 `pow_required` su `reason` ir iššūkiu `pow` `{ challenge, bits, expiresAt }`.
    2. Raskite nonce: dešimtainę ne daugiau kaip 20 skaitmenų eilutę, su kuria `SHA-256(challenge + ":" + nonce)` prasideda `bits` nulinių bitų (pirmiausia – reikšmingiausias pirmojo baito bitas). 18 bitų vidutiniškai reikalauja apie 260 000 maišos skaičiavimų.
    3. Vėl siųskite tą pačią užklausą su `"pow": { "challenge": "...", "nonce": "123456" }` turinyje.

    Iššūkis galioja 2 minutes ir tik vieną kartą, vienam galiniam punktui ir vienam kliento tinklui (IPv4 adresui arba IPv6 /64). Jis pasirašytas, todėl serveris apie jį nieko nesaugo, kol jis negrįžta. `reason` nurodo, kodėl įrodymas atmestas: `required`, `malformed`, `signature`, `endpoint`, `network`, `expired`, `bits`, `work` arba `replayed`.

    ## Pakartotinis autentifikavimas

    Keičiant paskyrą dar kartą prašoma slaptažodžio ir, kai įjungtas dviejų veiksnių autentifikavimas, antrojo veiksnio:

    - tik slaptažodžio: `POST /account/password` ir `POST /account/mfa/totp/setup`;
    - slaptažodžio ir autentifikavimo programėlės kodo (atkūrimo kodas atmetamas): `POST /account/mfa/recovery-codes`;
    - slaptažodžio ir autentifikavimo programėlės kodo arba atkūrimo kodo: `POST /account/mfa/totp/disable`, `POST /account/email`, `POST /account/export` ir `POST /account/delete`.

    Užklausų turinyje lauke `code` pateikiamas 6 skaitmenų autentifikavimo programėlės kodas, o lauke `recoveryCode` – atkūrimo kodas (`xxxx-xxxx-xx`; raidžių dydis, tarpai ir brūkšneliai nesvarbūs). Ten, kur atkūrimo kodai priimami, atkūrimo kodą galima siųsti ir lauke `code`. Kiekvienas kodas veikia vieną kartą: panaudotas autentifikavimo programėlės kodas atmetamas iki kito 30 sekundžių žingsnio, o panaudotas atkūrimo kodas išnyksta.

    | Būsena | `error` | Kada |
    |---|---|---|
    | 403 | `invalid_password` | Neteisingas slaptažodis arba slaptažodis buvo nustatytas iš naujo ar pakeistas, kol užklausa buvo vykdoma (niekas nepakeista). |
    | 403 | `mfa_code_required` | Dviejų veiksnių autentifikavimas įjungtas, o neišsiųstas nei `code`, nei `recoveryCode`. |
    | 403 | `invalid_code` | Neteisingas arba jau panaudotas kodas. |
    | 400 | `password_not_set` | Paskyra, prie kurios jungiamasi tik su Google, dar neturi slaptažodžio (slaptažodis nustatomas per „Pamiršote slaptažodį?“). |
    | 429 | `too_many_attempts` | Šioje paskyroje per daug nesėkmių arba išbandyta per daug kodų. |
    | 503 / 429 | `server_busy` / `rate_limited` | Slaptažodžių maišos eilė užimta. |

    Neteisingi slaptažodžiai ir kodai įskaičiuojami į paskyros nesėkmių skaitiklį ir registruojami kaip saugos įvykiai.

    ## Metrikos prievadas

    Be API prievado, serveris atsako paprastu HTTP metrikos prievade (`METRICS_PORT`, 9464, susietas su `METRICS_BIND`, pagal numatytuosius nustatymus 127.0.0.1; nedarykite jo viešai pasiekiamo). Jis nėra šios API dalis: `GET /healthz` atsako `ok`; `GET /readyz` atsako `ready`, kai paleidimas baigtas (kiekvienas skaidinys (shard) pakartotinai įvykdė savo žurnalą ir klausytuvai susieti) ir kol neprasidėjo išjungimas, kitu atveju – 503 `not ready`; `GET /metrics` pateikia Prometheus metrikas ir, kai nustatytas `METRICS_TOKEN`, reikalauja `Authorization: Bearer <token>` su būtent tuo žetonu.
  contact:
    name: Scacelith
    url: https://github.com/DarkCenobyte/scacelith-chess-server
  license:
    name: GPL-3.0-or-later
    identifier: GPL-3.0-or-later
externalDocs:
  description: Scacelith serverio išeitinis kodas ir jo informacinė dokumentacija (API.md, PROTOCOL.md, CONFIG.md).
  url: https://github.com/DarkCenobyte/scacelith-chess-server
servers:
  - url: https://caissa.scacelith.com/api/v1
    description: Oficialus serveris.
  - url: https://{host}:{port}/api/v1
    description: Bet kuris Scacelith serveris, pvz., bendruomenės serveris.
    variables:
      host:
        default: caissa.scacelith.com
        description: Viešasis serverio pagrindinio kompiuterio vardas (`SERVER_PUBLIC_HOST`).
      port:
        default: "443"
        description: Viešasis API prievadas (`PUBLIC_API_PORT`, o jei jis nenustatytas – `API_PORT`).
tags:
  - name: server-info
    x-displayName: Serverio informacija
    description: Ką klientui reikia žinoti prieš prisijungiant ar užmezgant ryšį.
  - name: health
    x-displayName: Būklės patikra
    description: |-
      Serverio gyvybingumas ir parengtis, skirti stebėsenai. Šie galiniai punktai atsako API prievade prieš autentifikavimą ir visas galinių punktų ribas, bet, kaip ir bet kuri užklausa, paima adresų lygmens žetoną; stebėsenos kompiuterį galima įrašyti į `ABUSE_EXEMPT`. Kiekvienam galiniam punktui veikia abu keliai: serverio šaknyje ir po `/api/v1`.

      Metrikos prievadas turi savo būklės galinius punktus, aprašytus įvade.
  - name: auth
    x-displayName: Registracija ir prisijungimas
    description: |-
      Paskyros kūrimas, prisijungimas slaptažodžiu (ir antruoju veiksniu), patvirtinimo ir slaptažodžio nustatymo iš naujo laiškai.

      `POST /auth/login`, `POST /auth/login/mfa`, `POST /auth/sso/google/finish`, `POST /auth/sso/google/link` ir `POST /auth/sso/complete` grąžina seansą `{ token, expiresAt, user }` (arba, pirmieji iš jų, antrąjį žingsnį).
  - name: google-sign-in
    x-displayName: Prisijungimas su Google
    description: |-
      Siūlomas, kai `GET /info` nurodo `sso.google: true`; kitu atveju kiekvienas toliau aprašytas galinis punktas atsako 404 `sso_disabled`. Svetainė prisijungia taip pat, su savo klientu, kai `sso.googleWeb` yra true (žr. toliau, **Svetainėje**). Žaidimas prisijungia per sistemos naršyklę, naudodamas RFC 8252 įdiegtų programų srautą (kliento tipas „Desktop app“): Google grąžina naršyklę į žaidimo klausytuvą adresu `127.0.0.1`, niekada į šį serverį, ir žaidimas niekada nemato Google prisijungimo duomenų.

      1. Klientas klausosi adresu `127.0.0.1:0` (prievadą parenka sistema) ir sukuria PKCE porą: `codeVerifier` iš 43–128 simbolių `[A-Za-z0-9._~-]` ir `codeChallenge = BASE64URL(SHA-256(codeVerifier))`, kurį sudaro 43 simboliai be užpildo.
      2. `POST /auth/sso/google/start` su iššūkiu ir prievadu grąžina Google URL ir bandymo `state`. Klientas patikrina URL (žr. toliau) ir atidaro jį naršyklėje.
      3. Google nukreipia naršyklę į `http://127.0.0.1:<port>/oauth2/google/<tag>?code=...&state=...`. Klientas priima tik pradžios atsakymo `state` ir po peradresavimo su `error=` nieko nesiunčia.
      4. `POST /auth/sso/google/finish` su bandymo ID, `codeVerifier`, `state` ir `code` (ir `iss`, jei Google jį atsiuntė).
      5. Atsakymas – seansas, dviejų veiksnių autentifikavimo žingsnis (tęskite su `POST /auth/login/mfa`), `needsUsername` naujam žaidėjui (tęskite su `POST /auth/sso/complete`) arba `needsPassword`, kai tą adresą naudoja paskyra su slaptažodžiu (tęskite su `POST /auth/sso/google/link`).

      **Kilmės žymė.** Peradresavimo URI yra žaidėjo pridėto serverio žymė, kad žaidimas atmestų URL, kurį kitas serveris gavo savo žaidėjams. Kilmė – tai pagrindinio kompiuterio vardas mažosiomis raidėmis (IPv6 adresas – laužtiniuose skliaustuose), `:` ir dešimtainis API prievadas, rašomas visada, įskaitant 443: serveris ima `SERVER_PUBLIC_HOST` ir savo viešąjį API prievadą, žaidimas – adresą, prie kurio yra prisijungęs. Žymė – tai pirmieji 22 simboliai, gauti SHA-256(UTF-8 `"scacelith-sso-origin-v1\n"` + kilmė) užkodavus base64url (be užpildo). Peradresavimo URI yra `"http://127.0.0.1:" + port + "/oauth2/google/" + tag`: visada IPv4 adresas ir žaidimo klausytuvo prievadas (nuo 1024 iki 65535). Serveris niekada neima URI, pagrindinio kompiuterio vardo ar kelio iš kliento.

      | Kilmė | Žymė |
      |---|---|
      | `play.scacelith.example:443` | `IhcScoV7eDOzTEcSnqPUPt` |
      | `localhost:8443` | `TFGx7zQ_8QlGZW5zpqznCr` |
      | `[::1]:8443` | `XToJm0DG5PjciEVmZa9Cho` |
      | `127.0.0.1:50443` | `3r653wM5ZjYsHcAJljmCwY` |

      Todėl prisijungimas su Google veikia tik žaidėjams, kurie pridėjo serverį būtent su `SERVER_PUBLIC_HOST` ir jo viešuoju API prievadu.

      **Ką žaidimas patikrina prieš atidarydamas naršyklę.** `authUrl` prasideda būtent `https://accounts.google.com/o/oauth2/v2/auth?`, yra sudarytas iš spausdinamųjų ASCII simbolių ir trumpesnis nei 4096 simboliai, o jo užklausos eilutėje yra lygiai vienas `response_type=code`, lygiai vienas `redirect_uri`, lygus URI, kurį žaidimas apskaičiuoja iš savo prievado ir kilmės žymės, lygiai vienas `state`, lygus atsakymo `state`, `code_challenge_method=S256` ir 43 simbolių `code_challenge`. Kitu atveju žaidimas sustabdo savo klausytuvą ir nieko neatidaro. `finish` ir `link` jis siunčia tik tam serveriui, kuris atsakė į `start`.

      **Svetainėje.** Svetainė (oficialioji: <https://scacelith.com/>) prisijungia su to paties Google projekto „Web application“ tipo klientu (`GOOGLE_WEB_CLIENT_ID`). Google grąžina naršyklę į vieną svetainės puslapį, kurį sukonfigūravo administratorius (`GOOGLE_WEB_REDIRECT_URI`, įrašytas kliento sąraše „Authorized redirect URIs“), ir tas puslapis siunčia kodą į `finish`; ir čia serveris neima peradresavimo URI iš kliento.

      1. Puslapis sukuria PKCE porą, kaip aprašyta aukščiau, laiko tikrintuvą (verifier) skirtuke (`sessionStorage`) ir siunčia `POST /auth/sso/google/web/start` su iššūkiu.
      2. Jis patikrina `authUrl` taip pat kaip žaidimas, tik `redirect_uri` turi būti savo paties kilmės puslapis, ir tada pereina į jį. Jokie Google scenarijai neįkeliami.
      3. Google nukreipia naršyklę į `GOOGLE_WEB_REDIRECT_URI?code=...&state=...`. Puslapis priima tik pradžios atsakymo `state` ir po peradresavimo su `error=` nieko nesiunčia.
      4. Toliau `finish`, `link` ir `complete` vyksta kaip ir žaidimo atveju. Bandymas įsimena, kad jį pradėjo svetainė: kodas iškeičiamas naudojant svetainės klientą ir puslapį, o ID žetone turi būti nurodytas tas klientas (žaidimo kliento kodas ar ID žetonas atmetamas su 502 `sso_failed`, ir atvirkščiai).

      Google paskyra pasiekia tą pačią Scacelith paskyrą ir iš žaidimo, ir iš svetainės: susiejime saugomas Google subjektas (`sub`), kuris identifikuoja Google paskyrą nepriklausomai nuo kliento. Svetainės kilmė turi būti įrašyta į `CORS_ORIGINS` (kitu atveju `check-config` įspėja).

      **Kurią paskyrą pasiekia prisijungimas su Google:** paskyrą, jau susietą su ta Google paskyra; kitu atveju – aktyvią paskyrą su adresu, kurį patvirtino Google (jei paskyra turi slaptažodį, `finish` atsako `needsPassword`, o susiejimas išsaugomas tik tada, kai patikrinamas jos slaptažodis, o po to, jei įjungtas, ir antrasis veiksnys; jei slaptažodžio neturi – 409 `sso_account_exists`); kitu atveju – naują paskyrą (`needsUsername`). Google paskyra niekada nesusiejama su esama paskyra vien pagal adresą. Paskyros adresu siunčiamas laiškas, kai prisijungimas su Google sukuria paskyrą ir kai jis pridedamas prie esamos paskyros.
  - name: sessions
    x-displayName: Seansai
    description: Prisijungę paskyros įrenginiai ir atsijungimas.
  - name: account
    x-displayName: Paskyra
    description: Paskyros rodinys, jos nuostatos ir slaptažodis.
  - name: two-step-verification
    x-displayName: Dviejų veiksnių autentifikavimas
    description: 'Autentifikavimo programėlės (TOTP, RFC 6238): SHA-1, 6 skaitmenys, 30 sekundžių žingsniai ir vieno žingsnio leidžiamasis nuokrypis į abi puses, taip pat vienkartiniai atkūrimo kodai.'
  - name: email-change
    x-displayName: El. pašto adreso keitimas
    description: Paskyros adreso keitimas, patvirtinamas nuoroda, išsiųsta naujuoju adresu.
  - name: data-export
    x-displayName: Duomenų eksportas
    description: Viskas, ką serveris saugo apie paskyrą, viename JSON faile.
  - name: account-deletion
    x-displayName: Paskyros ištrynimas
    description: Galutinis paskyros ištrynimas.
  - name: game-history
    x-displayName: Partijų istorija
    description: Prisijungusio žaidėjo paties partijos, filtruojamos ir skaidomos į puslapius.
  - name: games
    x-displayName: Partijos ir PGN
    description: |-
      Partijų įrašai su ėjimais ir laikrodžiais bei jų PGN failai.

      **Partijų kodai.** `status`: 1 `WhiteWins`, 2 `BlackWins`, 3 `Draw`, 4 `Aborted` (saugomos tik baigtos partijos). `result`: `1-0`, `0-1`, `1/2-1/2` arba `*` (nutraukta). `reason` su savo pavadinimu (`termination`) ir žodžiais, kuriais baigiasi PGN failo ėjimų tekstas (faile – anglų kalba, čia pateikiami su vertimu); kodai 7, 14 ir 21 reiškia lygiąsias (žaidėjas, kuriam baigėsi laikas, kuris pasidavė ar paliko partiją, žaidė prieš varžovą, kuris negalėjo paskelbti mato: FIDE 6.9 ir 5.1.2; taisyklę pateikia `DESIGN.md` 6.3), o kodais 4 ir 13 serveris partijos niekada nebaigia (jie priklauso bendram priežasčių sąrašui):

      | Kodas | `termination` | Žodžiai PGN faile |
      |---|---|---|
      | 1 | `Checkmate` | Checkmate (matas) |
      | 2 | `Resignation` | Resignation (pasidavimas) |
      | 3 | `Timeout` | Loss on time (pralaimėjimas dėl laiko) |
      | 4 | `IllegalMoves` | Second illegal move (forfeit) (antrasis neleistinas ėjimas, techninis pralaimėjimas) |
      | 5 | `Stalemate` | Stalemate (patas) |
      | 6 | `InsufficientMaterial` | Dead position (insufficient material) (negyva pozicija, nepakanka medžiagos) |
      | 7 | `TimeoutVsInsufficient` | Flag fall, but the opponent cannot checkmate (baigėsi laikas, bet varžovas negali paskelbti mato) |
      | 8 | `FivefoldRepetition` | Fivefold repetition (penkiagubas pozicijos pasikartojimas) |
      | 9 | `SeventyFiveMoves` | 75-move rule (75 ėjimų taisyklė) |
      | 10 | `ThreefoldClaim` | Threefold repetition (claimed) (trigubas pozicijos pasikartojimas, pareikalauta) |
      | 11 | `FiftyMoveClaim` | 50-move rule (claimed) (50 ėjimų taisyklė, pareikalauta) |
      | 12 | `Agreement` | Draw by agreement (lygiosios susitarus) |
      | 13 | `IllegalMovesVsInsufficient` | Second illegal move, but the opponent cannot checkmate (antrasis neleistinas ėjimas, bet varžovas negali paskelbti mato) |
      | 14 | `ResignationVsInsufficient` | Resignation, but the opponent cannot checkmate (pasidavimas, bet varžovas negali paskelbti mato) |
      | 20 | `Abandonment` | Abandoned (disconnected for too long) (partija palikta, per ilgai nebuvo ryšio) |
      | 21 | `AbandonmentVsInsufficient` | Abandoned, but the opponent cannot checkmate (partija palikta, bet varžovas negali paskelbti mato) |
      | 22 | `Aborted` | Game aborted (partija nutraukta) |
      | 23 | `NoShow` | Aborted: first move not played in time (nutraukta: pirmasis ėjimas nepadarytas laiku) |
      | 24 | `Forfeit` | Forfeit (fair play violation) (techninis pralaimėjimas, sąžiningo žaidimo pažeidimas) |
      | 25 | `ServerAborted` | Aborted by the server (nutraukė serveris) |
      | 26 | `BothDisconnected` | Aborted: both players disconnected (nutraukta: abu žaidėjai atsijungė) |

      Kodas 14 atsirado protokolo šalutinėje versijoje (minor) 1: anksčiau parašytas klientas nežinomą kodą turėtų rodyti kaip paprastą partijos pabaigą su jos `result`.
  - name: gifs
    x-displayName: Animuoti GIF
    description: |-
      Partija kaip animuotas GIF, kurį galima išsaugoti ar kuriuo galima pasidalyti: lenta iš viršaus, po vieną kadrą kiekvienai pozicijai nuo pradinės iki galutinės, žaidėjų vardai ir reitingai virš lentos, paskutinis ėjimas po ja, o paskutiniame kadre – rezultatas ir kaip baigėsi partija.

      **Kaina, talpykla ir kvotos.** GIF generuojamas atskiroje generavimo gijoje, niekada ne gijose, kurios vykdo partijas, ir žemiausiu CPU prioritetu: `GIF_THREADS` gijų (pagal numatytuosius nustatymus `WORKERS`), paleidžiamų su pirmuoju GIF ir sustabdomų po minutės be GIF. Iki `GIF_QUEUE_MAX` (4 x `WORKERS`) GIF laukia gijos, kiekvienas ne ilgiau kaip `GIF_QUEUE_TIMEOUT_MS` (10 s); generavimas gali trukti `GIF_RENDER_TIMEOUT_MS` (30 s). Sugeneruotus GIF serveris laiko `GIF_CACHE_MB` MB talpykloje (32 x `WORKERS`), pirmiausia šalindamas seniausiai naudotus; GIF iš talpyklos arba tas, kuris jau generuojamas kitai užklausai, generavimo nekainuoja. Talpyklos raktą sudaro viskas, kas keičia vaizdą, įskaitant vardus.

      Kiekviena užklausa įskaičiuojama į `gif` (30 per minutę vienam žaidėjui abiem galiniams punktams). GIF, kurį reikia sugeneruoti, taip pat įskaičiuojamas į generavimo ribas: vienam žaidėjui `GIF_USER_RENDERS_PER_MIN` (4) per minutę ir `GIF_USER_RENDERS_PER_HOUR` (30) per valandą; vienam klientui, visoms jo paskyroms kartu, `GIF_IP_RENDERS_PER_MIN` (12) per minutę ir `GIF_IP_RENDERS_PER_HOUR` (120) per valandą, o IPv6 /48 tinklui – 3 kartus daugiau. Klientas turėtų išsaugoti atsisiųstą failą, užuot prašęs jo dar kartą, ir po 429 ar 503 palaukti `retryAfter`.

      Dydžiai: `small` (32 px langeliai: 284 x 350 pikselių), `medium` (48 px: 424 x 515), `large` (72 px: 628 x 762); be koordinačių – 268 x 342, 400 x 503 ir 600 x 748. Pradinė pozicija rodoma bent 1 s (arba nurodytą delsą, jei ji ilgesnė), galutinė – 3 s, o GIF kartojamas ciklu; po pirmojo kadro saugoma tik besikeičianti vaizdo dalis. 40 ėjimų partija užima apie 135, 205 ir 325 KiB (mažas, vidutinis, didelis), 150 ėjimų partija – 0,5; 0,8 ir 1,2 MiB.
  - name: players
    x-displayName: Žaidėjai
    description: Vieši profiliai ir naujausios partijos. Tik vieši duomenys, niekada el. pašto adresas, seansas, sankcija ar sąžiningumo lygis. Ištrinta paskyra profilio neturi.
  - name: leaderboard
    x-displayName: Lyderių lentelė
    description: Geriausi kiekvienos oficialios kategorijos žaidėjai.
  - name: reports
    x-displayName: Pranešimai
    description: Pranešimas moderatoriams apie neseniai žaistos partijos varžovą.
  - name: pages
    x-displayName: HTML puslapiai
    description: |-
      Naršyklei skirti puslapiai, atidaromi iš el. laiškų nuorodų. Jų nuorodos veda į `https://<SERVER_PUBLIC_HOST>` (su `:<PUBLIC_API_PORT>`, kai jis nėra 443), už `/api/v1` ribų.

      - Puslapiai nevykdo jokio JavaScript ir neįkelia jokių išorinių išteklių. Jie pateikiami su `Content-Security-Policy: default-src 'none'; style-src 'unsafe-inline'; form-action 'self'; frame-ancestors 'none'; base-uri 'none'`.
      - `GET` tik parodo mygtuką arba formą, kad nuorodą atidarantis el. pašto skeneris jos nesunaudotų. Pakeitimas įvyksta per `POST`: forma, siunčiama kaip `application/x-www-form-urlencoded` (priimamas ir JSON turinys), su nuorodos žetonu paslėptame lauke. Du kartus pateiktas laukas atmetamas.
      - Klaidos (užklausų dažnio ribos, netinkami laukai) taip pat yra HTML puslapiai, pavadinti „Request refused“ (užklausa atmesta) arba, nuo 500, „Server error“ (serverio klaida). JSON formatu atsakoma tik į klaidas, aptiktas prieš nustatant puslapį (per ilgas ar netinkamo formato užklausos tikslas, adresų lygmuo).
x-tagGroups:
  - name: server
    x-displayName: Serveris
    tags:
      - server-info
      - health
  - name: accounts
    x-displayName: Paskyros
    tags:
      - auth
      - google-sign-in
      - sessions
      - account
      - two-step-verification
      - email-change
      - data-export
      - account-deletion
  - name: games
    x-displayName: Partijos
    tags:
      - game-history
      - games
      - gifs
  - name: community
    x-displayName: Žaidėjai ir pranešimai
    tags:
      - players
      - leaderboard
      - reports
  - name: browser-pages
    x-displayName: Naršyklės puslapiai
    tags:
      - pages
paths:
  /info:
    get:
      operationId: getServerInfo
      tags:
        - server-info
      summary: Gauti serverio pavadinimą, versijas, prievadus ir registracijos taisykles
      description: |-
        Ko klientui reikia prieš prisijungiant ar užmezgant ryšį: serverio pavadinimas ir ID, WebSocket protokolo versijos ir kur yra WebSocket, registracijos taisyklės, oficialios laiko kontrolės ir ribos, kurias klientas gali patikrinti prieš siųsdamas formą.

        **Ribos:** tik adresų lygmuo.
      security: []
      responses:
        '200':
          description: Serverio aprašas.
          headers:
            Cache-Control:
              $ref: '#/components/headers/CacheControl'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ServerInfo'
        '414':
          $ref: '#/components/responses/UriTooLong'
        '429':
          $ref: '#/components/responses/TooManyRequests'
          description: '`rate_limited`: adresų lygmuo.'
        '500':
          $ref: '#/components/responses/InternalError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
          description: '`timeout`. Taip pat `server_busy` su `retryAfter` nuo 2 iki 5, kai serveris labai atsilieka (žr. įvadą): užklausa nebuvo vykdyta.'
  /auth/register:
    post:
      operationId: registerAccount
      tags:
        - auth
      summary: Sukurti paskyrą
      description: |-
        Sukuria paskyrą: kai jos el. pašto adresas patvirtinamas juo išsiųsta nuoroda, arba iš karto, jei el. pašto patvirtinimas nenaudojamas.

        **Su el. pašto patvirtinimu** (`REQUIRE_EMAIL_VERIFICATION`, numatytasis variantas): 202 `verification_sent`. Paskyra dar nesukurta: registracija laukia 24 valandas ir tuo metu rezervuoja savo naudotojo vardą. Šiuo adresu siunčiama 24 valandas galiojanti nuoroda, ne dažniau kaip viena vienam adresui kas 5 minutes (`POST /auth/verify-email/resend` išsiunčia naują); paskyra sukuriama, o jos adresas patvirtinamas, kai nuoroda panaudojama (puslapio `/verify-email` mygtuku), ir tada žaidėjas gali prisijungti. Iki tol į prisijungimą tuo naudotojo vardu atsakoma `invalid_credentials`, kaip ir nežinomos paskyros atveju, o viešojo profilio nėra. Nauja registracija tuo pačiu adresu pakeičia laukiančiąją. Atsakymas toks pat ir tada, kai adresą jau naudoja kita paskyra: tada nuoroda nesiunčiama, o tos paskyros savininkas vietoj jos gauna pranešimą (ne dažniau kaip kartą per valandą), ir naudotojo vardas rezervuojamas taip pat, kad niekas neišduotų, ar adresu užregistruota paskyra. Registracija, kurios nuoroda nepanaudota, po 24 valandų panaikinama, ir jos naudotojo vardas vėl tampa laisvas.

        **Be el. pašto patvirtinimo** (`REQUIRE_EMAIL_VERIFICATION=false`): 201 `ready`, paskyra sukuriama iš karto ir gali prisijungti.

        **Taisyklės.** `username`: nuo `USERNAME_MIN` iki `USERNAME_MAX` simbolių (nuo 3 iki 20), raidės, skaitmenys, `_` ir `-`, pirmasis simbolis – raidė arba skaitmuo (`GET /info` juos pateikia kaip `limits`); rezervuoti vardai (`admin`, `moderator`, `deleted`...) ir kai kurie priešdėliai atmetami; unikalus neatsižvelgiant į raidžių dydį. `email`: pradžioje ir pabaigoje pašalinami tarpai, saugomas mažosiomis raidėmis, tik ASCII simboliai, domene turi būti taškas. `password`: bent `PASSWORD_MIN_LENGTH` (10) simbolių ir ne daugiau kaip 256 UTF-8 baitai; jame negali būti naudotojo vardo ar el. pašto adreso vietinės dalies, ir jis negali būti dažnai naudojamas slaptažodis.

        **Klaidos**, tikrinamos tokia tvarka: 403 `registration_closed`; 400 `invalid_username`; 400 `invalid_email`; 400 `weak_password`; 409 `username_taken`; 428 `pow_required`; slaptažodžių maišos eilės klaidos; 409 `email_taken` (tik be el. pašto patvirtinimo).

        **Ribos:** `auth`, tada `auth_register` (10 registracijų per valandą vienam klientui). **Darbo įrodymas:** visada, kai `POW_REGISTER_BITS` didesnis už 0.
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RegisterRequest'
            examples:
              first:
                summary: Pirmasis bandymas, be darbo įrodymo
                value:
                  username: alice
                  email: alice@example.org
                  password: correct horse battery
              withPow:
                summary: Išsiųsta dar kartą su darbo įrodymu
                value:
                  username: alice
                  email: alice@example.org
                  password: correct horse battery
                  pow:
                    challenge: eyJ2IjoxLCJlIjoicmVnaXN0ZXIiLCJiIjoxOCwieCI6MTc5MDg4Mjk5MTIwMH0.6Ku_D7kw3S69OYxj-9KxsXszRCwz6ECnOQ-B6-v2erM
                    nonce: '123456'
      responses:
        '201':
          description: Paskyra sukurta ir gali prisijungti (šiame serveryje el. pašto patvirtinimo nėra).
          headers:
            Cache-Control:
              $ref: '#/components/headers/CacheControl'
          content:
            application/json:
              schema:
                type: object
                required:
                  - status
                properties:
                  status:
                    const: ready
              example:
                status: ready
        '202':
          description: Registracija laukia patvirtinimo nuorodos (arba šiuo adresu jau užregistruota paskyra; atsakymas to neatskleidžia).
          headers:
            Cache-Control:
              $ref: '#/components/headers/CacheControl'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/VerificationSentStatus'
              example:
                status: verification_sent
        '400':
          $ref: '#/components/responses/BadRequest'
          description: '`invalid_request` (su `field`), `invalid_json`, `invalid_username`, `invalid_email` arba `weak_password` su `reason`: `too_short`, `too_long`, `contains_username`, `contains_email` arba `too_common`.'
        '403':
          $ref: '#/components/responses/Forbidden'
          description: '`registration_closed`: šiame serveryje registracija uždaryta (`GET /info` nurodo `registration: closed`).'
        '408':
          $ref: '#/components/responses/RequestTimeout'
        '409':
          $ref: '#/components/responses/Conflict'
          description: '`username_taken`: šis naudotojo vardas priklauso paskyrai arba jį rezervavo kito adreso laukianti registracija. `email_taken`: adresą naudoja kita paskyra (tik be el. pašto patvirtinimo).'
        '413':
          $ref: '#/components/responses/PayloadTooLarge'
        '414':
          $ref: '#/components/responses/UriTooLong'
        '415':
          $ref: '#/components/responses/UnsupportedMediaType'
        '428':
          $ref: '#/components/responses/PowRequired'
        '429':
          $ref: '#/components/responses/TooManyRequests'
          description: '`rate_limited`: riba `auth` arba `auth_register` arba slaptažodžių maišos eilė.'
        '500':
          $ref: '#/components/responses/InternalError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
          description: '`server_busy` (slaptažodžių maišos eilė arba duomenų bazė liko užrakinta), `timeout`. Taip pat `server_busy` su `retryAfter` nuo 2 iki 5, kai serveris labai atsilieka (žr. įvadą): užklausa nebuvo vykdyta.'
  /auth/login:
    post:
      operationId: logIn
      tags:
        - auth
      summary: Prisijungti slaptažodžiu
      description: |-
        Prisijungiama naudotojo vardu arba el. pašto adresu ir slaptažodžiu. Galimi du atsakymai, abu su būsena 200: seansas arba, kai įjungtas dviejų veiksnių autentifikavimas, antrasis žingsnis, kurį reikia užbaigti per 5 minutes su `POST /auth/login/mfa`.

        Nežinoma paskyra, neteisingas slaptažodis ir paskyra be slaptažodžio gauna tą patį atsakymą po tiek pat laiko: 401 `invalid_credentials`. Paskyros patikros (`banned`, `email_unverified`) atliekamos tik po teisingo slaptažodžio. Nuo `AUTH_FAILURES_PER_ACCOUNT` (5) nesėkmių vienu prisijungimo vardu kiekvienas bandymas turi palaukti (429 `too_many_attempts`).

        **Riba:** `auth`. **Darbo įrodymas:** tik nesėkmingų prisijungimų bangos metu (428 `pow_required`).
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/LoginRequest'
            example:
              login: alice
              password: correct horse battery
              clientLabel: Scacelith 1.4 (Windows)
      responses:
        '200':
          description: Seansas arba prisijungimo su dviejų veiksnių autentifikavimu antrasis žingsnis.
          headers:
            Cache-Control:
              $ref: '#/components/headers/CacheControl'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LoginAnswer'
              examples:
                session:
                  summary: Prisijungta
                  value:
                    token: sct_L_8GDd7uzfQ3QQWtqrsWXDTsFWzRwIvJcwIGHhjWPS8
                    expiresAt: 1798658839708
                    user:
                      id: 1
                      username: alice
                      email: alice@example.org
                      emailVerified: true
                      mfaEnabled: false
                      googleLinked: false
                      hasPassword: true
                      acceptChallenges: all
                      createdAt: 1790882839743
                      lastLoginAt: 1790882839708
                      pendingEmail: null
                mfaRequired:
                  summary: Dviejų veiksnių autentifikavimas įjungtas
                  value:
                    mfaRequired: true
                    mfaToken: mfa_m4wXAVhYCMWG0PX7MLcIpBeccBFg0oxPvoRujzGWjO8
                    expiresIn: 300
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
          description: '`invalid_credentials`: nežinoma paskyra, neteisingas slaptažodis arba paskyra be slaptažodžio.'
        '403':
          $ref: '#/components/responses/Forbidden'
          description: 'Tik po teisingo slaptažodžio: `banned` su `until` (Unix laikas, ms; nuolatinio sustabdymo atveju – `null`) arba `email_unverified` (senesnė paskyra, kurios adresas dar nepatvirtintas).'
        '408':
          $ref: '#/components/responses/RequestTimeout'
        '413':
          $ref: '#/components/responses/PayloadTooLarge'
        '414':
          $ref: '#/components/responses/UriTooLong'
        '415':
          $ref: '#/components/responses/UnsupportedMediaType'
        '428':
          $ref: '#/components/responses/PowRequired'
        '429':
          $ref: '#/components/responses/TooManyRequests'
          description: '`too_many_attempts` (šio prisijungimo vardo delsa po nesėkmių) arba `rate_limited` (riba `auth`, slaptažodžių maišos eilė).'
        '500':
          $ref: '#/components/responses/InternalError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
          description: '`server_busy` (slaptažodžių maišos eilė arba duomenų bazė liko užrakinta), `timeout`. Taip pat `server_busy` su `retryAfter` nuo 2 iki 5, kai serveris labai atsilieka (žr. įvadą): užklausa nebuvo vykdyta.'
  /auth/login/mfa:
    post:
      operationId: logInWithSecondFactor
      tags:
        - auth
      summary: Užbaigti prisijungimą antruoju veiksniu
      description: |-
        Prisijungimo su dviejų veiksnių autentifikavimu antrasis žingsnis po to, kai `POST /auth/login` arba prisijungimas su Google (`POST /auth/sso/google/finish` ar `POST /auth/sso/google/link`) atsakė `mfaRequired`. Siųskite `code` (6 skaitmenų autentifikavimo programėlės kodą arba atkūrimo kodą) arba `recoveryCode`. Čia panaudotas atkūrimo kodas išnyksta.

        Vienas žingsnis priima ne daugiau kaip 5 neteisingus kodus; žingsnis taip pat baigiasi, kai slaptažodis nustatomas iš naujo arba pakeičiamas. Po Google susiejimo slaptažodžio žingsnio susiejimas išsaugomas tik tada, kai kodas priimamas čia.

        **Ribos:** `auth` ir ne daugiau kaip `AUTH_MFA_PER_ACCOUNT` (10) kodų per 15 minučių paskyrai iš bet kurio adreso; viršijus kodas netikrinamas, todėl atkūrimo kodas nesunaudojamas.
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MfaLoginRequest'
            examples:
              authenticator:
                summary: Autentifikavimo programėlės kodas
                value:
                  mfaToken: mfa_m4wXAVhYCMWG0PX7MLcIpBeccBFg0oxPvoRujzGWjO8
                  code: '123456'
              recovery:
                summary: Atkūrimo kodas
                value:
                  mfaToken: mfa_m4wXAVhYCMWG0PX7MLcIpBeccBFg0oxPvoRujzGWjO8
                  recoveryCode: j7v5-3ezx-zn
      responses:
        '200':
          description: Prisijungta.
          headers:
            Cache-Control:
              $ref: '#/components/headers/CacheControl'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SessionAnswer'
        '400':
          $ref: '#/components/responses/BadRequest'
          description: '`invalid_request`: neišsiųstas nei `code`, nei `recoveryCode` arba turinys pažeidžia schemą; `invalid_json`.'
        '401':
          $ref: '#/components/responses/Unauthorized'
          description: '`invalid_mfa_token`: žingsnio galiojimas baigėsi, jis jau panaudotas arba baigėsi po 5 neteisingų kodų, arba nuo pirmojo žingsnio slaptažodis buvo nustatytas iš naujo ar pakeistas (prisijunkite iš naujo). `invalid_code`: neteisingas arba jau panaudotas kodas.'
        '403':
          $ref: '#/components/responses/Forbidden'
          description: '`banned` (su `until`), `email_unverified`.'
        '408':
          $ref: '#/components/responses/RequestTimeout'
        '409':
          $ref: '#/components/responses/Conflict'
          description: '`sso_already_linked`: tik po Google susiejimo slaptažodžio žingsnio – Google paskyra tuo metu buvo susieta su kita paskyra.'
        '410':
          $ref: '#/components/responses/Gone'
          description: '`sso_expired`: tik po Google susiejimo slaptažodžio žingsnio – paskyra tuo metu pasikeitė; pradėkite prisijungimą iš naujo.'
        '413':
          $ref: '#/components/responses/PayloadTooLarge'
        '414':
          $ref: '#/components/responses/UriTooLong'
        '415':
          $ref: '#/components/responses/UnsupportedMediaType'
        '429':
          $ref: '#/components/responses/TooManyRequests'
          description: '`too_many_attempts`: paskyros delsa po nesėkmių arba išnaudoti jos `AUTH_MFA_PER_ACCOUNT` paskutinių 15 minučių kodai. `rate_limited`: riba `auth`.'
        '500':
          $ref: '#/components/responses/InternalError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
          description: '`server_busy` su `retryAfter: 1`: duomenų bazė liko užrakinta (po Google susiejimo žingsnio niekas nebuvo susieta: pradėkite prisijungimą iš naujo). `timeout`. Taip pat `server_busy` su `retryAfter` nuo 2 iki 5, kai serveris labai atsilieka (žr. įvadą): užklausa nebuvo vykdyta.'
  /auth/logout:
    post:
      operationId: logOut
      tags:
        - sessions
      summary: Atjungti šį seansą
      description: |-
        Atšaukia naudoto žetono seansą. Su juo atidarytas WebSocket ryšys uždaromas. Be turinio (arba `{}`).

        **Riba:** `sessions`.
      security:
        - bearerAuth: []
      responses:
        '200':
          description: Atsijungta.
          headers:
            Cache-Control:
              $ref: '#/components/headers/CacheControl'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LoggedOutStatus'
              example:
                status: logged_out
        '400':
          $ref: '#/components/responses/BadRequest'
          description: '`invalid_request`: kitoks nei `{}` turinys; `invalid_json`.'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '408':
          $ref: '#/components/responses/RequestTimeout'
        '413':
          $ref: '#/components/responses/PayloadTooLarge'
        '414':
          $ref: '#/components/responses/UriTooLong'
        '415':
          $ref: '#/components/responses/UnsupportedMediaType'
        '429':
          $ref: '#/components/responses/TooManyRequests'
          description: '`rate_limited`: riba `sessions` arba paskyros biudžetas.'
        '500':
          $ref: '#/components/responses/InternalError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
          description: '`server_busy` (duomenų bazė liko užrakinta), `timeout`. Taip pat `server_busy` su `retryAfter` nuo 2 iki 5, kai serveris labai atsilieka (žr. įvadą): užklausa nebuvo vykdyta.'
  /auth/logout-all:
    post:
      operationId: logOutEverywhere
      tags:
        - sessions
      summary: Atjungti visus seansus
      description: |-
        Atšaukia visus paskyros seansus, įskaitant šį. Su jais atidaryti WebSocket ryšiai uždaromi. Be turinio (arba `{}`).

        **Riba:** `sessions`.
      security:
        - bearerAuth: []
      responses:
        '200':
          description: Visi seansai atjungti.
          headers:
            Cache-Control:
              $ref: '#/components/headers/CacheControl'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LoggedOutStatus'
              example:
                status: logged_out
        '400':
          $ref: '#/components/responses/BadRequest'
          description: '`invalid_request`: kitoks nei `{}` turinys; `invalid_json`.'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '408':
          $ref: '#/components/responses/RequestTimeout'
        '413':
          $ref: '#/components/responses/PayloadTooLarge'
        '414':
          $ref: '#/components/responses/UriTooLong'
        '415':
          $ref: '#/components/responses/UnsupportedMediaType'
        '429':
          $ref: '#/components/responses/TooManyRequests'
          description: '`rate_limited`: riba `sessions` arba paskyros biudžetas.'
        '500':
          $ref: '#/components/responses/InternalError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
          description: '`server_busy` (duomenų bazė liko užrakinta), `timeout`. Taip pat `server_busy` su `retryAfter` nuo 2 iki 5, kai serveris labai atsilieka (žr. įvadą): užklausa nebuvo vykdyta.'
  /auth/verify-email/resend:
    post:
      operationId: resendVerificationEmail
      tags:
        - auth
      summary: Dar kartą išsiųsti patvirtinimo nuorodą
      description: |-
        Dar kartą išsiunčia el. pašto patvirtinimo nuorodą. Atsakymas yra 202 `accepted`, kad ir koks būtų adresas, todėl jis niekada neatskleidžia, ar adresą naudoja paskyra ar registracija.

        Užklausa suveikia ne dažniau kaip kartą per 5 minutes vienam adresui. Šiuo adresu laukianti registracija vėl gauna visas 24 valandas, nepriklausomai nuo to, ar adresą naudoja kita paskyra, kad jos naudotojo vardas abiem atvejais liktų rezervuotas tiek pat laiko. Nuoroda siunčiama tik tai registracijai, kai adresu neužregistruota jokia paskyra (nauja, 24 valandas galiojanti nuoroda pakeičia ankstesnę), arba aktyviai, nepatvirtintai paskyrai su šiuo adresu.

        **Ribos:** `auth`, tada `auth_mail` (10 per valandą vienam klientui).
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/EmailRequest'
            example:
              email: alice@example.org
      responses:
        '202':
          description: Priimta (kad ir koks būtų adresas).
          headers:
            Cache-Control:
              $ref: '#/components/headers/CacheControl'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AcceptedStatus'
              example:
                status: accepted
        '400':
          $ref: '#/components/responses/BadRequest'
        '408':
          $ref: '#/components/responses/RequestTimeout'
        '413':
          $ref: '#/components/responses/PayloadTooLarge'
        '414':
          $ref: '#/components/responses/UriTooLong'
        '415':
          $ref: '#/components/responses/UnsupportedMediaType'
        '429':
          $ref: '#/components/responses/TooManyRequests'
          description: '`rate_limited`: riba `auth` arba `auth_mail` (kad ir koks būtų adresas).'
        '500':
          $ref: '#/components/responses/InternalError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
          description: '`server_busy` su `retryAfter: 1`: duomenų bazė liko užrakinta ieškant paskyros arba išsaugant nepatvirtintos paskyros nuorodą (laukiančios registracijos pratęsimas atliekamas tik esant galimybei ir niekada nesukelia užklausos nesėkmės); adreso 5 minutės vis tiek prasideda, todėl nauja užklausa per jas nieko nepadaro. `timeout`. Taip pat `server_busy` su `retryAfter` nuo 2 iki 5, kai serveris labai atsilieka (žr. įvadą): užklausa nebuvo vykdyta.'
  /auth/password/forgot:
    post:
      operationId: requestPasswordReset
      tags:
        - auth
      summary: Išsiųsti slaptažodžio nustatymo iš naujo nuorodą
      description: |-
        Išsiunčia vieną valandą galiojančią slaptažodžio nustatymo iš naujo nuorodą, kuri atidaro puslapį `/reset-password`. Atsakymas yra 202 `accepted`, kad ir koks būtų adresas. Nuoroda siunčiama tik aktyviai paskyrai, ne dažniau kaip kartą per 5 minutes vienam adresui. Paskyra, prie kurios jungiamasi tik su Google, taip nusistato pirmąjį slaptažodį.

        Slaptažodžio atkūrimui taikomos griežčiausios API ribos, visos skaičiuojamos visam serveriui: 3 užklausos per valandą (`AUTH_FORGOT_PER_HOUR`) ir 10 per 24 valandas (`AUTH_FORGOT_PER_DAY`) vienam klientui (IPv4 adresui arba IPv6 /64), IPv6 /48 tinklui – 3 kartus daugiau, be to, dar taikoma riba `auth` (20 per 10 minučių) ir vienas laiškas vienam adresui kas 5 minutes. Nei 202, nei 429 neatskleidžia, ar adresą naudoja paskyra. Naujo slaptažodžio nustatymas turi savo ribą (`auth_reset`).

        **Ribos:** `auth`, `auth_forgot`, tada `auth_forgot_day`.
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/EmailRequest'
            example:
              email: alice@example.org
      responses:
        '202':
          description: Priimta (kad ir koks būtų adresas).
          headers:
            Cache-Control:
              $ref: '#/components/headers/CacheControl'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AcceptedStatus'
              example:
                status: accepted
        '400':
          $ref: '#/components/responses/BadRequest'
        '408':
          $ref: '#/components/responses/RequestTimeout'
        '413':
          $ref: '#/components/responses/PayloadTooLarge'
        '414':
          $ref: '#/components/responses/UriTooLong'
        '415':
          $ref: '#/components/responses/UnsupportedMediaType'
        '429':
          $ref: '#/components/responses/TooManyRequests'
          description: '`rate_limited`: riba `auth`, `auth_forgot` arba `auth_forgot_day` (kad ir koks būtų adresas).'
        '500':
          $ref: '#/components/responses/InternalError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
          description: '`server_busy` su `retryAfter: 1`: duomenų bazė liko užrakinta ieškant paskyros arba išsaugant nuorodą; adreso 5 minutės vis tiek prasideda, todėl nauja užklausa per jas nuorodos neišsiunčia. `timeout`. Taip pat `server_busy` su `retryAfter` nuo 2 iki 5, kai serveris labai atsilieka (žr. įvadą): užklausa nebuvo vykdyta.'
  /auth/password/reset:
    post:
      operationId: resetPassword
      tags:
        - auth
      summary: Nustatyti naują slaptažodį su slaptažodžio nustatymo iš naujo nuorodos žetonu
      description: |-
        Nustato naują slaptažodį su slaptažodžio nustatymo iš naujo nuorodos žetonu (tą patį daro puslapis `/reset-password`). Naujam slaptažodžiui taikomos registracijos taisyklės.

        Nustatymas iš naujo turi šiuos padarinius vienoje transakcijoje (visi arba nė vieno): atšaukiami visi seansai ir laukiantis el. pašto keitimas; kitos paskyros slaptažodžio nustatymo iš naujo nuorodos nustoja veikti; adresas laikomas patvirtintu (nuoroda tai įrodė); savininkas gauna laišką. Dviejų veiksnių autentifikavimas nekeičiamas. Po slaptažodžių maišos eilės klaidos, 503 ar 500 niekas nepasikeitė: senasis slaptažodis ir seansai lieka, o nuoroda lieka galiojanti.

        **Ribos:** `auth`, tada `auth_reset` (10 per valandą vienam klientui, 30 IPv6 /48 tinklui, bendra su puslapiu: kiekvienas bandymas skaičiuoja slaptažodžio maišą).
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PasswordResetRequest'
            example:
              token: A0ATfmXCDKFC8UkSxLJCD85K7au5nMIrZ2P3K-1VWQg
              newPassword: a much better passphrase
      responses:
        '200':
          description: Slaptažodis pakeistas, visi seansai atjungti.
          headers:
            Cache-Control:
              $ref: '#/components/headers/CacheControl'
          content:
            application/json:
              schema:
                type: object
                required:
                  - status
                properties:
                  status:
                    const: password_reset
              example:
                status: password_reset
        '400':
          $ref: '#/components/responses/BadRequest'
          description: '`invalid_token`: nuoroda netinkama, panaudota arba nebegalioja, arba buvo išsiųsta adresu, kurio paskyra nebeturi. `weak_password` (su `reason`). `invalid_request`, `invalid_json`.'
        '408':
          $ref: '#/components/responses/RequestTimeout'
        '413':
          $ref: '#/components/responses/PayloadTooLarge'
        '414':
          $ref: '#/components/responses/UriTooLong'
        '415':
          $ref: '#/components/responses/UnsupportedMediaType'
        '429':
          $ref: '#/components/responses/TooManyRequests'
          description: '`rate_limited`: riba `auth` arba `auth_reset` arba slaptažodžių maišos eilė.'
        '500':
          $ref: '#/components/responses/InternalError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
          description: '`server_busy`: slaptažodžių maišos eilė arba duomenų bazė liko užrakinta (`retryAfter: 1`, niekas nepasikeitė). `timeout`. Taip pat `server_busy` su `retryAfter` nuo 2 iki 5, kai serveris labai atsilieka (žr. įvadą): užklausa nebuvo vykdyta.'
  /auth/sso/google/start:
    post:
      operationId: startGoogleSignIn
      tags:
        - google-sign-in
      summary: Pradėti prisijungimą su Google
      description: |-
        Pradeda prisijungimo su Google bandymą PKCE iššūkiui ir žaidimo klausytuvo prievadui adresu `127.0.0.1`. Atsakyme pateikiamas Google URL, kurį reikia atidaryti sistemos naršyklėje, ir bandymo `state`; bandymas galioja 10 minučių.

        `authUrl` apima `client_id`, `redirect_uri` (sudarytą iš `redirectPort` ir serverio kilmės žymės), `response_type=code`, `scope=openid email profile`, `state`, `nonce`, `code_challenge` (paties serverio tikrintuvo, skirto Google, S256) su `code_challenge_method=S256` ir `prompt=select_account`. Žaidimas jį patikrina prieš atidarydamas (žr. skyriaus aprašą).

        **Riba:** `sso_start`.
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SsoStartRequest'
            example:
              codeChallenge: 6e7diXEYxG7OTYw7STfNOltEeLxAilPthC_txzaE0xA
              redirectPort: 51234
      responses:
        '200':
          description: Bandymas.
          headers:
            Cache-Control:
              $ref: '#/components/headers/CacheControl'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SsoStartAnswer'
              example:
                attemptId: sso_ogvVEL75n7q90NIFsh-4zXVt-izc3WRbghsd6HDOtNQ
                authUrl: https://accounts.google.com/o/oauth2/v2/auth?client_id=1234567890-abc.apps.googleusercontent.com&redirect_uri=http%3A%2F%2F127.0.0.1%3A51234%2Foauth2%2Fgoogle%2FIhcScoV7eDOzTEcSnqPUPt&response_type=code&scope=openid%20email%20profile&state=yBjjEhGgqHdeqhUAzyx9jE2IhQ56bec53iRA7RvKRzQ&nonce=gYIKKhwnz1AqWfFxOC3R-ZvCACw06q74fEMtGEOJQWU&code_challenge=ZfEI6iZjePTHbnbevupaD9O7GjBCzcL-GJu2XGOdFGc&code_challenge_method=S256&prompt=select_account
                state: yBjjEhGgqHdeqhUAzyx9jE2IhQ56bec53iRA7RvKRzQ
                expiresIn: 600
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
          description: '`sso_disabled`: šiame serveryje prisijungimas su Google nesiūlomas.'
        '408':
          $ref: '#/components/responses/RequestTimeout'
        '413':
          $ref: '#/components/responses/PayloadTooLarge'
        '414':
          $ref: '#/components/responses/UriTooLong'
        '415':
          $ref: '#/components/responses/UnsupportedMediaType'
        '429':
          $ref: '#/components/responses/TooManyRequests'
          description: '`rate_limited`: riba `sso_start`.'
        '500':
          $ref: '#/components/responses/InternalError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
          description: '`server_busy` (duomenų bazė liko užrakinta), `timeout`. Taip pat `server_busy` su `retryAfter` nuo 2 iki 5, kai serveris labai atsilieka (žr. įvadą): užklausa nebuvo vykdyta.'
  /auth/sso/google/web/start:
    post:
      operationId: startGoogleWebSignIn
      tags:
        - google-sign-in
      summary: Pradėti prisijungimą su Google svetainėje
      description: |-
        Pradeda svetainės prisijungimo su Google bandymą jos puslapio PKCE iššūkiui. Atsakymas toks pat kaip žaidimui: Google URL, į kurį reikia pereiti, ir bandymo `state`; bandymas galioja 10 minučių. Siūlomas, kai `GET /info` nurodo `sso.googleWeb: true`.

        `authUrl` apima svetainės `client_id` ir `redirect_uri=GOOGLE_WEB_REDIRECT_URI`, o kiti parametrai tokie patys kaip žaidimo atveju. Puslapis jį patikrina prieš pereidamas į jį (žr. **Svetainėje** skyriaus apraše).

        **Riba:** `sso_start`, bendra su žaidimo pradžia.
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SsoWebStartRequest'
            example:
              codeChallenge: 6e7diXEYxG7OTYw7STfNOltEeLxAilPthC_txzaE0xA
      responses:
        '200':
          description: Bandymas.
          headers:
            Cache-Control:
              $ref: '#/components/headers/CacheControl'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SsoStartAnswer'
              example:
                attemptId: sso_ogvVEL75n7q90NIFsh-4zXVt-izc3WRbghsd6HDOtNQ
                authUrl: https://accounts.google.com/o/oauth2/v2/auth?client_id=1234567890-web.apps.googleusercontent.com&redirect_uri=https%3A%2F%2Fscacelith.com%2Faccount%2Fgoogle%2F&response_type=code&scope=openid%20email%20profile&state=yBjjEhGgqHdeqhUAzyx9jE2IhQ56bec53iRA7RvKRzQ&nonce=gYIKKhwnz1AqWfFxOC3R-ZvCACw06q74fEMtGEOJQWU&code_challenge=ZfEI6iZjePTHbnbevupaD9O7GjBCzcL-GJu2XGOdFGc&code_challenge_method=S256&prompt=select_account
                state: yBjjEhGgqHdeqhUAzyx9jE2IhQ56bec53iRA7RvKRzQ
                expiresIn: 600
        '400':
          $ref: '#/components/responses/BadRequest'
          description: '`invalid_request`: trūksta `codeChallenge` arba jis netinkamo formato, arba pateiktas bet kuris kitas laukas (įskaitant `redirectPort`).'
        '404':
          $ref: '#/components/responses/NotFound'
          description: '`sso_disabled`: šiame serveryje prisijungimas su Google nesiūlomas arba nesiūlomas svetainėms.'
        '408':
          $ref: '#/components/responses/RequestTimeout'
        '413':
          $ref: '#/components/responses/PayloadTooLarge'
        '414':
          $ref: '#/components/responses/UriTooLong'
        '415':
          $ref: '#/components/responses/UnsupportedMediaType'
        '429':
          $ref: '#/components/responses/TooManyRequests'
          description: '`rate_limited`: riba `sso_start` (bendra su žaidimo pradžia).'
        '500':
          $ref: '#/components/responses/InternalError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
          description: '`server_busy` (duomenų bazė liko užrakinta), `timeout`. Taip pat `server_busy` su `retryAfter` nuo 2 iki 5, kai serveris labai atsilieka (žr. įvadą): užklausa nebuvo vykdyta.'
  /auth/sso/google/finish:
    post:
      operationId: finishGoogleSignIn
      tags:
        - google-sign-in
      summary: Perduoti Google atsakymą serveriui
      description: |-
        Perduoda serveriui `code` ir `state`, kuriuos Google atsiuntė žaidimo klausytuvui arba svetainės puslapiui, kartu su bandymo ID ir PKCE tikrintuvu. Bandymo ID be tikrintuvo nenaudingas, o kodas nenaudingas be paties serverio PKCE tikrintuvo ir kliento slaptojo rakto.

        Serveris patikrina bandymą (nežinomas, panaudotas ar nebegaliojantis: 410), tada tikrintuvą (neteisingas tikrintuvas palieka bandymą tinkamą naudoti), tada vieną kartą panaudoja bandymą, patikrina `state` ir `iss`, iškeičia kodą su Google ir patikrina ID žetoną. Atsakymas (200) yra vienas iš šių:

        - `{ token, expiresAt, user }`: prisijungta prie susietos paskyros;
        - `{ mfaRequired, mfaToken, expiresIn }`: tęskite su `POST /auth/login/mfa`;
        - `{ needsUsername, ssoTicket, suggestedUsername }`: nauja paskyra; per 10 minučių tęskite su `POST /auth/sso/complete`. `suggestedUsername` sudaromas iš Google vardo arba adreso ir yra `""`, kai niekas netinka;
        - `{ needsPassword, linkTicket, username, expiresIn }`: paskyra su šiuo adresu turi slaptažodį; tęskite su `POST /auth/sso/google/link`. Kol kas niekas nesusieta.

        **Riba:** `sso_finish`.
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SsoFinishRequest'
            example:
              attemptId: sso_ogvVEL75n7q90NIFsh-4zXVt-izc3WRbghsd6HDOtNQ
              codeVerifier: Sb5SaoX0ByHGjJ0XQkEqaaPaJYx2BId4ncTT8tLM6W8
              state: yBjjEhGgqHdeqhUAzyx9jE2IhQ56bec53iRA7RvKRzQ
              code: 4/0AVMBsJhR2x7cKq9vT1pLmN3oW8yZ5aB6dE7fG8hJ
              iss: https://accounts.google.com
              clientLabel: Scacelith 1.4 (Windows)
      responses:
        '200':
          description: Seansas, antrasis žingsnis arba kitas pirmojo prisijungimo su Google žingsnis.
          headers:
            Cache-Control:
              $ref: '#/components/headers/CacheControl'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SsoFinishAnswer'
              examples:
                session:
                  summary: Prisijungta prie susietos paskyros
                  value:
                    token: sct_L_8GDd7uzfQ3QQWtqrsWXDTsFWzRwIvJcwIGHhjWPS8
                    expiresAt: 1798658839708
                    user:
                      id: 1
                      username: alice
                      email: alice@example.org
                      emailVerified: true
                      mfaEnabled: false
                      googleLinked: true
                      hasPassword: false
                      acceptChallenges: all
                      createdAt: 1790882839743
                      lastLoginAt: 1790882839708
                      pendingEmail: null
                mfaRequired:
                  summary: Dviejų veiksnių autentifikavimas įjungtas
                  value:
                    mfaRequired: true
                    mfaToken: mfa_m4wXAVhYCMWG0PX7MLcIpBeccBFg0oxPvoRujzGWjO8
                    expiresIn: 300
                needsUsername:
                  summary: Naujas žaidėjas
                  value:
                    needsUsername: true
                    ssoTicket: sso_gYIKKhwnz1AqWfFxOC3R-ZvCACw06q74fEMtGEOJQWU
                    suggestedUsername: alice
                needsPassword:
                  summary: Adresą naudoja paskyra su slaptažodžiu
                  value:
                    needsPassword: true
                    linkTicket: sso_ZfEI6iZjePTHbnbevupaD9O7GjBCzcL-GJu2XGOdFGc
                    username: alice
                    expiresIn: 600
        '400':
          $ref: '#/components/responses/BadRequest'
        '403':
          $ref: '#/components/responses/Forbidden'
          description: '`invalid_verifier`: tikrintuvas neatitinka bandymo iššūkio (bandymas lieka tinkamas naudoti). `sso_email_unverified`: Google nepatvirtino adreso. `registration_closed`. `account_disabled`. Tik susietai paskyrai: `banned` (su `until`), `email_unverified`.'
        '404':
          $ref: '#/components/responses/NotFound'
          description: '`sso_disabled`: šiame serveryje prisijungimas su Google nesiūlomas.'
        '408':
          $ref: '#/components/responses/RequestTimeout'
        '409':
          $ref: '#/components/responses/Conflict'
          description: '`sso_account_exists`: adresą naudoja aktyvi paskyra be slaptažodžio (ji neįvardijama).'
        '410':
          $ref: '#/components/responses/Gone'
          description: '`sso_expired`: nežinomas, panaudotas arba nebegaliojantis bandymas.'
        '413':
          $ref: '#/components/responses/PayloadTooLarge'
        '414':
          $ref: '#/components/responses/UriTooLong'
        '415':
          $ref: '#/components/responses/UnsupportedMediaType'
        '429':
          $ref: '#/components/responses/TooManyRequests'
          description: '`rate_limited`: riba `sso_finish`.'
        '500':
          $ref: '#/components/responses/InternalError'
        '502':
          $ref: '#/components/responses/BadGateway'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
          description: '`server_busy` (duomenų bazė liko užrakinta), `timeout`. Taip pat `server_busy` su `retryAfter` nuo 2 iki 5, kai serveris labai atsilieka (žr. įvadą): užklausa nebuvo vykdyta.'
  /auth/sso/google/link:
    post:
      operationId: linkGoogleAccount
      tags:
        - google-sign-in
      summary: Susieti Google su esama paskyra jos slaptažodžiu
      description: |-
        Po to, kai `finish` atsakė `needsPassword`: susieja Google su esama paskyra naudojant tos paskyros slaptažodį, įvestą žaidime. Atsakymas (200) yra seansas (susiejimas išsaugotas ir prisijungta) arba, kai įjungtas dviejų veiksnių autentifikavimas, `{ mfaRequired, mfaToken, expiresIn }`: tęskite su `POST /auth/login/mfa`, o susiejimas išsaugomas tik tada, kai ten priimamas kodas.

        Neteisingas slaptažodis palieka bilietą tinkamą naudoti, iš viso 5 bandymams. Bandymas įskaičiuojamas prieš pat slaptažodžio patikrą: 429 `too_many_attempts` ar 428 `pow_required` bandymo neįskaičiuoja, o slaptažodžių maišos eilės atmetimas (503 `server_busy`, 429 `rate_limited`) bandymą jau įskaičiavo ir laikomas paskyros nesėkme. Delsa po nesėkmių naudoja tą patį skaitiklį kaip `POST /auth/login`.

        **Riba:** `auth` (su IPv6 /48 skaičiavimu). **Darbo įrodymas:** kaip ir `POST /auth/login`.
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SsoLinkRequest'
            example:
              linkTicket: sso_ZfEI6iZjePTHbnbevupaD9O7GjBCzcL-GJu2XGOdFGc
              password: correct horse battery
              clientLabel: Scacelith 1.4 (Windows)
      responses:
        '200':
          description: Seansas arba antrasis prisijungimo žingsnis.
          headers:
            Cache-Control:
              $ref: '#/components/headers/CacheControl'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LoginAnswer'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
          description: '`invalid_credentials`: neteisingas slaptažodis; bilietas lieka galioti, iš viso 5 bandymams.'
        '403':
          $ref: '#/components/responses/Forbidden'
          description: '`banned` (su `until`), tik po teisingo slaptažodžio.'
        '404':
          $ref: '#/components/responses/NotFound'
          description: '`sso_disabled`: šiame serveryje prisijungimas su Google nesiūlomas.'
        '408':
          $ref: '#/components/responses/RequestTimeout'
        '409':
          $ref: '#/components/responses/Conflict'
          description: '`sso_already_linked`: per tą laiką Google paskyra buvo susieta su kita paskyra.'
        '410':
          $ref: '#/components/responses/Gone'
          description: '`sso_expired`: nežinomas, panaudotas arba nebegaliojantis bilietas, penktas neteisingas slaptažodis, paskyra, kurios būsena ar adresas pasikeitė po `finish`, arba paskyra, kurios būsena, adresas, slaptažodis ar dviejų veiksnių autentifikavimas pasikeitė, kol susiejimas buvo įrašomas. Pradėkite prisijungimą iš naujo.'
        '413':
          $ref: '#/components/responses/PayloadTooLarge'
        '414':
          $ref: '#/components/responses/UriTooLong'
        '415':
          $ref: '#/components/responses/UnsupportedMediaType'
        '428':
          $ref: '#/components/responses/PowRequired'
        '429':
          $ref: '#/components/responses/TooManyRequests'
          description: '`too_many_attempts` (paskyros delsa po nesėkmių) arba `rate_limited` (riba `auth`, slaptažodžių maišos eilė).'
        '500':
          $ref: '#/components/responses/InternalError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
          description: '`server_busy`: slaptažodžių maišos eilė arba duomenų bazė liko užrakinta (`retryAfter: 1`, niekas nesusieta). `timeout`. Taip pat `server_busy` su `retryAfter` nuo 2 iki 5, kai serveris labai atsilieka (žr. įvadą): užklausa nebuvo vykdyta.'
  /auth/sso/complete:
    post:
      operationId: completeGoogleSignUp
      tags:
        - google-sign-in
      summary: Sukurti paskyrą po pirmojo prisijungimo su Google
      description: |-
        Kai `finish` atsakė `needsUsername`: sukuria paskyrą su pasirinktu naudotojo vardu (taikomos registracijos taisyklės) ir atidaro jos seansą. Paskyra neturi slaptažodžio: `hasPassword` yra `false`, o „Pamiršote slaptažodį?“ (`POST /auth/password/forgot`) leidžia jį nustatyti.

        **Riba:** `auth` (su jos skaičiavimu pagal IPv6 /48).
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SsoCompleteRequest'
            example:
              ssoTicket: sso_gYIKKhwnz1AqWfFxOC3R-ZvCACw06q74fEMtGEOJQWU
              username: alice
              clientLabel: Scacelith 1.4 (Windows)
      responses:
        '200':
          description: Paskyra sukurta, jos seansas atidarytas.
          headers:
            Cache-Control:
              $ref: '#/components/headers/CacheControl'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SessionAnswer'
        '400':
          $ref: '#/components/responses/BadRequest'
          description: '`invalid_username`, `invalid_request`, `invalid_json`.'
        '403':
          $ref: '#/components/responses/Forbidden'
          description: '`registration_closed`.'
        '404':
          $ref: '#/components/responses/NotFound'
          description: '`sso_disabled`: šiame serveryje prisijungimas su Google nesiūlomas.'
        '408':
          $ref: '#/components/responses/RequestTimeout'
        '409':
          $ref: '#/components/responses/Conflict'
          description: '`username_taken` (šį naudotojo vardą turi paskyra arba jį rezervavo laukianti kito adreso registracija), `sso_already_linked`, `email_taken`.'
        '410':
          $ref: '#/components/responses/Gone'
          description: '`sso_expired`: nežinomas, panaudotas arba nebegaliojantis bilietas.'
        '413':
          $ref: '#/components/responses/PayloadTooLarge'
        '414':
          $ref: '#/components/responses/UriTooLong'
        '415':
          $ref: '#/components/responses/UnsupportedMediaType'
        '429':
          $ref: '#/components/responses/TooManyRequests'
          description: '`rate_limited`: riba `auth`.'
        '500':
          $ref: '#/components/responses/InternalError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
          description: '`server_busy` (duomenų bazė liko užrakinta), `timeout`. Taip pat `server_busy` su `retryAfter` nuo 2 iki 5, kai serveris labai atsilieka (žr. įvadą): užklausa nebuvo vykdyta.'
  /auth/sessions:
    get:
      operationId: listSessions
      tags:
        - sessions
      summary: Gauti prisijungusių įrenginių sąrašą
      description: |-
        Aktyvūs paskyros seansai (prisijungę įrenginiai), pradedant paskiausiai naudotu. `current` žymi seansą, kuris siunčia užklausą. `lastSeenAt` atnaujinamas ne dažniau kaip kas 5 minutes (vėliau, kol serveris labai atsilieka); `expiresAt` yra galutinė pabaiga, o neveiklumo riba gali baigti seansą anksčiau.

        **Riba:** `sessions`.
      security:
        - bearerAuth: []
      responses:
        '200':
          description: Aktyvūs seansai.
          headers:
            Cache-Control:
              $ref: '#/components/headers/CacheControl'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SessionList'
              example:
                sessions:
                  - id: 3
                    createdAt: 1790882839708
                    lastSeenAt: 1790882839708
                    expiresAt: 1798658839708
                    clientLabel: Laptop
                    current: false
                  - id: 1
                    createdAt: 1790882839708
                    lastSeenAt: 1790882839708
                    expiresAt: 1798658839708
                    clientLabel: Scacelith 1.4 (Windows)
                    current: true
        '401':
          $ref: '#/components/responses/Unauthorized'
        '414':
          $ref: '#/components/responses/UriTooLong'
        '429':
          $ref: '#/components/responses/TooManyRequests'
          description: '`rate_limited`: riba `sessions` arba paskyros biudžetas.'
        '500':
          $ref: '#/components/responses/InternalError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
          description: '`server_busy` (duomenų bazė liko užrakinta), `timeout`. Taip pat `server_busy` su `retryAfter` nuo 2 iki 5, kai serveris labai atsilieka (žr. įvadą): užklausa nebuvo vykdyta.'
  /auth/sessions/{id}:
    delete:
      operationId: revokeSession
      tags:
        - sessions
      summary: Atjungti vieną įrenginį
      description: |-
        Atjungia vieną paskyros seansą; galima atjungti ir dabartinį. Su juo atidarytas WebSocket uždaromas. Be turinio (arba `{}`).

        **Riba:** `sessions`.
      security:
        - bearerAuth: []
      parameters:
        - $ref: '#/components/parameters/SessionId'
      responses:
        '200':
          description: Seansas atjungtas.
          headers:
            Cache-Control:
              $ref: '#/components/headers/CacheControl'
          content:
            application/json:
              schema:
                type: object
                required:
                  - status
                properties:
                  status:
                    const: revoked
              example:
                status: revoked
        '400':
          $ref: '#/components/responses/BadRequest'
          description: '`invalid_request`: turinys kitoks nei `{}` arba `id`, kuris nėra tinkamai URL užkoduotas; `invalid_json`.'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
          description: '`not_found`: šioje paskyroje nėra aktyvaus seanso su šiuo ID.'
        '408':
          $ref: '#/components/responses/RequestTimeout'
        '413':
          $ref: '#/components/responses/PayloadTooLarge'
        '414':
          $ref: '#/components/responses/UriTooLong'
        '415':
          $ref: '#/components/responses/UnsupportedMediaType'
        '429':
          $ref: '#/components/responses/TooManyRequests'
          description: '`rate_limited`: riba `sessions` arba paskyros biudžetas.'
        '500':
          $ref: '#/components/responses/InternalError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
          description: '`server_busy` (duomenų bazė liko užrakinta), `timeout`. Taip pat `server_busy` su `retryAfter` nuo 2 iki 5, kai serveris labai atsilieka (žr. įvadą): užklausa nebuvo vykdyta.'
  /account/me:
    get:
      operationId: getAccount
      tags:
        - account
      summary: Gauti paskyrą, jos reitingus ir aktyvias sankcijas
      description: |-
        Paskyra taip, kaip ją mato jos žaidėjas: paskyros rodinys (taip pat kiekvieno prisijungimo atsakymo `user`), po vieną reitingo įrašą kiekvienai kategorijai, kurioje žaidėjas žaidė reitingines partijas, aktyvios sankcijos ir sustabdymas. Apsaugos nuo sukčiavimo sistemos nustatytas sąžiningumo lygis niekada nerodomas.

        **Riba:** `account`.
      security:
        - bearerAuth: []
      responses:
        '200':
          description: Paskyra.
          headers:
            Cache-Control:
              $ref: '#/components/headers/CacheControl'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AccountMe'
              example:
                user:
                  id: 1
                  username: alice
                  email: alice@example.org
                  emailVerified: true
                  mfaEnabled: true
                  googleLinked: false
                  hasPassword: true
                  acceptChallenges: all
                  createdAt: 1790882871478
                  lastLoginAt: 1790882902200
                  pendingEmail: alice.new@example.org
                ratings:
                  - category: '3+2'
                    rating: 1510
                    games: 2
                    wins: 1
                    draws: 1
                    losses: 0
                    peak: 1510
                    provisional: true
                sanctions: []
                ban: null
        '401':
          $ref: '#/components/responses/Unauthorized'
          description: '`unauthorized` arba `invalid_token` (taip pat, kai paskyra ištrinta).'
        '414':
          $ref: '#/components/responses/UriTooLong'
        '429':
          $ref: '#/components/responses/TooManyRequests'
          description: '`rate_limited`: riba `account` arba paskyros biudžetas.'
        '500':
          $ref: '#/components/responses/InternalError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
          description: '`server_busy` (duomenų bazė liko užrakinta), `timeout`. Taip pat `server_busy` su `retryAfter` nuo 2 iki 5, kai serveris labai atsilieka (žr. įvadą): užklausa nebuvo vykdyta.'
  /account/preferences:
    put:
      operationId: updatePreferences
      tags:
        - account
      summary: Priimti arba atmesti tiesioginius iššūkius
      description: |-
        Nustato, ar kiti žaidėjai gali mesti šiam žaidėjui iššūkį pagal vardą. Su `none` tiesioginiai iššūkiai atmetami: iššūkio siuntėjui pranešama, kad žaidėjas nepasiekiamas. Pakartotinis autentifikavimas nereikalingas.

        **Riba:** `account`.
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/Preferences'
            example:
              acceptChallenges: none
      responses:
        '200':
          description: Dabar galiojančios nuostatos.
          headers:
            Cache-Control:
              $ref: '#/components/headers/CacheControl'
          content:
            application/json:
              schema:
                type: object
                required:
                  - preferences
                properties:
                  preferences:
                    $ref: '#/components/schemas/Preferences'
              example:
                preferences:
                  acceptChallenges: none
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '408':
          $ref: '#/components/responses/RequestTimeout'
        '413':
          $ref: '#/components/responses/PayloadTooLarge'
        '414':
          $ref: '#/components/responses/UriTooLong'
        '415':
          $ref: '#/components/responses/UnsupportedMediaType'
        '429':
          $ref: '#/components/responses/TooManyRequests'
          description: '`rate_limited`: riba `account` arba paskyros biudžetas.'
        '500':
          $ref: '#/components/responses/InternalError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
          description: '`server_busy` (duomenų bazė liko užrakinta), `timeout`. Taip pat `server_busy` su `retryAfter` nuo 2 iki 5, kai serveris labai atsilieka (žr. įvadą): užklausa nebuvo vykdyta.'
  /account/password:
    post:
      operationId: changePassword
      tags:
        - account
      summary: Pakeisti slaptažodį
      description: |-
        Pakeičia slaptažodį. Reikia dabartinio slaptažodžio, bet nereikia antrojo veiksnio, net kai įjungtas dviejų veiksnių autentifikavimas. Naujam slaptažodžiui taikomos registracijos taisyklės (tikrinama po dabartinio slaptažodžio).

        Pakeitimas sukelia šiuos padarinius vienoje transakcijoje (visus arba nė vieno): atšaukia visus kitus seansus (šis lieka prisijungęs), nutraukia laukiantį el. pašto adreso keitimą, padaro paskyros slaptažodžio nustatymo iš naujo nuorodas neveikiančias ir išsiunčia savininkui laišką.

        **Pakartotinis autentifikavimas:** tik slaptažodis. **Ribos:** `reauth`, tada `reauth_user`.
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PasswordChangeRequest'
            example:
              currentPassword: correct horse battery
              newPassword: a much better passphrase
      responses:
        '200':
          description: Slaptažodis pakeistas.
          headers:
            Cache-Control:
              $ref: '#/components/headers/CacheControl'
          content:
            application/json:
              schema:
                type: object
                required:
                  - status
                properties:
                  status:
                    const: password_changed
              example:
                status: password_changed
        '400':
          $ref: '#/components/responses/BadRequest'
          description: '`password_not_set` (paskyra, kuri prisijungia tik per Google), `weak_password` (su `reason`), `invalid_request`, `invalid_json`.'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
          description: '`invalid_password`: neteisingas dabartinis slaptažodis arba slaptažodis buvo nustatytas iš naujo ar pakeistas, kol užklausa buvo tikrinama.'
        '408':
          $ref: '#/components/responses/RequestTimeout'
        '413':
          $ref: '#/components/responses/PayloadTooLarge'
        '414':
          $ref: '#/components/responses/UriTooLong'
        '415':
          $ref: '#/components/responses/UnsupportedMediaType'
        '429':
          $ref: '#/components/responses/TooManyRequests'
          description: '`too_many_attempts` (nesėkmingi paskyros pakartotiniai autentifikavimai) arba `rate_limited` (riba `reauth` ar `reauth_user`, paskyros biudžetas, slaptažodžių maišos eilė).'
        '500':
          $ref: '#/components/responses/InternalError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
          description: '`server_busy`: slaptažodžių maišos eilė arba duomenų bazė liko užrakinta (`retryAfter: 1`, niekas nepakeista). `timeout`. Taip pat `server_busy` su `retryAfter` nuo 2 iki 5, kai serveris labai atsilieka (žr. įvadą): užklausa nebuvo vykdyta.'
  /account/mfa/totp/setup:
    post:
      operationId: startTotpSetup
      tags:
        - two-step-verification
      summary: Pradėti įjungti dviejų veiksnių autentifikavimą
      description: |-
        Įrašo naują laukiantį autentifikavimo slaptąjį raktą, kuris pakeičia bet kurį ankstesnį laukiantį, ir grąžina jį autentifikavimo programėlei (kaip tekstą ir kaip `otpauth://` URI, rodytiną QR kodu). Dviejų veiksnių autentifikavimas dar neįjungtas: `POST /account/mfa/totp/enable` jį įjungia su šio slaptojo rakto kodu.

        **Pakartotinis autentifikavimas:** tik slaptažodis. **Ribos:** `reauth`, tada `reauth_user`.
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PasswordOnlyRequest'
            example:
              password: correct horse battery
      responses:
        '200':
          description: Laukiantis slaptasis raktas.
          headers:
            Cache-Control:
              $ref: '#/components/headers/CacheControl'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TotpSetup'
              example:
                secret: OCKJVMPMMKMPLBSIYLN6QQRMKIPC2VLH
                uri: otpauth://totp/Scacelith:alice?secret=OCKJVMPMMKMPLBSIYLN6QQRMKIPC2VLH&issuer=Scacelith&algorithm=SHA1&digits=6&period=30
                algorithm: SHA1
                digits: 6
                period: 30
        '400':
          $ref: '#/components/responses/BadRequest'
          description: '`password_not_set` (paskyra, kuri prisijungia tik per Google), `invalid_request`, `invalid_json`.'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
          description: '`invalid_password`.'
        '408':
          $ref: '#/components/responses/RequestTimeout'
        '409':
          $ref: '#/components/responses/Conflict'
          description: '`mfa_already_enabled` (tikrinama prieš slaptažodį).'
        '413':
          $ref: '#/components/responses/PayloadTooLarge'
        '414':
          $ref: '#/components/responses/UriTooLong'
        '415':
          $ref: '#/components/responses/UnsupportedMediaType'
        '429':
          $ref: '#/components/responses/TooManyRequests'
          description: '`too_many_attempts` (nesėkmingi paskyros pakartotiniai autentifikavimai) arba `rate_limited` (riba `reauth` ar `reauth_user`, paskyros biudžetas, slaptažodžių maišos eilė).'
        '500':
          $ref: '#/components/responses/InternalError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
          description: '`server_busy` (slaptažodžių maišos eilė arba duomenų bazė liko užrakinta), `timeout`. Taip pat `server_busy` su `retryAfter` nuo 2 iki 5, kai serveris labai atsilieka (žr. įvadą): užklausa nebuvo vykdyta.'
  /account/mfa/totp/enable:
    post:
      operationId: enableTotp
      tags:
        - two-step-verification
      summary: Baigti įjungti dviejų veiksnių autentifikavimą ir gauti atkūrimo kodus
      description: |-
        Įjungia dviejų veiksnių autentifikavimą su laukiančio slaptojo rakto kodu (lygiai 6 skaitmenys). Slaptažodžio čia neprašoma: jis buvo pateiktas nustatymo žingsnyje. Atsakyme yra 10 atkūrimo kodų, parodomų tik šį kartą.

        **Ribos:** `reauth`, tada `reauth_user`; neteisingas kodas įskaičiuojamas į paskyros nesėkmingus pakartotinius autentifikavimus.
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/TotpEnableRequest'
            example:
              code: '123456'
      responses:
        '200':
          description: Dviejų veiksnių autentifikavimas įjungtas.
          headers:
            Cache-Control:
              $ref: '#/components/headers/CacheControl'
          content:
            application/json:
              schema:
                type: object
                required:
                  - status
                  - recoveryCodes
                properties:
                  status:
                    const: mfa_enabled
                  recoveryCodes:
                    $ref: '#/components/schemas/RecoveryCodeList'
              example:
                status: mfa_enabled
                recoveryCodes:
                  - j7v5-3ezx-zn
                  - 4kqm-8w2p-hd
                  - x0ra-c6tn-5g
                  - mb3s-9yzj-k1
                  - 2hvd-pq7e-w8
                  - c5ng-0tka-xr
                  - zz4y-b1me-7q
                  - 8pjh-6dsw-3n
                  - t9xc-2gfk-0v
                  - q6wa-ner5-jy
        '400':
          $ref: '#/components/responses/BadRequest'
          description: '`invalid_request`: `code` nėra lygiai 6 skaitmenys arba turinys pažeidžia schemą; `invalid_json`.'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
          description: '`invalid_code`: neteisingas kodas (patikrinkite įrenginio laiką).'
        '408':
          $ref: '#/components/responses/RequestTimeout'
        '409':
          $ref: '#/components/responses/Conflict'
          description: '`mfa_already_enabled`; `mfa_setup_required`: nėra laukiančio slaptojo rakto (pirmiausia iškvieskite `POST /account/mfa/totp/setup`).'
        '413':
          $ref: '#/components/responses/PayloadTooLarge'
        '414':
          $ref: '#/components/responses/UriTooLong'
        '415':
          $ref: '#/components/responses/UnsupportedMediaType'
        '429':
          $ref: '#/components/responses/TooManyRequests'
          description: '`too_many_attempts` (nesėkmingi paskyros pakartotiniai autentifikavimai) arba `rate_limited` (riba `reauth` ar `reauth_user`, paskyros biudžetas).'
        '500':
          $ref: '#/components/responses/InternalError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
          description: '`server_busy` (duomenų bazė liko užrakinta), `timeout`. Taip pat `server_busy` su `retryAfter` nuo 2 iki 5, kai serveris labai atsilieka (žr. įvadą): užklausa nebuvo vykdyta.'
  /account/mfa/totp/disable:
    post:
      operationId: disableTotp
      tags:
        - two-step-verification
      summary: Išjungti dviejų veiksnių autentifikavimą
      description: |-
        Išjungia dviejų veiksnių autentifikavimą. Slaptasis raktas ir atkūrimo kodai ištrinami, o savininkas gauna laišką.

        **Pakartotinis autentifikavimas:** slaptažodis ir autentifikavimo programėlės kodas arba atkūrimo kodas (būtinas vienas iš `code` ar `recoveryCode`). **Ribos:** `reauth`, tada `reauth_user`.
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ReauthCredentials'
            example:
              password: correct horse battery
              code: '123456'
      responses:
        '200':
          description: Dviejų veiksnių autentifikavimas išjungtas.
          headers:
            Cache-Control:
              $ref: '#/components/headers/CacheControl'
          content:
            application/json:
              schema:
                type: object
                required:
                  - status
                properties:
                  status:
                    const: mfa_disabled
              example:
                status: mfa_disabled
        '400':
          $ref: '#/components/responses/BadRequest'
          description: '`password_not_set` (paskyra, kuri prisijungia tik per Google), `invalid_request`, `invalid_json`.'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
          description: '`mfa_code_required` (nėra nei `code`, nei `recoveryCode`; tikrinama prieš slaptažodį), `invalid_password`, `invalid_code`.'
        '408':
          $ref: '#/components/responses/RequestTimeout'
        '409':
          $ref: '#/components/responses/Conflict'
          description: '`mfa_not_enabled`.'
        '413':
          $ref: '#/components/responses/PayloadTooLarge'
        '414':
          $ref: '#/components/responses/UriTooLong'
        '415':
          $ref: '#/components/responses/UnsupportedMediaType'
        '429':
          $ref: '#/components/responses/TooManyRequests'
          description: '`too_many_attempts` (nesėkmingi pakartotiniai autentifikavimai arba šioje paskyroje išbandyta per daug kodų) arba `rate_limited` (riba `reauth` ar `reauth_user`, paskyros biudžetas, slaptažodžių maišos eilė).'
        '500':
          $ref: '#/components/responses/InternalError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
          description: '`server_busy` (slaptažodžių maišos eilė arba duomenų bazė liko užrakinta), `timeout`. Taip pat `server_busy` su `retryAfter` nuo 2 iki 5, kai serveris labai atsilieka (žr. įvadą): užklausa nebuvo vykdyta.'
  /account/mfa/recovery-codes:
    post:
      operationId: regenerateRecoveryCodes
      tags:
        - two-step-verification
      summary: Pakeisti atkūrimo kodus
      description: |-
        Vietoj atkūrimo kodų sukuria 10 naujų; senieji nustoja veikti.

        **Pakartotinis autentifikavimas:** slaptažodis ir autentifikavimo programėlės kodas lauke `code` (atkūrimo kodas atmetamas su 403 `invalid_code`). **Ribos:** `reauth`, tada `reauth_user`.
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RecoveryCodesRequest'
            example:
              password: correct horse battery
              code: '123456'
      responses:
        '200':
          description: Nauji atkūrimo kodai, parodomi tik šį kartą.
          headers:
            Cache-Control:
              $ref: '#/components/headers/CacheControl'
          content:
            application/json:
              schema:
                type: object
                required:
                  - recoveryCodes
                properties:
                  recoveryCodes:
                    $ref: '#/components/schemas/RecoveryCodeList'
              example:
                recoveryCodes:
                  - j7v5-3ezx-zn
                  - 4kqm-8w2p-hd
                  - x0ra-c6tn-5g
                  - mb3s-9yzj-k1
                  - 2hvd-pq7e-w8
                  - c5ng-0tka-xr
                  - zz4y-b1me-7q
                  - 8pjh-6dsw-3n
                  - t9xc-2gfk-0v
                  - q6wa-ner5-jy
        '400':
          $ref: '#/components/responses/BadRequest'
          description: '`password_not_set` (paskyra, kuri prisijungia tik per Google), `invalid_request`, `invalid_json`.'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
          description: '`invalid_password`, `mfa_code_required`, `invalid_code` (taip pat dėl atkūrimo kodo).'
        '408':
          $ref: '#/components/responses/RequestTimeout'
        '409':
          $ref: '#/components/responses/Conflict'
          description: '`mfa_not_enabled`.'
        '413':
          $ref: '#/components/responses/PayloadTooLarge'
        '414':
          $ref: '#/components/responses/UriTooLong'
        '415':
          $ref: '#/components/responses/UnsupportedMediaType'
        '429':
          $ref: '#/components/responses/TooManyRequests'
          description: '`too_many_attempts` (nesėkmingi pakartotiniai autentifikavimai arba šioje paskyroje išbandyta per daug kodų) arba `rate_limited` (riba `reauth` ar `reauth_user`, paskyros biudžetas, slaptažodžių maišos eilė).'
        '500':
          $ref: '#/components/responses/InternalError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
          description: '`server_busy` (slaptažodžių maišos eilė arba duomenų bazė liko užrakinta), `timeout`. Taip pat `server_busy` su `retryAfter` nuo 2 iki 5, kai serveris labai atsilieka (žr. įvadą): užklausa nebuvo vykdyta.'
  /account/email:
    post:
      operationId: changeEmail
      tags:
        - email-change
      summary: Pakeisti el. pašto adresą
      description: |-
        Pakeičia paskyros el. pašto adresą. Iš `newEmail` pašalinami tarpai pradžioje ir pabaigoje, raidės paverčiamos mažosiomis, tada adresas tikrinamas kaip registracijos adresas.

        **Su el. pašto patvirtinimu** (numatytasis būdas): 202 `verification_sent`. Naujuoju adresu išsiunčiama 24 valandas galiojanti nuoroda; ji atveria `/confirm-email-change`, o adresas pasikeičia tik tada, kai žaidėjas paspaudžia to puslapio mygtuką. Iki tol `GET /account/me` rodo naująjį adresą kaip `pendingEmail`. Nauja užklausa pakeičia laukiančią; slaptažodžio pakeitimas ar nustatymas iš naujo ją atšaukia. Tam pačiam naujam adresui per 5 minutes išsiunčiama ne daugiau kaip viena nuoroda, kad ir kas jos prašytų (užklausai dėl jau laukiančio keitimo paliekama anksčiau išsiųsta nuoroda, kuri lieka galioti); atsakymas nesiskiria. Dabartiniu adresu išsiunčiamas pranešimas, kad paprašyta pakeisti adresą į užmaskuotą adresą (`a***@example.org`). Atsakymas ir `pendingEmail` būna tokie patys, kai naująjį adresą jau naudoja kita paskyra: tada nuoroda nesiunčiama, todėl toks keitimas niekada neužbaigiamas, o to adreso savininkas vietoj to gauna pranešimą (ne dažniau kaip kartą per valandą).

        Kai nuoroda patvirtinama, adresas pasikeičia ir laikomas patvirtintu, įrenginiai lieka prisijungę, anksčiau išsiųstos nuorodos (patvirtinimo, slaptažodžio nustatymo iš naujo, kitų keitimų) nustoja veikti, o ankstesniu adresu apie tai pranešama, naująjį adresą užmaskavus.

        **Be el. pašto patvirtinimo** (`REQUIRE_EMAIL_VERIFICATION=false`): adresas pasikeičia iš karto (200 `email_changed`), o ankstesniu adresu apie tai pranešama. Jei adresą naudoja kita paskyra, atsakymas yra 409 `email_taken`, o to adreso savininkas gauna pranešimą.

        **Pakartotinis autentifikavimas:** slaptažodis ir, jei įjungtas dviejų veiksnių autentifikavimas, autentifikavimo programėlės kodas arba atkūrimo kodas. `invalid_email` ir `same_email` tikrinami prieš slaptažodį, todėl neįskaičiuojami kaip nesėkmės. **Ribos:** `reauth`, tada `reauth_user`.
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/EmailChangeRequest'
            example:
              newEmail: alice.new@example.org
              password: correct horse battery
              code: '123456'
      responses:
        '200':
          description: Adresas pakeistas iš karto (šiame serveryje el. pašto patvirtinimas nenaudojamas).
          headers:
            Cache-Control:
              $ref: '#/components/headers/CacheControl'
          content:
            application/json:
              schema:
                type: object
                required:
                  - status
                  - email
                properties:
                  status:
                    const: email_changed
                  email:
                    type: string
                    format: email
                    description: Naujasis adresas toks, koks įrašytas.
              example:
                status: email_changed
                email: alice.new@example.org
        '202':
          description: Naujuoju adresu išsiųsta patvirtinimo nuoroda (arba adresas priklauso kitai paskyrai; atsakymas to neatskleidžia).
          headers:
            Cache-Control:
              $ref: '#/components/headers/CacheControl'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/VerificationSentStatus'
              example:
                status: verification_sent
        '400':
          $ref: '#/components/responses/BadRequest'
          description: '`invalid_email`, `same_email` (dabartinis paskyros adresas), `password_not_set` (paskyra, kuri prisijungia tik per Google), `invalid_request`, `invalid_json`.'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
          description: '`invalid_password` (taip pat, kai užklausą aplenkė slaptažodžio pakeitimas ar nustatymas iš naujo: nuoroda nesiunčiama), `mfa_code_required`, `invalid_code`.'
        '408':
          $ref: '#/components/responses/RequestTimeout'
        '409':
          $ref: '#/components/responses/Conflict'
          description: '`email_taken`: tik be el. pašto patvirtinimo.'
        '413':
          $ref: '#/components/responses/PayloadTooLarge'
        '414':
          $ref: '#/components/responses/UriTooLong'
        '415':
          $ref: '#/components/responses/UnsupportedMediaType'
        '429':
          $ref: '#/components/responses/TooManyRequests'
          description: '`too_many_attempts` (nesėkmingi pakartotiniai autentifikavimai arba šioje paskyroje išbandyta per daug kodų) arba `rate_limited` (riba `reauth` ar `reauth_user`, paskyros biudžetas, slaptažodžių maišos eilė).'
        '500':
          $ref: '#/components/responses/InternalError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
          description: '`server_busy`: slaptažodžių maišos eilė arba duomenų bazė liko užrakinta (`retryAfter: 1`: niekas nepakeista, tą pačią užklausą galima siųsti dar kartą). `timeout`. Taip pat `server_busy` su `retryAfter` nuo 2 iki 5, kai serveris labai atsilieka (žr. įvadą): užklausa nebuvo vykdyta.'
  /account/export:
    post:
      operationId: exportAccountData
      tags:
        - data-export
      summary: Atsisiųsti paskyros duomenis
      description: |-
        Viskas, ką serveris saugo apie paskyrą, viename JSON faile, kurį galima įrašyti (`format` `scacelith-account-export`, `version` 1). Eksportuojant užregistruojamas saugos įvykis (`account_exported`). Dokumento masyvas `notes` paprasta anglų kalba paaiškina žaidėjui, kas neįtraukta.

        **Eksporte niekada nebūna:** slaptažodžio maišos, dviejų veiksnių autentifikavimo slaptojo rakto ir atkūrimo kodų; jokio seanso ar nuorodos žetono ir jokios jų maišos; apsaugos nuo sukčiavimo sistemos duomenų (sąžiningumo lygio ir balo, anomalijų, partijų analizės, pranešimo svorio); kitų žaidėjų pranešimų apie žaidėją; moderatorių tapatybių; kitų žaidėjų privačių duomenų (varžovai nurodomi viešu vardu ir reitingu, niekas neatskleidžia, ar kitam žaidėjui buvo skirta sankcija, ir neįtraukiamas joks IP adresas, kuris gali priklausyti kitam asmeniui).

        **Pakartotinis autentifikavimas:** slaptažodis ir, jei įjungtas dviejų veiksnių autentifikavimas, autentifikavimo programėlės kodas arba atkūrimo kodas. **Ribos:** `account_export` (5 per valandą vienam žaidėjui, visame serveryje; skaičiuojamas kiekvienas bandymas, įskaitant nesėkmingus; tikrinama pirmiausia), tada `reauth` ir `reauth_user`. **Laiko riba:** 60 sekundžių.
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ReauthCredentials'
            example:
              password: correct horse battery
              code: '123456'
      responses:
        '200':
          description: Eksporto dokumentas kaip priedas.
          headers:
            Cache-Control:
              $ref: '#/components/headers/CacheControl'
            Content-Disposition:
              description: '`attachment; filename="scacelith-account-<username>.json"`. Naudotojo vardo simboliai, kurie nėra raidės, skaitmenys, `_`, `.` ar `-`, pakeičiami į `_`.'
              schema:
                type: string
                pattern: '^attachment; filename="scacelith-account-[A-Za-z0-9_.-]+\.json"$'
              example: attachment; filename="scacelith-account-alice.json"
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AccountExport'
              example:
                format: scacelith-account-export
                version: 1
                exportedAt: 1790882839708
                server:
                  name: Scacelith
                  host: caissa.scacelith.com
                notes:
                  - This file holds the data Scacelith keeps about your account. Times are milliseconds since 1970-01-01 UTC.
                account:
                  id: 1
                  username: alice
                  email: alice@example.org
                  emailVerified: true
                  pendingEmail: null
                  mfaEnabled: false
                  googleLinked: false
                  googleEmail: null
                  hasPassword: true
                  acceptChallenges: all
                  createdAt: 1790882839743
                  lastLoginAt: 1790882839708
                ratings:
                  - category: '3+2'
                    rating: 1510
                    games: 2
                    wins: 1
                    draws: 1
                    losses: 0
                    peak: 1510
                    provisional: true
                    rated: true
                    countedGames: 2
                    updatedAt: 1790882839809
                ratingRefunds:
                  - day: 1790812800000
                    category: '3+2'
                    points: 9
                sessions:
                  - id: 1
                    createdAt: 1790882839708
                    lastSeenAt: 1790882839708
                    expiresAt: 1798658839708
                    revokedAt: null
                    clientLabel: Scacelith 1.4 (Windows)
                    ip: 203.0.113.7
                securityEvents:
                  - kind: login
                    at: 1790882839708
                    ip: 203.0.113.7
                    detail:
                      method: password
                sanctions: []
                conduct:
                  - kind: abort
                    at: 1790800000000
                reportsFiled:
                  - gameId: 4100000000001
                    reported: bob
                    category: other
                    comment: rude
                    createdAt: 1790882840000
                    status: open
                games:
                  total: 1
                  list:
                    - id: 4100000000001
                      category: '3+2'
                      rated: true
                      timeControl: '180+2'
                      white:
                        name: alice
                        rating: 1500
                        ratingAfter: 1510
                        ratingDiff: 10
                      black:
                        name: bob
                        rating: 1520
                        ratingAfter: 1510
                        ratingDiff: -10
                      color: white
                      status: 1
                      reason: 2
                      result: 1-0
                      termination: Resignation
                      plies: 41
                      startedAt: 1790620205000
                      endedAt: 1790620611000
                      baseMs: 180000
                      incMs: 2000
                      outcome: win
        '400':
          $ref: '#/components/responses/BadRequest'
          description: '`password_not_set` (paskyra, kuri prisijungia tik per Google), `invalid_request`, `invalid_json`.'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
          description: '`invalid_password`, `mfa_code_required`, `invalid_code`.'
        '408':
          $ref: '#/components/responses/RequestTimeout'
        '413':
          $ref: '#/components/responses/PayloadTooLarge'
        '414':
          $ref: '#/components/responses/UriTooLong'
        '415':
          $ref: '#/components/responses/UnsupportedMediaType'
        '429':
          $ref: '#/components/responses/TooManyRequests'
          description: '`too_many_attempts` (nesėkmingi pakartotiniai autentifikavimai arba šioje paskyroje išbandyta per daug kodų) arba `rate_limited` (riba `account_export`, `reauth` ar `reauth_user`, paskyros biudžetas, slaptažodžių maišos eilė).'
        '500':
          $ref: '#/components/responses/InternalError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
          description: '`busy` (duomenų bazė liko užrakinta, su antrašte `Retry-After: 1`), `server_busy` (slaptažodžių maišos eilė), `timeout` (60 sekundžių). Taip pat `server_busy` su `retryAfter` nuo 2 iki 5, kai serveris labai atsilieka (žr. įvadą): užklausa nebuvo vykdyta.'
  /account/delete:
    post:
      operationId: deleteAccount
      tags:
        - account-deletion
      summary: Ištrinti paskyrą
      description: |-
        Ištrina paskyrą; šio veiksmo atšaukti negalima.

        - Visi seansai iš karto atšaukiami: nuo tada žetonas gauna 401 `invalid_token`.
        - Naudotojo vardas tampa `deleted#<id>` paskyroje ir kiekviename partijos įraše.
        - Panaikinami: el. pašto adresas, slaptažodžio maiša, dviejų veiksnių autentifikavimo slaptasis raktas ir atkūrimo kodai, seansų ir nuorodų žetonai, susiejimas su Google, apsaugos nuo sukčiavimo sistemos sąžiningumo įrašas ir su saugos įvykiais saugomi IP adresai.
        - Reitingai ir partijos išsaugomi. Partijas galima toliau skaityti su anoniminiu vardu, o `GET /players/{username}` ankstesniam vardui atsako 404.

        **Pakartotinis autentifikavimas:** slaptažodis ir, jei įjungtas dviejų veiksnių autentifikavimas, autentifikavimo programėlės kodas arba atkūrimo kodas. **Ribos:** `reauth`, tada `reauth_user`.
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ReauthCredentials'
            example:
              password: correct horse battery
              code: '123456'
      responses:
        '200':
          description: Paskyra ištrinta.
          headers:
            Cache-Control:
              $ref: '#/components/headers/CacheControl'
          content:
            application/json:
              schema:
                type: object
                required:
                  - status
                properties:
                  status:
                    const: deleted
              example:
                status: deleted
        '400':
          $ref: '#/components/responses/BadRequest'
          description: '`password_not_set` (paskyra, kuri prisijungia tik per Google), `invalid_request`, `invalid_json`.'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
          description: '`invalid_password`, `mfa_code_required`, `invalid_code`.'
        '408':
          $ref: '#/components/responses/RequestTimeout'
        '413':
          $ref: '#/components/responses/PayloadTooLarge'
        '414':
          $ref: '#/components/responses/UriTooLong'
        '415':
          $ref: '#/components/responses/UnsupportedMediaType'
        '429':
          $ref: '#/components/responses/TooManyRequests'
          description: '`too_many_attempts` (nesėkmingi pakartotiniai autentifikavimai arba šioje paskyroje išbandyta per daug kodų) arba `rate_limited` (riba `reauth` ar `reauth_user`, paskyros biudžetas, slaptažodžių maišos eilė).'
        '500':
          $ref: '#/components/responses/InternalError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
          description: '`server_busy` (slaptažodžių maišos eilė arba duomenų bazė liko užrakinta), `timeout`. Taip pat `server_busy` su `retryAfter` nuo 2 iki 5, kai serveris labai atsilieka (žr. įvadą): užklausa nebuvo vykdyta.'
  /account/games:
    get:
      operationId: listAccountGames
      tags:
        - game-history
      summary: Gauti žaidėjo partijų sąrašą, filtruotą ir suskirstytą puslapiais
      description: |-
        Prisijungusio žaidėjo partijos, pradedant naujausia, filtruotos ir suskirstytos puslapiais, kartu su filtrą atitinkančių partijų skaičiumi. Visi užklausos eilutės parametrai neprivalomi, o tuščia reikšmė laikoma nenurodyta.

        Puslapiavimas: ankstesnio puslapio `next` perduokite kaip `before`; paskutiniame puslapyje `next` yra `null`. `total` nurodo filtrą atitinkančių partijų skaičių visuose puslapiuose.

        **Riba:** `account_games`.
      security:
        - bearerAuth: []
      parameters:
        - $ref: '#/components/parameters/BeforeQuery'
        - $ref: '#/components/parameters/LimitQuery'
        - name: category
          in: query
          required: false
          description: Oficialios kategorijos ID (`3+2` arba `3%2B2`) arba `custom` – visoms partijoms su kita laiko kontrole.
          schema:
            type: string
            pattern: '^\s*([0-9]+[+ ][0-9]+|custom)\s*$'
          example: '3+2'
        - name: rated
          in: query
          required: false
          description: Tik reitinginės (`true`) arba draugiškos (`false`) partijos.
          schema:
            type: string
            enum:
              - 'true'
              - 'false'
        - name: result
          in: query
          required: false
          description: Tik laimėtos, pralaimėtos arba lygiosiomis baigtos partijos žaidėjo požiūriu. Nutrauktos partijos rodomos tik be šio filtro.
          schema:
            type: string
            enum:
              - win
              - loss
              - draw
      responses:
        '200':
          description: Vienas istorijos puslapis.
          headers:
            Cache-Control:
              $ref: '#/components/headers/CacheControl'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HistoryPage'
              example:
                games:
                  - id: 4100000000001
                    category: '3+2'
                    rated: true
                    timeControl: '180+2'
                    white:
                      name: alice
                      rating: 1500
                      ratingAfter: 1510
                      ratingDiff: 10
                    black:
                      name: bob
                      rating: 1520
                      ratingAfter: 1510
                      ratingDiff: -10
                    color: white
                    status: 1
                    reason: 2
                    result: 1-0
                    termination: Resignation
                    plies: 41
                    startedAt: 1790620205000
                    endedAt: 1790620611000
                    baseMs: 180000
                    incMs: 2000
                    outcome: win
                next: null
                total: 1
        '400':
          $ref: '#/components/responses/BadRequest'
          description: '`invalid_cursor` (`before` nėra partijos ID), `invalid_limit` arba `invalid_filter` (`category`, `rated` ar `result`), kiekviena su `field`, nurodančiu parametrą.'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '414':
          $ref: '#/components/responses/UriTooLong'
        '429':
          $ref: '#/components/responses/TooManyRequests'
          description: '`rate_limited`: riba `account_games` arba paskyros biudžetas.'
        '500':
          $ref: '#/components/responses/InternalError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
          description: '`busy` (duomenų bazė liko užrakinta; `retryAfter: 1` turinyje, be antraštės `Retry-After`), `server_busy` (ieškant seanso duomenų bazė buvo užrakinta), `timeout`. Taip pat `server_busy` su `retryAfter` nuo 2 iki 5, kai serveris labai atsilieka (žr. įvadą): užklausa nebuvo vykdyta.'
  /games/{id}:
    get:
      operationId: getGame
      tags:
        - games
      summary: Gauti partijos įrašą su ėjimais ir laikrodžiais
      description: |-
        Vienos partijos įrašas su ėjimais ir laikrodžiais. Be žetono arba su žaidėjo, kuris šios partijos nežaidė, žetonu grąžinamas viešas atsakymas. Kai žetono savininkas žaidė šią partiją, atsakyme papildomai yra `you` ir `reportable`.

        **Riba:** `public_read` (vienam žaidėjui su žetonu, vienam klientui be jo).
      security:
        - {}
        - bearerAuth: []
      parameters:
        - $ref: '#/components/parameters/GameId'
      responses:
        '200':
          description: Partijos įrašas.
          headers:
            Cache-Control:
              $ref: '#/components/headers/CacheControl'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GameRecord'
              example:
                id: 4100000000001
                category: '3+2'
                rated: true
                timeControl: '180+2'
                white:
                  name: alice
                  rating: 1500
                  ratingAfter: 1510
                  ratingDiff: 10
                black:
                  name: bob
                  rating: 1520
                  ratingAfter: 1510
                  ratingDiff: -10
                status: 1
                reason: 2
                result: 1-0
                termination: Resignation
                plies: 2
                startedAt: 1790620205000
                endedAt: 1790620611000
                baseMs: 180000
                incMs: 2000
                statusName: WhiteWins
                rematchOf: null
                moves:
                  - uci: e2e4
                    spentMs: 0
                    clockMs: 180000
                  - uci: e7e5
                    spentMs: 1700
                    clockMs: 180300
                pgn:
                  Event: Scacelith rated 3+2
                  Site: caissa.scacelith.com
                  Date: '2026.09.28'
                  Round: '-'
                  White: alice
                  Black: bob
                  Result: 1-0
                  WhiteElo: 1500
                  BlackElo: 1520
                  TimeControl: '180+2'
                  Termination: Resignation
                  PlyCount: 2
                you: white
                reportable: true
        '400':
          $ref: '#/components/responses/BadRequest'
          description: '`invalid_game_id`: tai nėra teigiamas sveikasis skaičius, turintis ne daugiau kaip 16 skaitmenų (mažesnis nei 2^53); `invalid_request`: netinkama URL koduotė.'
        '401':
          $ref: '#/components/responses/Unauthorized'
          description: '`invalid_token`: išsiųstas žetonas negalioja.'
        '404':
          $ref: '#/components/responses/NotFound'
          description: '`not_found`: tokios partijos nėra.'
        '414':
          $ref: '#/components/responses/UriTooLong'
        '429':
          $ref: '#/components/responses/TooManyRequests'
          description: '`rate_limited`: riba `public_read` arba paskyros biudžetas.'
        '500':
          $ref: '#/components/responses/InternalError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
          description: '`busy` (duomenų bazė liko užrakinta; `retryAfter: 1` turinyje, be antraštės `Retry-After`), `server_busy` (ieškant seanso duomenų bazė buvo užrakinta), `timeout`. Taip pat `server_busy` su `retryAfter` nuo 2 iki 5, kai serveris labai atsilieka (žr. įvadą): užklausa nebuvo vykdyta.'
  /games/{id}/pgn:
    get:
      operationId: getGamePgn
      tags:
        - games
      summary: Atsisiųsti partiją PGN failu
      description: |-
        Ta pati partija PGN failu: viena partija, eilučių pabaigos `\n`, o ėjimų tekstas – eilutėmis, trumpesnėmis nei 80 stulpelių. Atsakymas toks pat su žetonu ir be jo.

        Žymės tokia tvarka: `Event` (`<SERVER_NAME> rated <category>` arba `<SERVER_NAME> casual <category>`), `Site` (`SERVER_PUBLIC_HOST`), `Date` (pradžios data pagal UTC), `Round`, `White`, `Black`, `Result` (`*` nutrauktai partijai), `UTCDate` ir `UTCTime` (pradžia), `WhiteElo` ir `BlackElo` (reitingai pradžioje arba `-`), `WhiteRatingDiff` ir `BlackRatingDiff` (pokyčiai, pvz., `+10` ir `-10`, kiekvienoje reitinginėje partijoje; `+0`, kai pagal reitingo taisykles reitingas nepasikeičia; draugiška, pasirinktinės laiko kontrolės ar nutraukta partija jų neturi), `TimeControl` (sekundėmis), `Termination` (standartinė PGN reikšmė: `normal`; `time forfeit` – baigėsi laikas, taip pat kai partija baigiasi lygiosiomis; `abandoned`; `rules infraction` – antrasis neleistinas ėjimas arba techninis pralaimėjimas už sąžiningo žaidimo pažeidimą; `unterminated` – nutraukta partija), `PlyCount`, `ScacelithGameId` (dešimtainis ID).

        Prie kiekvieno ėjimo yra `{[%clk h:mm:ss.f] [%emt h:mm:ss.f]}`: ėjimą padariusio žaidėjo laikrodis po ėjimo ir už ėjimą įskaitytas laikas, dešimtosiomis sekundės dalimis (neapvalinant). Reikšmė praleidžiama, kai įraše jos nėra. Po paskutinio ėjimo žodžiais nurodoma pabaigos priežastis, tada rezultatas.

        **Riba:** `public_read` (vienam žaidėjui su žetonu, vienam klientui be jo).
      security:
        - {}
        - bearerAuth: []
      parameters:
        - $ref: '#/components/parameters/GameId'
      responses:
        '200':
          description: PGN failas.
          headers:
            Cache-Control:
              $ref: '#/components/headers/CacheControl'
            Content-Disposition:
              description: '`attachment; filename="scacelith-<id>.pgn"`.'
              schema:
                type: string
                pattern: '^attachment; filename="scacelith-[1-9][0-9]{0,15}\.pgn"$'
              example: attachment; filename="scacelith-4100000000001.pgn"
          content:
            application/x-chess-pgn:
              schema:
                type: string
                description: 'PGN tekstas UTF-8 koduote (`Content-Type: application/x-chess-pgn; charset=utf-8`).'
              example: |
                [Event "Scacelith rated 3+2"]
                [Site "caissa.scacelith.com"]
                [Date "2026.09.28"]
                [Round "-"]
                [White "alice"]
                [Black "bob"]
                [Result "1-0"]
                [UTCDate "2026.09.28"]
                [UTCTime "18:30:05"]
                [WhiteElo "1500"]
                [BlackElo "1520"]
                [WhiteRatingDiff "+10"]
                [BlackRatingDiff "-10"]
                [TimeControl "180+2"]
                [Termination "normal"]
                [PlyCount "2"]
                [ScacelithGameId "4100000000001"]

                1. e4 {[%clk 0:03:00.0] [%emt 0:00:00.0]} 1... e5 {[%clk 0:03:00.3]
                [%emt 0:00:01.7]} {Resignation} 1-0
        '400':
          $ref: '#/components/responses/BadRequest'
          description: '`invalid_game_id`: tai nėra teigiamas sveikasis skaičius, turintis ne daugiau kaip 16 skaitmenų (mažesnis nei 2^53); `invalid_request`: netinkama URL koduotė.'
        '401':
          $ref: '#/components/responses/Unauthorized'
          description: '`invalid_token`: išsiųstas žetonas negalioja.'
        '404':
          $ref: '#/components/responses/NotFound'
          description: '`not_found`: tokios partijos nėra.'
        '414':
          $ref: '#/components/responses/UriTooLong'
        '429':
          $ref: '#/components/responses/TooManyRequests'
          description: '`rate_limited`: riba `public_read` arba paskyros biudžetas.'
        '500':
          $ref: '#/components/responses/InternalError'
          description: '`internal_error`: be kita ko, kai išsaugotų ėjimų neįmanoma atkartoti iki išsaugotos pabaigos (serveris tai įrašo į žurnalą).'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
          description: '`busy` (duomenų bazė liko užrakinta; `retryAfter: 1` turinyje, be antraštės `Retry-After`), `server_busy` (ieškant seanso duomenų bazė buvo užrakinta), `timeout`. Taip pat `server_busy` su `retryAfter` nuo 2 iki 5, kai serveris labai atsilieka (žr. įvadą): užklausa nebuvo vykdyta.'
  /games/{id}/gif:
    get:
      operationId: getGameGif
      tags:
        - gifs
      summary: Atsisiųsti šio serverio partiją animuotu GIF
      description: |-
        Partija animuotu GIF. Vardai ir reitingai imami iš partijos įrašo (reitingai pradžioje, ištrinta paskyra – kaip `deleted#<id>`), kaip ir rezultatas bei pabaiga (`Resignation`, `Loss on time`...).

        Tikrinama tokia tvarka: `gif_disabled`, paveikslėlio parinktys (`size`, `orientation`, `delay`, `coords`), partijos ID, partija, jos ilgis.

        **Reikia seanso:** kvotos skaičiuojamos kiekvienai paskyrai. **Ribos:** `gif` – kiekvienai užklausai; `gif_user_min`, `gif_user_hour`, `gif_ip_min` ir `gif_ip_hour` – tik kai GIF reikia sugeneruoti (žr. skyriaus aprašą). **Laiko riba:** `GIF_QUEUE_TIMEOUT_MS` + `GIF_RENDER_TIMEOUT_MS` + 5 sekundės (numatytoji – 45 sekundės).
      security:
        - bearerAuth: []
      parameters:
        - $ref: '#/components/parameters/GameId'
        - name: size
          in: query
          required: false
          description: 'Paveikslėlio dydis: `small` (32 px langeliai), `medium` (48 px) arba `large` (72 px).'
          schema:
            type: string
            enum:
              - small
              - medium
              - large
            default: medium
        - name: orientation
          in: query
          required: false
          description: Kurios spalvos figūros rodomos lentos apačioje.
          schema:
            type: string
            enum:
              - white
              - black
            default: white
        - name: delay
          in: query
          required: false
          description: Milisekundės vienam ėjimui (nuo 1 iki 6 dešimtainių skaitmenų).
          schema:
            type: integer
            minimum: 100
            maximum: 3000
            default: 500
        - name: coords
          in: query
          required: false
          description: Ar aplink lentą piešiamos vertikalių raidės ir horizontalių skaičiai (`1`), ar ne (`0`).
          schema:
            type: string
            enum:
              - '1'
              - '0'
            default: '1'
      responses:
        '200':
          $ref: '#/components/responses/GifFile'
        '400':
          $ref: '#/components/responses/BadRequest'
          description: '`invalid_option` su `field` (`size`, `orientation`, `delay` ar `coords`): neleistina reikšmė. `invalid_game_id`. `invalid_request`: netinkama URL koduotė.'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
          description: '`not_found`: tokios partijos nėra. `gif_disabled`: serveris išjungė GIF (`GIF_ENABLED=false`; ribos `gif` žetonas grąžinamas).'
        '414':
          $ref: '#/components/responses/UriTooLong'
        '422':
          $ref: '#/components/responses/UnprocessableContent'
        '429':
          $ref: '#/components/responses/TooManyRequests'
          description: '`rate_limited`: riba `gif`, generavimo riba arba paskyros biudžetas.'
        '500':
          $ref: '#/components/responses/InternalError'
          description: '`render_failed`: GIF nepavyko sugeneruoti (serveris priežastį įrašo į žurnalą). `internal_error`.'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
          description: '`server_busy` su `retryAfter` (nuo 3 iki 10 sekundžių) ir `Retry-After`: generavimo eilė pilna arba GIF laukė laisvos gijos `GIF_QUEUE_TIMEOUT_MS` (10 sekundžių); visi užklausos paimti ribų žetonai grąžinami. `busy`: duomenų bazė liko užrakinta (`retryAfter: 1` tik turinyje). `timeout`. Taip pat `server_busy` su `retryAfter` nuo 2 iki 5, kai serveris labai atsilieka (žr. įvadą): užklausa nebuvo vykdyta.'
  /gif:
    post:
      operationId: renderPgnGif
      tags:
        - gifs
      summary: Sukurti animuotą GIF iš bet kurios PGN formatu atsiųstos partijos
      description: |-
        Toks pat paveikslėlis kaip `GET /games/{id}/gif` bet kuriai partijai, atsiųstai PGN tekstu: žaidimo išsaugotai partijai, kitos svetainės eksportui, ranka surinktai partijai. Naudojama tik pirmoji teksto partija.

        - PGN skaitytuvas priima tai, ką priima paties žaidimo skaitytuvas: kiekvieną šio serverio rašomą PGN ir įprastus kitų svetainių eksportus (komentarai, variantai, NAG ir laikrodžio anotacijos praleidžiami; ėjimų numeriai ir SAN skaitomi atlaidžiai; žymė `FEN` nurodo pradinę padėtį, nebent `SetUp` yra `"0"`).
        - Vardai ir reitingai imami iš žymių `White`, `Black`, `WhiteElo` ir `BlackElo`. Iš raidžių pašalinami diakritiniai ženklai, kiti simboliai už spausdinamų ASCII ribų tampa `?`, o ilgi vardai sutrumpinami (iki 48 simbolių). Rezultatas imamas iš žymės `Result`, o jei jos nėra – iš ėjimų teksto pabaigos. Žymė `Termination` rodoma, nebent ji yra `normal`: tada viską pasako galutinė padėtis (matas, patas).
        - Šis galinis punktas pats tikrina savo turinį: nežinomas laukas arba trūkstamas ar ne eilutės tipo `pgn` duoda atsakymą 400 `invalid_request` su `field`; netinkama parinktis – 400 `invalid_option` (`delayMs` turi būti JSON skaičius, `coords` – loginė reikšmė; `null` atmetama).

        **Reikia seanso:** kvotos skaičiuojamos kiekvienai paskyrai. **Ribos:** kaip `GET /games/{id}/gif`. **Turinio dydžio riba:** 135 168 baitai, nepriklausomai nuo `HTTP_BODY_LIMIT` (PGN kaip JSON eilutė, įskaitant jos kaitos sekas). **Laiko riba:** numatytoji – 45 sekundės.
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/GifRequest'
            examples:
              short:
                summary: Trumpa ranka surinkta partija, be koordinačių
                value:
                  pgn: 1. f3 e5 2. g4 Qh4# 0-1
                  coords: false
              options:
                summary: PGN failas su visomis parinktimis
                value:
                  pgn: |
                    [White "alice"]
                    [Black "bob"]
                    [WhiteElo "1500"]
                    [BlackElo "1520"]
                    [Result "1-0"]

                    1. e4 e5 2. Bc4 Nc6 3. Qh5 Nf6 4. Qxf7# 1-0
                  size: small
                  orientation: black
                  delayMs: 800
                  coords: true
      responses:
        '200':
          $ref: '#/components/responses/GifFile'
        '400':
          $ref: '#/components/responses/BadRequest'
          description: '`invalid_request` su `field` (nežinomas laukas arba trūksta `pgn` ar tai nėra eilutė; be `field`, kai turinys nėra objektas); `invalid_json`; `invalid_option` su `field` (`size`, `orientation`, `delayMs` ar `coords`); `invalid_pgn` su `line` ir `column` (skaičiuojant nuo 1, stulpeliai – simboliais) ir skaitytuvo `message`: neleistinas ar dviprasmis ėjimas, sugadinta žymė, nežinoma šachmatų atmaina, daugiau nei 65 536 baitai.'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
          description: '`gif_disabled`: serveris išjungė GIF (`GIF_ENABLED=false`; ribos `gif` žetonas grąžinamas).'
        '408':
          $ref: '#/components/responses/RequestTimeout'
        '413':
          $ref: '#/components/responses/PayloadTooLarge'
          description: '`payload_too_large`: turinys didesnis nei 135 168 baitai.'
        '414':
          $ref: '#/components/responses/UriTooLong'
        '415':
          $ref: '#/components/responses/UnsupportedMediaType'
        '422':
          $ref: '#/components/responses/UnprocessableContent'
        '429':
          $ref: '#/components/responses/TooManyRequests'
          description: '`rate_limited`: riba `gif`, generavimo riba arba paskyros biudžetas.'
        '500':
          $ref: '#/components/responses/InternalError'
          description: '`render_failed`: GIF nepavyko sugeneruoti (serveris priežastį įrašo į žurnalą). `internal_error`.'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
          description: '`server_busy` su `retryAfter` (nuo 3 iki 10 sekundžių) ir `Retry-After`: generavimo eilė pilna arba GIF per ilgai laukė laisvos gijos; visi užklausos paimti ribų žetonai grąžinami. `timeout`. Taip pat `server_busy` su `retryAfter` nuo 2 iki 5, kai serveris labai atsilieka (žr. įvadą): užklausa nebuvo vykdyta.'
  /players/{username}:
    get:
      operationId: getPlayer
      tags:
        - players
      summary: Gauti viešą žaidėjo profilį
      description: |-
        Viešas žaidėjo profilis: reitingai oficialiose kategorijose (serverio tvarka) ir partijų skaičiai. `games.total` apima visas išsaugotas partijas, įskaitant draugiškas ir nutrauktas; `games.rated`, `wins`, `draws` ir `losses` sumuojami pagal reitingo įrašus, taigi apima tik reitingines partijas. Atsakymas toks pat su žetonu ir be jo.

        **Riba:** `public_read` (vienam žaidėjui su žetonu, vienam klientui be jo).
      security:
        - {}
        - bearerAuth: []
      parameters:
        - $ref: '#/components/parameters/Username'
      responses:
        '200':
          description: Profilis.
          headers:
            Cache-Control:
              $ref: '#/components/headers/CacheControl'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PlayerProfile'
              example:
                username: alice
                createdAt: 1790882839743
                ratings:
                  - category: '3+2'
                    rating: 1510
                    provisional: true
                    games: 2
                    wins: 1
                    draws: 1
                    losses: 0
                    peak: 1510
                games:
                  total: 3
                  rated: 2
                  wins: 1
                  draws: 1
                  losses: 0
        '400':
          $ref: '#/components/responses/BadRequest'
          description: '`invalid_username`: vardą sudaro ne 2–24 simboliai iš `[A-Za-z0-9_.-]`.'
        '401':
          $ref: '#/components/responses/Unauthorized'
          description: '`invalid_token`: išsiųstas žetonas negalioja.'
        '404':
          $ref: '#/components/responses/NotFound'
          description: '`not_found`: tokio žaidėjo nėra arba paskyra ištrinta.'
        '414':
          $ref: '#/components/responses/UriTooLong'
        '429':
          $ref: '#/components/responses/TooManyRequests'
          description: '`rate_limited`: riba `public_read` arba paskyros biudžetas.'
        '500':
          $ref: '#/components/responses/InternalError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
          description: '`busy` (duomenų bazė liko užrakinta; `retryAfter: 1` turinyje, be antraštės `Retry-After`), `server_busy` (ieškant seanso duomenų bazė buvo užrakinta), `timeout`. Taip pat `server_busy` su `retryAfter` nuo 2 iki 5, kai serveris labai atsilieka (žr. įvadą): užklausa nebuvo vykdyta.'
  /players/{username}/games:
    get:
      operationId: listPlayerGames
      tags:
        - players
      summary: Gauti žaidėjo paskutinių partijų sąrašą
      description: |-
        Paskutinės žaidėjo partijos, pradedant naujausia, suskirstytos puslapiais kaip istorija, bet be filtrų ir bendro skaičiaus. `color` yra šio žaidėjo spalva. `next` yra paskutinės partijos ID kiekvieną kartą, kai puslapis pilnas, todėl kitas puslapis gali būti tuščias. Pirmiausia ieškoma žaidėjo: nežinomam žaidėjui atsakoma 404, kad ir ką nurodytų užklausos eilutė.

        **Riba:** `public_read` (vienam žaidėjui su žetonu, vienam klientui be jo).
      security:
        - {}
        - bearerAuth: []
      parameters:
        - $ref: '#/components/parameters/Username'
        - $ref: '#/components/parameters/BeforeQuery'
        - $ref: '#/components/parameters/LimitQuery'
      responses:
        '200':
          description: Vienas žaidėjo partijų puslapis.
          headers:
            Cache-Control:
              $ref: '#/components/headers/CacheControl'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PlayerGamesPage'
              example:
                username: alice
                games:
                  - id: 4100000000001
                    category: '3+2'
                    rated: true
                    timeControl: '180+2'
                    white:
                      name: alice
                      rating: 1500
                      ratingAfter: 1510
                      ratingDiff: 10
                    black:
                      name: bob
                      rating: 1520
                      ratingAfter: 1510
                      ratingDiff: -10
                    color: white
                    status: 1
                    reason: 2
                    result: 1-0
                    termination: Resignation
                    plies: 41
                    startedAt: 1790620205000
                    endedAt: 1790620611000
                next: 4100000000001
        '400':
          $ref: '#/components/responses/BadRequest'
          description: '`invalid_username`, `invalid_cursor` (`before` nėra partijos ID), `invalid_limit` (šios dvi – be `field`).'
        '401':
          $ref: '#/components/responses/Unauthorized'
          description: '`invalid_token`: išsiųstas žetonas negalioja.'
        '404':
          $ref: '#/components/responses/NotFound'
          description: '`not_found`: tokio žaidėjo nėra arba paskyra ištrinta.'
        '414':
          $ref: '#/components/responses/UriTooLong'
        '429':
          $ref: '#/components/responses/TooManyRequests'
          description: '`rate_limited`: riba `public_read` arba paskyros biudžetas.'
        '500':
          $ref: '#/components/responses/InternalError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
          description: '`busy` (duomenų bazė liko užrakinta; `retryAfter: 1` turinyje, be antraštės `Retry-After`), `server_busy` (ieškant seanso duomenų bazė buvo užrakinta), `timeout`. Taip pat `server_busy` su `retryAfter` nuo 2 iki 5, kai serveris labai atsilieka (žr. įvadą): užklausa nebuvo vykdyta.'
  /leaderboard:
    get:
      operationId: getLeaderboard
      tags:
        - leaderboard
      summary: Gauti geriausius kategorijos žaidėjus
      description: |-
        100 geriausių oficialios kategorijos reitingo įrašų, turinčių bent `minGames` (`PROVISIONAL_GAMES`) įskaitytų partijų, neįtraukiant ištrintų paskyrų ir patvirtintų sukčių. Serveris kiekvieną lentelę perskaičiuoja ne dažniau kaip kas 10 sekundžių; `updatedAt` nurodo kada. Vienu metu vykdomas tik vienas kategorijos skaičiavimas: tuo metu kitos užklausos gauna ankstesnį sąrašą arba, jei sąrašo dar nėra, laukia skaičiavimo pabaigos.

        **Ribos:** tik adresų lygmuo.
      security: []
      parameters:
        - name: category
          in: query
          required: true
          description: Oficialios kategorijos ID (`3+2` arba `3%2B2`).
          schema:
            type: string
            pattern: '^\s*[0-9]+[+ ][0-9]+\s*$'
          example: '3+2'
        - name: limit
          in: query
          required: false
          description: Kiek žaidėjų, nuo 1 iki 100. Didesnis skaičius, turintis iki 3 skaitmenų, laikomas 100; tuščia reikšmė laikoma nenurodyta.
          schema:
            type: integer
            minimum: 1
            maximum: 999
            default: 100
      responses:
        '200':
          description: Lyderių lentelė.
          headers:
            Cache-Control:
              $ref: '#/components/headers/CacheControl'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Leaderboard'
              example:
                category: '3+2'
                minGames: 30
                updatedAt: 1790882839828
                players:
                  - rank: 1
                    username: bob
                    rating: 1874
                    games: 212
                    wins: 120
                    draws: 30
                    losses: 62
                    peak: 1901
        '400':
          $ref: '#/components/responses/BadRequest'
          description: '`invalid_category` (nenurodyta arba ne oficiali kategorija), `invalid_limit`.'
        '414':
          $ref: '#/components/responses/UriTooLong'
        '429':
          $ref: '#/components/responses/TooManyRequests'
          description: '`rate_limited`: adresų lygmuo.'
        '500':
          $ref: '#/components/responses/InternalError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
          description: '`busy` (duomenų bazė liko užrakinta; `retryAfter: 1` turinyje, be antraštės `Retry-After`), `timeout`. Taip pat `server_busy` su `retryAfter` nuo 2 iki 5, kai serveris labai atsilieka (žr. įvadą): užklausa nebuvo vykdyta.'
  /reports:
    post:
      operationId: reportPlayer
      tags:
        - reports
      summary: Pranešti apie neseniai žaistos partijos varžovą
      description: |-
        Praneša apie varžovą vienoje iš paties žaidėjo partijų, pasibaigusių per pastarąsias 7 dienas. Pranešimas pats savaime niekada nekeičia reitingo, sankcijos ar sąžiningumo lygio. Jis padidina peržiūros prioritetą, kurį mato moderatoriai, ir, išskyrus `abuse`, užsako partijos analizę šachmatų varikliu.

        Pranešimas apie tą patį varžovą dėl tos pačios partijos gauna tokį pat atsakymą ir nieko nekeičia; atsakymas niekada nieko neatskleidžia apie paskyrą, apie kurią pranešama. `GET /games/{id}` iš anksto nurodo partijos žaidėjams, ar pranešimas būtų priimtas (`reportable`).

        Šis galinis punktas pats tikrina savo turinį tokia tvarka: `gameId`, `reported`, `category`, `comment`; nesėkmės atveju atsakoma 400 `invalid_request` be `field`. Nežinomi laukai ignoruojami.

        **Ribos:** `reports` (vienam žaidėjui), tada `REPORTS_PER_DAY` (5) pranešimai vienam žaidėjui per 24 valandas (429 `report_limit`).
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ReportRequest'
            example:
              gameId: 4100000000001
              reported: bob
              category: cheating
              comment: engine-like play
      responses:
        '202':
          description: Pranešimas gautas (arba jau buvo pateiktas).
          headers:
            Cache-Control:
              $ref: '#/components/headers/CacheControl'
          content:
            application/json:
              schema:
                type: object
                required:
                  - status
                properties:
                  status:
                    const: received
              example:
                status: received
        '400':
          $ref: '#/components/responses/BadRequest'
          description: '`invalid_request` (be `field`), `invalid_json`.'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
          description: '`report_not_allowed`: tai nėra pranešėjo varžovas partijoje, pasibaigusioje per pastarąsias 7 dienas.'
        '408':
          $ref: '#/components/responses/RequestTimeout'
        '413':
          $ref: '#/components/responses/PayloadTooLarge'
        '414':
          $ref: '#/components/responses/UriTooLong'
        '415':
          $ref: '#/components/responses/UnsupportedMediaType'
        '429':
          $ref: '#/components/responses/TooManyRequests'
          description: '`report_limit` (`retryAfter: 3600`, su antrašte `Retry-After`): per pastarąsias 24 valandas pateikta `REPORTS_PER_DAY` pranešimų. `rate_limited`: riba `reports` arba paskyros biudžetas.'
        '500':
          $ref: '#/components/responses/InternalError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
          description: '`server_busy` (ieškant seanso duomenų bazė buvo užrakinta), `timeout`. Taip pat `server_busy` su `retryAfter` nuo 2 iki 5, kai serveris labai atsilieka (žr. įvadą): užklausa nebuvo vykdyta.'
  /verify-email:
    servers:
      - url: https://caissa.scacelith.com
        description: Oficialus serveris (puslapiai yra šaknyje, ne po `/api/v1`).
      - url: https://{host}:{port}
        description: Bet kuris Scacelith serveris (puslapiai yra šaknyje, ne po `/api/v1`).
        variables:
          host:
            default: caissa.scacelith.com
            description: Viešasis serverio pagrindinio kompiuterio vardas (`SERVER_PUBLIC_HOST`).
          port:
            default: "443"
            description: Viešasis API prievadas (`PUBLIC_API_PORT`, o jei jo nėra – `API_PORT`).
    get:
      operationId: showVerifyEmailPage
      tags:
        - pages
      summary: Rodyti el. pašto patvirtinimo puslapį
      description: |-
        El. pašto patvirtinimo nuorodos puslapis (registracija arba pakartotinai išsiųstas patvirtinimo laiškas). Jame rodomas tik mygtukas „Confirm my e-mail address“ (patvirtinti mano el. pašto adresą), kad nuorodą atidaręs laiškų skeneris jos nepanaudotų; mygtukas išsiunčia formą į `POST /verify-email`.

        **Riba:** `page`.
      security: []
      parameters:
        - $ref: '#/components/parameters/LinkTokenQuery'
      responses:
        '200':
          $ref: '#/components/responses/HtmlPage'
          description: Puslapis su patvirtinimo mygtuku.
        '400':
          $ref: '#/components/responses/HtmlBadRequest'
          description: Nuoroda netinkama arba nebegalioja (HTML puslapis).
        '414':
          $ref: '#/components/responses/UriTooLong'
        '429':
          $ref: '#/components/responses/HtmlTooManyRequests'
          description: Riba `page` (HTML puslapis) arba adresų lygmuo (JSON `rate_limited`).
        '500':
          $ref: '#/components/responses/HtmlInternalError'
        '503':
          $ref: '#/components/responses/HtmlServiceUnavailable'
    post:
      operationId: submitVerifyEmailPage
      tags:
        - pages
      summary: Patvirtinti el. pašto adresą
      description: |-
        Patvirtinimo puslapio forma. Ji patvirtina adresą; naujos registracijos atveju tada sukuriama paskyra, ir žaidėjas gali prisijungti.

        **Riba:** `auth`.
      security: []
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              $ref: '#/components/schemas/LinkTokenForm'
            example:
              token: asfioO4P0r_Zn5DxWzcQuF6Drgxu7wXGjCyS-UFbTaw
          application/json:
            schema:
              $ref: '#/components/schemas/LinkTokenForm'
            example:
              token: asfioO4P0r_Zn5DxWzcQuF6Drgxu7wXGjCyS-UFbTaw
      responses:
        '200':
          $ref: '#/components/responses/HtmlPage'
          description: Adresas patvirtintas (o naujos registracijos atveju sukurta paskyra).
        '400':
          $ref: '#/components/responses/HtmlBadRequest'
          description: Nuoroda netinkama, panaudota arba nebegalioja, arba forma netinkama (HTML puslapis).
        '408':
          $ref: '#/components/responses/HtmlRequestTimeout'
        '409':
          $ref: '#/components/responses/HtmlConflict'
          description: Naujos registracijos nuoroda, kai jos naudotojo vardą ar adresą per tą laiką užėmė kita paskyra; paskyra nesukuriama (HTML puslapis).
        '413':
          $ref: '#/components/responses/HtmlPayloadTooLarge'
        '414':
          $ref: '#/components/responses/UriTooLong'
        '415':
          $ref: '#/components/responses/HtmlUnsupportedMediaType'
        '429':
          $ref: '#/components/responses/HtmlTooManyRequests'
          description: Riba `auth` (HTML puslapis) arba adresų lygmuo (JSON `rate_limited`).
        '500':
          $ref: '#/components/responses/HtmlInternalError'
        '503':
          $ref: '#/components/responses/HtmlServiceUnavailable'
          description: 'Duomenų bazė liko užrakinta (`Retry-After: 1`) arba serveris labai atsilieka (`Retry-After` nuo 2 iki 5, žr. įvadą): niekas nepakeista, nuoroda vis dar veikia. Taip pat, kai baigiasi doroklės skirtasis laikas.'
  /reset-password:
    servers:
      - url: https://caissa.scacelith.com
        description: Oficialus serveris (puslapiai yra šaknyje, ne po `/api/v1`).
      - url: https://{host}:{port}
        description: Bet kuris Scacelith serveris (puslapiai yra šaknyje, ne po `/api/v1`).
        variables:
          host:
            default: caissa.scacelith.com
            description: Viešasis serverio pagrindinio kompiuterio vardas (`SERVER_PUBLIC_HOST`).
          port:
            default: "443"
            description: Viešasis API prievadas (`PUBLIC_API_PORT`, o jei jo nėra – `API_PORT`).
    get:
      operationId: showResetPasswordPage
      tags:
        - pages
      summary: Rodyti slaptažodžio nustatymo iš naujo formą
      description: |-
        Slaptažodžio nustatymo iš naujo nuorodos puslapis: naujo slaptažodžio forma (slaptažodis du kartus). Forma siunčiama į `POST /reset-password`.

        **Riba:** `page`.
      security: []
      parameters:
        - $ref: '#/components/parameters/LinkTokenQuery'
      responses:
        '200':
          $ref: '#/components/responses/HtmlPage'
          description: Naujo slaptažodžio forma.
        '400':
          $ref: '#/components/responses/HtmlBadRequest'
          description: Nuoroda netinkama (taip pat, kai ji buvo išsiųsta adresu, kurio paskyra nebeturi).
        '414':
          $ref: '#/components/responses/UriTooLong'
        '429':
          $ref: '#/components/responses/HtmlTooManyRequests'
          description: Riba `page` (HTML puslapis) arba adresų lygmuo (JSON `rate_limited`).
        '500':
          $ref: '#/components/responses/HtmlInternalError'
        '503':
          $ref: '#/components/responses/HtmlServiceUnavailable'
    post:
      operationId: submitResetPasswordPage
      tags:
        - pages
      summary: Nustatyti naują slaptažodį per nustatymo iš naujo formą
      description: |-
        Nustatymo iš naujo puslapio forma; ji vienoje transakcijoje atlieka tą patį, ką `POST /auth/password/reset`: visi įrenginiai atjungiami, laukiantis el. pašto adreso keitimas atšaukiamas, kitos nustatymo iš naujo nuorodos nustoja veikti, adresas laikomas patvirtintu, o savininkas gauna laišką.

        Pirmiausia tikrinama nuoroda, tada ar abu slaptažodžiai sutampa, tada slaptažodžio taisyklės. Kai serveris užimtas, forma grąžinama su `Retry-After`, o nuoroda lieka galioti.

        **Ribos:** `auth`, tada `auth_reset` (bendra su `POST /auth/password/reset`).
      security: []
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              $ref: '#/components/schemas/ResetPasswordForm'
            example:
              token: A0ATfmXCDKFC8UkSxLJCD85K7au5nMIrZ2P3K-1VWQg
              newPassword: a much better passphrase
              confirmPassword: a much better passphrase
          application/json:
            schema:
              $ref: '#/components/schemas/ResetPasswordForm'
            example:
              token: A0ATfmXCDKFC8UkSxLJCD85K7au5nMIrZ2P3K-1VWQg
              newPassword: a much better passphrase
              confirmPassword: a much better passphrase
      responses:
        '200':
          $ref: '#/components/responses/HtmlPage'
          description: Slaptažodis pakeistas, visi įrenginiai atjungti.
        '400':
          $ref: '#/components/responses/HtmlBadRequest'
          description: Vėl forma su klaida (slaptažodžiai nesutampa, silpnas slaptažodis), nuoroda netinkama arba forma netinkama (HTML puslapis).
        '408':
          $ref: '#/components/responses/HtmlRequestTimeout'
        '413':
          $ref: '#/components/responses/HtmlPayloadTooLarge'
        '414':
          $ref: '#/components/responses/UriTooLong'
        '415':
          $ref: '#/components/responses/HtmlUnsupportedMediaType'
        '429':
          $ref: '#/components/responses/HtmlTooManyRequests'
          description: Riba `auth` ar `auth_reset` (HTML puslapis), vėl forma su `Retry-After`, kai šio kliento laukia per daug slaptažodžių maišos skaičiavimų (nuoroda lieka galioti), arba adresų lygmuo (JSON `rate_limited`).
        '500':
          $ref: '#/components/responses/HtmlInternalError'
        '503':
          $ref: '#/components/responses/HtmlServiceUnavailable'
          description: 'Vėl forma su `Retry-After`, kai serveris užimtas (slaptažodžių maišos eilė arba duomenų bazė liko užrakinta); nuoroda lieka galioti. Kai serveris labai atsilieka (žr. įvadą), vietoj to – puslapis „Server error“ (serverio klaida) su `Retry-After` (nuo 2 iki 5): niekas nebuvo vykdyta, nuoroda lieka galioti. Taip pat, kai baigiasi doroklės skirtasis laikas.'
  /confirm-email-change:
    servers:
      - url: https://caissa.scacelith.com
        description: Oficialus serveris (puslapiai yra šaknyje, ne po `/api/v1`).
      - url: https://{host}:{port}
        description: Bet kuris Scacelith serveris (puslapiai yra šaknyje, ne po `/api/v1`).
        variables:
          host:
            default: caissa.scacelith.com
            description: Viešasis serverio pagrindinio kompiuterio vardas (`SERVER_PUBLIC_HOST`).
          port:
            default: "443"
            description: Viešasis API prievadas (`PUBLIC_API_PORT`, o jei jo nėra – `API_PORT`).
    get:
      operationId: showConfirmEmailChangePage
      tags:
        - pages
      summary: Rodyti el. pašto adreso keitimo patvirtinimo puslapį
      description: |-
        El. pašto adreso keitimo nuorodos puslapis: jame rodomas naujasis adresas ir paskyros vardas su mygtuku „Use this e-mail address“ (naudoti šį el. pašto adresą), kuris išsiunčia formą į `POST /confirm-email-change`.

        **Riba:** `page`.
      security: []
      parameters:
        - $ref: '#/components/parameters/LinkTokenQuery'
      responses:
        '200':
          $ref: '#/components/responses/HtmlPage'
          description: Puslapis su naujuoju adresu ir jo patvirtinimo mygtuku.
        '400':
          $ref: '#/components/responses/HtmlBadRequest'
          description: Nuoroda netinkama arba nebegalioja (taip pat, kai paskyros adresas pasikeitė po užklausos).
        '414':
          $ref: '#/components/responses/UriTooLong'
        '429':
          $ref: '#/components/responses/HtmlTooManyRequests'
          description: Riba `page` (HTML puslapis) arba adresų lygmuo (JSON `rate_limited`).
        '500':
          $ref: '#/components/responses/HtmlInternalError'
        '503':
          $ref: '#/components/responses/HtmlServiceUnavailable'
    post:
      operationId: submitConfirmEmailChangePage
      tags:
        - pages
      summary: Patvirtinti naująjį el. pašto adresą
      description: |-
        El. pašto adreso keitimo puslapio forma. Adresas pasikeičia ir laikomas patvirtintu; įrenginiai lieka prisijungę; anksčiau išsiųstos nuorodos (patvirtinimo, slaptažodžio nustatymo iš naujo, kitų keitimų) nustoja veikti; ankstesniu adresu apie tai pranešama, naująjį adresą užmaskavus.

        **Riba:** `auth`.
      security: []
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              $ref: '#/components/schemas/LinkTokenForm'
            example:
              token: lZ50AvPxUwbGqQCwWPIO28ONray7SzW0Cl_sLImwF3Q
          application/json:
            schema:
              $ref: '#/components/schemas/LinkTokenForm'
            example:
              token: lZ50AvPxUwbGqQCwWPIO28ONray7SzW0Cl_sLImwF3Q
      responses:
        '200':
          $ref: '#/components/responses/HtmlPage'
          description: Adresas pakeistas.
        '400':
          $ref: '#/components/responses/HtmlBadRequest'
          description: Nuoroda netinkama, panaudota arba nebegalioja, arba forma netinkama (HTML puslapis).
        '408':
          $ref: '#/components/responses/HtmlRequestTimeout'
        '409':
          $ref: '#/components/responses/HtmlConflict'
          description: Per tą laiką adresą užėmė kita paskyra (HTML puslapis).
        '413':
          $ref: '#/components/responses/HtmlPayloadTooLarge'
        '414':
          $ref: '#/components/responses/UriTooLong'
        '415':
          $ref: '#/components/responses/HtmlUnsupportedMediaType'
        '429':
          $ref: '#/components/responses/HtmlTooManyRequests'
          description: Riba `auth` (HTML puslapis) arba adresų lygmuo (JSON `rate_limited`).
        '500':
          $ref: '#/components/responses/HtmlInternalError'
        '503':
          $ref: '#/components/responses/HtmlServiceUnavailable'
          description: 'Duomenų bazė liko užrakinta (`Retry-After: 1`) arba serveris labai atsilieka (`Retry-After` nuo 2 iki 5, žr. įvadą): niekas nepakeista, nuoroda vis dar veikia. Taip pat, kai baigiasi doroklės skirtasis laikas.'
  /healthz:
    servers:
      - url: https://caissa.scacelith.com
        description: Oficialus serveris, šaknyje.
      - url: https://caissa.scacelith.com/api/v1
        description: Oficialus serveris, po `/api/v1`.
      - url: https://{host}:{port}
        description: Bet kuris Scacelith serveris, šaknyje.
        variables:
          host:
            default: caissa.scacelith.com
            description: Viešasis serverio pagrindinio kompiuterio vardas (`SERVER_PUBLIC_HOST`).
          port:
            default: "443"
            description: Viešasis API prievadas (`PUBLIC_API_PORT`, o jei jo nėra – `API_PORT`).
      - url: https://{host}:{port}/api/v1
        description: Bet kuris Scacelith serveris, po `/api/v1`.
        variables:
          host:
            default: caissa.scacelith.com
            description: Viešasis serverio pagrindinio kompiuterio vardas (`SERVER_PUBLIC_HOST`).
          port:
            default: "443"
            description: Viešasis API prievadas (`PUBLIC_API_PORT`, o jei jo nėra – `API_PORT`).
    get:
      operationId: getLiveness
      tags:
        - health
      summary: Patikrinti, ar procesas veikia
      description: |-
        Atsako 200 `ok`, kol procesas veikia. Veikia ir `HEAD`. Bet kuris kitas metodas gauna atsakymą 405 `method_not_allowed` su `Allow: GET, HEAD` (įskaitant `OPTIONS`).

        **Ribos:** tik adresų lygmuo.
      security: []
      responses:
        '200':
          description: Procesas veikia.
          headers:
            Cache-Control:
              $ref: '#/components/headers/CacheControl'
          content:
            application/json:
              schema:
                type: object
                required:
                  - status
                properties:
                  status:
                    const: ok
              example:
                status: ok
        '429':
          $ref: '#/components/responses/TooManyRequests'
          description: '`rate_limited`: adresų lygmuo.'
  /readyz:
    servers:
      - url: https://caissa.scacelith.com
        description: Oficialus serveris, šaknyje.
      - url: https://caissa.scacelith.com/api/v1
        description: Oficialus serveris, po `/api/v1`.
      - url: https://{host}:{port}
        description: Bet kuris Scacelith serveris, šaknyje.
        variables:
          host:
            default: caissa.scacelith.com
            description: Viešasis serverio pagrindinio kompiuterio vardas (`SERVER_PUBLIC_HOST`).
          port:
            default: "443"
            description: Viešasis API prievadas (`PUBLIC_API_PORT`, o jei jo nėra – `API_PORT`).
      - url: https://{host}:{port}/api/v1
        description: Bet kuris Scacelith serveris, po `/api/v1`.
        variables:
          host:
            default: caissa.scacelith.com
            description: Viešasis serverio pagrindinio kompiuterio vardas (`SERVER_PUBLIC_HOST`).
          port:
            default: "443"
            description: Viešasis API prievadas (`PUBLIC_API_PORT`, o jei jo nėra – `API_PORT`).
    get:
      operationId: getReadiness
      tags:
        - health
      summary: Patikrinti, ar serveris priima žaidėjus
      description: |-
        Atsako 200 `ready`, kai serveris priima žaidėjus, ir 503 `not_ready`, kol jis paleidžiamas ar stabdomas. Veikia ir `HEAD`. Bet kuris kitas metodas gauna atsakymą 405 `method_not_allowed` su `Allow: GET, HEAD` (įskaitant `OPTIONS`).

        **Ribos:** tik adresų lygmuo.
      security: []
      responses:
        '200':
          description: Serveris priima žaidėjus.
          headers:
            Cache-Control:
              $ref: '#/components/headers/CacheControl'
          content:
            application/json:
              schema:
                type: object
                required:
                  - status
                properties:
                  status:
                    const: ready
              example:
                status: ready
        '429':
          $ref: '#/components/responses/TooManyRequests'
          description: '`rate_limited`: adresų lygmuo.'
        '503':
          description: Serveris paleidžiamas arba stabdomas.
          headers:
            Cache-Control:
              $ref: '#/components/headers/CacheControl'
          content:
            application/json:
              schema:
                type: object
                required:
                  - status
                properties:
                  status:
                    const: not_ready
              example:
                status: not_ready
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: sct_[A-Za-z0-9_-]{43}
      description: |-
        Seanso žetonas (`sct_` ir 43 base64url simboliai) iš prisijungimo atsakymo, antraštėje `Authorization`: `Authorization: Bearer <token>`. Priešdėlis yra lygiai `Bearer` (skiriamos didžiosios ir mažosios raidės) ir vienas tarpas; reikšmė be jo arba žetonas, kurio ilgis nėra nuo 1 iki 512 spausdinamų ASCII simbolių, gauna atsakymą 401 `invalid_token` kaip ir bet kuris kitas netinkamas žetonas. Tuščia antraštė laikoma nenurodyta.
  parameters:
    GameId:
      name: id
      in: path
      required: true
      description: Partijos ID – teigiamas dešimtainis sveikasis skaičius, turintis ne daugiau kaip 16 skaitmenų, be pradinių nulių, mažesnis nei 2^53.
      schema:
        type: integer
        format: int64
        minimum: 1
        maximum: 9007199254740991
      example: 4100000000001
    SessionId:
      name: id
      in: path
      required: true
      description: Seanso ID, kaip jį pateikia `GET /auth/sessions`, užrašytas be pradinių nulių (`03` nėra 3 seansas).
      schema:
        type: integer
        format: int64
        minimum: 1
      example: 3
    Username:
      name: username
      in: path
      required: true
      description: Žaidėjo naudotojo vardas, neskiriant didžiųjų ir mažųjų raidžių. Serveris priima bet kurį vardą, kurį sudaro nuo 2 iki 24 simbolių iš `[A-Za-z0-9_.-]` (plačiausia kada nors leista taisyklė).
      schema:
        type: string
        pattern: '^[A-Za-z0-9_.-]{2,24}$'
      example: alice
    BeforeQuery:
      name: before
      in: query
      required: false
      description: Partijos ID; pateikiamos tik senesnės partijos. Perduokite ankstesnio puslapio `next`. Tuščia reikšmė laikoma nenurodyta.
      schema:
        type: integer
        format: int64
        minimum: 1
        maximum: 9007199254740991
      example: 4100000000002
    LimitQuery:
      name: limit
      in: query
      required: false
      description: Puslapio dydis, nuo 1 iki 50. Didesnis skaičius, turintis iki 3 skaitmenų, laikomas 50; tuščia reikšmė laikoma nenurodyta.
      schema:
        type: integer
        minimum: 1
        maximum: 999
        default: 20
    LinkTokenQuery:
      name: token
      in: query
      required: false
      description: Laiško nuorodos žetonas (43 base64url simboliai). Jei jo nėra arba jis neteisingas, rodomas puslapis „link invalid or expired“ (nuoroda netinkama arba nebegalioja).
      schema:
        type: string
        pattern: '^[A-Za-z0-9_-]{43}$'
      example: asfioO4P0r_Zn5DxWzcQuF6Drgxu7wXGjCyS-UFbTaw
  headers:
    CacheControl:
      description: Kiekvienas serverio atsakymas draudžia jį saugoti talpykloje.
      schema:
        type: string
        const: no-store
    RetryAfter:
      description: Kiek sekundžių palaukti prieš bandant dar kartą; ta pati reikšmė kaip `retryAfter` turinyje.
      schema:
        type: integer
        minimum: 1
      example: 30
    WwwAuthenticate:
      description: Bearer autentifikavimo iššūkis, su `error="invalid_token"` netinkamam žetonui.
      schema:
        type: string
        enum:
          - Bearer realm="scacelith"
          - Bearer realm="scacelith", error="invalid_token"
    ConnectionClose:
      description: Po šio atsakymo serveris uždaro ryšį.
      schema:
        type: string
        const: close
  responses:
    BadRequest:
      description: '`invalid_request` (su `field`, kai klaida yra turinio lauke) arba `invalid_json`.'
      headers:
        Cache-Control:
          $ref: '#/components/headers/CacheControl'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          examples:
            invalidRequest:
              summary: Laukas pažeidžia schemą
              value:
                error: invalid_request
                message: '"password" is required'
                field: password
            invalidJson:
              summary: Turinys nėra JSON
              value:
                error: invalid_json
                message: The body is not valid JSON.
            weakPassword:
              summary: Silpnas slaptažodis
              value:
                error: weak_password
                message: The password must have at least 10 characters.
                reason: too_short
            invalidPgn:
              summary: PGN, kurio neįmanoma perskaityti
              value:
                error: invalid_pgn
                message: illegal move 'Ke3'
                line: 1
                column: 13
    Unauthorized:
      description: '`unauthorized` (nėra antraštės `Authorization`) arba `invalid_token` (žetonas netaisyklingas, nebegalioja, atšauktas arba priklauso ištrintai paskyrai).'
      headers:
        Cache-Control:
          $ref: '#/components/headers/CacheControl'
        WWW-Authenticate:
          $ref: '#/components/headers/WwwAuthenticate'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          examples:
            unauthorized:
              summary: Nėra seanso
              value:
                error: unauthorized
                message: Log in first.
            invalidToken:
              summary: Netinkamas žetonas
              value:
                error: invalid_token
                message: The session is invalid or has expired; log in again.
    Forbidden:
      description: Užklausa atmesta.
      headers:
        Cache-Control:
          $ref: '#/components/headers/CacheControl'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          examples:
            invalidPassword:
              summary: Neteisingas slaptažodis pakartotinio autentifikavimo metu
              value:
                error: invalid_password
                message: Wrong password.
            banned:
              summary: Sustabdyta paskyra
              value:
                error: banned
                message: This account is banned.
                until: 1791487639708
    NotFound:
      description: '`not_found` arba funkcija, kurią šis serveris išjungė.'
      headers:
        Cache-Control:
          $ref: '#/components/headers/CacheControl'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error: not_found
            message: No such game.
    RequestTimeout:
      description: '`request_timeout`: turinys negautas per 10 sekundžių. Serveris uždaro ryšį.'
      headers:
        Cache-Control:
          $ref: '#/components/headers/CacheControl'
        Connection:
          $ref: '#/components/headers/ConnectionClose'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error: request_timeout
            message: The request body took too long.
    Conflict:
      description: Užklausa prieštarauja serverio būsenai.
      headers:
        Cache-Control:
          $ref: '#/components/headers/CacheControl'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error: username_taken
            message: This username is already taken.
    Gone:
      description: '`sso_expired`: prisijungimo su Google bandymas ar bilietas nežinomas, panaudotas arba nebegalioja; pradėkite prisijungimą iš naujo.'
      headers:
        Cache-Control:
          $ref: '#/components/headers/CacheControl'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error: sso_expired
            message: This sign-in has expired; start again from Scacelith.
    PayloadTooLarge:
      description: '`payload_too_large`: turinys viršija `HTTP_BODY_LIMIT` (numatytoji reikšmė – 16 384 baitai). Serveris uždaro ryšį.'
      headers:
        Cache-Control:
          $ref: '#/components/headers/CacheControl'
        Connection:
          $ref: '#/components/headers/ConnectionClose'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error: payload_too_large
            message: The body must not exceed 16384 bytes.
    UriTooLong:
      description: '`uri_too_long`: užklausos tikslas viršija 4096 simbolius.'
      headers:
        Cache-Control:
          $ref: '#/components/headers/CacheControl'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error: uri_too_long
            message: The URL is too long.
    UnsupportedMediaType:
      description: '`unsupported_media_type`: turinys nėra `application/json` arba jo koduotė nėra UTF-8.'
      headers:
        Cache-Control:
          $ref: '#/components/headers/CacheControl'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error: unsupported_media_type
            message: Content-Type must be application/json.
    UnprocessableContent:
      description: '`game_too_long`: partijoje yra daugiau nei `GIF_MAX_PLIES` (600) pusėjimių.'
      headers:
        Cache-Control:
          $ref: '#/components/headers/CacheControl'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error: game_too_long
            message: The game is too long for a GIF (742 plies, at most 600).
    PowRequired:
      description: '`pow_required`: išspręskite iššūkį `pow` ir dar kartą išsiųskite tą pačią užklausą su `pow: { challenge, nonce }`. `reason` nurodo priežastį: `required`, `malformed`, `signature`, `endpoint`, `network`, `expired`, `bits`, `work` arba `replayed`.'
      headers:
        Cache-Control:
          $ref: '#/components/headers/CacheControl'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error: pow_required
            message: Proof of work required.
            reason: required
            pow:
              challenge: eyJ2IjoxLCJlIjoicmVnaXN0ZXIiLCJiIjoxOCwieCI6MTc5MDg4Mjk5MTIwMH0.6Ku_D7kw3S69OYxj-9KxsXszRCwz6ECnOQ-B6-v2erM
              bits: 18
              expiresAt: 1790882991200
    TooManyRequests:
      description: '`rate_limited` (užklausų dažnio riba, paskyros biudžetas arba slaptažodžių maišos eilė) arba `too_many_attempts` (paskyros stabdymas), su `retryAfter` ir antrašte `Retry-After`.'
      headers:
        Cache-Control:
          $ref: '#/components/headers/CacheControl'
        Retry-After:
          $ref: '#/components/headers/RetryAfter'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          examples:
            rateLimited:
              summary: Užklausų dažnio riba
              value:
                error: rate_limited
                message: Too many requests; try again later.
                retryAfter: 30
            tooManyAttempts:
              summary: Per daug nesėkmių šioje paskyroje
              value:
                error: too_many_attempts
                message: Too many attempts; wait before trying again.
                retryAfter: 4
    InternalError:
      description: '`internal_error`: netikėta klaida. Serveris ją įrašo į žurnalą.'
      headers:
        Cache-Control:
          $ref: '#/components/headers/CacheControl'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error: internal_error
            message: Internal server error.
    BadGateway:
      description: '`sso_failed`: neteisingas `state` ar `iss` arba Google atmetė kodą ar atsiuntė ID žetoną, kurio nepavyksta patikrinti (įskaitant kito kliento – žaidimo ar svetainės – kodą ar ID žetoną). Jame niekada nebūna Google teksto.'
      headers:
        Cache-Control:
          $ref: '#/components/headers/CacheControl'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error: sso_failed
            message: Google sign-in could not be completed.
    ServiceUnavailable:
      description: '`server_busy`, `busy` arba `timeout`: bandykite dar kartą po `retryAfter`, kai ši reikšmė pateikta.'
      headers:
        Cache-Control:
          $ref: '#/components/headers/CacheControl'
        Retry-After:
          $ref: '#/components/headers/RetryAfter'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          examples:
            serverBusy:
              summary: Serveris užimtas
              value:
                error: server_busy
                message: The server is busy; try again in a few seconds.
                retryAfter: 9
            busy:
              summary: Duomenų bazė liko užrakinta (skaitymas)
              value:
                error: busy
                message: Try again shortly.
                retryAfter: 1
            timeout:
              summary: Serveris per ilgai užtruko
              value:
                error: timeout
                message: The server took too long to answer; try again.
    GifFile:
      description: Animuotas GIF kaip priedas.
      headers:
        Cache-Control:
          $ref: '#/components/headers/CacheControl'
        Content-Disposition:
          description: '`attachment; filename="scacelith-<id>.gif"` šio serverio partijai, `attachment; filename="scacelith-game.gif"` su `POST /gif` atsiųstam PGN.'
          schema:
            type: string
            pattern: '^attachment; filename="scacelith-([1-9][0-9]{0,15}|game)\.gif"$'
          example: attachment; filename="scacelith-4100000000001.gif"
        Content-Length:
          description: Failo dydis baitais.
          schema:
            type: integer
            minimum: 1
      content:
        image/gif:
          schema:
            type: string
            format: binary
    HtmlPage:
      description: HTML puslapis.
      headers:
        Cache-Control:
          $ref: '#/components/headers/CacheControl'
      content:
        text/html:
          schema:
            $ref: '#/components/schemas/HtmlDocument'
    HtmlBadRequest:
      description: HTML puslapis, paaiškinantis atmetimo priežastį.
      headers:
        Cache-Control:
          $ref: '#/components/headers/CacheControl'
      content:
        text/html:
          schema:
            $ref: '#/components/schemas/HtmlDocument'
    HtmlRequestTimeout:
      description: Forma negauta per 10 sekundžių (HTML puslapis). Serveris uždaro ryšį.
      headers:
        Cache-Control:
          $ref: '#/components/headers/CacheControl'
        Connection:
          $ref: '#/components/headers/ConnectionClose'
      content:
        text/html:
          schema:
            $ref: '#/components/schemas/HtmlDocument'
    HtmlConflict:
      description: HTML puslapis, paaiškinantis konfliktą.
      headers:
        Cache-Control:
          $ref: '#/components/headers/CacheControl'
      content:
        text/html:
          schema:
            $ref: '#/components/schemas/HtmlDocument'
    HtmlPayloadTooLarge:
      description: Forma viršija `HTTP_BODY_LIMIT` (HTML puslapis). Serveris uždaro ryšį.
      headers:
        Cache-Control:
          $ref: '#/components/headers/CacheControl'
        Connection:
          $ref: '#/components/headers/ConnectionClose'
      content:
        text/html:
          schema:
            $ref: '#/components/schemas/HtmlDocument'
    HtmlUnsupportedMediaType:
      description: Turinys nėra nei forma (`application/x-www-form-urlencoded`), nei JSON, arba jo koduotė nėra UTF-8 (HTML puslapis).
      headers:
        Cache-Control:
          $ref: '#/components/headers/CacheControl'
      content:
        text/html:
          schema:
            $ref: '#/components/schemas/HtmlDocument'
    HtmlTooManyRequests:
      description: Užklausų dažnio riba (HTML puslapis), su `Retry-After`.
      headers:
        Cache-Control:
          $ref: '#/components/headers/CacheControl'
        Retry-After:
          $ref: '#/components/headers/RetryAfter'
      content:
        text/html:
          schema:
            $ref: '#/components/schemas/HtmlDocument'
    HtmlInternalError:
      description: Netikėta klaida (HTML puslapis pavadinimu „Server error“ – serverio klaida). Serveris ją įrašo į žurnalą.
      headers:
        Cache-Control:
          $ref: '#/components/headers/CacheControl'
      content:
        text/html:
          schema:
            $ref: '#/components/schemas/HtmlDocument'
    HtmlServiceUnavailable:
      description: Serveris užimtas (duomenų bazė liko užrakinta arba serveris labai atsilieka; su `Retry-After`) arba per ilgai užtruko (HTML puslapis pavadinimu „Server error“ – serverio klaida).
      headers:
        Cache-Control:
          $ref: '#/components/headers/CacheControl'
        Retry-After:
          $ref: '#/components/headers/RetryAfter'
      content:
        text/html:
          schema:
            $ref: '#/components/schemas/HtmlDocument'
  schemas:
    Error:
      type: object
      description: Vienintelė visų JSON API klaidos atsakymų struktūra.
      required:
        - error
        - message
      properties:
        error:
          type: string
          pattern: '^[a-z][a-z0-9_]*$'
          description: Klaidos kodas `snake_case` formatu. Pagal jį klientas pasirenka, ką rodyti.
          examples:
            - rate_limited
        message:
          type: string
          description: Sakinys anglų kalba, skirtas žurnalams ir kaip atsarginis variantas.
        retryAfter:
          type: integer
          minimum: 1
          description: Kiek sekundžių palaukti prieš bandant dar kartą; būna tik tuose atmetimuose, kurie laikui bėgant baigiasi. Tada atsakyme taip pat yra antraštė `Retry-After` su ta pačia reikšme, išskyrus 503 `busy` skaitant istoriją, partijas, žaidėjus ir lyderių lentelę.
        field:
          type: string
          description: Laukas arba užklausos eilutės parametras, kuriame klaida (`invalid_request`, `invalid_option`, taip pat `GET /account/games` klaidos `invalid_cursor`, `invalid_limit` ir `invalid_filter`). Įdėtinis laukas nurodomas su tašku (`pow.nonce`).
        reason:
          type: string
          description: 'Priežastis: taisyklė, kurią pažeidžia `weak_password`, arba kodėl darbo įrodymo buvo paprašyta ar jis atmestas (`pow_required`).'
          enum:
            - too_short
            - too_long
            - contains_username
            - contains_email
            - too_common
            - required
            - malformed
            - signature
            - endpoint
            - network
            - expired
            - bits
            - work
            - replayed
        pow:
          $ref: '#/components/schemas/PowChallenge'
        until:
          type:
            - integer
            - 'null'
          format: int64
          description: 'Tik `banned`: sustabdymo pabaiga (Unix laikas, ms), `null`, jei sustabdyta visam laikui.'
        line:
          type: integer
          minimum: 1
          description: 'Tik `invalid_pgn`: klaidos eilutė, skaičiuojant nuo 1.'
        column:
          type: integer
          minimum: 1
          description: 'Tik `invalid_pgn`: klaidos stulpelis, skaičiuojant nuo 1, simboliais.'
    PowChallenge:
      type: object
      description: Darbo įrodymo iššūkis (atsakymo 428 `pow_required` laukas `pow`).
      required:
        - challenge
        - bits
        - expiresAt
      properties:
        challenge:
          type: string
          pattern: '^[A-Za-z0-9_-]{16,400}\.[A-Za-z0-9_-]{43}$'
          description: Pasirašytas iššūkis, kurį reikia grąžinti nepakeistą.
        bits:
          type: integer
          minimum: 1
          maximum: 26
          description: Kiek pradinių nulinių bitų turi turėti `SHA-256(challenge + ":" + nonce)`.
        expiresAt:
          type: integer
          format: int64
          description: Kada iššūkis nustoja galioti (Unix laikas, ms) – praėjus 2 minutėms po jo išdavimo.
    PowAnswer:
      type: object
      description: Atsakymas į darbo įrodymo iššūkį.
      additionalProperties: false
      required:
        - challenge
        - nonce
      properties:
        challenge:
          type: string
          minLength: 16
          maxLength: 512
          description: Atsakymo 428 `challenge`, toks, koks buvo pateiktas.
        nonce:
          type: string
          pattern: '^[0-9]{1,20}$'
          description: Dešimtainė eilutė iš ne daugiau kaip 20 skaitmenų, su kuria `SHA-256(challenge + ":" + nonce)` prasideda `bits` nuliniais bitais.
    ServerInfo:
      type: object
      description: Ko klientui reikia prieš prisijungiant ar užmezgant ryšį.
      required:
        - name
        - serverId
        - motd
        - protocol
        - wsPort
        - wsPath
        - registration
        - emailVerification
        - sso
        - mfa
        - pow
        - categories
        - limits
      properties:
        name:
          type: string
          description: Serverio pavadinimas (`SERVER_NAME`).
        serverId:
          type:
            - string
            - 'null'
          format: uuid
          description: UUID, kurį duomenų bazė gauna, kai paleidžiama pirmą kartą. Paleidus iš naujo jis nesikeičia. Jis yra `null`, kai duomenų bazė negali jo pateikti.
        motd:
          type: string
          description: Dienos pranešimas (`SERVER_MOTD`), gali būti tuščias.
        protocol:
          type: object
          description: WebSocket protokolas (`PROTOCOL.md`).
          required:
            - min
            - max
            - schema
            - subprotocol
          properties:
            min:
              type: integer
              minimum: 1
              description: Seniausia serverio palaikoma protokolo versija.
            max:
              type: integer
              minimum: 1
              description: Naujausia serverio palaikoma protokolo versija.
            schema:
              type: integer
              format: int64
              minimum: 0
              maximum: 4294967295
              description: 'Protokolo schemos kontrolinis kodas (pirmieji 4 jos kanoninės formos SHA-256 baitai kaip sveikasis skaičius be ženklo: `0xc649140f`, 3326678031, protokolo šalutinei versijai 2); tik informacinis, niekada nelyginamas.'
            subprotocol:
              type: string
              description: WebSocket subprotokolo identifikatorius (`Sec-WebSocket-Protocol`).
        wsPort:
          type: integer
          minimum: 0
          maximum: 65535
          description: WebSocket prievadas, kurį naudoja žaidėjai (`PUBLIC_WS_PORT`, jei jo nėra – `WS_PORT`, jei ir jo nėra – `API_PORT`).
        wsPath:
          type: string
          const: /ws
          description: WebSocket kelias.
        registration:
          type: string
          enum:
            - open
            - closed
          description: Ar galima kurti naujas paskyras.
        emailVerification:
          type: boolean
          description: Ar naujos paskyros turi patvirtinti savo adresą (`REQUIRE_EMAIL_VERIFICATION`).
        sso:
          type: object
          required:
            - google
            - googleWeb
          properties:
            google:
              type: boolean
              description: Ar žaidimui siūlomas prisijungimas su Google.
            googleWeb:
              type: boolean
              description: Ar prisijungimas su Google siūlomas ir svetainei (`POST /auth/sso/google/web/start`).
        mfa:
          type: boolean
          const: true
          description: Dviejų veiksnių autentifikavimas visada prieinamas.
        pow:
          type: object
          required:
            - register
          properties:
            register:
              type: integer
              minimum: 0
              maximum: 26
              description: Kiek darbo įrodymo bitų reikia registracijai (0 – nereikia).
        categories:
          type: array
          description: Oficialios (reitinginės) laiko kontrolės (`RATED_CATEGORIES`) serverio tvarka. Bet kuri kita laiko kontrolė yra `custom`.
          items:
            $ref: '#/components/schemas/Category'
        limits:
          type: object
          description: Taisyklės, kurias klientas gali patikrinti prieš siųsdamas formą.
          required:
            - usernameMin
            - usernameMax
            - usernamePattern
            - passwordMinLength
            - passwordMaxBytes
            - customTimeControls
            - reportsPerDay
            - wsMaxMessageBytes
          properties:
            usernameMin:
              type: integer
              description: Trumpiausio naudotojo vardo ilgis (`USERNAME_MIN`).
            usernameMax:
              type: integer
              description: Ilgiausio naudotojo vardo ilgis (`USERNAME_MAX`).
            usernamePattern:
              type: string
              description: Reguliarusis reiškinys, kurį turi atitikti naujas naudotojo vardas.
            passwordMinLength:
              type: integer
              description: Trumpiausio slaptažodžio ilgis simboliais (`PASSWORD_MIN_LENGTH`).
            passwordMaxBytes:
              type: integer
              description: Ilgiausio slaptažodžio ilgis UTF-8 baitais.
            customTimeControls:
              type: boolean
              description: Ar iššūkiuose ir privačiose partijose galima naudoti pasirinktines laiko kontroles.
            reportsPerDay:
              type: integer
              description: Kiek pranešimų žaidėjas gali pateikti per 24 valandas (`REPORTS_PER_DAY`).
            wsMaxMessageBytes:
              type: integer
              description: Didžiausias WebSocket pranešimas, kurį gali siųsti klientas, baitais.
      example:
        name: Scacelith
        serverId: 07dd26af-672a-43af-a8af-34011c7e977b
        motd: ''
        protocol:
          min: 1
          max: 1
          schema: 3326678031
          subprotocol: scacelith.rt1
        wsPort: 443
        wsPath: /ws
        registration: open
        emailVerification: true
        sso:
          google: false
          googleWeb: false
        mfa: true
        pow:
          register: 18
        categories:
          - id: '1+0'
            baseSec: 60
            incSec: 0
          - id: '3+2'
            baseSec: 180
            incSec: 2
        limits:
          usernameMin: 3
          usernameMax: 20
          usernamePattern: ^[A-Za-z0-9][A-Za-z0-9_-]*$
          passwordMinLength: 10
          passwordMaxBytes: 256
          customTimeControls: true
          reportsPerDay: 5
          wsMaxMessageBytes: 512
    Category:
      type: object
      description: Oficiali laiko kontrolė.
      required:
        - id
        - baseSec
        - incSec
      properties:
        id:
          type: string
          pattern: '^[0-9]+\+[0-9]+$'
          description: 'Kategorijos ID: minutės ir pridėtinio laiko sekundės (`3+2`).'
        baseSec:
          type: number
          minimum: 0
          description: Pagrindinis laikas sekundėmis.
        incSec:
          type: number
          minimum: 0
          description: Pridėtinis laikas už ėjimą sekundėmis.
    AccountView:
      type: object
      description: Paskyra taip, kaip ją mato jos žaidėjas (prisijungimo atsakymų ir `GET /account/me` laukas `user`).
      required:
        - id
        - username
        - email
        - emailVerified
        - mfaEnabled
        - googleLinked
        - hasPassword
        - acceptChallenges
        - createdAt
        - lastLoginAt
        - pendingEmail
      properties:
        id:
          type: integer
          format: int64
          description: Paskyros ID.
        username:
          type: string
          description: Naudotojo vardas.
        email:
          type: string
          format: email
          description: El. pašto adresas.
        emailVerified:
          type: boolean
          description: Ar adresas patvirtintas.
        mfaEnabled:
          type: boolean
          description: Ar įjungtas dviejų veiksnių autentifikavimas.
        googleLinked:
          type: boolean
          description: Ar susieta Google paskyra.
        hasPassword:
          type: boolean
          description: '`false` paskyrai, sukurtai per Google, kuri dar nenustatė slaptažodžio.'
        acceptChallenges:
          type: string
          enum:
            - all
            - none
          description: Ar priimami tiesioginiai iššūkiai pagal vardą (žr. `PUT /account/preferences`).
        createdAt:
          type: integer
          format: int64
          description: Kada paskyra sukurta (Unix laikas, ms).
        lastLoginAt:
          type:
            - integer
            - 'null'
          format: int64
          description: Paskutinis prisijungimas (Unix laikas, ms) arba `null`.
        pendingEmail:
          type:
            - string
            - 'null'
          format: email
          description: Naujas adresas, kai el. pašto adreso keitimas laukia patvirtinimo nuoroda, arba `null`.
    SessionAnswer:
      type: object
      description: Naujas seansas.
      required:
        - token
        - expiresAt
        - user
      properties:
        token:
          type: string
          pattern: '^sct_[A-Za-z0-9_-]{43}$'
          description: Seanso žetonas antraštei `Authorization` ir WebSocket.
        expiresAt:
          type: integer
          format: int64
          description: Galutinė seanso pabaiga (Unix laikas, ms); neveiklumo riba gali jį baigti anksčiau.
        user:
          $ref: '#/components/schemas/AccountView'
      example:
        token: sct_L_8GDd7uzfQ3QQWtqrsWXDTsFWzRwIvJcwIGHhjWPS8
        expiresAt: 1798658839708
        user:
          id: 1
          username: alice
          email: alice@example.org
          emailVerified: true
          mfaEnabled: true
          googleLinked: false
          hasPassword: true
          acceptChallenges: all
          createdAt: 1790882839743
          lastLoginAt: 1790882839708
          pendingEmail: null
    MfaChallenge:
      type: object
      description: Antrasis prisijungimo su dviejų veiksnių autentifikavimu žingsnis; tęskite su `POST /auth/login/mfa`.
      required:
        - mfaRequired
        - mfaToken
        - expiresIn
      properties:
        mfaRequired:
          type: boolean
          const: true
        mfaToken:
          type: string
          pattern: '^mfa_[A-Za-z0-9_-]{43}$'
          description: Žingsnio žetonas, skirtas `POST /auth/login/mfa`.
        expiresIn:
          type: integer
          const: 300
          description: Kiek sekundžių liko žingsniui užbaigti.
    LoginAnswer:
      description: Seansas arba antrasis prisijungimo su dviejų veiksnių autentifikavimu žingsnis.
      oneOf:
        - $ref: '#/components/schemas/SessionAnswer'
        - $ref: '#/components/schemas/MfaChallenge'
    SsoNeedsUsername:
      type: object
      description: Pirmasis prisijungimas su Google; tęskite su `POST /auth/sso/complete`.
      required:
        - needsUsername
        - ssoTicket
        - suggestedUsername
      properties:
        needsUsername:
          type: boolean
          const: true
        ssoTicket:
          type: string
          pattern: '^sso_[A-Za-z0-9_-]{43}$'
          description: Bilietas, skirtas `POST /auth/sso/complete`, galioja 10 minučių.
        suggestedUsername:
          type: string
          description: Naudotojo vardas, sudarytas iš Google vardo ar adreso, arba `""`, kai niekas netinka.
    SsoNeedsPassword:
      type: object
      description: Adresą naudoja paskyra su slaptažodžiu; tęskite su `POST /auth/sso/google/link`. Kol kas niekas nesusieta.
      required:
        - needsPassword
        - linkTicket
        - username
        - expiresIn
      properties:
        needsPassword:
          type: boolean
          const: true
        linkTicket:
          type: string
          pattern: '^sso_[A-Za-z0-9_-]{43}$'
          description: Bilietas, skirtas `POST /auth/sso/google/link`.
        username:
          type: string
          description: Tos paskyros naudotojo vardas (jį mato tik tas, kas Google įrodė, kad valdo šį adresą).
        expiresIn:
          type: integer
          const: 600
          description: Kiek sekundžių bilietas galioja.
    SsoFinishAnswer:
      description: '`POST /auth/sso/google/finish` atsakymas.'
      oneOf:
        - $ref: '#/components/schemas/SessionAnswer'
        - $ref: '#/components/schemas/MfaChallenge'
        - $ref: '#/components/schemas/SsoNeedsUsername'
        - $ref: '#/components/schemas/SsoNeedsPassword'
    SsoStartAnswer:
      type: object
      description: Prisijungimo su Google bandymas.
      required:
        - attemptId
        - authUrl
        - state
        - expiresIn
      properties:
        attemptId:
          type: string
          pattern: '^sso_[A-Za-z0-9_-]{43}$'
          description: Bandymas, skirtas `POST /auth/sso/google/finish`.
        authUrl:
          type: string
          format: uri
          description: Google URL, kurį reikia atidaryti sistemos naršyklėje, kai jis patikrintas.
        state:
          type: string
          pattern: '^[A-Za-z0-9_-]{43}$'
          description: '`state`, kurį Google turi grąžinti.'
        expiresIn:
          type: integer
          const: 600
          description: Kiek sekundžių bandymas galioja.
    VerificationSentStatus:
      type: object
      required:
        - status
      properties:
        status:
          const: verification_sent
    AcceptedStatus:
      type: object
      required:
        - status
      properties:
        status:
          const: accepted
    LoggedOutStatus:
      type: object
      required:
        - status
      properties:
        status:
          const: logged_out
    RegisterRequest:
      type: object
      additionalProperties: false
      required:
        - username
        - email
        - password
      properties:
        username:
          type: string
          minLength: 1
          maxLength: 64
          description: Naudotojo vardas; po to taikomos serverio taisyklės (žr. aprašą).
        email:
          type: string
          minLength: 1
          maxLength: 254
          description: El. pašto adresas. Pašalinami tarpai pradžioje ir pabaigoje, saugomas mažosiomis raidėmis.
        password:
          type: string
          minLength: 1
          maxLength: 1024
          description: Slaptažodis; po to taikomos slaptažodžio taisyklės.
        pow:
          $ref: '#/components/schemas/PowAnswer'
    LoginRequest:
      type: object
      additionalProperties: false
      required:
        - login
        - password
      properties:
        login:
          type: string
          minLength: 1
          maxLength: 254
          description: Naudotojo vardas arba el. pašto adresas (bet koks tekstas su `@`).
        password:
          type: string
          minLength: 1
          maxLength: 1024
        clientLabel:
          $ref: '#/components/schemas/ClientLabel'
        pow:
          $ref: '#/components/schemas/PowAnswer'
    ClientLabel:
      type: string
      maxLength: 64
      description: Neprivalomas. Rodomas prisijungusių įrenginių sąraše (pvz., `Scacelith 1.4 (Windows)`).
    MfaLoginRequest:
      type: object
      additionalProperties: false
      required:
        - mfaToken
      properties:
        mfaToken:
          type: string
          minLength: 1
          maxLength: 64
          description: Prisijungimo atsakymo (arba prisijungimo su Google atsakymo) `mfaToken`.
        code:
          type: string
          maxLength: 32
          description: Neprivalomas. 6 skaitmenų autentifikavimo programėlės kodas arba atkūrimo kodas.
        recoveryCode:
          type: string
          maxLength: 32
          description: Neprivalomas. Atkūrimo kodas. Reikia vieno iš `code` ir `recoveryCode`.
    EmailRequest:
      type: object
      additionalProperties: false
      required:
        - email
      properties:
        email:
          type: string
          minLength: 1
          maxLength: 254
          description: El. pašto adresas.
    PasswordResetRequest:
      type: object
      additionalProperties: false
      required:
        - token
        - newPassword
      properties:
        token:
          type: string
          minLength: 1
          maxLength: 128
          description: Nustatymo iš naujo nuorodos parametras `token`.
        newPassword:
          type: string
          minLength: 1
          maxLength: 1024
          description: Naujas slaptažodis; taikomos registracijos slaptažodžio taisyklės.
    SsoStartRequest:
      type: object
      additionalProperties: false
      required:
        - codeChallenge
        - redirectPort
      properties:
        codeChallenge:
          type: string
          pattern: '^[A-Za-z0-9_-]{43}$'
          description: Kliento `codeVerifier` S256 PKCE iššūkis (`BASE64URL(SHA-256(codeVerifier))`).
        redirectPort:
          type: integer
          minimum: 1024
          maximum: 65535
          description: Kliento klausytuvo prievadas adresu `127.0.0.1`.
    SsoWebStartRequest:
      type: object
      additionalProperties: false
      required:
        - codeChallenge
      properties:
        codeChallenge:
          type: string
          pattern: '^[A-Za-z0-9_-]{43}$'
          description: Puslapio `codeVerifier` S256 PKCE iššūkis (`BASE64URL(SHA-256(codeVerifier))`).
    SsoFinishRequest:
      type: object
      additionalProperties: false
      required:
        - attemptId
        - codeVerifier
        - state
        - code
      properties:
        attemptId:
          type: string
          minLength: 1
          maxLength: 64
          description: '`attemptId` iš `POST /auth/sso/google/start`.'
        codeVerifier:
          type: string
          pattern: '^[A-Za-z0-9._~-]{43,128}$'
          description: PKCE tikrintuvas; jo SHA-256 turi atitikti iššūkį, pateiktą per `start`.
        state:
          type: string
          pattern: '^[A-Za-z0-9_-]{43}$'
          description: '`state`, toks, kokį grąžino Google.'
        code:
          type: string
          minLength: 1
          maxLength: 2048
          pattern: '^[\x21-\x7E]+$'
          description: '`code`, toks, kokį grąžino Google (spausdinami ASCII simboliai be tarpų).'
        iss:
          type: string
          minLength: 1
          maxLength: 256
          description: Neprivalomas. `iss`, toks, kokį grąžino Google, jei grąžino.
        clientLabel:
          $ref: '#/components/schemas/ClientLabel'
    SsoLinkRequest:
      type: object
      additionalProperties: false
      required:
        - linkTicket
        - password
      properties:
        linkTicket:
          type: string
          minLength: 1
          maxLength: 64
          description: '`linkTicket` iš `finish`, galioja 10 minučių.'
        password:
          type: string
          minLength: 1
          maxLength: 1024
          description: Paskyros slaptažodis.
        clientLabel:
          $ref: '#/components/schemas/ClientLabel'
        pow:
          $ref: '#/components/schemas/PowAnswer'
    SsoCompleteRequest:
      type: object
      additionalProperties: false
      required:
        - ssoTicket
        - username
      properties:
        ssoTicket:
          type: string
          minLength: 1
          maxLength: 64
          description: '`ssoTicket` iš `finish`, galioja 10 minučių.'
        username:
          type: string
          minLength: 1
          maxLength: 64
          description: Naudotojo vardas; taikomos registracijos taisyklės.
        clientLabel:
          $ref: '#/components/schemas/ClientLabel'
    SessionList:
      type: object
      required:
        - sessions
      properties:
        sessions:
          type: array
          description: Aktyvūs seansai, pradedant paskiausiai naudotu.
          items:
            $ref: '#/components/schemas/SessionEntry'
    SessionEntry:
      type: object
      description: Aktyvus seansas (prisijungęs įrenginys).
      required:
        - id
        - createdAt
        - lastSeenAt
        - expiresAt
        - clientLabel
        - current
      properties:
        id:
          type: integer
          format: int64
          description: Seanso ID, skirtas `DELETE /auth/sessions/{id}`.
        createdAt:
          type: integer
          format: int64
          description: Prisijungimas (Unix laikas, ms).
        lastSeenAt:
          type: integer
          format: int64
          description: Paskutinis naudojimas (Unix laikas, ms), atnaujinamas ne dažniau kaip kas 5 minutes (vėliau, kol serveris labai atsilieka).
        expiresAt:
          type: integer
          format: int64
          description: Galutinė pabaiga (Unix laikas, ms); neveiklumo riba gali baigti seansą anksčiau.
        clientLabel:
          type:
            - string
            - 'null'
          description: Prisijungimo metu pateiktas `clientLabel` arba `null`, jei jis nebuvo pateiktas.
        current:
          type: boolean
          description: Ar tai seansas, kuris siunčia užklausą.
    AccountMe:
      type: object
      required:
        - user
        - ratings
        - sanctions
        - ban
      properties:
        user:
          $ref: '#/components/schemas/AccountView'
        ratings:
          type: array
          description: Po vieną įrašą kiekvienai kategorijai, kurioje žaidėjas žaidė reitingines partijas.
          items:
            $ref: '#/components/schemas/RatingSummary'
        sanctions:
          type: array
          description: Aktyvios sankcijos.
          items:
            $ref: '#/components/schemas/ActiveSanction'
        ban:
          description: '`{ until }`, kol paskyra sustabdyta, kitaip `null`.'
          oneOf:
            - $ref: '#/components/schemas/Ban'
            - type: 'null'
    RatingSummary:
      type: object
      description: Vienos kategorijos reitingo įrašas.
      required:
        - category
        - rating
        - games
        - wins
        - draws
        - losses
        - peak
        - provisional
      properties:
        category:
          type: string
          description: Kategorijos ID.
        rating:
          type: integer
          description: Reitingas.
        games:
          type: integer
          minimum: 0
          description: Kategorijoje sužaistų partijų skaičius (įskaitytų ir neįskaitytų).
        wins:
          type: integer
          minimum: 0
        draws:
          type: integer
          minimum: 0
        losses:
          type: integer
          minimum: 0
        peak:
          type: integer
          description: Aukščiausias pasiektas reitingas.
        provisional:
          type: boolean
          description: '`true`, kol reitingas dar nenustatytas arba turi mažiau nei `PROVISIONAL_GAMES` įskaitytų partijų (žaidimas jį rodo kaip `1510?`).'
    ActiveSanction:
      type: object
      required:
        - kind
        - reason
        - startsAt
        - endsAt
      properties:
        kind:
          type: string
          enum:
            - ban
            - mm_block
            - warning
          description: Sankcijos rūšis.
        reason:
          type:
            - string
            - 'null'
          description: Nurodyta priežastis, jei yra.
        startsAt:
          type: integer
          format: int64
          description: Pradžia (Unix laikas, ms).
        endsAt:
          type:
            - integer
            - 'null'
          format: int64
          description: Pabaiga (Unix laikas, ms), `null`, kai sankcija neterminuota.
    Ban:
      type: object
      required:
        - until
      properties:
        until:
          type:
            - integer
            - 'null'
          format: int64
          description: Sustabdymo pabaiga (Unix laikas, ms), `null`, kai sustabdyta visam laikui.
    Preferences:
      type: object
      additionalProperties: false
      required:
        - acceptChallenges
      properties:
        acceptChallenges:
          type: string
          enum:
            - all
            - none
          description: '`all` – priimti tiesioginius iššūkius pagal vardą, `none` – juos atmesti.'
    PasswordChangeRequest:
      type: object
      additionalProperties: false
      required:
        - currentPassword
        - newPassword
      properties:
        currentPassword:
          type: string
          minLength: 1
          maxLength: 1024
          description: Dabartinis slaptažodis.
        newPassword:
          type: string
          minLength: 1
          maxLength: 1024
          description: Naujas slaptažodis; taikomos registracijos slaptažodžio taisyklės.
    PasswordOnlyRequest:
      type: object
      additionalProperties: false
      required:
        - password
      properties:
        password:
          type: string
          minLength: 1
          maxLength: 1024
          description: Paskyros slaptažodis.
    TotpEnableRequest:
      type: object
      additionalProperties: false
      required:
        - code
      properties:
        code:
          type: string
          pattern: '^[0-9]{6}$'
          description: Laukiančio slaptojo rakto kodas, lygiai 6 skaitmenys.
    ReauthCredentials:
      type: object
      additionalProperties: false
      required:
        - password
      properties:
        password:
          type: string
          minLength: 1
          maxLength: 1024
          description: Paskyros slaptažodis.
        code:
          type: string
          minLength: 1
          maxLength: 32
          description: Neprivalomas; reikalingas, kai įjungtas dviejų veiksnių autentifikavimas (arba `recoveryCode`). Autentifikavimo programėlės kodas arba atkūrimo kodas.
        recoveryCode:
          type: string
          minLength: 1
          maxLength: 32
          description: Neprivalomas. Atkūrimo kodas (`xxxx-xxxx-xx`; raidžių dydis, tarpai ir brūkšneliai nesvarbūs).
    RecoveryCodesRequest:
      type: object
      additionalProperties: false
      required:
        - password
        - code
      properties:
        password:
          type: string
          minLength: 1
          maxLength: 1024
          description: Paskyros slaptažodis.
        code:
          type: string
          minLength: 1
          maxLength: 32
          description: Autentifikavimo programėlės kodas (atkūrimo kodas atmetamas).
    EmailChangeRequest:
      type: object
      additionalProperties: false
      required:
        - newEmail
        - password
      properties:
        newEmail:
          type: string
          minLength: 1
          maxLength: 254
          description: Naujas adresas; pašalinami tarpai pradžioje ir pabaigoje, raidės paverčiamos mažosiomis, tada tikrinamas kaip registracijos adresas.
        password:
          type: string
          minLength: 1
          maxLength: 1024
          description: Paskyros slaptažodis.
        code:
          type: string
          minLength: 1
          maxLength: 32
          description: Neprivalomas; reikalingas, kai įjungtas dviejų veiksnių autentifikavimas (arba `recoveryCode`). Autentifikavimo programėlės kodas arba atkūrimo kodas.
        recoveryCode:
          type: string
          minLength: 1
          maxLength: 32
          description: Neprivalomas. Atkūrimo kodas.
    TotpSetup:
      type: object
      description: Laukiantis slaptasis raktas autentifikavimo programėlei.
      required:
        - secret
        - uri
        - algorithm
        - digits
        - period
      properties:
        secret:
          type: string
          pattern: '^[A-Z2-7]+$'
          description: Slaptasis raktas base32 formatu, kurį reikia įvesti į programėlę.
        uri:
          type: string
          format: uri
          description: URI `otpauth://totp/...`, kurį reikia parodyti kaip QR kodą.
        algorithm:
          type: string
          const: SHA1
        digits:
          type: integer
          const: 6
        period:
          type: integer
          const: 30
          description: Žingsnio trukmė sekundėmis.
    RecoveryCodeList:
      type: array
      description: 10 atkūrimo kodų, parodomų tik šį kartą. Kiekvienas veikia vieną kartą.
      minItems: 10
      maxItems: 10
      items:
        type: string
        pattern: '^[0-9a-hjkmnp-tv-z]{4}-[0-9a-hjkmnp-tv-z]{4}-[0-9a-hjkmnp-tv-z]{2}$'
    PlayerSide:
      type: object
      description: Viena partijos pusė.
      required:
        - name
        - rating
        - ratingAfter
        - ratingDiff
      properties:
        name:
          type: string
          description: Vardas partijos įraše (`deleted#<id>` ištrintai paskyrai).
        rating:
          type:
            - integer
            - 'null'
          description: Reitingas pradžioje, `null`, kai nežinomas.
        ratingAfter:
          type:
            - integer
            - 'null'
          description: Reitingas po partijos. Reitinginėje partijoje jis yra visada; `null` tik partijai, kuri neturi įtakos reitingams (draugiškai, pasirinktinės laiko kontrolės, nutrauktai).
        ratingDiff:
          type:
            - integer
            - 'null'
          description: Reitingo pokytis; `0`, kai pagal reitingo taisykles reitingas nepasikeičia (FIDE nulinio rezultato taisyklė, varžovas be reitingo, pirmosios žaidėjo be reitingo partijos); `null` tais pačiais atvejais kaip `ratingAfter`.
    GameSummary:
      type: object
      description: Išsaugotos partijos santrauka.
      required:
        - id
        - category
        - rated
        - timeControl
        - white
        - black
        - status
        - reason
        - result
        - termination
        - plies
        - startedAt
        - endedAt
      properties:
        id:
          type: integer
          format: int64
          minimum: 1
          maximum: 9007199254740991
          description: Partijos ID.
        category:
          type: string
          description: Oficialios kategorijos ID (`3+2`) arba `custom`.
        rated:
          type: boolean
          description: Ar partija turi įtakos reitingams.
        timeControl:
          type: string
          pattern: '^[0-9]+\+[0-9]+$'
          description: Laiko kontrolė sekundėmis (`180+2`).
        white:
          $ref: '#/components/schemas/PlayerSide'
        black:
          $ref: '#/components/schemas/PlayerSide'
        status:
          type: integer
          enum:
            - 1
            - 2
            - 3
            - 4
          description: '1 `WhiteWins`, 2 `BlackWins`, 3 `Draw`, 4 `Aborted` (žr. partijų kodus).'
        reason:
          type: integer
          minimum: 0
          maximum: 255
          description: Pabaigos priežasties kodas (žr. partijų kodus).
        result:
          type: string
          enum:
            - 1-0
            - 0-1
            - 1/2-1/2
            - '*'
          description: Rezultatas; nutrauktai partijai – `*`.
        termination:
          $ref: '#/components/schemas/Termination'
        plies:
          type: integer
          minimum: 0
          description: Pusėjimių skaičius.
        startedAt:
          type: integer
          format: int64
          description: Pradžia (Unix laikas, ms).
        endedAt:
          type: integer
          format: int64
          description: Pabaiga (Unix laikas, ms).
    Termination:
      type: string
      description: Pabaigos priežasties pavadinimas (žr. partijų kodus); `Unknown` – protokolui nežinomam kodui.
      enum:
        - Checkmate
        - Resignation
        - Timeout
        - IllegalMoves
        - Stalemate
        - InsufficientMaterial
        - TimeoutVsInsufficient
        - FivefoldRepetition
        - SeventyFiveMoves
        - ThreefoldClaim
        - FiftyMoveClaim
        - Agreement
        - IllegalMovesVsInsufficient
        - ResignationVsInsufficient
        - Abandonment
        - AbandonmentVsInsufficient
        - Aborted
        - NoShow
        - Forfeit
        - ServerAborted
        - BothDisconnected
        - Unknown
    PlayerGameSummary:
      description: Partijos santrauka vieno žaidėjo požiūriu.
      allOf:
        - $ref: '#/components/schemas/GameSummary'
        - type: object
          required:
            - color
          properties:
            color:
              type: string
              enum:
                - white
                - black
              description: Žaidėjo spalva.
    HistoryGameSummary:
      description: Partija iš prisijungusio žaidėjo istorijos.
      allOf:
        - $ref: '#/components/schemas/PlayerGameSummary'
        - type: object
          required:
            - baseMs
            - incMs
            - outcome
          properties:
            baseMs:
              type: integer
              format: int64
              minimum: 0
              description: Pagrindinis laikas milisekundėmis.
            incMs:
              type: integer
              format: int64
              minimum: 0
              description: Pridėtinis laikas milisekundėmis.
            outcome:
              type: string
              enum:
                - win
                - loss
                - draw
                - aborted
              description: Baigtis žaidėjo požiūriu.
    HistoryPage:
      type: object
      required:
        - games
        - next
        - total
      properties:
        games:
          type: array
          description: Puslapio partijos, pradedant naujausia.
          items:
            $ref: '#/components/schemas/HistoryGameSummary'
        next:
          type:
            - integer
            - 'null'
          format: int64
          description: ID, kurį reikia perduoti kaip `before` kitam puslapiui gauti, arba `null` paskutiniame puslapyje.
        total:
          type: integer
          minimum: 0
          description: Filtrą atitinkančių partijų skaičius visuose puslapiuose.
    MoveRecord:
      type: object
      description: Vienas partijos pusėjimis.
      required:
        - uci
        - spentMs
        - clockMs
      properties:
        uci:
          type: string
          pattern: '^[a-h][1-8][a-h][1-8][nbrq]?$'
          description: Ėjimas UCI notacija; pėstininko virsmo atveju pridedama `n`, `b`, `r` arba `q`.
        spentMs:
          type:
            - integer
            - 'null'
          minimum: 0
          description: Už ėjimą įskaitytas laikas (ms), `null`, kai įraše jo nėra.
        clockMs:
          type:
            - integer
            - 'null'
          minimum: 0
          description: Ėjimą padariusio žaidėjo laikrodis po ėjimo (ms), `null`, kai įraše jo nėra.
    PgnTagsView:
      type: object
      description: Pagrindinės PGN žymės, skirtos rodyti. `Termination` čia yra pabaigos priežasties pavadinimas; PGN faile naudojamos standartinės PGN reikšmės.
      required:
        - Event
        - Site
        - Date
        - Round
        - White
        - Black
        - Result
        - WhiteElo
        - BlackElo
        - TimeControl
        - Termination
        - PlyCount
      properties:
        Event:
          type: string
          description: '`<SERVER_NAME> rated <category>` arba `<SERVER_NAME> casual <category>`.'
        Site:
          type: string
          description: '`SERVER_PUBLIC_HOST`.'
        Date:
          type: string
          pattern: '^[0-9]{4}\.[0-9]{2}\.[0-9]{2}$'
          description: Pradžios data pagal UTC.
        Round:
          type: string
          const: '-'
        White:
          type: string
        Black:
          type: string
        Result:
          type: string
          enum:
            - 1-0
            - 0-1
            - 1/2-1/2
            - '*'
        WhiteElo:
          description: Reitingas pradžioje arba `"-"`.
          oneOf:
            - type: integer
            - type: string
              const: '-'
        BlackElo:
          description: Reitingas pradžioje arba `"-"`.
          oneOf:
            - type: integer
            - type: string
              const: '-'
        TimeControl:
          type: string
          pattern: '^[0-9]+\+[0-9]+$'
        Termination:
          $ref: '#/components/schemas/Termination'
        PlyCount:
          type: integer
          minimum: 0
    GameRecord:
      description: Partijos įrašas su ėjimais ir laikrodžiais. Jame yra partijos santraukos laukai, išskyrus `color` ir `outcome`.
      allOf:
        - $ref: '#/components/schemas/GameSummary'
        - type: object
          required:
            - baseMs
            - incMs
            - statusName
            - rematchOf
            - moves
            - pgn
          properties:
            baseMs:
              type: integer
              format: int64
              minimum: 0
              description: Pagrindinis laikas milisekundėmis.
            incMs:
              type: integer
              format: int64
              minimum: 0
              description: Pridėtinis laikas milisekundėmis.
            statusName:
              type: string
              enum:
                - WhiteWins
                - BlackWins
                - Draw
                - Aborted
              description: '`status` pavadinimas.'
            rematchOf:
              type:
                - integer
                - 'null'
              format: int64
              description: Partijos, kurios revanšas yra ši partija, ID arba `null`.
            moves:
              type: array
              description: Po vieną įrašą kiekvienam pusėjimiui.
              items:
                $ref: '#/components/schemas/MoveRecord'
            pgn:
              $ref: '#/components/schemas/PgnTagsView'
            you:
              type: string
              enum:
                - white
                - black
              description: Tik kai žetono savininkas žaidė šią partiją; jo spalva.
            reportable:
              type: boolean
              description: Tik kai žetono savininkas žaidė šią partiją; `true`, kai `POST /reports` dabar priimtų pranešimą apie varžovą dėl šios partijos (partija baigėsi per pastarąsias 7 dienas, dienos kvota neišnaudota, o apie varžovą dėl šios partijos dar nepranešta).
    PlayerProfile:
      type: object
      required:
        - username
        - createdAt
        - ratings
        - games
      properties:
        username:
          type: string
          description: Naudotojo vardas, kaip jį užrašė žaidėjas.
        createdAt:
          type: integer
          format: int64
          description: Kada paskyra sukurta (Unix laikas, ms).
        ratings:
          type: array
          description: Tik oficialios kategorijos, serverio tvarka.
          items:
            $ref: '#/components/schemas/RatingSummary'
        games:
          type: object
          required:
            - total
            - rated
            - wins
            - draws
            - losses
          properties:
            total:
              type: integer
              minimum: 0
              description: Visos išsaugotos partijos, įskaitant draugiškas ir nutrauktas.
            rated:
              type: integer
              minimum: 0
              description: Reitinginės partijos, susumuotos pagal reitingo įrašus.
            wins:
              type: integer
              minimum: 0
            draws:
              type: integer
              minimum: 0
            losses:
              type: integer
              minimum: 0
    PlayerGamesPage:
      type: object
      required:
        - username
        - games
        - next
      properties:
        username:
          type: string
          description: Naudotojo vardas, kaip jį užrašė žaidėjas.
        games:
          type: array
          description: Puslapio partijos, pradedant naujausia.
          items:
            $ref: '#/components/schemas/PlayerGameSummary'
        next:
          type:
            - integer
            - 'null'
          format: int64
          description: Paskutinės partijos ID, kai puslapis pilnas (tada kitas puslapis gali būti tuščias), kitaip `null`.
    Leaderboard:
      type: object
      required:
        - category
        - minGames
        - updatedAt
        - players
      properties:
        category:
          type: string
          description: Kategorijos ID.
        minGames:
          type: integer
          minimum: 0
          description: Kiek įskaitytų partijų reikia, kad įrašas būtų įtrauktas į sąrašą (`PROVISIONAL_GAMES`).
        updatedAt:
          type: integer
          format: int64
          description: Kada lentelė apskaičiuota (Unix laikas, ms).
        players:
          type: array
          maxItems: 100
          items:
            $ref: '#/components/schemas/LeaderboardEntry'
    LeaderboardEntry:
      type: object
      required:
        - rank
        - username
        - rating
        - games
        - wins
        - draws
        - losses
        - peak
      properties:
        rank:
          type: integer
          minimum: 1
        username:
          type: string
        rating:
          type: integer
        games:
          type: integer
          minimum: 0
        wins:
          type: integer
          minimum: 0
        draws:
          type: integer
          minimum: 0
        losses:
          type: integer
          minimum: 0
        peak:
          type: integer
    GifRequest:
      type: object
      additionalProperties: false
      required:
        - pgn
      properties:
        pgn:
          type: string
          description: PGN tekstas, ne daugiau kaip 65 536 UTF-8 baitai. Naudojama tik pirmoji partija.
        size:
          type: string
          enum:
            - small
            - medium
            - large
          default: medium
          description: Neprivalomas. Paveikslėlio dydis.
        orientation:
          type: string
          enum:
            - white
            - black
          default: white
          description: Neprivalomas. Kurios spalvos figūros rodomos lentos apačioje.
        delayMs:
          type: integer
          minimum: 100
          maximum: 3000
          default: 500
          description: Neprivalomas. Milisekundės vienam ėjimui, JSON skaičius su sveikąja reikšme.
        coords:
          type: boolean
          default: true
          description: Neprivalomas. Ar aplink lentą piešiamos vertikalių raidės ir horizontalių skaičiai.
    ReportRequest:
      type: object
      required:
        - gameId
        - reported
        - category
      properties:
        gameId:
          description: Partija, nurodyta sveikuoju skaičiumi arba 1–16 skaitmenų eilute.
          oneOf:
            - type: integer
              format: int64
              minimum: 1
              maximum: 9007199254740991
            - type: string
              pattern: '^[0-9]{1,16}$'
        reported:
          type: string
          minLength: 1
          maxLength: 24
          description: Varžovo naudotojo vardas, kaip partijos įraše arba koks jis dabar, neskiriant didžiųjų ir mažųjų raidžių. Jis negali būti tuščias.
        category:
          type: string
          enum:
            - cheating
            - abuse
            - other
          description: Apie ką pranešama.
        comment:
          type:
            - string
            - 'null'
          description: Neprivalomas. Ne daugiau kaip 500 simbolių, pašalinus valdymo simbolius (išskyrus tabuliaciją ir eilutės lūžį) bei tarpus teksto pradžioje ir pabaigoje.
    AccountExport:
      type: object
      description: Viskas, ką serveris saugo apie paskyrą (laikai – Unix laikas, ms).
      required:
        - format
        - version
        - exportedAt
        - server
        - notes
        - account
        - ratings
        - ratingRefunds
        - sessions
        - securityEvents
        - sanctions
        - conduct
        - reportsFiled
        - games
      properties:
        format:
          type: string
          const: scacelith-account-export
        version:
          type: integer
          const: 1
        exportedAt:
          type: integer
          format: int64
          description: Kada atliktas eksportas.
        server:
          type: object
          required:
            - name
            - host
          properties:
            name:
              type: string
              description: '`SERVER_NAME`.'
            host:
              type: string
              description: '`SERVER_PUBLIC_HOST`.'
        notes:
          type: array
          description: Kas yra faile ir kas į jį neįtraukta, paprasta anglų kalba žaidėjui.
          items:
            type: string
        account:
          description: '`GET /account/me` paskyros rodinys su `googleEmail`.'
          allOf:
            - $ref: '#/components/schemas/AccountView'
            - type: object
              required:
                - googleEmail
              properties:
                googleEmail:
                  type:
                    - string
                    - 'null'
                  description: Susietos Google paskyros adresas arba `null`.
        ratings:
          type: array
          description: Pilni reitingo įrašai.
          items:
            $ref: '#/components/schemas/ExportRating'
        ratingRefunds:
          type: array
          description: Reitingo taškai, grąžinti nustačius, kad varžovas sukčiavo, susumuoti pagal UTC dieną ir kategoriją, pradedant naujausiais. Nei partijos, nei sukčiai neįvardijami.
          items:
            $ref: '#/components/schemas/RatingRefund'
        sessions:
          type: array
          description: Visi išsaugoti seansai, pradedant naujausiu, be jokių žetonų. Saugojimo termino valymas ištrina nebegaliojantį seansą, o atjungtą – praėjus dienai po atsijungimo.
          items:
            $ref: '#/components/schemas/ExportSession'
        securityEvents:
          type: array
          description: Saugos įvykiai, pradedant naujausiu, saugomi `RETENTION_SECURITY_DAYS` dienų.
          items:
            $ref: '#/components/schemas/SecurityEvent'
        sanctions:
          type: array
          description: Visos sankcijos, įskaitant panaikintas. Moderatoriaus vardas niekada neįtraukiamas.
          items:
            $ref: '#/components/schemas/ExportSanction'
        conduct:
          type: array
          description: Elgesio įvykiai, užregistruoti dėl žaidėjo paliktų ir nutrauktų partijų bei partijų, kuriose jis laiku nepadarė pirmojo ėjimo, saugomi 30 dienų.
          items:
            $ref: '#/components/schemas/ConductEvent'
        reportsFiled:
          type: array
          description: Žaidėjo pateikti pranešimai.
          items:
            $ref: '#/components/schemas/FiledReport'
        games:
          type: object
          required:
            - total
            - list
          properties:
            total:
              type: integer
              minimum: 0
              description: Partijų skaičius.
            list:
              type: array
              description: Visos partijos, pradedant naujausia, kaip `GET /account/games` santraukos. Ėjimai pateikiami per `GET /games/{id}`, o PGN – per `GET /games/{id}/pgn`.
              items:
                $ref: '#/components/schemas/HistoryGameSummary'
    ExportRating:
      description: Pilnas reitingo įrašas.
      allOf:
        - $ref: '#/components/schemas/RatingSummary'
        - type: object
          required:
            - rated
            - countedGames
            - updatedAt
          properties:
            rated:
              type: boolean
              description: Ar žaidėjas jau baigė etapą be reitingo.
            countedGames:
              type: integer
              minimum: 0
              description: Į reitingą įskaitytų partijų skaičius.
            updatedAt:
              type: integer
              format: int64
              description: Kada įrašas paskutinį kartą pasikeitė.
    RatingRefund:
      type: object
      required:
        - day
        - category
        - points
      properties:
        day:
          type: integer
          format: int64
          description: Dienos 00:00 UTC (Unix laikas, ms).
        category:
          type: string
        points:
          type: integer
          description: Tą dieną toje kategorijoje grąžinti taškai.
    ExportSession:
      type: object
      required:
        - id
        - createdAt
        - lastSeenAt
        - expiresAt
        - revokedAt
        - clientLabel
        - ip
      properties:
        id:
          type: integer
          format: int64
        createdAt:
          type: integer
          format: int64
        lastSeenAt:
          type: integer
          format: int64
        expiresAt:
          type: integer
          format: int64
        revokedAt:
          type:
            - integer
            - 'null'
          format: int64
          description: Kada seansas atjungtas, arba `null`.
        clientLabel:
          type:
            - string
            - 'null'
        ip:
          type:
            - string
            - 'null'
          description: Prisijungimo adresas, ištrinamas po `RETENTION_IP_DAYS` dienų.
    SecurityEvent:
      type: object
      description: |-
        Saugos įvykis. `ip` pateikiamas tik veiksmams, atliktiems prisijungus, naudojant paskyros slaptažodį (ir antrąjį veiksnį) arba jos adresu išsiųstą nuorodą: `register`, `email_verified`, `login`, `sso_login`, `sso_linked`, `sso_account_created`, `recovery_code_used`, `password_reset`, `password_changed`, `reauth_failed`, `mfa_setup_started`, `mfa_enabled`, `mfa_disabled`, `recovery_codes_regenerated`, `session_revoked`, `sessions_revoked_all`, `email_change_requested`, `email_changed`, `email_change_refused` ir `account_exported`; visų kitų rūšių įvykiai turi `ip: null`, o `ip` ištrinamas po `RETENTION_IP_DAYS` dienų.

        `detail` išsaugo tik šiuos laukus: `login`: `method`; `sso_login`, `sso_account_created`: `provider`; `sso_linked`: `provider` ir `method` (`password` arba `password+totp`); `login_failed`: `failures`; `login_lockout`: `retryAfterMs`; `mfa_failed`: `attempts`; `recovery_code_used`: `remaining`; `reauth_failed`: `factor`; `session_revoked` ir `sessions_revoked_all`: `reason`; `email_change_refused`: `reason`; `sanction_auto`: `kind`, `gameId`, `until`. Įvykis `moderator_action` išsaugo tik `{ action }` veiksmams `ban`, `unban`, `reset_mfa`, `verify_email` ir `revoke_sessions` (kiti moderatorių veiksmai neįtraukiami). Visų kitų rūšių įvykiai turi `detail: null`. Įvykiai `rating_refund` neįtraukiami: jų taškai yra lauke `ratingRefunds`.
      required:
        - kind
        - at
        - ip
        - detail
      properties:
        kind:
          type: string
          description: Įvykio rūšis (`login`, `password_changed`...).
        at:
          type: integer
          format: int64
          description: Kada įvyko.
        ip:
          type:
            - string
            - 'null'
        detail:
          type:
            - object
            - 'null'
    ExportSanction:
      type: object
      required:
        - id
        - kind
        - reason
        - source
        - gameId
        - startsAt
        - endsAt
        - createdAt
        - liftedAt
      properties:
        id:
          type: integer
          format: int64
        kind:
          type: string
          enum:
            - ban
            - mm_block
            - warning
        reason:
          type:
            - string
            - 'null'
        source:
          type: string
          enum:
            - auto
            - moderator
        gameId:
          type:
            - integer
            - 'null'
          format: int64
        startsAt:
          type: integer
          format: int64
        endsAt:
          type:
            - integer
            - 'null'
          format: int64
        createdAt:
          type: integer
          format: int64
        liftedAt:
          type:
            - integer
            - 'null'
          format: int64
    ConductEvent:
      type: object
      required:
        - kind
        - at
      properties:
        kind:
          type: string
          enum:
            - abandon
            - abort
            - noshow
        at:
          type: integer
          format: int64
    FiledReport:
      type: object
      required:
        - gameId
        - reported
        - category
        - comment
        - createdAt
        - status
      properties:
        gameId:
          type:
            - integer
            - 'null'
          format: int64
        reported:
          type: string
          description: Dabartinis viešas žaidėjo, apie kurį pranešta, vardas.
        category:
          type: string
          enum:
            - cheating
            - abuse
            - other
        comment:
          type:
            - string
            - 'null'
        createdAt:
          type: integer
          format: int64
        status:
          type: string
          enum:
            - open
            - closed
          description: Ar pranešimas dar atviras (ar žaidėjui, apie kurį pranešta, skirta sankcija, nenurodoma).
    LinkTokenForm:
      type: object
      additionalProperties: false
      required:
        - token
      properties:
        token:
          type: string
          minLength: 1
          maxLength: 128
          description: Laiško nuorodos žetonas (paslėptas puslapio formos laukas).
    ResetPasswordForm:
      type: object
      additionalProperties: false
      required:
        - token
        - newPassword
        - confirmPassword
      properties:
        token:
          type: string
          minLength: 1
          maxLength: 128
          description: Nustatymo iš naujo nuorodos žetonas (paslėptas formos laukas).
        newPassword:
          type: string
          minLength: 1
          maxLength: 1024
          description: Naujas slaptažodis; taikomos registracijos slaptažodžio taisyklės.
        confirmPassword:
          type: string
          minLength: 1
          maxLength: 1024
          description: Naujas slaptažodis dar kartą; jis turi sutapti.
    HtmlDocument:
      type: string
      contentMediaType: text/html
      description: HTML puslapis UTF-8 koduote (`text/html; charset=utf-8`), be JavaScript ir išorinių išteklių.
