openapi: 3.1.1
info:
  title: Scacelith servera HTTPS API
  version: "1.1.0"
  summary: Ikviena Scacelith servera HTTPS API — gan oficiālā servera, gan kopienas serveru.
  description: |-
    Šo HTTPS API nodrošina ikviens Scacelith serveris — gan oficiālais `caissa.scacelith.com`, gan jebkurš kopienas serveris. Spēle to izmanto visam, kas notiek ārpus pašas partijas: reģistrēšanās un pierakstīšanās (ieskaitot divu faktoru autentifikāciju un Google), konta lapa, partiju vēsture, partiju PGN lejupielādes un animēti GIF, pierakstītās ierīces, datu lejupielāde, konta dzēšana un ziņojumi.

    Spēlēšana reāllaikā (pretinieku meklēšana, izaicinājumi, gājieni, pulksteņi) notiek caur tā paša servera WebSocket (`wss://<host>/ws`), ko atver ar šī API sesijas marķieri; tā ir aprakstīta failā `PROTOCOL.md`, nevis šeit.

    Tālāk norādītās noklusējuma vērtības ir servera ar neskartu konfigurāciju vērtības; kopienas serveris tās var mainīt (`CONFIG.md` uzskaita visus iestatījumus).

    ## Bāzes URL, ports un transports

    - Oficiālais serveris: `https://caissa.scacelith.com/api/v1`, TCP ports 443.
    - Kopienas serveris: `https://<SERVER_PUBLIC_HOST>[:<port>]/api/v1`. `API_PORT` pēc noklusējuma ir 443; aiz NAT vai starpniekservera spēlētāju izmantotais ports ir `PUBLIC_API_PORT`.
    - Spēles WebSocket pēc noklusējuma ir tajā pašā portā (`WS_PORT` to pārceļ; `GET /info` paziņo klientam, kur tas atrodas).
    - HTTP/1.1 pār TLS. Ar `TLS_MODE=proxy` TLS servera priekšā terminē reversais starpniekserveris. `TLS_MODE=off` (vienkāršs HTTP) ir paredzēts tikai lokālai izstrādei.
    - HTML lapas, ko atver no e-pasta vēstuļu saitēm (`/verify-email`, `/reset-password`, `/confirm-email-change`), atrodas servera saknē, ārpus `/api/v1`. Stāvokļa pārbaudes galapunkti atbild gan saknē, gan zem `/api/v1`. Pierakstīšanās ar Google nenotiek nevienā servera lapā: Google novirza pārlūku atpakaļ uz pašu spēli, adresē `127.0.0.1`, vai uz tās tīmekļa vietnes lapu, kas pierakstīšanos sāka (`GOOGLE_WEB_REDIRECT_URI`).
    - Beigu slīpsvītra tiek ignorēta (`/api/v1/info/` ir tas pats, kas `/api/v1/info`), un ceļa parametri tiek URL atkodēti (uz parametru, kas nav derīgā URL kodējumā, atbilde ir 400 `invalid_request`).

    ## Pieprasījumi

    - **JSON pamatteksti.** `POST`, `PUT` un `DELETE` pieprasījumi satur JSON objektu ar `Content-Type: application/json`. Cits `charset` nekā UTF-8 tiek noraidīts, tāpat kā jebkurš cits satura tips (415 `unsupported_media_type`). Tukšs pamatteksts tiek uzskatīts par `{}`, un tieši to pieņem galapunkti bez parametriem (`POST /auth/logout`, `POST /auth/logout-all`, `DELETE /auth/sessions/{id}`).
    - **Izmērs un laiks.** Pamatteksts ir ierobežots līdz `HTTP_BODY_LIMIT` baitiem (pēc noklusējuma 16 384; `POST /gif` ir savs ierobežojums — 135 168 baiti): 413 `payload_too_large`. Tam jāpienāk 10 sekunžu laikā: 408 `request_timeout`. Pēc jebkuras no šīm kļūdām serveris aizver savienojumu.
    - **Stingras shēmas.** Lauks, ko galapunkts nepazīst, tiek noraidīts, lauks, kas nav atzīmēts kā neobligāts, ir obligāts, un tiek pārbaudīti tipi un garumi. Jebkuras no šīm kļūmēm gadījumā atbilde ir 400 `invalid_request`, un `field` nosauc lauku (ligzdotam laukam — ar punktiem, piemēram, `pow.nonce`). Virknēs nedrīkst būt vadības rakstzīmju. Garumus skaita UTF-16 koda vienībās, tāpēc rakstzīme ārpus pamata daudzvalodu plaknes (emocijzīme) tiek skaitīta divreiz. Uz pamattekstu, kas nav JSON, atbilde ir 400 `invalid_json`. `POST /gif` un `POST /reports` savus pamattekstus pārbauda paši (skatiet katra galapunkta aprakstu).
    - **Vaicājuma virknes.** Tiek ņemta vērā parametra pirmā parādīšanās, nezināmi parametri tiek ignorēti, un `+` tiek atkodēts kā atstarpe: galapunkti, kas pieņem laika kontroles kategoriju, pieņem gan `3%2B2`, gan `3+2`.
    - **Metodes.** `HEAD` ir `GET` bez pamatteksta. Uz `OPTIONS` esošam ceļam atbilde ir 204 ar galveni `Allow` (un CORS priekšpārbaudes galvenēm, skatiet tālāk). Uz jebkuru citu metodi, kuras ceļam nav, atbilde ir 405 `method_not_allowed` ar `Allow`.
    - **Pieprasījuma mērķis.** Tas nedrīkst pārsniegt 4096 rakstzīmes (414 `uri_too_long`). Pieprasījums, ko nemaz nevar parsēt vai kura galvas daļa ir pārāk liela vai pienāk pārāk lēni, saņem tukšu atbildi 400, 431 vai 408, un savienojums tiek aizvērts.
    - **CORS: atļauto saraksts, pēc noklusējuma izslēgts.** API apkalpo spēli, kas nesūta `Origin`. Ja `CORS_ORIGINS` ir tukšs (noklusējums), neviena `Access-Control-*` galvene netiek sūtīta nekad, tāpēc neviena tīmekļa lapa nevar nolasīt atbildi. `CORS_ORIGINS` uzskaita tīmekļa izcelsmes, kuru lapas drīkst izsaukt API no pārlūka, katru tieši tā, kā pārlūki to sūta galvenē `Origin`: `https://host` vai `https://host:port`, mazajiem burtiem, bez ceļa, beigu slīpsvītras vai noklusējuma porta; `http://` tikai `localhost`, `127.0.0.1` un `[::1]` (lokālai izstrādei); bez aizstājējzīmēm. Oficiālais serveris savai tīmekļa vietnei iestata `CORS_ORIGINS=https://scacelith.com,https://www.scacelith.com`. Tādā gadījumā:
      - Katra atbilde ceļam zem `/api/v1`, izņemot stāvokļa pārbaudes galapunktus, gan veiksmīga, gan kļūdas atbilde (ieskaitot adrešu slāņa 429), satur `Vary: Origin`. Ja pieprasījuma `Origin` baitu pa baitam sakrīt ar kādu no uzskaitītajām izcelsmēm, atbilde satur arī `Access-Control-Allow-Origin: <that origin>` un, izņemot atbildi uz priekšpārbaudi, `Access-Control-Expose-Headers: Retry-After, Content-Disposition`, lai lapa varētu nolasīt kļūdu kodus, `Retry-After` un PGN un GIF lejupielāžu failu nosaukumus.
      - Uz priekšpārbaudi (`OPTIONS` ar `Access-Control-Request-Method`) no uzskaitītas izcelsmes esošam ceļam atbilde ir 204 bez pamatteksta, ar `Allow`, `Access-Control-Allow-Origin`, `Access-Control-Allow-Methods` (`Allow` metodes), `Access-Control-Allow-Headers: Authorization, Content-Type`, `Access-Control-Max-Age: 600` un `Vary: Origin`. Tai nav vajadzīgs marķieris, un tā netiek ieskaitīta ne konta budžetā, ne galapunktu ierobežojumos; tāpat kā jebkurš pieprasījums, tā paņem vienu adrešu slāņa žetonu. Pārlūks priekšpārbaudi izmanto atkārtoti 10 minūtes, bet tikai tieši tam pašam URL, ieskaitot vaicājuma virkni. Pieprasījumam bez `Authorization` un bez JSON pamatteksta, piemēram, `GET /info` vai `GET /leaderboard`, priekšpārbaude nav vajadzīga.
      - Jebkura cita atbilde uz priekšpārbaudi nozīmē neizdevušos priekšpārbaudi, pat ar CORS galvenēm: 404 neesošam ceļam vai adrešu slāņa 429. Tad pārlūks pieprasījumu nenosūta, un lapa redz tīkla kļūdu bez atbildes un tās `Retry-After`.
      - `Access-Control-Allow-Credentials` netiek sūtīts nekad, tāpat kā `Access-Control-Allow-Origin: *`: lapa sūta savu sesijas marķieri galvenē `Authorization: Bearer`, tāpat kā spēle, un API neiestata nevienu sīkfailu. HTML lapas, stāvokļa pārbaudes galapunkti un tukšās atbildes uz nenolasāmiem pieprasījumiem nesatur CORS galvenes. WebSocket neizmanto CORS: `WS_ALLOWED_ORIGINS` uzskaita lapas, kas drīkst to atvērt.
      - Neuzskaitītas izcelsmes lapa neko nenolasa, un tās starpvietņu rakstīšanas pieprasījumi netiek nosūtīti nekad: JSON pamattekstiem un `Authorization` vajadzīga priekšpārbaude, kuras atbildē šai izcelsmei nav `Access-Control-Allow-Origin`. `Cross-Origin-Resource-Policy: same-origin` paliek katrā atbildē: tā attiecas tikai uz `no-cors` pieprasījumiem (cita vietne iegulst atbildi), nekad uz uzskaitītas lapas `cors` pieprasījumiem.

    ## Atbildes un kļūdas

    - Atbildes ir JSON formātā UTF-8 kodējumā, izņemot PGN lejupielādi (`application/x-chess-pgn`), animētos GIF (`image/gif`) un HTML lapas. Laiki ir milisekundes kopš 1970. gada 1. janvāra (UTC). ID ir veseli skaitļi. Partiju ID ir līdz 16 cipariem, taču paliek mazāki par 2^53, tāpēc JSON skaitlis (double) tos glabā precīzi.
    - Katra API atbilde satur `Cache-Control: no-store`, `X-Content-Type-Options: nosniff`, `Referrer-Policy: no-referrer`, `X-Frame-Options: DENY`, `Cross-Origin-Resource-Policy: same-origin` un `Content-Security-Policy: default-src 'none'; frame-ancestors 'none'` (HTML lapām ir sava politika). Ar `TLS_MODE=native` tā satur arī `Strict-Transport-Security: max-age=31536000`. Atbildes, ko klausītājs raksta pats, pirms API (stāvokļa pārbaudes galapunktu `GET` un `HEAD`, adrešu slāņa 429 un 500 `internal_error`, ja API darbība pilnībā neizdodas), satur tās pašas galvenes, izņemot `Cross-Origin-Resource-Policy`, un to `Content-Type` ir `application/json` bez rakstzīmju kopas norādes.
    - Atbilde jānolasa 60 sekunžu laikā no brīža, kad serverim tā ir gatava; tas ir svarīgi tikai lielām atbildēm (GIF, datu eksports, garš PGN). Laiks, ko serveris patērē atbildes sagatavošanai, netiek ieskaitīts ne šajā laikā, ne 30 sekundēs bez neviena saņemta vai nosūtīta baita, pēc kurām savienojums tiek aizvērts.

    Kļūdām ir viena forma (shēma `Error`):

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

    `message` ir paredzēts žurnāliem un kā rezerves variants; ko parādīt, klients izvēlas pēc `error`. `retryAfter` (sekundes) ir tikai atteikumos, kas ar laiku beidzas, un tad atbilde satur arī galveni `Retry-After` ar to pašu vērtību; vienīgais izņēmums ir vēstures, partiju, spēlētāju un līderu saraksta lasīšanas 503 `busy`, kurā ir tikai lauks. Dažas kļūdas pievieno laukus: `field` (nederīga ievade), `reason` (`weak_password`, `pow_required`), `pow` (`pow_required`), `until` (`banned`), `line` un `column` (`invalid_pgn`).

    Kļūdas, ko var atgriezt jebkurš galapunkts:

    | Statuss | `error` | Kad |
    |---|---|---|
    | 400 | `invalid_request` | Pamatteksts pārkāpj galapunkta shēmu (`field` norāda, kur), pieprasījuma mērķis vai `Content-Length` ir nepareizi veidots, ceļa parametrs nav derīgā URL kodējumā vai pamatteksts tika nogriezts. |
    | 400 | `invalid_json` | Pamatteksts nav JSON. |
    | 401 | `unauthorized` | Nav galvenes `Authorization` galapunktā, kam vajadzīga sesija. |
    | 401 | `invalid_token` | Marķieris ir nepareizi veidots, novecojis, atsaukts vai pieder dzēstam kontam; arī galapunktos, kur sesija nav obligāta. |
    | 404 | `not_found` | Šāda galapunkta nav. To izmanto arī daži galapunkti: šādas partijas, spēlētāja vai sesijas nav. |
    | 405 | `method_not_allowed` | Ceļš pastāv citām metodēm (skatiet `Allow`). |
    | 408 | `request_timeout` | Pamatteksts nepienāca 10 sekunžu laikā. |
    | 413 | `payload_too_large` | Pamatteksts pārsniedz `HTTP_BODY_LIMIT` (`POST /gif` — 135 168 baitus). |
    | 414 | `uri_too_long` | Pieprasījuma mērķis pārsniedz 4096 rakstzīmes. |
    | 415 | `unsupported_media_type` | Pamatteksts nav `application/json`, vai tā kodējums nav UTF-8. |
    | 429 | `rate_limited` | Pieprasījumu biežuma ierobežojums (skatiet tālāk): `retryAfter` un galvene `Retry-After`. |
    | 500 | `internal_error` | Neparedzēta kļūme. Serveris to reģistrē žurnālā. |
    | 503 | `timeout` | Serveris neatbildēja 30 sekunžu laikā (eksportam — 60 sekunžu, GIF ar noklusējuma iestatījumiem — 45 sekunžu laikā). |
    | 503 | `server_busy` | Sesijas uzmeklēšana vai konta izmaiņa konstatēja, ka datubāze ir bloķēta (`retryAfter` 1), vai paroļu jaukšanas rinda ir pilna (skatiet tālāk). Arī ar `retryAfter` no 2 līdz 5, ja serveris ir stipri atpalicis: `POST`, `PUT`, `PATCH` vai `DELETE` gadījumā datubāzē gaida pārāk daudz ierakstīšanas operāciju; jebkuram pieprasījumam — pārāk daudz agrāku pieprasījumu joprojām darbojas pēc savas noildzes, vai sesijas marķieri neizdevās pārbaudīt 5 sekunžu laikā. Pieprasījums netika izpildīts, un tā pieprasījumu biežuma ierobežojumi neko nepaņēma. |

    Lasīšanas galapunkti (partiju vēsture, partijas, spēlētāji, līderu saraksts) un eksports atbild ar 503 `busy` un `retryAfter: 1`, ja datubāze palika bloķēta.

    Galapunkti, kas pārbauda paroli vai aprēķina tās jaucējvērtību, var atbildēt arī ar vienu no šīm kļūdām, abas ar nejaušu `retryAfter` no 5 līdz 15 sekundēm:

    - 503 `server_busy`: servera paroļu jaukšanas rinda (`PASSWORD_HASH_QUEUE_MAX`) ir pilna, vai gaidīšanas laiks beidzās.
    - 429 `rate_limited`: kad rinda ir pa pusei pilna, šim klientam (IPv4 adresei vai IPv6 /48) rindā jau gaida `PASSWORD_HASH_WAITERS_PER_SOURCE` jaukšanas operācijas. Šis 429 atdod atpakaļ pieprasījumu biežuma ierobežojumu žetonus, ko pieprasījums paņēma.

    Nekas netika mainīts, un neviens neveiksmīgs mēģinājums netika ieskaitīts (izņemot `POST /auth/sso/google/link`, kura biļetes mēģinājums un konta neveiksme tika ieskaitīti pirms jaukšanas), un atiestatīšanas saite paliek derīga.

    HTML lapas savas kļūdas atgriež kā HTML lapas ar tiem pašiem statusa kodiem.

    ## Autentifikācija

    Galapunkti, kam vajadzīga sesija, pieņem nesēja marķieri (bearer token) galvenē `Authorization` (shēma `bearerAuth`):

    ```
    Authorization: Bearer sct_L_8GDd7uzfQ3QQWtqrsWXDTsFWzRwIvJcwIGHhjWPS8
    ```

    - **Marķiera iegūšana.** Marķieri (`sct_`, kam seko 43 base64url rakstzīmes) izsniedz `POST /auth/login`, pēc tam `POST /auth/login/mfa`, ja ir ieslēgta divu faktoru autentifikācija, `POST /auth/sso/google/finish` un `POST /auth/sso/google/link` (pierakstīšanās ar Google), kā arī `POST /auth/sso/complete`. Katrs no tiem atbild ar `{ token, expiresAt, user }`. Serveris glabā tikai marķiera SHA-256.
    - **Obligāta sesija.** Ja galvenes nav, atbilde ir 401 `unauthorized` ar `WWW-Authenticate: Bearer realm="scacelith"`. Uz nederīgu marķieri atbilde ir 401 `invalid_token` ar `WWW-Authenticate: Bearer realm="scacelith", error="invalid_token"`. Klientam vajadzētu aizmirst marķieri, kas saņem `invalid_token`, un pierakstīties vēlreiz.
    - **Neobligāta sesija.** Galapunktos `GET /games/{id}`, `GET /games/{id}/pgn`, `GET /players/{username}` un `GET /players/{username}/games` sesija nav obligāta. Bez galvenes tie atgriež publisko skatu; ja galvene ir nosūtīta, tās marķierim jābūt derīgam.
    - **Aizņemta krātuve.** Ja marķiera pārbaudes laikā datubāze paliek bloķēta, galapunkts, kas pieprasa vai pieņem sesiju, atbild ar 503 `server_busy` un `retryAfter: 1` (un `Retry-After`); ja pārbaude ilgst vairāk nekā 5 sekundes, tas atbild ar 503 `server_busy` un `retryAfter` no 2 līdz 5. Abos gadījumos marķiera derīgums netika izvērtēts: saglabājiet to un nosūtiet pieprasījumu vēlreiz.
    - **Darbības ilgums.** Sesija beidzas, kad pienāk pirmais no diviem termiņiem: `SESSION_MAX_DAYS` (90) dienas pēc pierakstīšanās (tas ir `expiresAt`) vai `SESSION_IDLE_DAYS` (30) dienas bez lietošanas. Katra lietošana atbīda dīkstāves ierobežojumu; serveris jauno vērtību ieraksta fonā, ne biežāk kā reizi 5 minūtēs. Kamēr servera datubāzē gaida daudz ierakstīšanas operāciju, serveris šo ierakstīšanu atliek līdz kādam vēlākam pieprasījumam, izņemot pēdējo stundu pirms dīkstāves ierobežojuma, tāpēc sesija, kas tiek lietota, nebeidzas. Kontam var būt ne vairāk kā `MAX_SESSIONS_PER_USER` (10) sesiju: jauna pierakstīšanās atsauc vecākās sesijas, kas pārsniedz šo skaitu.
    - **Atsaukšana.** Izrakstīšanās (`POST /auth/logout`, `POST /auth/logout-all`, `DELETE /auth/sessions/{id}`) atsauc sesijas; to dara arī paroles maiņa (pārējās sesijas), paroles atiestatīšana un konta dzēšana (visas sesijas), kā arī administrators. Atsaukšana caur API stājas spēkā nekavējoties, un WebSocket, kas atvērts ar atsauktu sesiju, tiek aizvērts. Paroles maiņa vai atiestatīšana un konta dzēšana atsauc sesijas tajā pašā transakcijā, kurā notiek pati izmaiņa: ja kāda no tām neizdodas, neviena sesija netika atsaukta un nekas cits arī netika mainīts. Serveris sesiju uzmeklēšanas rezultātus kešo 30 sekundes, tāpēc atsaukšana ar administratora komandu, kas ir atsevišķs process, stājas spēkā 30 sekunžu laikā.
    - **Darbības joma.** Marķieris pieder vienam serverim un atver arī tā WebSocket. Nekad nesūtiet to citam serverim.

    ## Pieprasījumu biežuma ierobežojumi un citi palēninājumi

    Ierobežojumi attiecas uz vienu no divām jomām. **Klients:** IPv4 adrese vai IPv6 /64 (starpniekservera režīmā adrese tiek ņemta no `X-Forwarded-For`, ko sūta `TRUSTED_PROXIES` adrese); daži ierobežojumi papildus katram /64 tīklam skaita arī katru IPv6 /48 kopumā. **Spēlētājs:** pierakstītais konts neatkarīgi no tā adreses; ierobežojums, ko skaita katram spēlētājam galapunktā, kur sesija nav obligāta, pieprasījumam bez marķiera tiek skaitīts katram klientam.

    Katrs ierobežojums ir žetonu spainis (token bucket), kurā ietilpst `limit` pieprasījumi un kas nepārtraukti piepildās ar ātrumu `limit / window`; `retryAfter` ir laiks līdz nākamajam žetonam. Ierobežojumi, kas atzīmēti kā *koplietoti*, tiek skaitīti arī tāda paša garuma slīdošajā logā, lai nevienā logā nebūtu daudz vairāk par `limit` pieprasījumiem. Katrs ierobežojums tiek skaitīts visam serverim kopumā. Ja viens no galapunkta ierobežojumiem pieprasījumu noraida, žetoni, ko pārējie tā ierobežojumi par šo pieprasījumu paņēma, tiek atdoti atpakaļ. Uz atteikumu atbilde ir 429 `rate_limited` ar `retryAfter` un `Retry-After`.

    - **Adrešu slānis.** Katrs pieprasījums (jebkurš ceļš un metode, ieskaitot stāvokļa pārbaudes galapunktus un WebSocket jaunināšanas pieprasījumus) vispirms paņem vienu žetonu no sava klienta budžeta: `HTTP_RATE_PER_IP` (600) minūtē ar pusminūtes apjoma uzrāvienu (burst) un `HTTP_RATE_PER_PREFIX` (4 x `HTTP_RATE_PER_IP`) IPv6 /48 tīklam. Klientam vienlaikus var būt ne vairāk kā `IP_MAX_INFLIGHT` (32 x `WORKERS`) apstrādē esošu pieprasījumu (virs tā — `retryAfter` 1). Klients, kas turpina pēc atteikumiem, tiek bloķēts: `ABUSE_BLOCK_REFUSALS_PER_MIN` (600) atteikumi vienā minūtē to bloķē uz 1 minūti, pēc tam uz 4, 16 un 60 minūtēm pie katras jaunas bloķēšanas 6 stundu laikā. Ierobežojumu `auth`, `auth_*` un `reauth` atteikums skaitās kā 5; to ierobežojumu atteikumi, ko skaita katram spēlētājam, netiek skaitīti nekad. Adreses no `ABUSE_EXEMPT` šo slāni izlaiž, bet ne konta budžetu un galapunktu ierobežojumus.
    - **Konta budžets.** Katrs pieprasījums ar derīgu sesijas marķieri tiek ieskaitīts arī tā konta budžetā: `USER_RATE_PER_MIN` (120) minūtē visiem galapunktiem kopā neatkarīgi no adreses, ar pusminūtes apjoma uzrāvienu.
    - **Galapunktu ierobežojumi:**

    | Ierobežojums | Noklusējums | Skaita katram | Galapunkti |
    |---|---|---|---|
    | `auth` | `AUTH_RATE_PER_IP` (20) / 10 min, koplietots | klientam un `AUTH_RATE_PER_PREFIX` (5 x `AUTH_RATE_PER_IP`) katram IPv6 /48 | `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) / stundā, koplietots | klientam, 3 reizes vairāk katram IPv6 /48 | `POST /auth/register` |
    | `auth_mail` | `AUTH_MAIL_PER_HOUR` (10) / stundā, koplietots | klientam, 3 reizes vairāk katram IPv6 /48 | `POST /auth/verify-email/resend` |
    | `auth_forgot` | `AUTH_FORGOT_PER_HOUR` (3) / stundā, koplietots | klientam, 3 reizes vairāk katram IPv6 /48 | `POST /auth/password/forgot` |
    | `auth_forgot_day` | `AUTH_FORGOT_PER_DAY` (10) / 24 stundās, koplietots | klientam, 3 reizes vairāk katram IPv6 /48 | `POST /auth/password/forgot` |
    | `auth_reset` | `AUTH_RESET_PER_HOUR` (10) / stundā, koplietots | klientam, 3 reizes vairāk katram IPv6 /48 | `POST /auth/password/reset`, `POST /reset-password` |
    | `reauth` | tādi paši skaitļi kā `auth`, savs spainis, koplietots | klientam un 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, koplietots | spēlētājam | tie paši galapunkti, kas `reauth` |
    | `account` | 60 / min | spēlētājam | `GET /account/me`, `PUT /account/preferences` |
    | `account_games` | 60 / min | spēlētājam | `GET /account/games` |
    | `account_export` | 5 / stundā, koplietots | spēlētājam | `POST /account/export` (pārbauda pirms `reauth`; tiek skaitīts katrs mēģinājums) |
    | `sessions` | 60 / min | spēlētājam | `POST /auth/logout`, `/auth/logout-all`, `GET /auth/sessions`, `DELETE /auth/sessions/{id}` |
    | `public_read` | 60 / min | spēlētājam (klientam bez marķiera) | `GET /players/{username}`, `/players/{username}/games`, `/games/{id}`, `/games/{id}/pgn` (viens spainis visiem četriem) |
    | `gif` | 30 / min | spēlētājam | `GET /games/{id}/gif`, `POST /gif` (viens spainis abiem) |
    | `gif_user_min`, `gif_user_hour` | `GIF_USER_RENDERS_PER_MIN` (4) / min un `GIF_USER_RENDERS_PER_HOUR` (30) / stundā, koplietoti | spēlētājam | tie paši divi, tikai tad, ja GIF ir jāizveido (nevis jāņem no kešatmiņas) |
    | `gif_ip_min`, `gif_ip_hour` | `GIF_IP_RENDERS_PER_MIN` (12) / min un `GIF_IP_RENDERS_PER_HOUR` (120) / stundā, koplietoti | klientam (visiem tā kontiem kopā), 3 reizes vairāk katram IPv6 /48 | tie paši, tāpat kā iepriekš |
    | `reports` | 30 / stundā | spēlētājam | `POST /reports` |
    | `sso_start` | 30 / 10 min, koplietots | klientam un 90 katram IPv6 /48 | `POST /auth/sso/google/start`, `POST /auth/sso/google/web/start` (kopā) |
    | `sso_finish` | 30 / min | klientam | `POST /auth/sso/google/finish` |
    | `page` | 60 / min | klientam | `GET /verify-email`, `/reset-password`, `/confirm-email-change` |

    `GET /info` un `GET /leaderboard` nav sava ierobežojuma: tikai adrešu slānis. Galapunkts ar vairākiem ierobežojumiem tos pārbauda tādā secībā, kāda norādīta tā aprakstā.

    Serveris pieprasījumu apstrādā šādā secībā: adrešu slānis, pēc tam stāvokļa pārbaudes galapunkti; galapunkta atrašana; pārbaudes, ka serveris nav stipri atpalicis (503 `server_busy`): pārāk daudz agrāku pieprasījumu joprojām darbojas pēc savas noildzes un, `POST`, `PUT`, `PATCH` vai `DELETE` gadījumā, datubāzes neizpildīto ierakstīšanas operāciju uzkrājums; autentifikācija 5 sekunžu laikā (pēc tam 503 `server_busy`); konta budžets, ja pieprasījumam ir sesija; galapunkta ierobežojumi; pamatteksts; pats galapunkts (GIF galapunkti savus renderēšanas ierobežojumus piemēro tikai tad, ja GIF ir jāizveido). Tātad pieprasījums, kas noraidīts marķiera dēļ, neiztērē nevienu galapunkta žetonu, bet pieprasījums ar nederīgu pamattekstu tos iztērē.

    Citi palēninājumi, ar kuriem atbild paši galapunkti:

    - **Neveiksmīgas pierakstīšanās ar vienu pierakstīšanās vārdu** (lietotājvārdu vai e-pastu): sākot no `AUTH_FAILURES_PER_ACCOUNT` (5) neveiksmēm, katram mēģinājumam jāgaida divreiz ilgāk nekā iepriekšējam (2 s, 4 s un tā tālāk, līdz 15 minūtēm): 429 `too_many_attempts` ar `retryAfter`. Skaitītājs tiek notīrīts pēc stundas bez neveiksmēm.
    - **Neveiksmīgi otrie faktori pierakstoties:** tas pats noteikums, sākot no konta 5. nepareizā koda. Viens pierakstīšanās solis pieņem ne vairāk kā 5 nepareizus kodus.
    - **Viena konta otrā faktora kodi:** ne vairāk kā `AUTH_MFA_PER_ACCOUNT` (10) kodi (autentifikācijas lietotnes vai atkopšanas kodi, pareizi vai nepareizi) 15 minūtēs, no jebkuras adreses, pierakstoties un atkārtotās autentifikācijās; virs tā — 429 `too_many_attempts` pirms koda pārbaudes, tāpēc atkopšanas kods netiek iztērēts.
    - **Konta neveiksmīgas atkārtotas autentifikācijas** (nepareiza parole vai kods): tas pats noteikums (429 `too_many_attempts`), kopīgs visiem galapunktiem, kas veic atkārtotu autentifikāciju.
    - **E-pasta vēstules:** viena apstiprinājuma vai atiestatīšanas vēstule uz adresi ik pēc 5 minūtēm (atbilde paliek tāda pati); viens paziņojums „kāds mēģināja izmantot jūsu adresi” uz adresi stundā.
    - **Ziņojumi:** `REPORTS_PER_DAY` (5) katram spēlētājam 24 stundās: 429 `report_limit`.

    ## Darba apliecinājums

    `POST /auth/register` vienmēr pieprasa darba apliecinājumu, ja `POW_REGISTER_BITS` ir lielāks par 0 (pēc noklusējuma 18; `GET /info` to norāda kā `pow.register`). `POST /auth/login` un `POST /auth/sso/google/link` (abiem viena veida izaicinājums) to pieprasa tikai 5 minūtes pēc tam, kad serveris konstatē neveiksmīgu pierakstīšanos vilni (`POW_LOGIN_TRIGGER_PER_MIN`, pēc tam `POW_LOGIN_BITS`); klients to uzzina no atbildes.

    1. Uz pieprasījumu bez apliecinājuma (vai ar noraidītu apliecinājumu) atbilde ir 428 `pow_required` ar `reason` un `pow` izaicinājumu `{ challenge, bits, expiresAt }`.
    2. Atrodiet nonce: decimālu virkni ne garāku par 20 cipariem, kurai `SHA-256(challenge + ":" + nonce)` sākas ar `bits` nulles bitiem (sākot ar pirmā baita nozīmīgāko bitu). 18 bitiem vidēji vajag aptuveni 260 000 jaucējvērtību aprēķinu.
    3. Nosūtiet to pašu pieprasījumu vēlreiz ar `"pow": { "challenge": "...", "nonce": "123456" }` pamattekstā.

    Izaicinājums ir derīgs 2 minūtes un tikai vienu reizi, vienam galapunktam un vienam klienta tīklam (IPv4 adresei vai IPv6 /64). Tas ir parakstīts, tāpēc serveris par to neko neglabā, līdz tas atgriežas. `reason` norāda, kāpēc apliecinājums tika noraidīts: `required`, `malformed`, `signature`, `endpoint`, `network`, `expired`, `bits`, `work` vai `replayed`.

    ## Atkārtota autentifikācija

    Konta izmaiņas vēlreiz pieprasa paroli un, ja ir ieslēgta divu faktoru autentifikācija, otro faktoru:

    - tikai paroli: `POST /account/password` un `POST /account/mfa/totp/setup`;
    - paroli un autentifikācijas lietotnes kodu (atkopšanas kods tiek noraidīts): `POST /account/mfa/recovery-codes`;
    - paroli un autentifikācijas lietotnes kodu vai atkopšanas kodu: `POST /account/mfa/totp/disable`, `POST /account/email`, `POST /account/export` un `POST /account/delete`.

    Pamattekstos `code` satur 6 ciparu autentifikācijas lietotnes kodu, bet `recoveryCode` — atkopšanas kodu (`xxxx-xxxx-xx`; burtu reģistrs, atstarpes un defises nav svarīgas). Atkopšanas kodu var nosūtīt arī laukā `code` tur, kur atkopšanas kodi tiek pieņemti. Katrs kods darbojas vienu reizi: izmantots autentifikācijas lietotnes kods tiek noraidīts līdz nākamajam 30 sekunžu solim, un atkopšanas kods pēc izmantošanas vairs nav derīgs.

    | Statuss | `error` | Kad |
    |---|---|---|
    | 403 | `invalid_password` | Nepareiza parole, vai parole tika atiestatīta vai nomainīta pieprasījuma izpildes laikā (nekas netika mainīts). |
    | 403 | `mfa_code_required` | Ir ieslēgta divu faktoru autentifikācija, un netika nosūtīts ne `code`, ne `recoveryCode`. |
    | 403 | `invalid_code` | Nepareizs vai jau izmantots kods. |
    | 400 | `password_not_set` | Kontam, kas izmanto tikai Google, vēl nav paroles (to iestata ar „Aizmirsāt paroli?”). |
    | 429 | `too_many_attempts` | Šajā kontā ir pārāk daudz neveiksmju vai izmēģināts pārāk daudz kodu. |
    | 503 / 429 | `server_busy` / `rate_limited` | Paroļu jaukšanas rinda ir aizņemta. |

    Nepareizas paroles un kodi tiek ieskaitīti konta neveiksmju skaitītājā un reģistrēti kā drošības notikumi.

    ## Metriku ports

    Papildus API portam serveris atbild ar vienkāršu HTTP metriku portā (`METRICS_PORT`, 9464, piesaistīts `METRICS_BIND`, pēc noklusējuma 127.0.0.1; nepadariet to publiski pieejamu). Tas nav daļa no šī API: `GET /healthz` atbild `ok`; `GET /readyz` atbild `ready`, kad palaišana ir pabeigta (katrs šards ir atkārtoti izpildījis savu žurnālu un klausītāji ir piesaistīti), un līdz brīdim, kad sākas izslēgšana, citādi 503 `not ready`; `GET /metrics` sniedz Prometheus metrikas un, ja ir iestatīts `METRICS_TOKEN`, pieprasa `Authorization: Bearer <token>` tieši ar šo marķieri.
  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 servera pirmkods un tā atsauces dokumentācija (API.md, PROTOCOL.md, CONFIG.md).
  url: https://github.com/DarkCenobyte/scacelith-chess-server
servers:
  - url: https://caissa.scacelith.com/api/v1
    description: Oficiālais serveris.
  - url: https://{host}:{port}/api/v1
    description: Jebkurš Scacelith serveris, piemēram, kopienas serveris.
    variables:
      host:
        default: caissa.scacelith.com
        description: Servera publiskais resursdatora nosaukums (`SERVER_PUBLIC_HOST`).
      port:
        default: "443"
        description: Publiskais API ports (`PUBLIC_API_PORT`, citādi `API_PORT`).
tags:
  - name: server-info
    x-displayName: Servera informācija
    description: Kas klientam jāzina, pirms tas pierakstās vai izveido savienojumu.
  - name: health
    x-displayName: Stāvokļa pārbaude
    description: |-
      Servera darbspēja un gatavība uzraudzībai. Šie galapunkti atbild API portā pirms autentifikācijas un visiem galapunktu ierobežojumiem, taču, tāpat kā jebkurš pieprasījums, paņem vienu adrešu slāņa žetonu; uzraudzības resursdatoru var norādīt `ABUSE_EXEMPT`. Katram galapunktam darbojas abi ceļi: servera saknē un zem `/api/v1`.

      Metriku portam ir savi stāvokļa pārbaudes galapunkti, kas aprakstīti ievadā.
  - name: auth
    x-displayName: Reģistrēšanās un pierakstīšanās
    description: |-
      Konta izveide, pierakstīšanās ar paroli (un otro faktoru), apstiprinājuma un paroles atiestatīšanas e-pasta vēstules.

      `POST /auth/login`, `POST /auth/login/mfa`, `POST /auth/sso/google/finish`, `POST /auth/sso/google/link` un `POST /auth/sso/complete` atgriež sesiju `{ token, expiresAt, user }` (pirmie no tiem tā vietā var atgriezt otro soli).
  - name: google-sign-in
    x-displayName: Pierakstīšanās ar Google
    description: |-
      Pieejama, ja `GET /info` norāda `sso.google: true`; citādi katrs tālāk aprakstītais galapunkts atbild ar 404 `sso_disabled`. Tīmekļa vietne pierakstās tāpat ar savu klientu, ja `sso.googleWeb` ir true (tālāk, **Tīmekļa vietnē**). Spēle pierakstās caur sistēmas pārlūku ar RFC 8252 instalētās lietotnes plūsmu (klients „Desktop app”): Google novirza pārlūku atpakaļ uz spēles klausītāju adresē `127.0.0.1`, nekad uz šo serveri, un spēle nekad neredz Google akreditācijas datus.

      1. Klients klausās adresē `127.0.0.1:0` (portu izvēlas sistēma) un izveido PKCE pāri: `codeVerifier` no 43 līdz 128 rakstzīmēm `[A-Za-z0-9._~-]` un `codeChallenge = BASE64URL(SHA-256(codeVerifier))`, kuram ir 43 rakstzīmes un nav aizpildījuma.
      2. `POST /auth/sso/google/start` ar izaicinājumu un portu atgriež Google URL un mēģinājuma `state`. Klients pārbauda URL (skatiet tālāk) un atver to pārlūkā.
      3. Google novirza pārlūku uz `http://127.0.0.1:<port>/oauth2/google/<tag>?code=...&state=...`. Klients pieņem tikai sākuma atbildes `state` un pēc novirzīšanas ar `error=` neko nesūta.
      4. `POST /auth/sso/google/finish` ar mēģinājuma ID, `codeVerifier`, `state` un `code` (un `iss`, ja Google to nosūtīja).
      5. Atbilde ir sesija, divu faktoru autentifikācijas solis (turpiniet ar `POST /auth/login/mfa`), `needsUsername` jaunam spēlētājam (turpiniet ar `POST /auth/sso/complete`) vai `needsPassword`, ja adresi izmanto konts ar paroli (turpiniet ar `POST /auth/sso/google/link`).

      **Izcelsmes birka.** Novirzīšanas URI satur tā servera birku, ko spēlētājs pievienoja, lai spēle noraidītu URL, ko cits serveris ieguvis saviem spēlētājiem. Izcelsme ir resursdatora nosaukums mazajiem burtiem (IPv6 literālis kvadrātiekavās), `:` un decimālais API ports, kas tiek rakstīts vienmēr, arī 443: serveris izmanto `SERVER_PUBLIC_HOST` un savu publisko API portu, spēle — adresi, ar kuru tā ir savienota. Birka ir pirmās 22 rakstzīmes no SHA-256(UTF-8 `"scacelith-sso-origin-v1\n"` + izcelsme) base64url kodējuma (bez aizpildījuma). Novirzīšanas URI ir `"http://127.0.0.1:" + port + "/oauth2/google/" + tag`: vienmēr IPv4 literālis un spēles klausītāja ports (no 1024 līdz 65535). Serveris nekad neņem URI, resursdatoru vai ceļu no klienta.

      | Izcelsme | Birka |
      |---|---|
      | `play.scacelith.example:443` | `IhcScoV7eDOzTEcSnqPUPt` |
      | `localhost:8443` | `TFGx7zQ_8QlGZW5zpqznCr` |
      | `[::1]:8443` | `XToJm0DG5PjciEVmZa9Cho` |
      | `127.0.0.1:50443` | `3r653wM5ZjYsHcAJljmCwY` |

      Tāpēc pierakstīšanās ar Google darbojas tikai tiem spēlētājiem, kuri pievienoja serveri tieši ar `SERVER_PUBLIC_HOST` un tā publisko API portu.

      **Ko spēle pārbauda, pirms atver pārlūku.** `authUrl` sākas tieši ar `https://accounts.google.com/o/oauth2/v2/auth?`, sastāv no drukājamām ASCII rakstzīmēm un ir īsāks par 4096 rakstzīmēm, un tā vaicājumā ir tieši viens `response_type=code`, tieši viens `redirect_uri`, kas vienāds ar URI, ko spēle aprēķina no sava porta un savas izcelsmes birkas, tieši viens `state`, kas vienāds ar atbildes `state`, `code_challenge_method=S256` un 43 rakstzīmju `code_challenge`. Citādi spēle aptur savu klausītāju un neko neatver. `finish` un `link` tā sūta tikai tam serverim, kas atbildēja uz `start`.

      **Tīmekļa vietnē.** Tīmekļa vietne (oficiālā: <https://scacelith.com/>) pierakstās ar tā paša Google projekta klientu „Web application” (`GOOGLE_WEB_CLIENT_ID`). Google novirza pārlūku atpakaļ uz vienu vietnes lapu, ko konfigurējis uzturētājs (`GOOGLE_WEB_REDIRECT_URI`, norādīta klienta sarakstā „Authorized redirect URIs”), un šī lapa nosūta kodu uz `finish`; arī šeit serveris neņem novirzīšanas URI no klienta.

      1. Lapa izveido PKCE pāri, kā aprakstīts iepriekš, glabā verificētāju cilnē (`sessionStorage`) un nosūta `POST /auth/sso/google/web/start` ar izaicinājumu.
      2. Tā pārbauda `authUrl` tāpat kā spēle, izņemot to, ka `redirect_uri` jābūt lapai tās pašas izcelsmē, un pēc tam pāriet uz šo URL. Neviens Google skripts netiek ielādēts.
      3. Google novirza pārlūku uz `GOOGLE_WEB_REDIRECT_URI?code=...&state=...`. Lapa pieņem tikai sākuma atbildes `state` un pēc novirzīšanas ar `error=` neko nesūta.
      4. `finish`, `link` un `complete` seko tāpat kā spēlei. Mēģinājums atceras, ka to sāka tīmekļa vietne: kods tiek apmainīts ar vietnes klientu un lapu, un ID marķierim jānorāda šis klients (spēles klienta kods vai ID marķieris izraisa kļūdu 502 `sso_failed`, un otrādi).

      Google konts gan no spēles, gan no tīmekļa vietnes nonāk tajā pašā Scacelith kontā: saistījums glabā Google subjektu (`sub`), kas identificē Google kontu neatkarīgi no klienta. Vietnes izcelsmei jābūt `CORS_ORIGINS` (citādi `check-config` brīdina).

      **Kurā kontā nonāk pierakstīšanās ar Google:** kontā, kas jau saistīts ar šo Google kontu; citādi aktīvā kontā ar adresi, ko apstiprināja Google (ja kontam ir parole, `finish` atbild ar `needsPassword`, un saistījums tiek saglabāts tikai tad, kad ir pārbaudīta tā parole un pēc tam, ja ieslēgts, otrais faktors; ja paroles nav — 409 `sso_account_exists`); citādi jaunā kontā (`needsUsername`). Google konts nekad netiek saistīts ar esošu kontu tikai pēc adreses. Uz konta adresi tiek nosūtīta e-pasta vēstule, kad pierakstīšanās ar Google izveido kontu un kad tā tiek pievienota esošam kontam.
  - name: sessions
    x-displayName: Sesijas
    description: Konta pierakstītās ierīces un izrakstīšanās.
  - name: account
    x-displayName: Konts
    description: Konta skats, tā preferences un parole.
  - name: two-step-verification
    x-displayName: Divu faktoru autentifikācija
    description: Autentifikācijas lietotnes (TOTP, RFC 6238) ar SHA-1, 6 cipariem, 30 sekunžu soļiem un viena soļa pielaidi abos virzienos, kā arī vienreizlietojami atkopšanas kodi.
  - name: email-change
    x-displayName: E-pasta adreses maiņa
    description: Konta adreses maiņa, ko apstiprina ar saiti, kas nosūtīta uz jauno adresi.
  - name: data-export
    x-displayName: Datu eksports
    description: Viss, ko serveris glabā par kontu, vienā JSON failā.
  - name: account-deletion
    x-displayName: Konta dzēšana
    description: Konta neatgriezeniska dzēšana.
  - name: game-history
    x-displayName: Partiju vēsture
    description: Pierakstītā spēlētāja paša partijas, filtrētas un sadalītas lapās.
  - name: games
    x-displayName: Partijas un PGN
    description: |-
      Partiju pieraksti ar to gājieniem un pulksteņiem, kā arī to PGN faili.

      **Partiju kodi.** `status`: 1 `WhiteWins`, 2 `BlackWins`, 3 `Draw`, 4 `Aborted` (tiek glabātas tikai pabeigtas partijas). `result`: `1-0`, `0-1`, `1/2-1/2` vai `*` (anulēta). `reason` ar tā nosaukumu (`termination`) un vārdiem, ar kuriem beidzas PGN faila gājienu teksts (failā angliski, tālāk ar tulkojumu); kodi 7, 14 un 21 nozīmē neizšķirtu (spēlētājam, kuram beidzās laiks, kurš padevās vai pameta partiju, pretī bija pretinieks, kas nevarēja pieteikt matu: FIDE 6.9 un 5.1.2; noteikums ir dots `DESIGN.md` 6.3), un serveris nekad nebeidz partiju ar kodiem 4 un 13 (tie pieder kopīgajam iemeslu sarakstam):

      | Kods | `termination` | Vārdi PGN failā |
      |---|---|---|
      | 1 | `Checkmate` | Checkmate (mats) |
      | 2 | `Resignation` | Resignation (padošanās) |
      | 3 | `Timeout` | Loss on time (laika pārsniegšana) |
      | 4 | `IllegalMoves` | Second illegal move (forfeit) (otrais neatļautais gājiens, zaudējums) |
      | 5 | `Stalemate` | Stalemate (pats) |
      | 6 | `InsufficientMaterial` | Dead position (insufficient material) (mats nav iespējams, nepietiekams materiāls) |
      | 7 | `TimeoutVsInsufficient` | Flag fall, but the opponent cannot checkmate (karodziņš nokrita, bet pretinieks nevar pieteikt matu) |
      | 8 | `FivefoldRepetition` | Fivefold repetition (pieckārtēja atkārtošanās) |
      | 9 | `SeventyFiveMoves` | 75-move rule (75 gājienu noteikums) |
      | 10 | `ThreefoldClaim` | Threefold repetition (claimed) (trīskārtēja atkārtošanās, pēc pieprasījuma) |
      | 11 | `FiftyMoveClaim` | 50-move rule (claimed) (50 gājienu noteikums, pēc pieprasījuma) |
      | 12 | `Agreement` | Draw by agreement (neizšķirts pēc vienošanās) |
      | 13 | `IllegalMovesVsInsufficient` | Second illegal move, but the opponent cannot checkmate (otrais neatļautais gājiens, bet pretinieks nevar pieteikt matu) |
      | 14 | `ResignationVsInsufficient` | Resignation, but the opponent cannot checkmate (padošanās, bet pretinieks nevar pieteikt matu) |
      | 20 | `Abandonment` | Abandoned (disconnected for too long) (partija pamesta, pārāk ilgi bez savienojuma) |
      | 21 | `AbandonmentVsInsufficient` | Abandoned, but the opponent cannot checkmate (partija pamesta, bet pretinieks nevar pieteikt matu) |
      | 22 | `Aborted` | Game aborted (partija anulēta) |
      | 23 | `NoShow` | Aborted: first move not played in time (anulēta: pirmais gājiens netika izdarīts laikā) |
      | 24 | `Forfeit` | Forfeit (fair play violation) (tehniskais zaudējums, godīgas spēles pārkāpums) |
      | 25 | `ServerAborted` | Aborted by the server (serveris anulēja partiju) |
      | 26 | `BothDisconnected` | Aborted: both players disconnected (anulēta: abi spēlētāji atvienojās) |

      Kods 14 ir jauns protokola apakšversijā 1: klientam, kas uzrakstīts pirms tās, nezināms kods būtu jārāda kā vienkāršas partijas beigas ar tās `result`.
  - name: gifs
    x-displayName: Animēti GIF
    description: |-
      Partija kā animēts GIF, ko saglabāt vai kopīgot: galdiņš no augšas, viens kadrs katrai pozīcijai no sākuma līdz beigu pozīcijai, spēlētāju vārdi un reitingi virs galdiņa, pēdējais gājiens zem tā, bet pēdējā kadrā — rezultāts un tas, kā partija beidzās.

      **Izmaksas, kešatmiņa un kvotas.** GIF tiek veidots atsevišķā renderēšanas pavedienā, nekad pavedienos, kas apkalpo partijas, un ar zemāko CPU prioritāti: `GIF_THREADS` pavedieni (pēc noklusējuma `WORKERS`), kas tiek palaisti ar pirmo GIF un apturēti pēc minūtes bez neviena GIF. Līdz `GIF_QUEUE_MAX` (4 x `WORKERS`) GIF gaida pavedienu, katrs ne ilgāk kā `GIF_QUEUE_TIMEOUT_MS` (10 s); renderēšana drīkst ilgt `GIF_RENDER_TIMEOUT_MS` (30 s). Serveris izveidotos GIF glabā `GIF_CACHE_MB` MB lielā kešatmiņā (32 x `WORKERS`), pirmos izmetot visilgāk neizmantotos; GIF no kešatmiņas vai tāds, kas jau tiek veidots citam pieprasījumam, netiek skaitīts kā renderēšana. Kešatmiņas atslēga ietver visu, kas maina attēlu, arī vārdus.

      Katrs pieprasījums tiek ieskaitīts ierobežojumā `gif` (30 minūtē katram spēlētājam abiem galapunktiem kopā). GIF, kas ir jāizveido, tiek ieskaitīts arī renderēšanas ierobežojumos: katram spēlētājam `GIF_USER_RENDERS_PER_MIN` (4) minūtē un `GIF_USER_RENDERS_PER_HOUR` (30) stundā; katram klientam, visiem tā kontiem kopā, `GIF_IP_RENDERS_PER_MIN` (12) minūtē un `GIF_IP_RENDERS_PER_HOUR` (120) stundā, un 3 reizes vairāk katram IPv6 /48. Klientam vajadzētu saglabāt lejupielādēto failu, nevis pieprasīt to vēlreiz, un pēc 429 vai 503 nogaidīt `retryAfter`.

      Izmēri: `small` (32 px lauciņi: 284 x 350 pikseļi), `medium` (48 px: 424 x 515), `large` (72 px: 628 x 762); bez koordinātām 268 x 342, 400 x 503 un 600 x 748. Sākuma pozīcija tiek rādīta vismaz 1 s (vai aiztures laiku, ja tas ir ilgāks), beigu pozīcija — 3 s, un GIF atkārtojas; pēc pirmā kadra tiek saglabāta tikai tā attēla daļa, kas mainās. 40 gājienu partija aizņem aptuveni 135, 205 un 325 KiB (small, medium, large), 150 gājienu partija — 0,5, 0,8 un 1,2 MiB.
  - name: players
    x-displayName: Spēlētāji
    description: Publiskie profili un nesenās partijas. Tikai publiski dati, nekad e-pasta adrese, sesija, sankcija vai godīguma līmenis. Dzēstam kontam profila nav.
  - name: leaderboard
    x-displayName: Līderu saraksts
    description: Labākie spēlētāji katrā oficiālajā kategorijā.
  - name: reports
    x-displayName: Ziņojumi
    description: Ziņošana moderatoriem par nesenas partijas pretinieku.
  - name: pages
    x-displayName: HTML lapas
    description: |-
      Lapas pārlūkam, ko atver no e-pasta vēstuļu saitēm. To saites norāda uz `https://<SERVER_PUBLIC_HOST>` (ar `:<PUBLIC_API_PORT>`, ja tas nav 443), ārpus `/api/v1`.

      - Lapas neizpilda JavaScript un neielādē ārējus resursus. Tās tiek pasniegtas ar `Content-Security-Policy: default-src 'none'; style-src 'unsafe-inline'; form-action 'self'; frame-ancestors 'none'; base-uri 'none'`.
      - `GET` tikai parāda pogu vai veidlapu, lai e-pasta skeneris, kas atver saiti, to neiztērētu. Izmaiņa notiek ar `POST`: veidlapa, kas nosūtīta kā `application/x-www-form-urlencoded` (pieņemts arī JSON pamatteksts), ar saites marķieri slēptā laukā. Divreiz norādīts lauks tiek noraidīts.
      - Arī kļūdas (pieprasījumu biežuma ierobežojumi, nederīgi lauki) ir HTML lapas ar virsrakstu „Request refused” (pieprasījums noraidīts) vai, sākot no 500, „Server error” (servera kļūda). JSON formātā atbild tikai kļūmes, kas konstatētas, pirms lapa ir atrasta (pārāk garš vai nepareizi veidots pieprasījuma mērķis, adrešu slānis).
x-tagGroups:
  - name: server
    x-displayName: Serveris
    tags:
      - server-info
      - health
  - name: accounts
    x-displayName: Konti
    tags:
      - auth
      - google-sign-in
      - sessions
      - account
      - two-step-verification
      - email-change
      - data-export
      - account-deletion
  - name: games
    x-displayName: Partijas
    tags:
      - game-history
      - games
      - gifs
  - name: community
    x-displayName: Spēlētāji un ziņojumi
    tags:
      - players
      - leaderboard
      - reports
  - name: browser-pages
    x-displayName: Pārlūka lapas
    tags:
      - pages
paths:
  /info:
    get:
      operationId: getServerInfo
      tags:
        - server-info
      summary: Iegūt servera nosaukumu, versijas, portus un reģistrēšanās noteikumus
      description: |-
        Kas klientam vajadzīgs, pirms tas pierakstās vai izveido savienojumu: servera nosaukums un ID, WebSocket protokola versijas un WebSocket atrašanās vieta, reģistrēšanās noteikumi, oficiālās laika kontroles un ierobežojumi, ko klients var pārbaudīt pirms veidlapas nosūtīšanas.

        **Ierobežojumi:** tikai adrešu slānis.
      security: []
      responses:
        '200':
          description: Servera apraksts.
          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`: adrešu slānis.'
        '500':
          $ref: '#/components/responses/InternalError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
          description: '`timeout`. Arī `server_busy` ar `retryAfter` no 2 līdz 5, ja serveris ir stipri atpalicis (skatiet ievadu): pieprasījums netika izpildīts.'
  /auth/register:
    post:
      operationId: registerAccount
      tags:
        - auth
      summary: Izveidot kontu
      description: |-
        Izveido kontu: kad tā e-pasta adrese ir apstiprināta ar uz to nosūtīto saiti, vai nekavējoties, ja e-pasta apstiprināšana netiek prasīta.

        **Ar e-pasta apstiprināšanu** (`REQUIRE_EMAIL_VERIFICATION`, noklusējums): 202 `verification_sent`. Konts vēl neeksistē: reģistrēšanās gaida 24 stundas un tikmēr rezervē savu lietotājvārdu. Uz adresi tiek nosūtīta saite, kas derīga šīs 24 stundas, ne vairāk kā viena uz adresi ik pēc 5 minūtēm (`POST /auth/verify-email/resend` nosūta jaunu); konts tiek izveidots un tā adrese apstiprināta, kad saite tiek izmantota (lapas `/verify-email` poga), un pēc tam spēlētājs var pierakstīties. Līdz tam uz pierakstīšanos ar šo lietotājvārdu atbilde ir `invalid_credentials`, tāpat kā nezināmam kontam, un publiskā profila nav. Jauna reģistrēšanās ar to pašu adresi aizstāj gaidošo. Atbilde ir tāda pati, ja adresi jau izmanto cits konts: tad saite netiek nosūtīta, tā vietā konta īpašnieks saņem paziņojumu (ne vairāk kā vienu stundā), un lietotājvārds tiek rezervēts tāpat, lai nekas neatklātu, vai adresei ir konts. Reģistrēšanās, kuras saite netika izmantota, pēc 24 stundām tiek atmesta, un tās lietotājvārds atkal ir brīvs.

        **Bez e-pasta apstiprināšanas** (`REQUIRE_EMAIL_VERIFICATION=false`): 201 `ready`, konts tiek izveidots nekavējoties, un ar to var pierakstīties.

        **Noteikumi.** `username`: no `USERNAME_MIN` līdz `USERNAME_MAX` rakstzīmēm (no 3 līdz 20), burti, cipari, `_` un `-`, sākas ar burtu vai ciparu (`GET /info` tos norāda kā `limits`); rezervēti vārdi (`admin`, `moderator`, `deleted`...) un daži prefiksi tiek noraidīti; unikāls neatkarīgi no burtu reģistra. `email`: bez sākuma un beigu atstarpēm, glabāts mazajiem burtiem, tikai ASCII ar domēnu, kurā ir punkts. `password`: vismaz `PASSWORD_MIN_LENGTH` (10) rakstzīmes un ne vairāk kā 256 baiti UTF-8 kodējumā; tā nedrīkst saturēt lietotājvārdu vai e-pasta adreses vietējo daļu un nedrīkst būt bieži lietota parole.

        **Kļūdas**, pārbaudītas šādā secībā: 403 `registration_closed`; 400 `invalid_username`; 400 `invalid_email`; 400 `weak_password`; 409 `username_taken`; 428 `pow_required`; paroļu jaukšanas rindas kļūdas; 409 `email_taken` (tikai bez e-pasta apstiprināšanas).

        **Ierobežojumi:** `auth`, pēc tam `auth_register` (10 reģistrēšanās pieprasījumi stundā katram klientam). **Darba apliecinājums:** vienmēr, ja `POW_REGISTER_BITS` ir lielāks par 0.
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RegisterRequest'
            examples:
              first:
                summary: Pirmais mēģinājums bez darba apliecinājuma
                value:
                  username: alice
                  email: alice@example.org
                  password: correct horse battery
              withPow:
                summary: Nosūtīts vēlreiz ar darba apliecinājumu
                value:
                  username: alice
                  email: alice@example.org
                  password: correct horse battery
                  pow:
                    challenge: eyJ2IjoxLCJlIjoicmVnaXN0ZXIiLCJiIjoxOCwieCI6MTc5MDg4Mjk5MTIwMH0.6Ku_D7kw3S69OYxj-9KxsXszRCwz6ECnOQ-B6-v2erM
                    nonce: '123456'
      responses:
        '201':
          description: Konts ir izveidots, un ar to var pierakstīties (šajā serverī e-pasta apstiprināšana netiek prasīta).
          headers:
            Cache-Control:
              $ref: '#/components/headers/CacheControl'
          content:
            application/json:
              schema:
                type: object
                required:
                  - status
                properties:
                  status:
                    const: ready
              example:
                status: ready
        '202':
          description: Reģistrēšanās gaida, kad tiks izmantota tās apstiprinājuma saite (vai adresei jau ir konts; atbilde to neatklāj).
          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` (ar `field`), `invalid_json`, `invalid_username`, `invalid_email` vai `weak_password` ar `reason`: `too_short`, `too_long`, `contains_username`, `contains_email` vai `too_common`.'
        '403':
          $ref: '#/components/responses/Forbidden'
          description: '`registration_closed`: šajā serverī reģistrēšanās ir slēgta (`GET /info` norāda `registration: closed`).'
        '408':
          $ref: '#/components/responses/RequestTimeout'
        '409':
          $ref: '#/components/responses/Conflict'
          description: '`username_taken`: lietotājvārds pieder kādam kontam, vai to rezervējusi citas adreses gaidoša reģistrēšanās. `email_taken`: adresi izmanto cits konts (tikai bez e-pasta apstiprināšanas).'
        '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`: ierobežojums `auth` vai `auth_register`, vai paroļu jaukšanas rinda.'
        '500':
          $ref: '#/components/responses/InternalError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
          description: '`server_busy` (paroļu jaukšanas rinda vai datubāze palika bloķēta), `timeout`. Arī `server_busy` ar `retryAfter` no 2 līdz 5, ja serveris ir stipri atpalicis (skatiet ievadu): pieprasījums netika izpildīts.'
  /auth/login:
    post:
      operationId: logIn
      tags:
        - auth
      summary: Pierakstīties ar paroli
      description: |-
        Pierakstās ar lietotājvārdu vai e-pasta adresi un paroli. Iespējamas divas atbildes, abas ar statusu 200: sesija vai, ja ir ieslēgta divu faktoru autentifikācija, otrais solis, kas 5 minūšu laikā jāpabeidz ar `POST /auth/login/mfa`.

        Nezināms konts, nepareiza parole un konts bez paroles saņem vienādu atbildi pēc vienāda laika: 401 `invalid_credentials`. Konta pārbaudes (`banned`, `email_unverified`) notiek tikai pēc pareizas paroles. Sākot no `AUTH_FAILURES_PER_ACCOUNT` (5) neveiksmēm ar vienu pierakstīšanās vārdu, katram mēģinājumam jāgaida (429 `too_many_attempts`).

        **Ierobežojums:** `auth`. **Darba apliecinājums:** tikai neveiksmīgu pierakstīšanos viļņa laikā (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: Sesija vai pierakstīšanās otrais solis, ja ir ieslēgta divu faktoru autentifikācija.
          headers:
            Cache-Control:
              $ref: '#/components/headers/CacheControl'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LoginAnswer'
              examples:
                session:
                  summary: Pierakstīšanās izdevās
                  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: Divu faktoru autentifikācija ir ieslēgta
                  value:
                    mfaRequired: true
                    mfaToken: mfa_m4wXAVhYCMWG0PX7MLcIpBeccBFg0oxPvoRujzGWjO8
                    expiresIn: 300
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
          description: '`invalid_credentials`: nezināms konts, nepareiza parole vai konts bez paroles.'
        '403':
          $ref: '#/components/responses/Forbidden'
          description: 'Tikai pēc pareizas paroles: `banned` ar `until` (Unix laiks, ms; `null`, ja konts apturēts uz visiem laikiem) vai `email_unverified` (vecāks konts, kura adrese vēl nav apstiprināta).'
        '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` (šī pierakstīšanās vārda neveiksmju aizture) vai `rate_limited` (ierobežojums `auth`, paroļu jaukšanas rinda).'
        '500':
          $ref: '#/components/responses/InternalError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
          description: '`server_busy` (paroļu jaukšanas rinda vai datubāze palika bloķēta), `timeout`. Arī `server_busy` ar `retryAfter` no 2 līdz 5, ja serveris ir stipri atpalicis (skatiet ievadu): pieprasījums netika izpildīts.'
  /auth/login/mfa:
    post:
      operationId: logInWithSecondFactor
      tags:
        - auth
      summary: Pabeigt pierakstīšanos ar otro faktoru
      description: |-
        Pierakstīšanās otrais solis, ja ir ieslēgta divu faktoru autentifikācija, pēc tam, kad `POST /auth/login` vai pierakstīšanās ar Google (`POST /auth/sso/google/finish` vai `POST /auth/sso/google/link`) atbildēja ar `mfaRequired`. Nosūtiet `code` (6 ciparu autentifikācijas lietotnes kodu vai atkopšanas kodu) vai `recoveryCode`. Šeit izmantots atkopšanas kods vairs nav derīgs.

        Viens solis pieņem ne vairāk kā 5 nepareizus kodus; solis beidzas arī tad, ja parole tiek atiestatīta vai nomainīta. Pēc Google saistīšanas paroles soļa saistījums tiek saglabāts tikai tad, ja kods šeit tiek pieņemts.

        **Ierobežojumi:** `auth` un ne vairāk kā `AUTH_MFA_PER_ACCOUNT` (10) kodi 15 minūtēs kontam, no jebkuras adreses; virs tā kods netiek pārbaudīts, tāpēc atkopšanas kods netiek iztērēts.
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MfaLoginRequest'
            examples:
              authenticator:
                summary: Autentifikācijas lietotnes kods
                value:
                  mfaToken: mfa_m4wXAVhYCMWG0PX7MLcIpBeccBFg0oxPvoRujzGWjO8
                  code: '123456'
              recovery:
                summary: Atkopšanas kods
                value:
                  mfaToken: mfa_m4wXAVhYCMWG0PX7MLcIpBeccBFg0oxPvoRujzGWjO8
                  recoveryCode: j7v5-3ezx-zn
      responses:
        '200':
          description: Pierakstīšanās izdevās.
          headers:
            Cache-Control:
              $ref: '#/components/headers/CacheControl'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SessionAnswer'
        '400':
          $ref: '#/components/responses/BadRequest'
          description: '`invalid_request`: netika nosūtīts ne `code`, ne `recoveryCode`, vai pamatteksts pārkāpj shēmu; `invalid_json`.'
        '401':
          $ref: '#/components/responses/Unauthorized'
          description: '`invalid_mfa_token`: soļa termiņš beidzās, tas jau tika izmantots vai beidzās pēc 5 nepareiziem kodiem, vai arī kopš pirmā soļa parole tika atiestatīta vai nomainīta (pierakstieties vēlreiz). `invalid_code`: nepareizs vai jau izmantots kods.'
        '403':
          $ref: '#/components/responses/Forbidden'
          description: '`banned` (ar `until`), `email_unverified`.'
        '408':
          $ref: '#/components/responses/RequestTimeout'
        '409':
          $ref: '#/components/responses/Conflict'
          description: '`sso_already_linked`: tikai pēc Google saistīšanas paroles soļa — Google konts pa to laiku tika saistīts ar citu kontu.'
        '410':
          $ref: '#/components/responses/Gone'
          description: '`sso_expired`: tikai pēc Google saistīšanas paroles soļa — konts pa to laiku mainījās; sāciet pierakstīšanos no jauna.'
        '413':
          $ref: '#/components/responses/PayloadTooLarge'
        '414':
          $ref: '#/components/responses/UriTooLong'
        '415':
          $ref: '#/components/responses/UnsupportedMediaType'
        '429':
          $ref: '#/components/responses/TooManyRequests'
          description: '`too_many_attempts`: konta neveiksmju aizture, vai tā `AUTH_MFA_PER_ACCOUNT` kodi pēdējās 15 minūtēs ir izsmelti. `rate_limited`: ierobežojums `auth`.'
        '500':
          $ref: '#/components/responses/InternalError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
          description: '`server_busy` ar `retryAfter: 1`: datubāze palika bloķēta (pēc Google saistīšanas soļa nekas netika saistīts: sāciet pierakstīšanos no jauna). `timeout`. Arī `server_busy` ar `retryAfter` no 2 līdz 5, ja serveris ir stipri atpalicis (skatiet ievadu): pieprasījums netika izpildīts.'
  /auth/logout:
    post:
      operationId: logOut
      tags:
        - sessions
      summary: Izrakstīt šo sesiju
      description: |-
        Atsauc izmantotā marķiera sesiju. Ar to atvērtais WebSocket tiek aizvērts. Bez pamatteksta (vai `{}`).

        **Ierobežojums:** `sessions`.
      security:
        - bearerAuth: []
      responses:
        '200':
          description: Izrakstīšanās izdevās.
          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`: pamatteksts, kas nav `{}`; `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`: ierobežojums `sessions` vai konta budžets.'
        '500':
          $ref: '#/components/responses/InternalError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
          description: '`server_busy` (datubāze palika bloķēta), `timeout`. Arī `server_busy` ar `retryAfter` no 2 līdz 5, ja serveris ir stipri atpalicis (skatiet ievadu): pieprasījums netika izpildīts.'
  /auth/logout-all:
    post:
      operationId: logOutEverywhere
      tags:
        - sessions
      summary: Izrakstīt visas sesijas
      description: |-
        Atsauc visas konta sesijas, arī šo. Ar tām atvērtie WebSocket savienojumi tiek aizvērti. Bez pamatteksta (vai `{}`).

        **Ierobežojums:** `sessions`.
      security:
        - bearerAuth: []
      responses:
        '200':
          description: Visas sesijas ir izrakstītas.
          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`: pamatteksts, kas nav `{}`; `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`: ierobežojums `sessions` vai konta budžets.'
        '500':
          $ref: '#/components/responses/InternalError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
          description: '`server_busy` (datubāze palika bloķēta), `timeout`. Arī `server_busy` ar `retryAfter` no 2 līdz 5, ja serveris ir stipri atpalicis (skatiet ievadu): pieprasījums netika izpildīts.'
  /auth/verify-email/resend:
    post:
      operationId: resendVerificationEmail
      tags:
        - auth
      summary: Nosūtīt apstiprinājuma saiti vēlreiz
      description: |-
        Vēlreiz nosūta e-pasta apstiprinājuma saiti. Atbilde ir 202 `accepted` neatkarīgi no adreses, lai tā nekad neatklātu, vai adresi izmanto kāds konts vai reģistrēšanās.

        Pieprasījums iedarbojas ne biežāk kā reizi 5 minūtēs katrai adresei. Gaidoša reģistrēšanās ar šo adresi atkal saņem savas 24 stundas neatkarīgi no tā, vai adresi izmanto cits konts, lai tās lietotājvārds abos gadījumos paliktu rezervēts tikpat ilgi. Saite tiek nosūtīta tikai par šo reģistrēšanos, ja adresei nav konta (jauna saite, derīga 24 stundas, aizstāj iepriekšējo), vai par aktīvu, neapstiprinātu kontu ar šo adresi.

        **Ierobežojumi:** `auth`, pēc tam `auth_mail` (10 stundā katram klientam).
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/EmailRequest'
            example:
              email: alice@example.org
      responses:
        '202':
          description: Pieņemts (neatkarīgi no adreses).
          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`: ierobežojums `auth` vai `auth_mail` (neatkarīgi no adreses).'
        '500':
          $ref: '#/components/responses/InternalError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
          description: '`server_busy` ar `retryAfter: 1`: datubāze palika bloķēta, kamēr tika uzmeklēts konts vai saglabāta neapstiprināta konta saite (gaidošas reģistrēšanās atjaunošana tiek veikta pēc iespējas un nekad neizraisa pieprasījuma kļūmi); adreses 5 minūtes tik un tā sākas, tāpēc jauns pieprasījums šajā laikā neko nedara. `timeout`. Arī `server_busy` ar `retryAfter` no 2 līdz 5, ja serveris ir stipri atpalicis (skatiet ievadu): pieprasījums netika izpildīts.'
  /auth/password/forgot:
    post:
      operationId: requestPasswordReset
      tags:
        - auth
      summary: Nosūtīt paroles atiestatīšanas saiti
      description: |-
        Nosūta paroles atiestatīšanas saiti, kas derīga vienu stundu un atver lapu `/reset-password`. Atbilde ir 202 `accepted` neatkarīgi no adreses. Saite tiek nosūtīta tikai aktīvam kontam, ne biežāk kā reizi 5 minūtēs katrai adresei. Konts, kas izmanto tikai Google, šādi iestata savu pirmo paroli.

        Paroles atkopšanai ir visstingrākie API ierobežojumi, un tos visus skaita visam serverim kopumā: 3 pieprasījumi stundā (`AUTH_FORGOT_PER_HOUR`) un 10 pieprasījumi 24 stundās (`AUTH_FORGOT_PER_DAY`) katram klientam (IPv4 adresei vai IPv6 /64), 3 reizes vairāk katram IPv6 /48, papildus ierobežojumam `auth` (20 pieprasījumi 10 minūtēs) un vienai e-pasta vēstulei uz adresi ik pēc 5 minūtēm. Ne 202, ne 429 neatklāj, vai adresi izmanto kāds konts. Jaunās paroles iestatīšanai ir savs ierobežojums (`auth_reset`).

        **Ierobežojumi:** `auth`, `auth_forgot`, pēc tam `auth_forgot_day`.
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/EmailRequest'
            example:
              email: alice@example.org
      responses:
        '202':
          description: Pieņemts (neatkarīgi no adreses).
          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`: ierobežojums `auth`, `auth_forgot` vai `auth_forgot_day` (neatkarīgi no adreses).'
        '500':
          $ref: '#/components/responses/InternalError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
          description: '`server_busy` ar `retryAfter: 1`: datubāze palika bloķēta, kamēr tika uzmeklēts konts vai saglabāta saite; adreses 5 minūtes tik un tā sākas, tāpēc jauns pieprasījums šajā laikā saiti nenosūta. `timeout`. Arī `server_busy` ar `retryAfter` no 2 līdz 5, ja serveris ir stipri atpalicis (skatiet ievadu): pieprasījums netika izpildīts.'
  /auth/password/reset:
    post:
      operationId: resetPassword
      tags:
        - auth
      summary: Iestatīt jaunu paroli ar atiestatīšanas saites marķieri
      description: |-
        Iestata jaunu paroli ar atiestatīšanas saites marķieri (lapa `/reset-password` dara to pašu). Jaunajai parolei ir spēkā reģistrēšanās noteikumi.

        Atiestatīšanai ir šādas sekas vienā transakcijā (visas vai neviena): tā atsauc visas sesijas un atceļ gaidošu e-pasta maiņu; pārējās konta atiestatīšanas saites pārstāj darboties; adrese tiek uzskatīta par apstiprinātu (saite to pierādīja); īpašnieks saņem e-pasta vēstuli. Divu faktoru autentifikācija netiek skarta. Pēc paroļu jaukšanas rindas kļūdas, 503 vai 500 nekas nav mainījies: vecā parole un sesijas paliek, un saite paliek derīga.

        **Ierobežojumi:** `auth`, pēc tam `auth_reset` (10 stundā katram klientam, 30 katram IPv6 /48, kopīgs ar lapu: katrs mēģinājums aprēķina paroles jaucējvērtību).
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PasswordResetRequest'
            example:
              token: A0ATfmXCDKFC8UkSxLJCD85K7au5nMIrZ2P3K-1VWQg
              newPassword: a much better passphrase
      responses:
        '200':
          description: Parole ir nomainīta, un visas sesijas ir izrakstītas.
          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`: saite ir nederīga, izmantota vai novecojusi, vai arī tika nosūtīta uz adresi, kas kontam vairs nepieder. `weak_password` (ar `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`: ierobežojums `auth` vai `auth_reset`, vai paroļu jaukšanas rinda.'
        '500':
          $ref: '#/components/responses/InternalError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
          description: '`server_busy`: paroļu jaukšanas rinda vai datubāze palika bloķēta (`retryAfter: 1`, nekas nav mainījies). `timeout`. Arī `server_busy` ar `retryAfter` no 2 līdz 5, ja serveris ir stipri atpalicis (skatiet ievadu): pieprasījums netika izpildīts.'
  /auth/sso/google/start:
    post:
      operationId: startGoogleSignIn
      tags:
        - google-sign-in
      summary: Sākt pierakstīšanos ar Google
      description: |-
        Sāk pierakstīšanās ar Google mēģinājumu PKCE izaicinājumam un spēles klausītāja portam adresē `127.0.0.1`. Atbilde sniedz Google URL, kas jāatver sistēmas pārlūkā, un mēģinājuma `state`; mēģinājums ir derīgs 10 minūtes.

        `authUrl` satur `client_id`, `redirect_uri` (veidots no `redirectPort` un servera izcelsmes birkas), `response_type=code`, `scope=openid email profile`, `state`, `nonce`, `code_challenge` (servera paša Google verificētāja S256) ar `code_challenge_method=S256` un `prompt=select_account`. Spēle to pārbauda, pirms to atver (skatiet sadaļas aprakstu).

        **Ierobežojums:** `sso_start`.
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SsoStartRequest'
            example:
              codeChallenge: 6e7diXEYxG7OTYw7STfNOltEeLxAilPthC_txzaE0xA
              redirectPort: 51234
      responses:
        '200':
          description: Mēģinājums.
          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`: šajā serverī pierakstīšanās ar Google netiek piedāvāta.'
        '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`: ierobežojums `sso_start`.'
        '500':
          $ref: '#/components/responses/InternalError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
          description: '`server_busy` (datubāze palika bloķēta), `timeout`. Arī `server_busy` ar `retryAfter` no 2 līdz 5, ja serveris ir stipri atpalicis (skatiet ievadu): pieprasījums netika izpildīts.'
  /auth/sso/google/web/start:
    post:
      operationId: startGoogleWebSignIn
      tags:
        - google-sign-in
      summary: Sākt pierakstīšanos ar Google tīmekļa vietnē
      description: |-
        Sāk tīmekļa vietnes pierakstīšanās ar Google mēģinājumu tās lapas PKCE izaicinājumam. Atbilde ir tāda pati kā spēlei: Google URL, uz kuru jāpāriet, un mēģinājuma `state`; mēģinājums ir derīgs 10 minūtes. Pieejams, ja `GET /info` norāda `sso.googleWeb: true`.

        `authUrl` satur vietnes `client_id` un `redirect_uri=GOOGLE_WEB_REDIRECT_URI`, un pārējie parametri ir tādi paši kā spēlei. Lapa to pārbauda, pirms pāriet uz to (skatiet **Tīmekļa vietnē** sadaļas aprakstā).

        **Ierobežojums:** `sso_start`, kopīgs ar spēles sākumu.
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SsoWebStartRequest'
            example:
              codeChallenge: 6e7diXEYxG7OTYw7STfNOltEeLxAilPthC_txzaE0xA
      responses:
        '200':
          description: Mēģinājums.
          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ūkst `codeChallenge` vai tas ir nepareizi veidots, vai ir norādīts jebkurš cits lauks (arī `redirectPort`).'
        '404':
          $ref: '#/components/responses/NotFound'
          description: '`sso_disabled`: šajā serverī pierakstīšanās ar Google netiek piedāvāta vai netiek piedāvāta tīmekļa vietnēm.'
        '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`: ierobežojums `sso_start` (kopīgs ar spēles sākumu).'
        '500':
          $ref: '#/components/responses/InternalError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
          description: '`server_busy` (datubāze palika bloķēta), `timeout`. Arī `server_busy` ar `retryAfter` no 2 līdz 5, ja serveris ir stipri atpalicis (skatiet ievadu): pieprasījums netika izpildīts.'
  /auth/sso/google/finish:
    post:
      operationId: finishGoogleSignIn
      tags:
        - google-sign-in
      summary: Nodot Google atbildi serverim
      description: |-
        Nodod serverim `code` un `state`, ko Google nosūtīja spēles klausītājam vai vietnes lapai, kopā ar mēģinājuma ID un PKCE verificētāju. Mēģinājuma ID bez verificētāja ir bezjēdzīgs, un kods ir bezjēdzīgs bez servera paša PKCE verificētāja un klienta noslēpuma.

        Serveris pārbauda mēģinājumu (nezināms, izmantots vai novecojis: 410), pēc tam verificētāju (nepareizs verificētājs atstāj mēģinājumu izmantojamu), tad vienreiz izmanto mēģinājumu, pārbauda `state` un `iss`, apmaina kodu ar Google un pārbauda ID marķieri. Atbilde (200) ir viena no šīm:

        - `{ token, expiresAt, user }`: pierakstīšanās saistītajā kontā izdevās;
        - `{ mfaRequired, mfaToken, expiresIn }`: turpiniet ar `POST /auth/login/mfa`;
        - `{ needsUsername, ssoTicket, suggestedUsername }`: jauns konts; 10 minūšu laikā turpiniet ar `POST /auth/sso/complete`. `suggestedUsername` tiek veidots no Google vārda vai adreses un ir `""`, ja nekas neder;
        - `{ needsPassword, linkTicket, username, expiresIn }`: kontam ar šo adresi ir parole; turpiniet ar `POST /auth/sso/google/link`. Pagaidām nekas nav saistīts.

        **Ierobežojums:** `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: Sesija, otrais solis vai pirmās pierakstīšanās ar Google nākamais solis.
          headers:
            Cache-Control:
              $ref: '#/components/headers/CacheControl'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SsoFinishAnswer'
              examples:
                session:
                  summary: Pierakstīšanās saistītajā kontā izdevās
                  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: Divu faktoru autentifikācija ir ieslēgta
                  value:
                    mfaRequired: true
                    mfaToken: mfa_m4wXAVhYCMWG0PX7MLcIpBeccBFg0oxPvoRujzGWjO8
                    expiresIn: 300
                needsUsername:
                  summary: Jauns spēlētājs
                  value:
                    needsUsername: true
                    ssoTicket: sso_gYIKKhwnz1AqWfFxOC3R-ZvCACw06q74fEMtGEOJQWU
                    suggestedUsername: alice
                needsPassword:
                  summary: Adresi izmanto konts ar paroli
                  value:
                    needsPassword: true
                    linkTicket: sso_ZfEI6iZjePTHbnbevupaD9O7GjBCzcL-GJu2XGOdFGc
                    username: alice
                    expiresIn: 600
        '400':
          $ref: '#/components/responses/BadRequest'
        '403':
          $ref: '#/components/responses/Forbidden'
          description: '`invalid_verifier`: verificētājs neatbilst mēģinājuma izaicinājumam (mēģinājums paliek izmantojams). `sso_email_unverified`: Google nav apstiprinājis adresi. `registration_closed`. `account_disabled`. Tikai saistītam kontam: `banned` (ar `until`), `email_unverified`.'
        '404':
          $ref: '#/components/responses/NotFound'
          description: '`sso_disabled`: šajā serverī pierakstīšanās ar Google netiek piedāvāta.'
        '408':
          $ref: '#/components/responses/RequestTimeout'
        '409':
          $ref: '#/components/responses/Conflict'
          description: '`sso_account_exists`: adresi izmanto aktīvs konts bez paroles (tas netiek nosaukts).'
        '410':
          $ref: '#/components/responses/Gone'
          description: '`sso_expired`: nezināms, izmantots vai novecojis mēģinājums.'
        '413':
          $ref: '#/components/responses/PayloadTooLarge'
        '414':
          $ref: '#/components/responses/UriTooLong'
        '415':
          $ref: '#/components/responses/UnsupportedMediaType'
        '429':
          $ref: '#/components/responses/TooManyRequests'
          description: '`rate_limited`: ierobežojums `sso_finish`.'
        '500':
          $ref: '#/components/responses/InternalError'
        '502':
          $ref: '#/components/responses/BadGateway'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
          description: '`server_busy` (datubāze palika bloķēta), `timeout`. Arī `server_busy` ar `retryAfter` no 2 līdz 5, ja serveris ir stipri atpalicis (skatiet ievadu): pieprasījums netika izpildīts.'
  /auth/sso/google/link:
    post:
      operationId: linkGoogleAccount
      tags:
        - google-sign-in
      summary: Saistīt Google ar esošu kontu, izmantojot tā paroli
      description: |-
        Pēc tam, kad `finish` atbildēja ar `needsPassword`: saista Google ar esošo kontu, izmantojot šī konta paroli, kas ievadīta spēlē. Atbilde (200) ir sesija (saistījums ir saglabāts, un spēlētājs ir pierakstījies) vai, ja ir ieslēgta divu faktoru autentifikācija, `{ mfaRequired, mfaToken, expiresIn }`: turpiniet ar `POST /auth/login/mfa`, un saistījums tiek saglabāts tikai tad, ja tur tiek pieņemts kods.

        Nepareiza parole atstāj biļeti izmantojamu, kopā 5 mēģinājumiem. Mēģinājums tiek ieskaitīts tieši pirms paroles pārbaudes: 429 `too_many_attempts` vai 428 `pow_required` neieskaita nevienu, bet paroļu jaukšanas rindas atteikums (503 `server_busy`, 429 `rate_limited`) vienu jau ir ieskaitījis, un tas skaitās kā konta neveiksme. Neveiksmju aizturei tiek izmantots tas pats skaitītājs, kas `POST /auth/login`.

        **Ierobežojums:** `auth` (ar tā IPv6 /48 skaitīšanu). **Darba apliecinājums:** tāpat kā `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: Sesija vai pierakstīšanās otrais solis.
          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`: nepareiza parole; biļete saglabājas, kopā 5 mēģinājumiem.'
        '403':
          $ref: '#/components/responses/Forbidden'
          description: '`banned` (ar `until`), tikai pēc pareizas paroles.'
        '404':
          $ref: '#/components/responses/NotFound'
          description: '`sso_disabled`: šajā serverī pierakstīšanās ar Google netiek piedāvāta.'
        '408':
          $ref: '#/components/responses/RequestTimeout'
        '409':
          $ref: '#/components/responses/Conflict'
          description: '`sso_already_linked`: Google konts pa to laiku tika saistīts ar citu kontu.'
        '410':
          $ref: '#/components/responses/Gone'
          description: '`sso_expired`: biļete ir nezināma, jau izmantota vai novecojusi; 5. nepareizā parole; konts, kura statuss vai adrese mainījās kopš `finish`; vai konts, kura statuss, adrese, parole vai divu faktoru autentifikācija mainījās, kamēr tika saglabāta sasaiste. Sāciet pierakstīšanos no jauna.'
        '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` (konta neveiksmju aizture) vai `rate_limited` (ierobežojums `auth`, paroļu jaukšanas rinda).'
        '500':
          $ref: '#/components/responses/InternalError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
          description: '`server_busy`: paroļu jaukšanas rinda vai datubāze palika bloķēta (`retryAfter: 1`, nekas netika sasaistīts). `timeout`. Arī `server_busy` ar `retryAfter` no 2 līdz 5, ja serveris ir stipri atpalicis (skatiet ievadu): pieprasījums netika izpildīts.'
  /auth/sso/complete:
    post:
      operationId: completeGoogleSignUp
      tags:
        - google-sign-in
      summary: Izveidot kontu pirmajā pierakstīšanās reizē ar Google
      description: |-
        Pēc tam, kad `finish` atbildēja ar `needsUsername`: izveido kontu ar izvēlēto lietotājvārdu (piemēro reģistrēšanās noteikumus), un tajā tiek veikta pierakstīšanās. Kontam nav paroles: `hasPassword` ir `false`, un ar „Aizmirsāt paroli?” (`POST /auth/password/forgot`) tam var iestatīt paroli.

        **Ierobežojums:** `auth` (ar tā IPv6 /48 uzskaiti).
      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: Konts ir izveidots, un tajā ir veikta pierakstīšanās.
          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`: šajā serverī pierakstīšanās ar Google netiek piedāvāta.'
        '408':
          $ref: '#/components/responses/RequestTimeout'
        '409':
          $ref: '#/components/responses/Conflict'
          description: '`username_taken` (lietotājvārds pieder kādam kontam, vai to aizņem citas adreses gaidoša reģistrēšanās), `sso_already_linked`, `email_taken`.'
        '410':
          $ref: '#/components/responses/Gone'
          description: '`sso_expired`: biļete ir nezināma, jau izmantota vai novecojusi.'
        '413':
          $ref: '#/components/responses/PayloadTooLarge'
        '414':
          $ref: '#/components/responses/UriTooLong'
        '415':
          $ref: '#/components/responses/UnsupportedMediaType'
        '429':
          $ref: '#/components/responses/TooManyRequests'
          description: '`rate_limited`: ierobežojums `auth`.'
        '500':
          $ref: '#/components/responses/InternalError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
          description: '`server_busy` (datubāze palika bloķēta), `timeout`. Arī `server_busy` ar `retryAfter` no 2 līdz 5, ja serveris ir stipri atpalicis (skatiet ievadu): pieprasījums netika izpildīts.'
  /auth/sessions:
    get:
      operationId: listSessions
      tags:
        - sessions
      summary: Uzskaitīt pierakstītās ierīces
      description: |-
        Konta aktīvās sesijas (pierakstītās ierīces), sākot ar pēdējo izmantoto. `current` apzīmē sesiju, kas veic pieprasījumu. `lastSeenAt` tiek atjaunināts ne biežāk kā reizi 5 minūtēs (vēlāk, kamēr serveris ir stipri atpalicis); `expiresAt` ir absolūtās beigas, un dīkstāves ierobežojums var sesiju izbeigt ātrāk.

        **Ierobežojums:** `sessions`.
      security:
        - bearerAuth: []
      responses:
        '200':
          description: Aktīvās sesijas.
          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`: ierobežojums `sessions` vai konta budžets.'
        '500':
          $ref: '#/components/responses/InternalError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
          description: '`server_busy` (datubāze palika bloķēta), `timeout`. Arī `server_busy` ar `retryAfter` no 2 līdz 5, ja serveris ir stipri atpalicis (skatiet ievadu): pieprasījums netika izpildīts.'
  /auth/sessions/{id}:
    delete:
      operationId: revokeSession
      tags:
        - sessions
      summary: Izrakstīt vienu ierīci
      description: |-
        Izraksta vienu konta sesiju; izrakstīt var arī pašreizējo. Ar to atvērtais WebSocket tiek aizvērts. Bez pamatteksta (vai `{}`).

        **Ierobežojums:** `sessions`.
      security:
        - bearerAuth: []
      parameters:
        - $ref: '#/components/parameters/SessionId'
      responses:
        '200':
          description: Sesija ir izrakstīta.
          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`: pamatteksts, kas nav `{}`, vai `id`, kas nav derīgā URL kodējumā; `invalid_json`.'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
          description: '`not_found`: šim kontam nav aktīvas sesijas ar šādu 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`: ierobežojums `sessions` vai konta budžets.'
        '500':
          $ref: '#/components/responses/InternalError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
          description: '`server_busy` (datubāze palika bloķēta), `timeout`. Arī `server_busy` ar `retryAfter` no 2 līdz 5, ja serveris ir stipri atpalicis (skatiet ievadu): pieprasījums netika izpildīts.'
  /account/me:
    get:
      operationId: getAccount
      tags:
        - account
      summary: Iegūt kontu, tā reitingus un aktīvās sankcijas
      description: |-
        Konts, kā to redz tā spēlētājs: konta skats (arī katras pierakstīšanās atbildes `user`), pa vienam reitinga ierakstam katrā kategorijā, kurā spēlētājs ir spēlējis reitinga partijas, aktīvās sankcijas un konta apturēšana. Pretkrāpšanas sistēmas godīguma līmenis nekad netiek rādīts.

        **Ierobežojums:** `account`.
      security:
        - bearerAuth: []
      responses:
        '200':
          description: Konts.
          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` vai `invalid_token` (arī tad, ja konts ir dzēsts).'
        '414':
          $ref: '#/components/responses/UriTooLong'
        '429':
          $ref: '#/components/responses/TooManyRequests'
          description: '`rate_limited`: ierobežojums `account` vai konta budžets.'
        '500':
          $ref: '#/components/responses/InternalError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
          description: '`server_busy` (datubāze palika bloķēta), `timeout`. Arī `server_busy` ar `retryAfter` no 2 līdz 5, ja serveris ir stipri atpalicis (skatiet ievadu): pieprasījums netika izpildīts.'
  /account/preferences:
    put:
      operationId: updatePreferences
      tags:
        - account
      summary: Pieņemt vai noraidīt tiešos izaicinājumus
      description: |-
        Nosaka, vai citi spēlētāji var izaicināt šo spēlētāju pēc vārda. Ar `none` tiešie izaicinājumi tiek noraidīti: izaicinātājam tiek paziņots, ka spēlētājs nav pieejams. Bez atkārtotas autentifikācijas.

        **Ierobežojums:** `account`.
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/Preferences'
            example:
              acceptChallenges: none
      responses:
        '200':
          description: Pašlaik spēkā esošās preferences.
          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`: ierobežojums `account` vai konta budžets.'
        '500':
          $ref: '#/components/responses/InternalError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
          description: '`server_busy` (datubāze palika bloķēta), `timeout`. Arī `server_busy` ar `retryAfter` no 2 līdz 5, ja serveris ir stipri atpalicis (skatiet ievadu): pieprasījums netika izpildīts.'
  /account/password:
    post:
      operationId: changePassword
      tags:
        - account
      summary: Mainīt paroli
      description: |-
        Maina paroli. Nepieciešama pašreizējā parole, bet ne otrais faktors, pat ja ir ieslēgta divu faktoru autentifikācija. Jaunajai parolei piemēro reģistrēšanās noteikumus (tos pārbauda pēc pašreizējās paroles).

        Maiņai ir šādas sekas, vienā transakcijā (visas vai neviena): tā atsauc visas pārējās sesijas (šī paliek pierakstīta), atceļ gaidošu e-pasta maiņu, padara konta paroles atiestatīšanas saites nederīgas un nosūta īpašniekam e-pasta vēstuli.

        **Atkārtota autentifikācija:** tikai parole. **Ierobežojumi:** `reauth`, pēc tam `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: Parole ir nomainīta.
          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` (konts, kurā pierakstās tikai ar Google), `weak_password` (ar `reason`), `invalid_request`, `invalid_json`.'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
          description: '`invalid_password`: nepareiza pašreizējā parole, vai arī paroles atiestatīšana vai maiņa notika, kamēr pieprasījums tika pārbaudīts.'
        '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` (konta neveiksmīgās atkārtotās autentifikācijas) vai `rate_limited` (ierobežojums `reauth` vai `reauth_user`, konta budžets, paroļu jaukšanas rinda).'
        '500':
          $ref: '#/components/responses/InternalError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
          description: '`server_busy`: paroļu jaukšanas rinda vai datubāze palika bloķēta (`retryAfter: 1`, nekas netika mainīts). `timeout`. Arī `server_busy` ar `retryAfter` no 2 līdz 5, ja serveris ir stipri atpalicis (skatiet ievadu): pieprasījums netika izpildīts.'
  /account/mfa/totp/setup:
    post:
      operationId: startTotpSetup
      tags:
        - two-step-verification
      summary: Sākt divu faktoru autentifikācijas ieslēgšanu
      description: |-
        Saglabā jaunu gaidošu autentifikācijas lietotnes slepeno atslēgu, kas aizstāj jebkuru iepriekšējo gaidošo, un atgriež to autentifikācijas lietotnei (kā tekstu un kā `otpauth://` URI, ko parādīt kā QR kodu). Divu faktoru autentifikācija vēl nav ieslēgta: `POST /account/mfa/totp/enable` to ieslēdz ar šīs slepenās atslēgas kodu.

        **Atkārtota autentifikācija:** tikai parole. **Ierobežojumi:** `reauth`, pēc tam `reauth_user`.
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PasswordOnlyRequest'
            example:
              password: correct horse battery
      responses:
        '200':
          description: Gaidošā slepenā atslēga.
          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` (konts, kurā pierakstās tikai ar 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` (pārbauda pirms paroles).'
        '413':
          $ref: '#/components/responses/PayloadTooLarge'
        '414':
          $ref: '#/components/responses/UriTooLong'
        '415':
          $ref: '#/components/responses/UnsupportedMediaType'
        '429':
          $ref: '#/components/responses/TooManyRequests'
          description: '`too_many_attempts` (konta neveiksmīgās atkārtotās autentifikācijas) vai `rate_limited` (ierobežojums `reauth` vai `reauth_user`, konta budžets, paroļu jaukšanas rinda).'
        '500':
          $ref: '#/components/responses/InternalError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
          description: '`server_busy` (paroļu jaukšanas rinda vai datubāze palika bloķēta), `timeout`. Arī `server_busy` ar `retryAfter` no 2 līdz 5, ja serveris ir stipri atpalicis (skatiet ievadu): pieprasījums netika izpildīts.'
  /account/mfa/totp/enable:
    post:
      operationId: enableTotp
      tags:
        - two-step-verification
      summary: Pabeigt divu faktoru autentifikācijas ieslēgšanu un saņemt atkopšanas kodus
      description: |-
        Ieslēdz divu faktoru autentifikāciju ar gaidošās slepenās atslēgas kodu (tieši 6 cipari). Parole šeit netiek prasīta; tā tika norādīta iestatīšanas laikā. Atbildē ir 10 atkopšanas kodu, kas tiek parādīti tikai šoreiz.

        **Ierobežojumi:** `reauth`, pēc tam `reauth_user`; nepareizs kods tiek ieskaitīts konta atkārtotās autentifikācijas neveiksmēs.
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/TotpEnableRequest'
            example:
              code: '123456'
      responses:
        '200':
          description: Divu faktoru autentifikācija ir ieslēgta.
          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` nav tieši 6 cipari, vai pamatteksts neatbilst shēmai; `invalid_json`.'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
          description: '`invalid_code`: nepareizs kods (pārbaudiet ierīces laiku).'
        '408':
          $ref: '#/components/responses/RequestTimeout'
        '409':
          $ref: '#/components/responses/Conflict'
          description: '`mfa_already_enabled`; `mfa_setup_required`: nav gaidošas slepenās atslēgas (vispirms izsauciet `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` (konta neveiksmīgās atkārtotās autentifikācijas) vai `rate_limited` (ierobežojums `reauth` vai `reauth_user`, konta budžets).'
        '500':
          $ref: '#/components/responses/InternalError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
          description: '`server_busy` (datubāze palika bloķēta), `timeout`. Arī `server_busy` ar `retryAfter` no 2 līdz 5, ja serveris ir stipri atpalicis (skatiet ievadu): pieprasījums netika izpildīts.'
  /account/mfa/totp/disable:
    post:
      operationId: disableTotp
      tags:
        - two-step-verification
      summary: Izslēgt divu faktoru autentifikāciju
      description: |-
        Izslēdz divu faktoru autentifikāciju. Slepenā atslēga un atkopšanas kodi tiek dzēsti, un īpašnieks saņem e-pasta vēstuli.

        **Atkārtota autentifikācija:** parole un autentifikācijas lietotnes kods vai atkopšanas kods (nepieciešams viens no `code` vai `recoveryCode`). **Ierobežojumi:** `reauth`, pēc tam `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: Divu faktoru autentifikācija ir izslēgta.
          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` (konts, kurā pierakstās tikai ar Google), `invalid_request`, `invalid_json`.'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
          description: '`mfa_code_required` (nav ne `code`, ne `recoveryCode`; pārbauda pirms paroles), `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` (neveiksmīgas atkārtotās autentifikācijas vai šajā kontā izmēģināts pārāk daudz kodu) vai `rate_limited` (ierobežojums `reauth` vai `reauth_user`, konta budžets, paroļu jaukšanas rinda).'
        '500':
          $ref: '#/components/responses/InternalError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
          description: '`server_busy` (paroļu jaukšanas rinda vai datubāze palika bloķēta), `timeout`. Arī `server_busy` ar `retryAfter` no 2 līdz 5, ja serveris ir stipri atpalicis (skatiet ievadu): pieprasījums netika izpildīts.'
  /account/mfa/recovery-codes:
    post:
      operationId: regenerateRecoveryCodes
      tags:
        - two-step-verification
      summary: Aizstāt atkopšanas kodus
      description: |-
        Aizstāj atkopšanas kodus ar 10 jauniem; vecie vairs nedarbojas.

        **Atkārtota autentifikācija:** parole un autentifikācijas lietotnes kods laukā `code` (atkopšanas kods tiek noraidīts ar 403 `invalid_code`). **Ierobežojumi:** `reauth`, pēc tam `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: Jaunie atkopšanas kodi, kas tiek parādīti tikai šoreiz.
          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` (konts, kurā pierakstās tikai ar Google), `invalid_request`, `invalid_json`.'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
          description: '`invalid_password`, `mfa_code_required`, `invalid_code` (arī atkopšanas kodam).'
        '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` (neveiksmīgas atkārtotās autentifikācijas vai šajā kontā izmēģināts pārāk daudz kodu) vai `rate_limited` (ierobežojums `reauth` vai `reauth_user`, konta budžets, paroļu jaukšanas rinda).'
        '500':
          $ref: '#/components/responses/InternalError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
          description: '`server_busy` (paroļu jaukšanas rinda vai datubāze palika bloķēta), `timeout`. Arī `server_busy` ar `retryAfter` no 2 līdz 5, ja serveris ir stipri atpalicis (skatiet ievadu): pieprasījums netika izpildīts.'
  /account/email:
    post:
      operationId: changeEmail
      tags:
        - email-change
      summary: Mainīt e-pasta adresi
      description: |-
        Maina konta e-pasta adresi. Laukam `newEmail` tiek noņemtas sākuma un beigu atstarpes, tas tiek pārveidots mazajiem burtiem un pēc tam pārbaudīts tāpat kā reģistrēšanās adrese.

        **Ar e-pasta apstiprināšanu** (noklusējums): 202 `verification_sent`. Uz jauno adresi tiek nosūtīta saite, kas derīga 24 stundas; tā atver `/confirm-email-change`, un adrese mainās tikai tad, kad spēlētājs nospiež šīs lapas pogu. Līdz tam `GET /account/me` rāda jauno adresi kā `pendingEmail`. Jauns pieprasījums aizstāj gaidošo; paroles maiņa vai atiestatīšana to atceļ. Uz konkrētu jauno adresi tiek nosūtīta ne vairāk kā viena saite 5 minūtēs, neatkarīgi no tā, kurš to pieprasa (pieprasījums par jau gaidošo maiņu saglabā iepriekš nosūtīto saiti, kas paliek derīga); atbilde ir tāda pati. Uz pašreizējo adresi tiek nosūtīts paziņojums, ka pieprasīta maiņa uz maskētu adresi (`a***@example.org`). Atbilde un `pendingEmail` ir tādas pašas, ja jauno adresi jau izmanto cits konts: tad saite netiek nosūtīta, tāpēc šī maiņa nekad netiek pabeigta, un tās vietā šīs adreses īpašnieks saņem paziņojumu (ne vairāk kā vienu stundā).

        Kad saite ir apstiprināta, adrese mainās un tiek uzskatīta par apstiprinātu, ierīces paliek pierakstītas, iepriekš nosūtītās saites (apstiprinājuma, paroles atiestatīšanas, citu maiņu) vairs nedarbojas, un iepriekšējā adrese saņem paziņojumu, kurā jaunā adrese ir maskēta.

        **Bez e-pasta apstiprināšanas** (`REQUIRE_EMAIL_VERIFICATION=false`): adrese mainās uzreiz (200 `email_changed`), un iepriekšējā adrese saņem paziņojumu. Ja adresi izmanto cits konts, atbilde ir 409 `email_taken`, un šīs adreses īpašnieks saņem paziņojumu.

        **Atkārtota autentifikācija:** parole un, ja ir ieslēgta divu faktoru autentifikācija, autentifikācijas lietotnes kods vai atkopšanas kods. `invalid_email` un `same_email` tiek pārbaudīti pirms paroles, tāpēc tie neskaitās kā neveiksme. **Ierobežojumi:** `reauth`, pēc tam `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: Adrese tika mainīta uzreiz (šajā serverī nav e-pasta apstiprināšanas).
          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: Jaunā adrese, kā tā saglabāta.
              example:
                status: email_changed
                email: alice.new@example.org
        '202':
          description: Uz jauno adresi tika nosūtīta apstiprinājuma saite (vai arī adrese pieder citam kontam; atbilde to neatklāj).
          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` (konta pašreizējā adrese), `password_not_set` (konts, kurā pierakstās tikai ar Google), `invalid_request`, `invalid_json`.'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
          description: '`invalid_password` (arī tad, ja paroles maiņa vai atiestatīšana apsteidza pieprasījumu: saite netiek nosūtīta), `mfa_code_required`, `invalid_code`.'
        '408':
          $ref: '#/components/responses/RequestTimeout'
        '409':
          $ref: '#/components/responses/Conflict'
          description: '`email_taken`: tikai bez e-pasta apstiprināšanas.'
        '413':
          $ref: '#/components/responses/PayloadTooLarge'
        '414':
          $ref: '#/components/responses/UriTooLong'
        '415':
          $ref: '#/components/responses/UnsupportedMediaType'
        '429':
          $ref: '#/components/responses/TooManyRequests'
          description: '`too_many_attempts` (neveiksmīgas atkārtotās autentifikācijas vai šajā kontā izmēģināts pārāk daudz kodu) vai `rate_limited` (ierobežojums `reauth` vai `reauth_user`, konta budžets, paroļu jaukšanas rinda).'
        '500':
          $ref: '#/components/responses/InternalError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
          description: '`server_busy`: paroļu jaukšanas rinda vai datubāze palika bloķēta (`retryAfter: 1`: nekas netika mainīts, un to pašu pieprasījumu var nosūtīt vēlreiz). `timeout`. Arī `server_busy` ar `retryAfter` no 2 līdz 5, ja serveris ir stipri atpalicis (skatiet ievadu): pieprasījums netika izpildīts.'
  /account/export:
    post:
      operationId: exportAccountData
      tags:
        - data-export
      summary: Lejupielādēt konta datus
      description: |-
        Viss, ko serveris glabā par kontu, kā viens saglabājams JSON fails (`format` `scacelith-account-export`, `version` 1). Eksports reģistrē drošības notikumu (`account_exported`). Dokumenta masīvs `notes` vienkāršā angļu valodā paskaidro spēlētājam, kas nav iekļauts.

        **Eksportā nekad nav:** paroles jaucējvērtības, divu faktoru autentifikācijas slepenās atslēgas un atkopšanas kodu; neviena sesijas vai saites marķiera, ne arī tā jaucējvērtības; pretkrāpšanas sistēmas datu (godīguma līmeņa un vērtējuma, anomāliju, partiju analīzes, ziņojuma svara); ziņojumu, ko citi spēlētāji iesnieguši par šo spēlētāju; moderatoru identitāšu; citu spēlētāju privāto datu (pretinieki redzami ar savu publisko vārdu un reitingu, nekas neatklāj, vai citam spēlētājam piemērota sankcija, un netiek iekļauta neviena IP adrese, kas varētu piederēt citai personai).

        **Atkārtota autentifikācija:** parole un, ja ir ieslēgta divu faktoru autentifikācija, autentifikācijas lietotnes kods vai atkopšanas kods. **Ierobežojumi:** `account_export` (5 stundā katram spēlētājam, visa servera mērogā; tiek skaitīts katrs mēģinājums, arī neveiksmīgie; pārbauda vispirms), pēc tam `reauth` un `reauth_user`. **Laika ierobežojums:** 60 sekundes.
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ReauthCredentials'
            example:
              password: correct horse battery
              code: '123456'
      responses:
        '200':
          description: Eksporta dokuments kā pielikums.
          headers:
            Cache-Control:
              $ref: '#/components/headers/CacheControl'
            Content-Disposition:
              description: '`attachment; filename="scacelith-account-<username>.json"`. Lietotājvārdā rakstzīmes, kas nav burti, cipari, `_`, `.` un `-`, tiek aizstātas ar `_`.'
              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` (konts, kurā pierakstās tikai ar 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` (neveiksmīgas atkārtotās autentifikācijas vai šajā kontā izmēģināts pārāk daudz kodu) vai `rate_limited` (ierobežojums `account_export`, `reauth` vai `reauth_user`, konta budžets, paroļu jaukšanas rinda).'
        '500':
          $ref: '#/components/responses/InternalError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
          description: '`busy` (datubāze palika bloķēta, ar galveni `Retry-After: 1`), `server_busy` (paroļu jaukšanas rinda), `timeout` (60 sekundes). Arī `server_busy` ar `retryAfter` no 2 līdz 5, ja serveris ir stipri atpalicis (skatiet ievadu): pieprasījums netika izpildīts.'
  /account/delete:
    post:
      operationId: deleteAccount
      tags:
        - account-deletion
      summary: Dzēst kontu
      description: |-
        Dzēš kontu; šo darbību nevar atcelt.

        - Visas sesijas tiek nekavējoties atsauktas: no šī brīža marķieris saņem 401 `invalid_token`.
        - Lietotājvārds kļūst par `deleted#<id>` gan kontā, gan katrā partijas pierakstā.
        - Tiek izdzēsti: e-pasta adrese, paroles jaucējvērtība, divu faktoru autentifikācijas slepenā atslēga un atkopšanas kodi, sesijas un saišu marķieri, sasaiste ar Google, pretkrāpšanas sistēmas godīguma ieraksts un IP adreses, kas saglabātas kopā ar drošības notikumiem.
        - Reitingi un partijas tiek saglabāti. Partijas joprojām var skatīt ar anonīmo vārdu, un `GET /players/{username}` iepriekšējam vārdam atbild ar 404.

        **Atkārtota autentifikācija:** parole un, ja ir ieslēgta divu faktoru autentifikācija, autentifikācijas lietotnes kods vai atkopšanas kods. **Ierobežojumi:** `reauth`, pēc tam `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: Konts ir dzēsts.
          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` (konts, kurā pierakstās tikai ar 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` (neveiksmīgas atkārtotās autentifikācijas vai šajā kontā izmēģināts pārāk daudz kodu) vai `rate_limited` (ierobežojums `reauth` vai `reauth_user`, konta budžets, paroļu jaukšanas rinda).'
        '500':
          $ref: '#/components/responses/InternalError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
          description: '`server_busy` (paroļu jaukšanas rinda vai datubāze palika bloķēta), `timeout`. Arī `server_busy` ar `retryAfter` no 2 līdz 5, ja serveris ir stipri atpalicis (skatiet ievadu): pieprasījums netika izpildīts.'
  /account/games:
    get:
      operationId: listAccountGames
      tags:
        - game-history
      summary: Uzskaitīt spēlētāja partijas ar filtriem un lapošanu
      description: |-
        Pierakstītā spēlētāja partijas, sākot ar jaunākajām, filtrētas un sadalītas lapās, kopā ar filtram atbilstošo partiju skaitu. Visi vaicājuma parametri ir neobligāti, un tukša vērtība tiek uzskatīta par neesošu.

        Lapošana: nododiet iepriekšējās lapas `next` kā `before`; pēdējā lapā `next` ir `null`. `total` skaita filtram atbilstošās partijas visās lapās kopā.

        **Ierobežojums:** `account_games`.
      security:
        - bearerAuth: []
      parameters:
        - $ref: '#/components/parameters/BeforeQuery'
        - $ref: '#/components/parameters/LimitQuery'
        - name: category
          in: query
          required: false
          description: Oficiālās kategorijas ID (`3+2` vai `3%2B2`) vai `custom` visām partijām ar citu laika kontroli.
          schema:
            type: string
            pattern: '^\s*([0-9]+[+ ][0-9]+|custom)\s*$'
          example: '3+2'
        - name: rated
          in: query
          required: false
          description: Tikai reitinga (`true`) vai nereitinga (`false`) partijas.
          schema:
            type: string
            enum:
              - 'true'
              - 'false'
        - name: result
          in: query
          required: false
          description: Tikai uzvarētās, zaudētās vai neizšķirtās partijas no spēlētāja viedokļa. Anulētās partijas parādās tikai bez šī filtra.
          schema:
            type: string
            enum:
              - win
              - loss
              - draw
      responses:
        '200':
          description: Viena vēstures lapa.
          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` nav partijas ID), `invalid_limit` vai `invalid_filter` (`category`, `rated` vai `result`), katrs ar `field`, kas nosauc parametru.'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '414':
          $ref: '#/components/responses/UriTooLong'
        '429':
          $ref: '#/components/responses/TooManyRequests'
          description: '`rate_limited`: ierobežojums `account_games` vai konta budžets.'
        '500':
          $ref: '#/components/responses/InternalError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
          description: '`busy` (datubāze palika bloķēta; `retryAfter: 1` pamattekstā, bez galvenes `Retry-After`), `server_busy` (sesijas uzmeklēšanas laikā datubāze bija bloķēta), `timeout`. Arī `server_busy` ar `retryAfter` no 2 līdz 5, ja serveris ir stipri atpalicis (skatiet ievadu): pieprasījums netika izpildīts.'
  /games/{id}:
    get:
      operationId: getGame
      tags:
        - games
      summary: Iegūt partijas pierakstu ar gājieniem un pulksteņiem
      description: |-
        Viens partijas pieraksts ar gājieniem un pulksteņiem. Bez marķiera vai ar tāda spēlētāja marķieri, kurš šo partiju nespēlēja, atbilde ir publiskā. Ja marķiera spēlētājs šo partiju spēlēja, atbildei tiek pievienoti `you` un `reportable`.

        **Ierobežojums:** `public_read` (ar marķieri — katram spēlētājam, bez tā — katram klientam).
      security:
        - {}
        - bearerAuth: []
      parameters:
        - $ref: '#/components/parameters/GameId'
      responses:
        '200':
          description: Partijas pieraksts.
          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`: nav pozitīvs vesels skaitlis ar ne vairāk kā 16 cipariem (mazāks par 2^53); `invalid_request`: nederīgs URL kodējums.'
        '401':
          $ref: '#/components/responses/Unauthorized'
          description: '`invalid_token`: tika nosūtīts marķieris, un tas nav derīgs.'
        '404':
          $ref: '#/components/responses/NotFound'
          description: '`not_found`: šādas partijas nav.'
        '414':
          $ref: '#/components/responses/UriTooLong'
        '429':
          $ref: '#/components/responses/TooManyRequests'
          description: '`rate_limited`: ierobežojums `public_read` vai konta budžets.'
        '500':
          $ref: '#/components/responses/InternalError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
          description: '`busy` (datubāze palika bloķēta; `retryAfter: 1` pamattekstā, bez galvenes `Retry-After`), `server_busy` (sesijas uzmeklēšanas laikā datubāze bija bloķēta), `timeout`. Arī `server_busy` ar `retryAfter` no 2 līdz 5, ja serveris ir stipri atpalicis (skatiet ievadu): pieprasījums netika izpildīts.'
  /games/{id}/pgn:
    get:
      operationId: getGamePgn
      tags:
        - games
      summary: Lejupielādēt partiju kā PGN failu
      description: |-
        Tā pati partija kā PGN fails: viena partija ar rindu beigām `\n` un gājienu tekstu rindās, kas īsākas par 80 kolonnām. Atbilde ir tāda pati ar marķieri un bez tā.

        Birkas šādā secībā: `Event` (`<SERVER_NAME> rated <category>` vai `<SERVER_NAME> casual <category>`), `Site` (`SERVER_PUBLIC_HOST`), `Date` (sākuma datums pēc UTC), `Round`, `White`, `Black`, `Result` (`*` anulētai partijai), `UTCDate` un `UTCTime` (sākums), `WhiteElo` un `BlackElo` (reitingi sākumā vai `-`), `WhiteRatingDiff` un `BlackRatingDiff` (izmaiņas, piemēram, `+10` un `-10`, katrā reitinga partijā, `+0`, ja reitinga noteikumi atstāj reitingu nemainīgu; nereitinga partijai, partijai ar pielāgotu laika kontroli un anulētai partijai nav neviena no tām), `TimeControl` (sekundēs), `Termination` (PGN standarta vērtība: `normal`; `time forfeit` — karodziņa nokrišana, arī tad, ja partija beidzas neizšķirti; `abandoned`; `rules infraction` — otrais neatļautais gājiens vai tehniskais zaudējums par godīgas spēles pārkāpumu; `unterminated` — anulēta partija), `PlyCount`, `ScacelithGameId` (decimālais ID).

        Katram gājienam ir `{[%clk h:mm:ss.f] [%emt h:mm:ss.f]}`: gājiena izdarītāja pulksteņa laiks pēc gājiena un gājienam patērētais laiks, sekundes desmitdaļās (noapaļots uz leju). Vērtība tiek izlaista, ja pierakstā tās nav. Pēc pēdējā gājiena seko beigu iemesls vārdos, pēc tam rezultāts.

        **Ierobežojums:** `public_read` (ar marķieri — katram spēlētājam, bez tā — katram klientam).
      security:
        - {}
        - bearerAuth: []
      parameters:
        - $ref: '#/components/parameters/GameId'
      responses:
        '200':
          description: PGN fails.
          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 teksts UTF-8 kodējumā (`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`: nav pozitīvs vesels skaitlis ar ne vairāk kā 16 cipariem (mazāks par 2^53); `invalid_request`: nederīgs URL kodējums.'
        '401':
          $ref: '#/components/responses/Unauthorized'
          description: '`invalid_token`: tika nosūtīts marķieris, un tas nav derīgs.'
        '404':
          $ref: '#/components/responses/NotFound'
          description: '`not_found`: šādas partijas nav.'
        '414':
          $ref: '#/components/responses/UriTooLong'
        '429':
          $ref: '#/components/responses/TooManyRequests'
          description: '`rate_limited`: ierobežojums `public_read` vai konta budžets.'
        '500':
          $ref: '#/components/responses/InternalError'
          description: '`internal_error`: cita starpā, ja saglabātos gājienus nevar izspēlēt no jauna līdz saglabātajām beigām (serveris to reģistrē žurnālā).'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
          description: '`busy` (datubāze palika bloķēta; `retryAfter: 1` pamattekstā, bez galvenes `Retry-After`), `server_busy` (sesijas uzmeklēšanas laikā datubāze bija bloķēta), `timeout`. Arī `server_busy` ar `retryAfter` no 2 līdz 5, ja serveris ir stipri atpalicis (skatiet ievadu): pieprasījums netika izpildīts.'
  /games/{id}/gif:
    get:
      operationId: getGameGif
      tags:
        - gifs
      summary: Lejupielādēt šī servera partiju kā animētu GIF
      description: |-
        Partija kā animēts GIF. Vārdi un reitingi ir no partijas pieraksta (reitingi sākumā, dzēsts konts kā `deleted#<id>`), tāpat arī rezultāts un beigas (`Resignation`, `Loss on time`...).

        Pārbaudes notiek šādā secībā: `gif_disabled`, attēla opcijas (`size`, `orientation`, `delay`, `coords`), partijas ID, partija, tās garums.

        **Nepieciešama sesija:** kvotas tiek skaitītas katram kontam. **Ierobežojumi:** `gif` katram pieprasījumam; `gif_user_min`, `gif_user_hour`, `gif_ip_min` un `gif_ip_hour` tikai tad, kad GIF ir jāizveido (skatiet sadaļas aprakstu). **Laika ierobežojums:** `GIF_QUEUE_TIMEOUT_MS` + `GIF_RENDER_TIMEOUT_MS` + 5 sekundes (pēc noklusējuma 45 sekundes).
      security:
        - bearerAuth: []
      parameters:
        - $ref: '#/components/parameters/GameId'
        - name: size
          in: query
          required: false
          description: 'Attēla izmērs: `small` (32 px lauciņi), `medium` (48 px) vai `large` (72 px).'
          schema:
            type: string
            enum:
              - small
              - medium
              - large
            default: medium
        - name: orientation
          in: query
          required: false
          description: Puse, kas atrodas galdiņa apakšā.
          schema:
            type: string
            enum:
              - white
              - black
            default: white
        - name: delay
          in: query
          required: false
          description: Milisekundes uz gājienu (no 1 līdz 6 decimālcipariem).
          schema:
            type: integer
            minimum: 100
            maximum: 3000
            default: 500
        - name: coords
          in: query
          required: false
          description: Vai ap galdiņu tiek attēloti vertikāļu burti un horizontāļu numuri (`1`) vai ne (`0`).
          schema:
            type: string
            enum:
              - '1'
              - '0'
            default: '1'
      responses:
        '200':
          $ref: '#/components/responses/GifFile'
        '400':
          $ref: '#/components/responses/BadRequest'
          description: '`invalid_option` ar `field` (`size`, `orientation`, `delay` vai `coords`): vērtība nav starp atļautajām. `invalid_game_id`. `invalid_request`: nederīgs URL kodējums.'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
          description: '`not_found`: šādas partijas nav. `gif_disabled`: serveris ir izslēdzis GIF (`GIF_ENABLED=false`; ierobežojuma `gif` žetons tiek atdots atpakaļ).'
        '414':
          $ref: '#/components/responses/UriTooLong'
        '422':
          $ref: '#/components/responses/UnprocessableContent'
        '429':
          $ref: '#/components/responses/TooManyRequests'
          description: '`rate_limited`: ierobežojums `gif`, renderēšanas ierobežojums vai konta budžets.'
        '500':
          $ref: '#/components/responses/InternalError'
          description: '`render_failed`: GIF nevarēja izveidot (iemeslu serveris reģistrē žurnālā). `internal_error`.'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
          description: '`server_busy` ar `retryAfter` (no 3 līdz 10 sekundēm) un `Retry-After`: renderēšanas rinda ir pilna, vai GIF gaidīja brīvu pavedienu `GIF_QUEUE_TIMEOUT_MS` (10 sekundes); visi ierobežojumu žetoni, ko pieprasījums paņēma, tiek atdoti atpakaļ. `busy`: datubāze palika bloķēta (`retryAfter: 1` tikai pamattekstā). `timeout`. Arī `server_busy` ar `retryAfter` no 2 līdz 5, ja serveris ir stipri atpalicis (skatiet ievadu): pieprasījums netika izpildīts.'
  /gif:
    post:
      operationId: renderPgnGif
      tags:
        - gifs
      summary: Izveidot animētu GIF no jebkuras partijas, kas nosūtīta kā PGN
      description: |-
        Tāds pats attēls kā `GET /games/{id}/gif` jebkurai partijai, kas nosūtīta kā PGN teksts: spēles saglabātai partijai, citas vietnes eksportam, ar roku ierakstītai partijai. Tiek izmantota tikai teksta pirmā partija.

        - PGN lasītājs pieņem to pašu, ko pieņem spēles lasītājs: katru PGN, ko raksta šis serveris, un citu vietņu ierastos eksportus (komentāri, varianti, NAG un pulksteņa anotācijas tiek izlaisti; gājienu numuri un SAN tiek lasīti iecietīgi; birka `FEN` nosaka sākuma pozīciju, ja vien `SetUp` nav `"0"`).
        - Vārdi un reitingi tiek ņemti no birkām `White`, `Black`, `WhiteElo` un `BlackElo`. Burtiem tiek noņemtas diakritiskās zīmes, citas rakstzīmes ārpus drukājamā ASCII kļūst par `?`, un gari vārdi tiek saīsināti (48 rakstzīmes). Rezultāts tiek ņemts no birkas `Result`, citādi no gājienu teksta beigām. Birka `Termination` tiek parādīta, ja vien tā nav `normal`: tad par notikušo liecina beigu pozīcija (mats, pats).
        - Šis galapunkts pats pārbauda savu pamattekstu: uz nezināmu lauku vai uz `pgn`, kas trūkst vai nav virkne, tas atbild ar 400 `invalid_request` ar `field`; uz nepareizu opciju — ar 400 `invalid_option` (`delayMs` ir jābūt JSON skaitlim, `coords` — Būla vērtībai; `null` tiek noraidīts).

        **Nepieciešama sesija:** kvotas tiek skaitītas katram kontam. **Ierobežojumi:** kā `GET /games/{id}/gif`. **Pamatteksta ierobežojums:** 135 168 baiti neatkarīgi no `HTTP_BODY_LIMIT` (PGN kā JSON virkne, ieskaitot tās atsoļa secības). **Laika ierobežojums:** pēc noklusējuma 45 sekundes.
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/GifRequest'
            examples:
              short:
                summary: Īsa ierakstīta partija bez koordinātām
                value:
                  pgn: 1. f3 e5 2. g4 Qh4# 0-1
                  coords: false
              options:
                summary: PGN fails ar visām opcijām
                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` ar `field` (nezināms lauks vai `pgn` trūkst vai nav virkne; bez `field`, ja pamatteksts nav objekts); `invalid_json`; `invalid_option` ar `field` (`size`, `orientation`, `delayMs` vai `coords`); `invalid_pgn` ar `line` un `column` (no 1, kolonnas rakstzīmēs) un lasītāja `message`: neatļauts vai neviennozīmīgs gājiens, bojāta birka, nezināms šaha paveids, vairāk nekā 65 536 baiti.'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
          description: '`gif_disabled`: serveris ir izslēdzis GIF (`GIF_ENABLED=false`; ierobežojuma `gif` žetons tiek atdots atpakaļ).'
        '408':
          $ref: '#/components/responses/RequestTimeout'
        '413':
          $ref: '#/components/responses/PayloadTooLarge'
          description: '`payload_too_large`: pamatteksts, kas lielāks par 135 168 baitiem.'
        '414':
          $ref: '#/components/responses/UriTooLong'
        '415':
          $ref: '#/components/responses/UnsupportedMediaType'
        '422':
          $ref: '#/components/responses/UnprocessableContent'
        '429':
          $ref: '#/components/responses/TooManyRequests'
          description: '`rate_limited`: ierobežojums `gif`, renderēšanas ierobežojums vai konta budžets.'
        '500':
          $ref: '#/components/responses/InternalError'
          description: '`render_failed`: GIF nevarēja izveidot (iemeslu serveris reģistrē žurnālā). `internal_error`.'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
          description: '`server_busy` ar `retryAfter` (no 3 līdz 10 sekundēm) un `Retry-After`: renderēšanas rinda ir pilna, vai GIF pārāk ilgi gaidīja brīvu pavedienu; visi ierobežojumu žetoni, ko pieprasījums paņēma, tiek atdoti atpakaļ. `timeout`. Arī `server_busy` ar `retryAfter` no 2 līdz 5, ja serveris ir stipri atpalicis (skatiet ievadu): pieprasījums netika izpildīts.'
  /players/{username}:
    get:
      operationId: getPlayer
      tags:
        - players
      summary: Iegūt spēlētāja publisko profilu
      description: |-
        Spēlētāja publiskais profils: reitingi oficiālajās kategorijās (servera noteiktajā secībā) un partiju skaits. `games.total` skaita visas saglabātās partijas, ieskaitot nereitinga un anulētās partijas; `games.rated`, `wins`, `draws` un `losses` tiek summēti pa reitinga ierakstiem, tātad ietver tikai reitinga partijas. Atbilde ir tāda pati ar marķieri un bez tā.

        **Ierobežojums:** `public_read` (ar marķieri — katram spēlētājam, bez tā — katram klientam).
      security:
        - {}
        - bearerAuth: []
      parameters:
        - $ref: '#/components/parameters/Username'
      responses:
        '200':
          description: Profils.
          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`: nav no 2 līdz 24 rakstzīmēm no `[A-Za-z0-9_.-]`.'
        '401':
          $ref: '#/components/responses/Unauthorized'
          description: '`invalid_token`: tika nosūtīts marķieris, un tas nav derīgs.'
        '404':
          $ref: '#/components/responses/NotFound'
          description: '`not_found`: šāda spēlētāja nav, vai konts ir dzēsts.'
        '414':
          $ref: '#/components/responses/UriTooLong'
        '429':
          $ref: '#/components/responses/TooManyRequests'
          description: '`rate_limited`: ierobežojums `public_read` vai konta budžets.'
        '500':
          $ref: '#/components/responses/InternalError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
          description: '`busy` (datubāze palika bloķēta; `retryAfter: 1` pamattekstā, bez galvenes `Retry-After`), `server_busy` (sesijas uzmeklēšanas laikā datubāze bija bloķēta), `timeout`. Arī `server_busy` ar `retryAfter` no 2 līdz 5, ja serveris ir stipri atpalicis (skatiet ievadu): pieprasījums netika izpildīts.'
  /players/{username}/games:
    get:
      operationId: listPlayerGames
      tags:
        - players
      summary: Uzskaitīt spēlētāja nesenās partijas
      description: |-
        Spēlētāja nesenās partijas, sākot ar jaunākajām, sadalītas lapās tāpat kā vēsture, bet bez filtriem un kopskaita. `color` ir šī spēlētāja puse. Ja lapa ir pilna, `next` ir pēdējās partijas ID, tāpēc nākamā lapa var būt tukša. Vispirms tiek uzmeklēts spēlētājs: nezināms spēlētājs dod 404 neatkarīgi no vaicājuma.

        **Ierobežojums:** `public_read` (ar marķieri — katram spēlētājam, bez tā — katram klientam).
      security:
        - {}
        - bearerAuth: []
      parameters:
        - $ref: '#/components/parameters/Username'
        - $ref: '#/components/parameters/BeforeQuery'
        - $ref: '#/components/parameters/LimitQuery'
      responses:
        '200':
          description: Viena spēlētāja partiju lapa.
          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` nav partijas ID), `invalid_limit` (šie divi bez `field`).'
        '401':
          $ref: '#/components/responses/Unauthorized'
          description: '`invalid_token`: tika nosūtīts marķieris, un tas nav derīgs.'
        '404':
          $ref: '#/components/responses/NotFound'
          description: '`not_found`: šāda spēlētāja nav, vai konts ir dzēsts.'
        '414':
          $ref: '#/components/responses/UriTooLong'
        '429':
          $ref: '#/components/responses/TooManyRequests'
          description: '`rate_limited`: ierobežojums `public_read` vai konta budžets.'
        '500':
          $ref: '#/components/responses/InternalError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
          description: '`busy` (datubāze palika bloķēta; `retryAfter: 1` pamattekstā, bez galvenes `Retry-After`), `server_busy` (sesijas uzmeklēšanas laikā datubāze bija bloķēta), `timeout`. Arī `server_busy` ar `retryAfter` no 2 līdz 5, ja serveris ir stipri atpalicis (skatiet ievadu): pieprasījums netika izpildīts.'
  /leaderboard:
    get:
      operationId: getLeaderboard
      tags:
        - leaderboard
      summary: Iegūt kategorijas labākos spēlētājus
      description: |-
        Oficiālās kategorijas 100 labākie reitinga ieraksti ar vismaz `minGames` (`PROVISIONAL_GAMES`) ieskaitītām partijām, izņemot dzēstus kontus un apstiprinātus krāpniekus. Serveris katru sarakstu pārrēķina ne biežāk kā reizi 10 sekundēs; `updatedAt` norāda, kad. Vienā kategorijā vienlaikus notiek tikai viens aprēķins: tikmēr pārējie pieprasījumi saņem iepriekšējo sarakstu vai, ja saraksta vēl nav, gaida aprēķinu.

        **Ierobežojumi:** tikai adrešu slānis.
      security: []
      parameters:
        - name: category
          in: query
          required: true
          description: Oficiālās kategorijas ID (`3+2` vai `3%2B2`).
          schema:
            type: string
            pattern: '^\s*[0-9]+[+ ][0-9]+\s*$'
          example: '3+2'
        - name: limit
          in: query
          required: false
          description: Cik spēlētāju, no 1 līdz 100. Lielāks skaitlis ar ne vairāk kā 3 cipariem tiek uzskatīts par 100; tukša vērtība tiek uzskatīta par neesošu.
          schema:
            type: integer
            minimum: 1
            maximum: 999
            default: 100
      responses:
        '200':
          description: Līderu saraksts.
          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` (trūkst vai nav oficiāla kategorija), `invalid_limit`.'
        '414':
          $ref: '#/components/responses/UriTooLong'
        '429':
          $ref: '#/components/responses/TooManyRequests'
          description: '`rate_limited`: adrešu slānis.'
        '500':
          $ref: '#/components/responses/InternalError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
          description: '`busy` (datubāze palika bloķēta; `retryAfter: 1` pamattekstā, bez galvenes `Retry-After`), `timeout`. Arī `server_busy` ar `retryAfter` no 2 līdz 5, ja serveris ir stipri atpalicis (skatiet ievadu): pieprasījums netika izpildīts.'
  /reports:
    post:
      operationId: reportPlayer
      tags:
        - reports
      summary: Ziņot par nesenas partijas pretinieku
      description: |-
        Ziņo par pretinieku vienā no paša spēlētāja partijām, kas beigusies pēdējo 7 dienu laikā. Ziņojums pats par sevi nekad nemaina reitingu, sankciju vai godīguma līmeni. Tas paaugstina pārskatīšanas prioritāti, ko redz moderatori, un, izņemot `abuse`, pieprasa partijas analīzi ar šaha dzinēju.

        Ziņojums par to pašu pretinieku tajā pašā partijā saņem tādu pašu atbildi un neko nemaina; atbilde nekad neko neatklāj par kontu, par kuru ziņots. `GET /games/{id}` jau iepriekš paziņo partijas spēlētājiem, vai ziņojums tiktu pieņemts (`reportable`).

        Šis galapunkts pats pārbauda savu pamattekstu secībā `gameId`, `reported`, `category`, `comment`: kļūdas gadījumā tas atbild ar 400 `invalid_request` bez `field`. Lauki, ko tas nepazīst, tiek ignorēti.

        **Ierobežojumi:** `reports` (katram spēlētājam), pēc tam `REPORTS_PER_DAY` (5) ziņojumi katram spēlētājam 24 stundās (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: Ziņojums ir saņemts (vai jau bija iesniegts).
          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` (bez `field`), `invalid_json`.'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
          description: '`report_not_allowed`: nav ziņotāja pretinieks partijā, kas beigusies pēdējo 7 dienu laikā.'
        '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`, ar galveni `Retry-After`): pēdējās 24 stundās iesniegti `REPORTS_PER_DAY` ziņojumi. `rate_limited`: ierobežojums `reports` vai konta budžets.'
        '500':
          $ref: '#/components/responses/InternalError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
          description: '`server_busy` (sesijas uzmeklēšanas laikā datubāze bija bloķēta), `timeout`. Arī `server_busy` ar `retryAfter` no 2 līdz 5, ja serveris ir stipri atpalicis (skatiet ievadu): pieprasījums netika izpildīts.'
  /verify-email:
    servers:
      - url: https://caissa.scacelith.com
        description: Oficiālais serveris (lapas atrodas saknē, ārpus `/api/v1`).
      - url: https://{host}:{port}
        description: Jebkurš Scacelith serveris (lapas atrodas saknē, ārpus `/api/v1`).
        variables:
          host:
            default: caissa.scacelith.com
            description: Servera publiskais resursdatora nosaukums (`SERVER_PUBLIC_HOST`).
          port:
            default: "443"
            description: Publiskais API ports (`PUBLIC_API_PORT`, citādi `API_PORT`).
    get:
      operationId: showVerifyEmailPage
      tags:
        - pages
      summary: Parādīt e-pasta apstiprināšanas lapu
      description: |-
        E-pasta apstiprināšanas saites lapa (reģistrēšanās vai atkārtoti nosūtīts apstiprinājuma e-pasts). Tā rāda tikai pogu „Confirm my e-mail address” (apstiprināt manu e-pasta adresi), lai e-pasta skeneris, kas atver saiti, to neizlietotu; poga nosūta veidlapu uz `POST /verify-email`.

        **Ierobežojums:** `page`.
      security: []
      parameters:
        - $ref: '#/components/parameters/LinkTokenQuery'
      responses:
        '200':
          $ref: '#/components/responses/HtmlPage'
          description: Lapa ar apstiprināšanas pogu.
        '400':
          $ref: '#/components/responses/HtmlBadRequest'
          description: Saite ir nederīga vai novecojusi (HTML lapa).
        '414':
          $ref: '#/components/responses/UriTooLong'
        '429':
          $ref: '#/components/responses/HtmlTooManyRequests'
          description: Ierobežojums `page` (HTML lapa) vai adrešu slānis (JSON `rate_limited`).
        '500':
          $ref: '#/components/responses/HtmlInternalError'
        '503':
          $ref: '#/components/responses/HtmlServiceUnavailable'
    post:
      operationId: submitVerifyEmailPage
      tags:
        - pages
      summary: Apstiprināt e-pasta adresi
      description: |-
        Apstiprināšanas lapas veidlapa. Tā apstiprina adresi; jaunas reģistrēšanās gadījumā tad tiek izveidots konts, un spēlētājs var pierakstīties.

        **Ierobežojums:** `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: Adrese ir apstiprināta (un jaunas reģistrēšanās konts ir izveidots).
        '400':
          $ref: '#/components/responses/HtmlBadRequest'
          description: Saite ir nederīga, izmantota vai novecojusi, vai arī veidlapa ir nederīga (HTML lapa).
        '408':
          $ref: '#/components/responses/HtmlRequestTimeout'
        '409':
          $ref: '#/components/responses/HtmlConflict'
          description: Saite jaunai reģistrēšanās, kuras lietotājvārdu vai adresi pa to laiku aizņēmis cits konts; konts netiek izveidots (HTML lapa).
        '413':
          $ref: '#/components/responses/HtmlPayloadTooLarge'
        '414':
          $ref: '#/components/responses/UriTooLong'
        '415':
          $ref: '#/components/responses/HtmlUnsupportedMediaType'
        '429':
          $ref: '#/components/responses/HtmlTooManyRequests'
          description: Ierobežojums `auth` (HTML lapa) vai adrešu slānis (JSON `rate_limited`).
        '500':
          $ref: '#/components/responses/HtmlInternalError'
        '503':
          $ref: '#/components/responses/HtmlServiceUnavailable'
          description: 'Datubāze palika bloķēta (`Retry-After: 1`) vai serveris ir stipri atpalicis (`Retry-After` no 2 līdz 5, skatiet ievadu): nekas netika mainīts, un saite joprojām darbojas. Arī apstrādātāja noildzes gadījumā.'
  /reset-password:
    servers:
      - url: https://caissa.scacelith.com
        description: Oficiālais serveris (lapas atrodas saknē, ārpus `/api/v1`).
      - url: https://{host}:{port}
        description: Jebkurš Scacelith serveris (lapas atrodas saknē, ārpus `/api/v1`).
        variables:
          host:
            default: caissa.scacelith.com
            description: Servera publiskais resursdatora nosaukums (`SERVER_PUBLIC_HOST`).
          port:
            default: "443"
            description: Publiskais API ports (`PUBLIC_API_PORT`, citādi `API_PORT`).
    get:
      operationId: showResetPasswordPage
      tags:
        - pages
      summary: Parādīt paroles atiestatīšanas veidlapu
      description: |-
        Paroles atiestatīšanas saites lapa: jaunās paroles veidlapa (parole divreiz). Veidlapa tiek nosūtīta uz `POST /reset-password`.

        **Ierobežojums:** `page`.
      security: []
      parameters:
        - $ref: '#/components/parameters/LinkTokenQuery'
      responses:
        '200':
          $ref: '#/components/responses/HtmlPage'
          description: Jaunās paroles veidlapa.
        '400':
          $ref: '#/components/responses/HtmlBadRequest'
          description: Saite ir nederīga (arī tad, ja tā tika nosūtīta uz adresi, kas kontam vairs nepieder).
        '414':
          $ref: '#/components/responses/UriTooLong'
        '429':
          $ref: '#/components/responses/HtmlTooManyRequests'
          description: Ierobežojums `page` (HTML lapa) vai adrešu slānis (JSON `rate_limited`).
        '500':
          $ref: '#/components/responses/HtmlInternalError'
        '503':
          $ref: '#/components/responses/HtmlServiceUnavailable'
    post:
      operationId: submitResetPasswordPage
      tags:
        - pages
      summary: Iestatīt jaunu paroli no atiestatīšanas veidlapas
      description: |-
        Atiestatīšanas lapas veidlapa; tā dara to pašu, ko `POST /auth/password/reset`, vienā transakcijā: visas ierīces tiek izrakstītas, gaidoša e-pasta maiņa tiek atcelta, pārējās atiestatīšanas saites vairs nedarbojas, adrese tiek uzskatīta par apstiprinātu, un īpašnieks saņem e-pasta vēstuli.

        Vispirms tiek pārbaudīta saite, tad tas, vai abas paroles sakrīt, tad paroles noteikumi. Ja serveris ir aizņemts, veidlapa tiek atgriezta ar `Retry-After`, un saite paliek derīga.

        **Ierobežojumi:** `auth`, pēc tam `auth_reset` (kopīgs ar `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: Parole ir nomainīta, un visas ierīces ir izrakstītas.
        '400':
          $ref: '#/components/responses/HtmlBadRequest'
          description: Atkal veidlapa ar kļūdu (paroles atšķiras, vāja parole), saite ir nederīga vai veidlapa ir nederīga (HTML lapa).
        '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: Ierobežojums `auth` vai `auth_reset` (HTML lapa), atkal veidlapa ar `Retry-After`, ja šim klientam pārāk daudz paroļu jaucējvērtību gaida aprēķinu (saite paliek derīga), vai adrešu slānis (JSON `rate_limited`).
        '500':
          $ref: '#/components/responses/HtmlInternalError'
        '503':
          $ref: '#/components/responses/HtmlServiceUnavailable'
          description: 'Atkal veidlapa ar `Retry-After`, ja serveris ir aizņemts (paroļu jaukšanas rinda vai datubāze palika bloķēta); saite paliek derīga. Ja serveris ir stipri atpalicis (skatiet ievadu), tās vietā lapa „Server error” (servera kļūda) ar `Retry-After` (no 2 līdz 5): nekas netika izpildīts, un saite paliek derīga. Arī apstrādātāja noildzes gadījumā.'
  /confirm-email-change:
    servers:
      - url: https://caissa.scacelith.com
        description: Oficiālais serveris (lapas atrodas saknē, ārpus `/api/v1`).
      - url: https://{host}:{port}
        description: Jebkurš Scacelith serveris (lapas atrodas saknē, ārpus `/api/v1`).
        variables:
          host:
            default: caissa.scacelith.com
            description: Servera publiskais resursdatora nosaukums (`SERVER_PUBLIC_HOST`).
          port:
            default: "443"
            description: Publiskais API ports (`PUBLIC_API_PORT`, citādi `API_PORT`).
    get:
      operationId: showConfirmEmailChangePage
      tags:
        - pages
      summary: Parādīt e-pasta maiņas apstiprināšanas lapu
      description: |-
        E-pasta maiņas saites lapa: tā rāda jauno adresi un konta vārdu ar pogu „Use this e-mail address” (izmantot šo e-pasta adresi), kas nosūta veidlapu uz `POST /confirm-email-change`.

        **Ierobežojums:** `page`.
      security: []
      parameters:
        - $ref: '#/components/parameters/LinkTokenQuery'
      responses:
        '200':
          $ref: '#/components/responses/HtmlPage'
          description: Lapa ar jauno adresi un tās apstiprināšanas pogu.
        '400':
          $ref: '#/components/responses/HtmlBadRequest'
          description: Saite ir nederīga vai novecojusi (arī tad, ja konta adrese kopš pieprasījuma ir mainījusies).
        '414':
          $ref: '#/components/responses/UriTooLong'
        '429':
          $ref: '#/components/responses/HtmlTooManyRequests'
          description: Ierobežojums `page` (HTML lapa) vai adrešu slānis (JSON `rate_limited`).
        '500':
          $ref: '#/components/responses/HtmlInternalError'
        '503':
          $ref: '#/components/responses/HtmlServiceUnavailable'
    post:
      operationId: submitConfirmEmailChangePage
      tags:
        - pages
      summary: Apstiprināt jauno e-pasta adresi
      description: |-
        E-pasta maiņas lapas veidlapa. Adrese mainās un tiek uzskatīta par apstiprinātu; ierīces paliek pierakstītas; iepriekš nosūtītās saites (apstiprinājuma, paroles atiestatīšanas, citu maiņu) vairs nedarbojas; iepriekšējā adrese saņem paziņojumu, kurā jaunā adrese ir maskēta.

        **Ierobežojums:** `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: Adrese ir nomainīta.
        '400':
          $ref: '#/components/responses/HtmlBadRequest'
          description: Saite ir nederīga, izmantota vai novecojusi, vai arī veidlapa ir nederīga (HTML lapa).
        '408':
          $ref: '#/components/responses/HtmlRequestTimeout'
        '409':
          $ref: '#/components/responses/HtmlConflict'
          description: Cits konts pa to laiku aizņēma šo adresi (HTML lapa).
        '413':
          $ref: '#/components/responses/HtmlPayloadTooLarge'
        '414':
          $ref: '#/components/responses/UriTooLong'
        '415':
          $ref: '#/components/responses/HtmlUnsupportedMediaType'
        '429':
          $ref: '#/components/responses/HtmlTooManyRequests'
          description: Ierobežojums `auth` (HTML lapa) vai adrešu slānis (JSON `rate_limited`).
        '500':
          $ref: '#/components/responses/HtmlInternalError'
        '503':
          $ref: '#/components/responses/HtmlServiceUnavailable'
          description: 'Datubāze palika bloķēta (`Retry-After: 1`) vai serveris ir stipri atpalicis (`Retry-After` no 2 līdz 5, skatiet ievadu): nekas netika mainīts, un saite joprojām darbojas. Arī apstrādātāja noildzes gadījumā.'
  /healthz:
    servers:
      - url: https://caissa.scacelith.com
        description: Oficiālais serveris, saknē.
      - url: https://caissa.scacelith.com/api/v1
        description: Oficiālais serveris, zem `/api/v1`.
      - url: https://{host}:{port}
        description: Jebkurš Scacelith serveris, saknē.
        variables:
          host:
            default: caissa.scacelith.com
            description: Servera publiskais resursdatora nosaukums (`SERVER_PUBLIC_HOST`).
          port:
            default: "443"
            description: Publiskais API ports (`PUBLIC_API_PORT`, citādi `API_PORT`).
      - url: https://{host}:{port}/api/v1
        description: Jebkurš Scacelith serveris, zem `/api/v1`.
        variables:
          host:
            default: caissa.scacelith.com
            description: Servera publiskais resursdatora nosaukums (`SERVER_PUBLIC_HOST`).
          port:
            default: "443"
            description: Publiskais API ports (`PUBLIC_API_PORT`, citādi `API_PORT`).
    get:
      operationId: getLiveness
      tags:
        - health
      summary: Pārbaudīt, vai process darbojas
      description: |-
        Atbild ar 200 `ok`, kamēr process darbojas. Darbojas arī `HEAD`. Jebkura cita metode saņem atbildi 405 `method_not_allowed` ar `Allow: GET, HEAD` (arī `OPTIONS`).

        **Ierobežojumi:** tikai adrešu slānis.
      security: []
      responses:
        '200':
          description: Process darbojas.
          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`: adrešu slānis.'
  /readyz:
    servers:
      - url: https://caissa.scacelith.com
        description: Oficiālais serveris, saknē.
      - url: https://caissa.scacelith.com/api/v1
        description: Oficiālais serveris, zem `/api/v1`.
      - url: https://{host}:{port}
        description: Jebkurš Scacelith serveris, saknē.
        variables:
          host:
            default: caissa.scacelith.com
            description: Servera publiskais resursdatora nosaukums (`SERVER_PUBLIC_HOST`).
          port:
            default: "443"
            description: Publiskais API ports (`PUBLIC_API_PORT`, citādi `API_PORT`).
      - url: https://{host}:{port}/api/v1
        description: Jebkurš Scacelith serveris, zem `/api/v1`.
        variables:
          host:
            default: caissa.scacelith.com
            description: Servera publiskais resursdatora nosaukums (`SERVER_PUBLIC_HOST`).
          port:
            default: "443"
            description: Publiskais API ports (`PUBLIC_API_PORT`, citādi `API_PORT`).
    get:
      operationId: getReadiness
      tags:
        - health
      summary: Pārbaudīt, vai serveris pieņem spēlētājus
      description: |-
        Atbild ar 200 `ready`, kad serveris pieņem spēlētājus, un ar 503 `not_ready`, kamēr tas startē vai apstājas. Darbojas arī `HEAD`. Jebkura cita metode saņem atbildi 405 `method_not_allowed` ar `Allow: GET, HEAD` (arī `OPTIONS`).

        **Ierobežojumi:** tikai adrešu slānis.
      security: []
      responses:
        '200':
          description: Serveris pieņem spēlētā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`: adrešu slānis.'
        '503':
          description: Serveris startē vai apstājas.
          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: |-
        Sesijas marķieris (`sct_`, kam seko 43 base64url rakstzīmes) no pierakstīšanās atbildes, galvenē `Authorization`: `Authorization: Bearer <token>`. Prefikss ir tieši `Bearer` (reģistrjutīgs) un viena atstarpe; vērtība bez tā vai marķieris, kas nav no 1 līdz 512 drukājamām ASCII rakstzīmēm, saņem atbildi 401 `invalid_token` tāpat kā jebkurš cits nederīgs marķieris. Tukša galvene tiek uzskatīta par galvenes neesamību.
  parameters:
    GameId:
      name: id
      in: path
      required: true
      description: Partijas ID — pozitīvs decimāls vesels skaitlis ar ne vairāk kā 16 cipariem, bez sākuma nullēm, mazāks par 2^53.
      schema:
        type: integer
        format: int64
        minimum: 1
        maximum: 9007199254740991
      example: 4100000000001
    SessionId:
      name: id
      in: path
      required: true
      description: Sesijas ID, kā to sniedz `GET /auth/sessions`, rakstīts bez sākuma nullēm (`03` nav sesija 3).
      schema:
        type: integer
        format: int64
        minimum: 1
      example: 3
    Username:
      name: username
      in: path
      required: true
      description: Spēlētāja lietotājvārds, neatkarīgi no burtu reģistra. Serveris pieņem jebkuru vārdu no 2 līdz 24 rakstzīmēm no `[A-Za-z0-9_.-]` (plašākais noteikums, ko tas jebkad ir atļāvis).
      schema:
        type: string
        pattern: '^[A-Za-z0-9_.-]{2,24}$'
      example: alice
    BeforeQuery:
      name: before
      in: query
      required: false
      description: Partijas ID; tiek uzskaitītas tikai vecākas partijas. Nododiet iepriekšējās lapas `next`. Tukša vērtība tiek uzskatīta par neesošu.
      schema:
        type: integer
        format: int64
        minimum: 1
        maximum: 9007199254740991
      example: 4100000000002
    LimitQuery:
      name: limit
      in: query
      required: false
      description: Lapas izmērs, no 1 līdz 50. Lielāks skaitlis ar ne vairāk kā 3 cipariem tiek uzskatīts par 50; tukša vērtība tiek uzskatīta par neesošu.
      schema:
        type: integer
        minimum: 1
        maximum: 999
        default: 20
    LinkTokenQuery:
      name: token
      in: query
      required: false
      description: E-pasta saites marķieris (43 base64url rakstzīmes). Ja tā nav vai tas ir nepareizs, tiek parādīta lapa „link invalid or expired” (saite ir nederīga vai novecojusi).
      schema:
        type: string
        pattern: '^[A-Za-z0-9_-]{43}$'
      example: asfioO4P0r_Zn5DxWzcQuF6Drgxu7wXGjCyS-UFbTaw
  headers:
    CacheControl:
      description: Katra servera atbilde aizliedz kešošanu.
      schema:
        type: string
        const: no-store
    RetryAfter:
      description: Sekundes, kas jāgaida pirms atkārtota mēģinājuma; tā pati vērtība kā `retryAfter` pamattekstā.
      schema:
        type: integer
        minimum: 1
      example: 30
    WwwAuthenticate:
      description: Bearer izaicinājums ar `error="invalid_token"` nederīgam marķierim.
      schema:
        type: string
        enum:
          - Bearer realm="scacelith"
          - Bearer realm="scacelith", error="invalid_token"
    ConnectionClose:
      description: Pēc šīs atbildes serveris aizver savienojumu.
      schema:
        type: string
        const: close
  responses:
    BadRequest:
      description: '`invalid_request` (ar `field`, ja vainīgs kāds pamatteksta lauks) vai `invalid_json`.'
      headers:
        Cache-Control:
          $ref: '#/components/headers/CacheControl'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          examples:
            invalidRequest:
              summary: Lauks neatbilst shēmai
              value:
                error: invalid_request
                message: '"password" is required'
                field: password
            invalidJson:
              summary: Pamatteksts nav JSON
              value:
                error: invalid_json
                message: The body is not valid JSON.
            weakPassword:
              summary: Vāja parole
              value:
                error: weak_password
                message: The password must have at least 10 characters.
                reason: too_short
            invalidPgn:
              summary: PGN, ko nevar nolasīt
              value:
                error: invalid_pgn
                message: illegal move 'Ke3'
                line: 1
                column: 13
    Unauthorized:
      description: '`unauthorized` (nav galvenes `Authorization`) vai `invalid_token` (marķieris ir nepareizi veidots, novecojis, atsaukts vai pieder dzēstam kontam).'
      headers:
        Cache-Control:
          $ref: '#/components/headers/CacheControl'
        WWW-Authenticate:
          $ref: '#/components/headers/WwwAuthenticate'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          examples:
            unauthorized:
              summary: Nav sesijas
              value:
                error: unauthorized
                message: Log in first.
            invalidToken:
              summary: Nederīgs marķieris
              value:
                error: invalid_token
                message: The session is invalid or has expired; log in again.
    Forbidden:
      description: Pieprasījums ir noraidīts.
      headers:
        Cache-Control:
          $ref: '#/components/headers/CacheControl'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          examples:
            invalidPassword:
              summary: Nepareiza parole atkārtotas autentifikācijas laikā
              value:
                error: invalid_password
                message: Wrong password.
            banned:
              summary: Apturēts konts
              value:
                error: banned
                message: This account is banned.
                until: 1791487639708
    NotFound:
      description: '`not_found` vai funkcija, ko šis serveris ir izslēdzis.'
      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`: pamatteksts nepienāca 10 sekunžu laikā. Serveris aizver savienojumu.'
      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: Pieprasījums ir pretrunā ar servera stāvokli.
      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`: pierakstīšanās ar Google mēģinājums vai biļete ir nezināma, jau izmantota vai novecojusi; sāciet pierakstīšanos no jauna.'
      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`: pamatteksts pārsniedz `HTTP_BODY_LIMIT` (pēc noklusējuma 16 384 baiti). Serveris aizver savienojumu.'
      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`: pieprasījuma mērķis pārsniedz 4096 rakstzīmes.'
      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`: pamatteksts nav `application/json`, vai tā rakstzīmju kopa nav 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`: partijā ir vairāk nekā `GIF_MAX_PLIES` (600) pusgājienu.'
      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`: atrisiniet izaicinājumu `pow` un nosūtiet to pašu pieprasījumu vēlreiz ar `pow: { challenge, nonce }`. `reason` norāda iemeslu: `required`, `malformed`, `signature`, `endpoint`, `network`, `expired`, `bits`, `work` vai `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` (pieprasījumu biežuma ierobežojums, konta budžets vai paroļu jaukšanas rinda) vai `too_many_attempts` (konta palēninājums), ar `retryAfter` un galveni `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: Pieprasījumu biežuma ierobežojums
              value:
                error: rate_limited
                message: Too many requests; try again later.
                retryAfter: 30
            tooManyAttempts:
              summary: Pārāk daudz neveiksmju šajā kontā
              value:
                error: too_many_attempts
                message: Too many attempts; wait before trying again.
                retryAfter: 4
    InternalError:
      description: '`internal_error`: neparedzēta kļūme. Serveris to reģistrē žurnālā.'
      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`: nepareizs `state` vai `iss`, vai arī Google noraidīja kodu vai nosūtīja ID marķieri, kas neiztur pārbaudi (ieskaitot otra klienta — spēles vai vietnes — kodu vai ID marķieri). Tas nekad nesatur Google tekstu.'
      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` vai `timeout`: mēģiniet vēlreiz pēc `retryAfter`, ja tas ir norādīts.'
      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 ir aizņemts
              value:
                error: server_busy
                message: The server is busy; try again in a few seconds.
                retryAfter: 9
            busy:
              summary: Datubāze palika bloķēta (lasīšana)
              value:
                error: busy
                message: Try again shortly.
                retryAfter: 1
            timeout:
              summary: Serverim apstrāde aizņēma pārāk ilgu laiku
              value:
                error: timeout
                message: The server took too long to answer; try again.
    GifFile:
      description: Animētais GIF kā pielikums.
      headers:
        Cache-Control:
          $ref: '#/components/headers/CacheControl'
        Content-Disposition:
          description: '`attachment; filename="scacelith-<id>.gif"` šī servera partijai, `attachment; filename="scacelith-game.gif"` PGN tekstam, kas nosūtīts ar `POST /gif`.'
          schema:
            type: string
            pattern: '^attachment; filename="scacelith-([1-9][0-9]{0,15}|game)\.gif"$'
          example: attachment; filename="scacelith-4100000000001.gif"
        Content-Length:
          description: Faila izmērs baitos.
          schema:
            type: integer
            minimum: 1
      content:
        image/gif:
          schema:
            type: string
            format: binary
    HtmlPage:
      description: HTML lapa.
      headers:
        Cache-Control:
          $ref: '#/components/headers/CacheControl'
      content:
        text/html:
          schema:
            $ref: '#/components/schemas/HtmlDocument'
    HtmlBadRequest:
      description: HTML lapa, kas izskaidro atteikumu.
      headers:
        Cache-Control:
          $ref: '#/components/headers/CacheControl'
      content:
        text/html:
          schema:
            $ref: '#/components/schemas/HtmlDocument'
    HtmlRequestTimeout:
      description: Veidlapa nepienāca 10 sekunžu laikā (HTML lapa). Serveris aizver savienojumu.
      headers:
        Cache-Control:
          $ref: '#/components/headers/CacheControl'
        Connection:
          $ref: '#/components/headers/ConnectionClose'
      content:
        text/html:
          schema:
            $ref: '#/components/schemas/HtmlDocument'
    HtmlConflict:
      description: HTML lapa, kas izskaidro konfliktu.
      headers:
        Cache-Control:
          $ref: '#/components/headers/CacheControl'
      content:
        text/html:
          schema:
            $ref: '#/components/schemas/HtmlDocument'
    HtmlPayloadTooLarge:
      description: Veidlapa pārsniedz `HTTP_BODY_LIMIT` (HTML lapa). Serveris aizver savienojumu.
      headers:
        Cache-Control:
          $ref: '#/components/headers/CacheControl'
        Connection:
          $ref: '#/components/headers/ConnectionClose'
      content:
        text/html:
          schema:
            $ref: '#/components/schemas/HtmlDocument'
    HtmlUnsupportedMediaType:
      description: Pamatteksts nav ne veidlapa (`application/x-www-form-urlencoded`), ne JSON, vai tā rakstzīmju kopa nav UTF-8 (HTML lapa).
      headers:
        Cache-Control:
          $ref: '#/components/headers/CacheControl'
      content:
        text/html:
          schema:
            $ref: '#/components/schemas/HtmlDocument'
    HtmlTooManyRequests:
      description: Pieprasījumu biežuma ierobežojums (HTML lapa) ar `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: Neparedzēta kļūme (HTML lapa ar virsrakstu „Server error”). Serveris to reģistrē žurnālā.
      headers:
        Cache-Control:
          $ref: '#/components/headers/CacheControl'
      content:
        text/html:
          schema:
            $ref: '#/components/schemas/HtmlDocument'
    HtmlServiceUnavailable:
      description: Serveris ir aizņemts (datubāze palika bloķēta vai serveris ir stipri atpalicis; ar `Retry-After`), vai apstrāde aizņēma pārāk ilgu laiku (HTML lapa ar virsrakstu „Server error”).
      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: Vienotā forma visām JSON API kļūdu atbildēm.
      required:
        - error
        - message
      properties:
        error:
          type: string
          pattern: '^[a-z][a-z0-9_]*$'
          description: Kļūdas kods formātā `snake_case`. Klients pēc tā izvēlas, ko rādīt.
          examples:
            - rate_limited
        message:
          type: string
          description: Teikums angļu valodā, paredzēts žurnāliem un kā rezerves variants.
        retryAfter:
          type: integer
          minimum: 1
          description: Sekundes, kas jāgaida pirms atkārtota mēģinājuma; ir tikai tiem atteikumiem, kas ar laiku beidzas. Tad atbildē ir arī galvene `Retry-After` ar to pašu vērtību, izņemot 503 `busy` vēstures, partijas, spēlētāja un līderu saraksta lasīšanā.
        field:
          type: string
          description: Vainīgais lauks vai vaicājuma parametrs (`invalid_request`, `invalid_option`, kā arī `GET /account/games` kļūdām `invalid_cursor`, `invalid_limit` un `invalid_filter`). Ligzdots lauks tiek rakstīts ar punktu (`pow.nonce`).
        reason:
          type: string
          description: 'Iemesls: noteikums, ko pārkāpj `weak_password`, vai tas, kāpēc darba apliecinājums tika pieprasīts vai noraidīts (`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: 'Tikai `banned`: apturēšanas beigas (Unix laiks, ms), `null` apturēšanai uz visiem laikiem.'
        line:
          type: integer
          minimum: 1
          description: 'Tikai `invalid_pgn`: kļūdas rinda, sākot no 1.'
        column:
          type: integer
          minimum: 1
          description: 'Tikai `invalid_pgn`: kļūdas kolonna, sākot no 1, rakstzīmēs.'
    PowChallenge:
      type: object
      description: Darba apliecinājuma izaicinājums (atbildes 428 `pow_required` lauks `pow`).
      required:
        - challenge
        - bits
        - expiresAt
      properties:
        challenge:
          type: string
          pattern: '^[A-Za-z0-9_-]{16,400}\.[A-Za-z0-9_-]{43}$'
          description: Parakstītais izaicinājums, kas jānosūta atpakaļ nemainīts.
        bits:
          type: integer
          minimum: 1
          maximum: 26
          description: Cik sākuma nulles bitu jābūt vērtībai `SHA-256(challenge + ":" + nonce)`.
        expiresAt:
          type: integer
          format: int64
          description: Kad izaicinājuma derīgums beidzas (Unix laiks, ms) — 2 minūtes pēc tā izsniegšanas.
    PowAnswer:
      type: object
      description: Atbilde uz darba apliecinājuma izaicinājumu.
      additionalProperties: false
      required:
        - challenge
        - nonce
      properties:
        challenge:
          type: string
          minLength: 16
          maxLength: 512
          description: Atbildes 428 `challenge` tieši tāds, kāds tas tika izsniegts.
        nonce:
          type: string
          pattern: '^[0-9]{1,20}$'
          description: Decimāla virkne ar ne vairāk kā 20 cipariem, tāda, ka `SHA-256(challenge + ":" + nonce)` sākas ar `bits` nulles bitiem.
    ServerInfo:
      type: object
      description: Tas, kas klientam nepieciešams pirms pierakstīšanās vai savienojuma izveides.
      required:
        - name
        - serverId
        - motd
        - protocol
        - wsPort
        - wsPath
        - registration
        - emailVerification
        - sso
        - mfa
        - pow
        - categories
        - limits
      properties:
        name:
          type: string
          description: Servera nosaukums (`SERVER_NAME`).
        serverId:
          type:
            - string
            - 'null'
          format: uuid
          description: UUID, ko datubāze saņem pirmajā startēšanas reizē. Tas paliek nemainīgs arī pēc restartēšanas. Tas ir `null`, ja datubāze to nevar sniegt.
        motd:
          type: string
          description: Dienas ziņa (`SERVER_MOTD`), var būt tukša.
        protocol:
          type: object
          description: WebSocket protokols (`PROTOCOL.md`).
          required:
            - min
            - max
            - schema
            - subprotocol
          properties:
            min:
              type: integer
              minimum: 1
              description: Vecākā protokola versija, ko serveris atbalsta.
            max:
              type: integer
              minimum: 1
              description: Jaunākā protokola versija, ko serveris atbalsta.
            schema:
              type: integer
              format: int64
              minimum: 0
              maximum: 4294967295
              description: 'Protokola shēmas nospiedums (tās kanoniskās formas SHA-256 pirmie 4 baiti kā vesels skaitlis bez zīmes: `0xc649140f`, 3326678031, protokola apakšversijai 2); informatīvs, nekad netiek salīdzināts.'
            subprotocol:
              type: string
              description: WebSocket apakšprotokola identifikators (`Sec-WebSocket-Protocol`).
        wsPort:
          type: integer
          minimum: 0
          maximum: 65535
          description: WebSocket ports, ko izmanto spēlētāji (`PUBLIC_WS_PORT`, citādi `WS_PORT`, citādi `API_PORT`).
        wsPath:
          type: string
          const: /ws
          description: WebSocket ceļš.
        registration:
          type: string
          enum:
            - open
            - closed
          description: Vai var izveidot jaunus kontus.
        emailVerification:
          type: boolean
          description: Vai jaunajiem kontiem jāapstiprina sava adrese (`REQUIRE_EMAIL_VERIFICATION`).
        sso:
          type: object
          required:
            - google
            - googleWeb
          properties:
            google:
              type: boolean
              description: Vai spēlei tiek piedāvāta pierakstīšanās ar Google.
            googleWeb:
              type: boolean
              description: Vai pierakstīšanās ar Google tiek piedāvāta arī vietnei (`POST /auth/sso/google/web/start`).
        mfa:
          type: boolean
          const: true
          description: Divu faktoru autentifikācija vienmēr ir pieejama.
        pow:
          type: object
          required:
            - register
          properties:
            register:
              type: integer
              minimum: 0
              maximum: 26
              description: Darba apliecinājuma biti, kas nepieciešami reģistrēšanās laikā (0 — nav nepieciešams).
        categories:
          type: array
          description: Oficiālās (reitinga) laika kontroles (`RATED_CATEGORIES`) servera noteiktajā secībā. Jebkura cita laika kontrole ir `custom`.
          items:
            $ref: '#/components/schemas/Category'
        limits:
          type: object
          description: Noteikumi, ko klients var pārbaudīt pirms veidlapas nosūtīšanas.
          required:
            - usernameMin
            - usernameMax
            - usernamePattern
            - passwordMinLength
            - passwordMaxBytes
            - customTimeControls
            - reportsPerDay
            - wsMaxMessageBytes
          properties:
            usernameMin:
              type: integer
              description: Lietotājvārda minimālais garums (`USERNAME_MIN`).
            usernameMax:
              type: integer
              description: Lietotājvārda maksimālais garums (`USERNAME_MAX`).
            usernamePattern:
              type: string
              description: Regulārā izteiksme, kurai jāatbilst jaunam lietotājvārdam.
            passwordMinLength:
              type: integer
              description: Paroles minimālais garums rakstzīmēs (`PASSWORD_MIN_LENGTH`).
            passwordMaxBytes:
              type: integer
              description: Paroles maksimālais garums UTF-8 baitos.
            customTimeControls:
              type: boolean
              description: Vai izaicinājumos un privātās partijās var izmantot pielāgotas laika kontroles.
            reportsPerDay:
              type: integer
              description: Cik ziņojumu spēlētājs drīkst iesniegt 24 stundās (`REPORTS_PER_DAY`).
            wsMaxMessageBytes:
              type: integer
              description: Lielākā WebSocket ziņa, ko klients drīkst nosūtīt, baitos.
      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: Oficiāla laika kontrole.
      required:
        - id
        - baseSec
        - incSec
      properties:
        id:
          type: string
          pattern: '^[0-9]+\+[0-9]+$'
          description: 'Kategorijas ID: minūtes un pieskaitījuma sekundes (`3+2`).'
        baseSec:
          type: number
          minimum: 0
          description: Pamatlaiks sekundēs.
        incSec:
          type: number
          minimum: 0
          description: Pieskaitījums par gājienu sekundēs.
    AccountView:
      type: object
      description: Konts, kā to redz tā spēlētājs (pierakstīšanās atbilžu un `GET /account/me` lauks `user`).
      required:
        - id
        - username
        - email
        - emailVerified
        - mfaEnabled
        - googleLinked
        - hasPassword
        - acceptChallenges
        - createdAt
        - lastLoginAt
        - pendingEmail
      properties:
        id:
          type: integer
          format: int64
          description: Konta ID.
        username:
          type: string
          description: Lietotājvārds.
        email:
          type: string
          format: email
          description: E-pasta adrese.
        emailVerified:
          type: boolean
          description: Vai adrese ir apstiprināta.
        mfaEnabled:
          type: boolean
          description: Vai divu faktoru autentifikācija ir ieslēgta.
        googleLinked:
          type: boolean
          description: Vai ir saistīts Google konts.
        hasPassword:
          type: boolean
          description: '`false` kontam, kas izveidots ar Google un kuram vēl nav iestatīta parole.'
        acceptChallenges:
          type: string
          enum:
            - all
            - none
          description: Vai tiek pieņemti tiešie izaicinājumi pēc vārda (skatiet `PUT /account/preferences`).
        createdAt:
          type: integer
          format: int64
          description: Kad konts tika izveidots (Unix laiks, ms).
        lastLoginAt:
          type:
            - integer
            - 'null'
          format: int64
          description: Pēdējā pierakstīšanās (Unix laiks, ms) vai `null`.
        pendingEmail:
          type:
            - string
            - 'null'
          format: email
          description: Jaunā adrese e-pasta maiņai, kas gaida apstiprinājumu ar saiti, vai `null`.
    SessionAnswer:
      type: object
      description: Jauna sesija.
      required:
        - token
        - expiresAt
        - user
      properties:
        token:
          type: string
          pattern: '^sct_[A-Za-z0-9_-]{43}$'
          description: Sesijas marķieris galvenei `Authorization` un WebSocket savienojumam.
        expiresAt:
          type: integer
          format: int64
          description: Sesijas absolūtās beigas (Unix laiks, ms); dīkstāves ierobežojums to var izbeigt ātrāk.
        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: Pierakstīšanās otrais solis ar divu faktoru autentifikāciju; turpiniet ar `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: Šī soļa marķieris pieprasījumam `POST /auth/login/mfa`.
        expiresIn:
          type: integer
          const: 300
          description: Atlikušās sekundes soļa pabeigšanai.
    LoginAnswer:
      description: Sesija vai pierakstīšanās otrais solis ar divu faktoru autentifikāciju.
      oneOf:
        - $ref: '#/components/schemas/SessionAnswer'
        - $ref: '#/components/schemas/MfaChallenge'
    SsoNeedsUsername:
      type: object
      description: Pirmā pierakstīšanās ar Google; turpiniet ar `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: Biļete pieprasījumam `POST /auth/sso/complete`, derīga 10 minūtes.
        suggestedUsername:
          type: string
          description: Lietotājvārds, kas izveidots no Google vārda vai adreses, vai `""`, ja nekas neder.
    SsoNeedsPassword:
      type: object
      description: Šo adresi izmanto konts ar paroli; turpiniet ar `POST /auth/sso/google/link`. Pagaidām nekas nav sasaistīts.
      required:
        - needsPassword
        - linkTicket
        - username
        - expiresIn
      properties:
        needsPassword:
          type: boolean
          const: true
        linkTicket:
          type: string
          pattern: '^sso_[A-Za-z0-9_-]{43}$'
          description: Biļete pieprasījumam `POST /auth/sso/google/link`.
        username:
          type: string
          description: Šī konta lietotājvārds (to redz tikai tas, kurš ir apliecinājis šo adresi Google).
        expiresIn:
          type: integer
          const: 600
          description: Cik sekundes biļete paliek derīga.
    SsoFinishAnswer:
      description: Pieprasījuma `POST /auth/sso/google/finish` atbilde.
      oneOf:
        - $ref: '#/components/schemas/SessionAnswer'
        - $ref: '#/components/schemas/MfaChallenge'
        - $ref: '#/components/schemas/SsoNeedsUsername'
        - $ref: '#/components/schemas/SsoNeedsPassword'
    SsoStartAnswer:
      type: object
      description: Pierakstīšanās ar Google mēģinājums.
      required:
        - attemptId
        - authUrl
        - state
        - expiresIn
      properties:
        attemptId:
          type: string
          pattern: '^sso_[A-Za-z0-9_-]{43}$'
          description: Mēģinājums pieprasījumam `POST /auth/sso/google/finish`.
        authUrl:
          type: string
          format: uri
          description: Google URL, kas pēc pārbaudes jāatver sistēmas pārlūkprogrammā.
        state:
          type: string
          pattern: '^[A-Za-z0-9_-]{43}$'
          description: '`state`, kas Google jānosūta atpakaļ.'
        expiresIn:
          type: integer
          const: 600
          description: Cik sekundes mēģinājums paliek derīgs.
    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: Lietotājvārds; pēc tam piemēro servera noteikumus (skatiet aprakstu).
        email:
          type: string
          minLength: 1
          maxLength: 254
          description: E-pasta adrese. Tiek noņemtas sākuma un beigu atstarpes, un tā tiek saglabāta mazajiem burtiem.
        password:
          type: string
          minLength: 1
          maxLength: 1024
          description: Parole; pēc tam piemēro paroles noteikumus.
        pow:
          $ref: '#/components/schemas/PowAnswer'
    LoginRequest:
      type: object
      additionalProperties: false
      required:
        - login
        - password
      properties:
        login:
          type: string
          minLength: 1
          maxLength: 254
          description: Lietotājvārds vai e-pasta adrese (jebkurš teksts ar `@`).
        password:
          type: string
          minLength: 1
          maxLength: 1024
        clientLabel:
          $ref: '#/components/schemas/ClientLabel'
        pow:
          $ref: '#/components/schemas/PowAnswer'
    ClientLabel:
      type: string
      maxLength: 64
      description: Neobligāts. Tiek rādīts pierakstīto ierīču sarakstā (piemēram, `Scacelith 1.4 (Windows)`).
    MfaLoginRequest:
      type: object
      additionalProperties: false
      required:
        - mfaToken
      properties:
        mfaToken:
          type: string
          minLength: 1
          maxLength: 64
          description: Pierakstīšanās atbildes (vai pierakstīšanās ar Google atbildes) `mfaToken`.
        code:
          type: string
          maxLength: 32
          description: Neobligāts. 6 ciparu autentifikācijas lietotnes kods vai atkopšanas kods.
        recoveryCode:
          type: string
          maxLength: 32
          description: Neobligāts. Atkopšanas kods. Nepieciešams viens no `code` un `recoveryCode`.
    EmailRequest:
      type: object
      additionalProperties: false
      required:
        - email
      properties:
        email:
          type: string
          minLength: 1
          maxLength: 254
          description: E-pasta adrese.
    PasswordResetRequest:
      type: object
      additionalProperties: false
      required:
        - token
        - newPassword
      properties:
        token:
          type: string
          minLength: 1
          maxLength: 128
          description: Atiestatīšanas saites parametrs `token`.
        newPassword:
          type: string
          minLength: 1
          maxLength: 1024
          description: Jaunā parole; piemēro reģistrēšanās paroles noteikumus.
    SsoStartRequest:
      type: object
      additionalProperties: false
      required:
        - codeChallenge
        - redirectPort
      properties:
        codeChallenge:
          type: string
          pattern: '^[A-Za-z0-9_-]{43}$'
          description: Klienta `codeVerifier` S256 PKCE izaicinājums (`BASE64URL(SHA-256(codeVerifier))`).
        redirectPort:
          type: integer
          minimum: 1024
          maximum: 65535
          description: Klienta klausītāja ports adresē `127.0.0.1`.
    SsoWebStartRequest:
      type: object
      additionalProperties: false
      required:
        - codeChallenge
      properties:
        codeChallenge:
          type: string
          pattern: '^[A-Za-z0-9_-]{43}$'
          description: Lapas `codeVerifier` S256 PKCE izaicinājums (`BASE64URL(SHA-256(codeVerifier))`).
    SsoFinishRequest:
      type: object
      additionalProperties: false
      required:
        - attemptId
        - codeVerifier
        - state
        - code
      properties:
        attemptId:
          type: string
          minLength: 1
          maxLength: 64
          description: '`attemptId` no `POST /auth/sso/google/start`.'
        codeVerifier:
          type: string
          pattern: '^[A-Za-z0-9._~-]{43,128}$'
          description: PKCE verificētājs; tā SHA-256 jāatbilst izaicinājumam, kas tika nodots `start`.
        state:
          type: string
          pattern: '^[A-Za-z0-9_-]{43}$'
          description: '`state` tāds, kādu Google nosūtīja atpakaļ.'
        code:
          type: string
          minLength: 1
          maxLength: 2048
          pattern: '^[\x21-\x7E]+$'
          description: '`code` tāds, kādu Google nosūtīja atpakaļ (drukājams ASCII bez atstarpēm).'
        iss:
          type: string
          minLength: 1
          maxLength: 256
          description: Neobligāts. `iss` tāds, kādu Google nosūtīja atpakaļ, ja nosūtīja.
        clientLabel:
          $ref: '#/components/schemas/ClientLabel'
    SsoLinkRequest:
      type: object
      additionalProperties: false
      required:
        - linkTicket
        - password
      properties:
        linkTicket:
          type: string
          minLength: 1
          maxLength: 64
          description: '`linkTicket` no `finish`, derīgs 10 minūtes.'
        password:
          type: string
          minLength: 1
          maxLength: 1024
          description: Konta parole.
        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` no `finish`, derīgs 10 minūtes.'
        username:
          type: string
          minLength: 1
          maxLength: 64
          description: Lietotājvārds; piemēro reģistrēšanās noteikumus.
        clientLabel:
          $ref: '#/components/schemas/ClientLabel'
    SessionList:
      type: object
      required:
        - sessions
      properties:
        sessions:
          type: array
          description: Aktīvās sesijas, sākot ar pēdējo izmantoto.
          items:
            $ref: '#/components/schemas/SessionEntry'
    SessionEntry:
      type: object
      description: Aktīva sesija (pierakstīta ierīce).
      required:
        - id
        - createdAt
        - lastSeenAt
        - expiresAt
        - clientLabel
        - current
      properties:
        id:
          type: integer
          format: int64
          description: Sesijas ID pieprasījumam `DELETE /auth/sessions/{id}`.
        createdAt:
          type: integer
          format: int64
          description: Pierakstīšanās (Unix laiks, ms).
        lastSeenAt:
          type: integer
          format: int64
          description: Pēdējā izmantošana (Unix laiks, ms), tiek atjaunināta ne biežāk kā reizi 5 minūtēs (vēlāk, kamēr serveris ir stipri atpalicis).
        expiresAt:
          type: integer
          format: int64
          description: Absolūtās beigas (Unix laiks, ms); dīkstāves ierobežojums var sesiju izbeigt ātrāk.
        clientLabel:
          type:
            - string
            - 'null'
          description: Pierakstīšanās `clientLabel` vai `null`, ja tas netika nosūtīts.
        current:
          type: boolean
          description: Vai šī ir sesija, kas veic pieprasījumu.
    AccountMe:
      type: object
      required:
        - user
        - ratings
        - sanctions
        - ban
      properties:
        user:
          $ref: '#/components/schemas/AccountView'
        ratings:
          type: array
          description: Pa vienam ierakstam katrā kategorijā, kurā spēlētājs ir spēlējis reitinga partijas.
          items:
            $ref: '#/components/schemas/RatingSummary'
        sanctions:
          type: array
          description: Aktīvās sankcijas.
          items:
            $ref: '#/components/schemas/ActiveSanction'
        ban:
          description: '`{ until }`, kamēr konts ir apturēts, citādi `null`.'
          oneOf:
            - $ref: '#/components/schemas/Ban'
            - type: 'null'
    RatingSummary:
      type: object
      description: Vienas kategorijas reitinga ieraksts.
      required:
        - category
        - rating
        - games
        - wins
        - draws
        - losses
        - peak
        - provisional
      properties:
        category:
          type: string
          description: Kategorijas ID.
        rating:
          type: integer
          description: Reitings.
        games:
          type: integer
          minimum: 0
          description: Kategorijā nospēlētās partijas (ieskaitītas vai ne).
        wins:
          type: integer
          minimum: 0
        draws:
          type: integer
          minimum: 0
        losses:
          type: integer
          minimum: 0
        peak:
          type: integer
          description: Augstākais sasniegtais reitings.
        provisional:
          type: boolean
          description: '`true`, kamēr reitings vēl nav noteikts vai tam ir mazāk nekā `PROVISIONAL_GAMES` ieskaitītu partiju (spēle to rāda kā `1510?`).'
    ActiveSanction:
      type: object
      required:
        - kind
        - reason
        - startsAt
        - endsAt
      properties:
        kind:
          type: string
          enum:
            - ban
            - mm_block
            - warning
          description: Sankcijas veids.
        reason:
          type:
            - string
            - 'null'
          description: Norādītais iemesls, ja tāds ir.
        startsAt:
          type: integer
          format: int64
          description: Sākums (Unix laiks, ms).
        endsAt:
          type:
            - integer
            - 'null'
          format: int64
          description: Beigas (Unix laiks, ms), `null`, ja sankcija ir pastāvīga.
    Ban:
      type: object
      required:
        - until
      properties:
        until:
          type:
            - integer
            - 'null'
          format: int64
          description: Apturēšanas beigas (Unix laiks, ms), `null`, ja tā ir uz visiem laikiem.
    Preferences:
      type: object
      additionalProperties: false
      required:
        - acceptChallenges
      properties:
        acceptChallenges:
          type: string
          enum:
            - all
            - none
          description: '`all` — pieņemt tiešos izaicinājumus pēc vārda, `none` — tos noraidīt.'
    PasswordChangeRequest:
      type: object
      additionalProperties: false
      required:
        - currentPassword
        - newPassword
      properties:
        currentPassword:
          type: string
          minLength: 1
          maxLength: 1024
          description: Pašreizējā parole.
        newPassword:
          type: string
          minLength: 1
          maxLength: 1024
          description: Jaunā parole; piemēro reģistrēšanās paroles noteikumus.
    PasswordOnlyRequest:
      type: object
      additionalProperties: false
      required:
        - password
      properties:
        password:
          type: string
          minLength: 1
          maxLength: 1024
          description: Konta parole.
    TotpEnableRequest:
      type: object
      additionalProperties: false
      required:
        - code
      properties:
        code:
          type: string
          pattern: '^[0-9]{6}$'
          description: Gaidošās slepenās atslēgas kods, tieši 6 cipari.
    ReauthCredentials:
      type: object
      additionalProperties: false
      required:
        - password
      properties:
        password:
          type: string
          minLength: 1
          maxLength: 1024
          description: Konta parole.
        code:
          type: string
          minLength: 1
          maxLength: 32
          description: Neobligāts; nepieciešams, ja ir ieslēgta divu faktoru autentifikācija (vai `recoveryCode`). Autentifikācijas lietotnes kods vai atkopšanas kods.
        recoveryCode:
          type: string
          minLength: 1
          maxLength: 32
          description: Neobligāts. Atkopšanas kods (`xxxx-xxxx-xx`; burtu reģistrs, atstarpes un defises nav svarīgas).
    RecoveryCodesRequest:
      type: object
      additionalProperties: false
      required:
        - password
        - code
      properties:
        password:
          type: string
          minLength: 1
          maxLength: 1024
          description: Konta parole.
        code:
          type: string
          minLength: 1
          maxLength: 32
          description: Autentifikācijas lietotnes kods (atkopšanas kods tiek noraidīts).
    EmailChangeRequest:
      type: object
      additionalProperties: false
      required:
        - newEmail
        - password
      properties:
        newEmail:
          type: string
          minLength: 1
          maxLength: 254
          description: Jaunā adrese; tiek noņemtas sākuma un beigu atstarpes, tā tiek pārveidota mazajiem burtiem un pēc tam pārbaudīta tāpat kā reģistrēšanās adrese.
        password:
          type: string
          minLength: 1
          maxLength: 1024
          description: Konta parole.
        code:
          type: string
          minLength: 1
          maxLength: 32
          description: Neobligāts; nepieciešams, ja ir ieslēgta divu faktoru autentifikācija (vai `recoveryCode`). Autentifikācijas lietotnes kods vai atkopšanas kods.
        recoveryCode:
          type: string
          minLength: 1
          maxLength: 32
          description: Neobligāts. Atkopšanas kods.
    TotpSetup:
      type: object
      description: Gaidošā slepenā atslēga autentifikācijas lietotnei.
      required:
        - secret
        - uri
        - algorithm
        - digits
        - period
      properties:
        secret:
          type: string
          pattern: '^[A-Z2-7]+$'
          description: Slepenā atslēga base32 formātā, ko ievadīt lietotnē.
        uri:
          type: string
          format: uri
          description: URI `otpauth://totp/...`, ko parādīt kā QR kodu.
        algorithm:
          type: string
          const: SHA1
        digits:
          type: integer
          const: 6
        period:
          type: integer
          const: 30
          description: Viena soļa ilgums sekundēs.
    RecoveryCodeList:
      type: array
      description: 10 atkopšanas kodi, kas tiek parādīti tikai šoreiz. Katrs darbojas vienu reizi.
      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 partijas puse.
      required:
        - name
        - rating
        - ratingAfter
        - ratingDiff
      properties:
        name:
          type: string
          description: Vārds partijas pierakstā (`deleted#<id>` dzēstam kontam).
        rating:
          type:
            - integer
            - 'null'
          description: Reitings sākumā, `null`, ja nav zināms.
        ratingAfter:
          type:
            - integer
            - 'null'
          description: Reitings pēc partijas. Reitinga partijai tas ir vienmēr; `null` tikai partijai, kas netiek ieskaitīta reitingos (nereitinga, ar pielāgotu laika kontroli, anulēta).
        ratingDiff:
          type:
            - integer
            - 'null'
          description: Reitinga izmaiņa, `0`, ja reitinga noteikumi atstāj reitingu nemainīgu (FIDE nulles rezultāta noteikums, pretinieks bez noteikta reitinga, spēlētāja bez noteikta reitinga pirmās partijas); `null` tāpat kā `ratingAfter`.
    GameSummary:
      type: object
      description: Saglabātas partijas kopsavilkums.
      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: Partijas ID.
        category:
          type: string
          description: Oficiālās kategorijas ID (`3+2`) vai `custom`.
        rated:
          type: boolean
          description: Vai partija tiek ieskaitīta reitingos.
        timeControl:
          type: string
          pattern: '^[0-9]+\+[0-9]+$'
          description: Laika kontrole sekundēs (`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` (skatiet partiju kodus).'
        reason:
          type: integer
          minimum: 0
          maximum: 255
          description: Beigu iemesla kods (skatiet partiju kodus).
        result:
          type: string
          enum:
            - 1-0
            - 0-1
            - 1/2-1/2
            - '*'
          description: Rezultāts, `*` anulētai partijai.
        termination:
          $ref: '#/components/schemas/Termination'
        plies:
          type: integer
          minimum: 0
          description: Pusgājienu skaits.
        startedAt:
          type: integer
          format: int64
          description: Sākums (Unix laiks, ms).
        endedAt:
          type: integer
          format: int64
          description: Beigas (Unix laiks, ms).
    Termination:
      type: string
      description: Beigu iemesla nosaukums (skatiet partiju kodus); `Unknown` kodam, ko protokols nepazīst.
      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: Partijas kopsavilkums no viena spēlētāja puses.
      allOf:
        - $ref: '#/components/schemas/GameSummary'
        - type: object
          required:
            - color
          properties:
            color:
              type: string
              enum:
                - white
                - black
              description: Spēlētāja puse.
    HistoryGameSummary:
      description: Partija no pierakstītā spēlētāja vēstures.
      allOf:
        - $ref: '#/components/schemas/PlayerGameSummary'
        - type: object
          required:
            - baseMs
            - incMs
            - outcome
          properties:
            baseMs:
              type: integer
              format: int64
              minimum: 0
              description: Pamatlaiks milisekundēs.
            incMs:
              type: integer
              format: int64
              minimum: 0
              description: Pieskaitījums milisekundēs.
            outcome:
              type: string
              enum:
                - win
                - loss
                - draw
                - aborted
              description: Iznākums no spēlētāja viedokļa.
    HistoryPage:
      type: object
      required:
        - games
        - next
        - total
      properties:
        games:
          type: array
          description: Lapas partijas, sākot ar jaunākajām.
          items:
            $ref: '#/components/schemas/HistoryGameSummary'
        next:
          type:
            - integer
            - 'null'
          format: int64
          description: ID, kas jānodod kā `before` nākamajai lapai, vai `null` pēdējā lapā.
        total:
          type: integer
          minimum: 0
          description: Filtram atbilstošo partiju skaits visās lapās kopā.
    MoveRecord:
      type: object
      description: Viens partijas pusgājiens.
      required:
        - uci
        - spentMs
        - clockMs
      properties:
        uci:
          type: string
          pattern: '^[a-h][1-8][a-h][1-8][nbrq]?$'
          description: Gājiens UCI notācijā, bandinieka pārvēršanai pievienojot `n`, `b`, `r` vai `q`.
        spentMs:
          type:
            - integer
            - 'null'
          minimum: 0
          description: Gājienam patērētais laiks (ms), `null`, ja pierakstā tā nav.
        clockMs:
          type:
            - integer
            - 'null'
          minimum: 0
          description: Gājiena izdarītāja pulksteņa laiks pēc gājiena (ms), `null`, ja pierakstā tā nav.
    PgnTagsView:
      type: object
      description: Galvenās PGN birkas attēlošanai. `Termination` šeit ir beigu iemesla nosaukums; PGN failā tiek izmantotas PGN standarta vērtības.
      required:
        - Event
        - Site
        - Date
        - Round
        - White
        - Black
        - Result
        - WhiteElo
        - BlackElo
        - TimeControl
        - Termination
        - PlyCount
      properties:
        Event:
          type: string
          description: '`<SERVER_NAME> rated <category>` vai `<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: Sākuma datums pēc 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: Reitings sākumā vai `"-"`.
          oneOf:
            - type: integer
            - type: string
              const: '-'
        BlackElo:
          description: Reitings sākumā vai `"-"`.
          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: Partijas pieraksts ar gājieniem un pulksteņiem. Tam ir partijas kopsavilkuma lauki, bez `color` un `outcome`.
      allOf:
        - $ref: '#/components/schemas/GameSummary'
        - type: object
          required:
            - baseMs
            - incMs
            - statusName
            - rematchOf
            - moves
            - pgn
          properties:
            baseMs:
              type: integer
              format: int64
              minimum: 0
              description: Pamatlaiks milisekundēs.
            incMs:
              type: integer
              format: int64
              minimum: 0
              description: Pieskaitījums milisekundēs.
            statusName:
              type: string
              enum:
                - WhiteWins
                - BlackWins
                - Draw
                - Aborted
              description: Lauka `status` nosaukums.
            rematchOf:
              type:
                - integer
                - 'null'
              format: int64
              description: Tās partijas ID, kuras revanšs ir šī partija, vai `null`.
            moves:
              type: array
              description: Viens ieraksts katram pusgājienam.
              items:
                $ref: '#/components/schemas/MoveRecord'
            pgn:
              $ref: '#/components/schemas/PgnTagsView'
            you:
              type: string
              enum:
                - white
                - black
              description: Tikai tad, ja marķiera spēlētājs spēlēja šo partiju; šī spēlētāja puse.
            reportable:
              type: boolean
              description: Tikai tad, ja marķiera spēlētājs spēlēja šo partiju; `true`, ja `POST /reports` tagad pieņemtu ziņojumu par pretinieku šajā partijā (partija beidzās pēdējo 7 dienu laikā, dienas kvota nav izlietota, un par pretinieku šajā partijā vēl nav ziņots).
    PlayerProfile:
      type: object
      required:
        - username
        - createdAt
        - ratings
        - games
      properties:
        username:
          type: string
          description: Lietotājvārds, kā to uzrakstījis spēlētājs.
        createdAt:
          type: integer
          format: int64
          description: Kad konts tika izveidots (Unix laiks, ms).
        ratings:
          type: array
          description: Tikai oficiālās kategorijas, servera noteiktajā secībā.
          items:
            $ref: '#/components/schemas/RatingSummary'
        games:
          type: object
          required:
            - total
            - rated
            - wins
            - draws
            - losses
          properties:
            total:
              type: integer
              minimum: 0
              description: Visas saglabātās partijas, ieskaitot nereitinga un anulētās partijas.
            rated:
              type: integer
              minimum: 0
              description: Reitinga partijas, summētas pa reitinga ierakstiem.
            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: Lietotājvārds, kā to uzrakstījis spēlētājs.
        games:
          type: array
          description: Lapas partijas, sākot ar jaunākajām.
          items:
            $ref: '#/components/schemas/PlayerGameSummary'
        next:
          type:
            - integer
            - 'null'
          format: int64
          description: Pēdējās partijas ID, ja lapa ir pilna (nākamā lapa tad var būt tukša), citādi `null`.
    Leaderboard:
      type: object
      required:
        - category
        - minGames
        - updatedAt
        - players
      properties:
        category:
          type: string
          description: Kategorijas ID.
        minGames:
          type: integer
          minimum: 0
          description: Ieskaitīto partiju skaits, kas ierakstam nepieciešams, lai tas tiktu iekļauts sarakstā (`PROVISIONAL_GAMES`).
        updatedAt:
          type: integer
          format: int64
          description: Kad saraksts tika aprēķināts (Unix laiks, 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 teksts, ne vairāk kā 65 536 UTF-8 baiti. Tiek izmantota tikai pirmā partija.
        size:
          type: string
          enum:
            - small
            - medium
            - large
          default: medium
          description: Neobligāts. Attēla izmērs.
        orientation:
          type: string
          enum:
            - white
            - black
          default: white
          description: Neobligāts. Puse, kas atrodas galdiņa apakšā.
        delayMs:
          type: integer
          minimum: 100
          maximum: 3000
          default: 500
          description: Neobligāts. Milisekundes uz gājienu, JSON skaitlis ar veselu vērtību.
        coords:
          type: boolean
          default: true
          description: Neobligāts. Vai ap galdiņu tiek attēloti vertikāļu burti un horizontāļu numuri.
    ReportRequest:
      type: object
      required:
        - gameId
        - reported
        - category
      properties:
        gameId:
          description: Partija kā vesels skaitlis vai virkne no 1 līdz 16 cipariem.
          oneOf:
            - type: integer
              format: int64
              minimum: 1
              maximum: 9007199254740991
            - type: string
              pattern: '^[0-9]{1,16}$'
        reported:
          type: string
          minLength: 1
          maxLength: 24
          description: Pretinieka lietotājvārds, kā partijas pierakstā vai kā tas ir tagad, neatkarīgi no burtu reģistra. Tas nedrīkst būt tukšs.
        category:
          type: string
          enum:
            - cheating
            - abuse
            - other
          description: Par ko ir ziņojums.
        comment:
          type:
            - string
            - 'null'
          description: Neobligāts. Ne vairāk kā 500 rakstzīmes pēc tam, kad ir noņemtas vadības rakstzīmes (izņemot tabulēšanas un jaunas rindas rakstzīmi) un tekstam noņemtas sākuma un beigu atstarpes.
    AccountExport:
      type: object
      description: Viss, ko serveris glabā par kontu (laiki — Unix laiks, 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: Kad eksports tika izveidots.
        server:
          type: object
          required:
            - name
            - host
          properties:
            name:
              type: string
              description: '`SERVER_NAME`.'
            host:
              type: string
              description: '`SERVER_PUBLIC_HOST`.'
        notes:
          type: array
          description: Ko fails satur un ko neiekļauj, vienkāršā angļu valodā spēlētājam.
          items:
            type: string
        account:
          description: Konta skats no `GET /account/me` ar `googleEmail`.
          allOf:
            - $ref: '#/components/schemas/AccountView'
            - type: object
              required:
                - googleEmail
              properties:
                googleEmail:
                  type:
                    - string
                    - 'null'
                  description: Saistītā Google konta adrese vai `null`.
        ratings:
          type: array
          description: Pilnie reitinga ieraksti.
          items:
            $ref: '#/components/schemas/ExportRating'
        ratingRefunds:
          type: array
          description: Reitinga punkti, kas atdoti atpakaļ pēc tam, kad pretinieks tika atzīts par krāpnieku, summēti pa UTC dienām un kategorijām, sākot ar jaunākajiem. Netiek nosauktas ne partijas, ne krāpnieki.
          items:
            $ref: '#/components/schemas/RatingRefund'
        sessions:
          type: array
          description: Visas saglabātās sesijas, sākot ar jaunākajām, bez neviena marķiera. Glabāšanas termiņa tīrīšana dzēš novecojušu sesiju, bet izrakstītu sesiju — dienu pēc izrakstīšanās.
          items:
            $ref: '#/components/schemas/ExportSession'
        securityEvents:
          type: array
          description: Drošības notikumi, sākot ar jaunākajiem; tiek glabāti `RETENTION_SECURITY_DAYS` dienas.
          items:
            $ref: '#/components/schemas/SecurityEvent'
        sanctions:
          type: array
          description: Visas sankcijas, ieskaitot atceltās. Moderatora vārds nekad netiek iekļauts.
          items:
            $ref: '#/components/schemas/ExportSanction'
        conduct:
          type: array
          description: Uzvedības notikumi, kas reģistrēti spēlētāja pamestajās un anulētajās partijās un partijās, kurās pirmais gājiens netika izdarīts laikā; tiek glabāti 30 dienas.
          items:
            $ref: '#/components/schemas/ConductEvent'
        reportsFiled:
          type: array
          description: Spēlētāja iesniegtie ziņojumi.
          items:
            $ref: '#/components/schemas/FiledReport'
        games:
          type: object
          required:
            - total
            - list
          properties:
            total:
              type: integer
              minimum: 0
              description: Partiju skaits.
            list:
              type: array
              description: Visas partijas, sākot ar jaunākajām, kā `GET /account/games` kopsavilkumi. Gājieni ir pieejami `GET /games/{id}`, bet PGN — `GET /games/{id}/pgn`.
              items:
                $ref: '#/components/schemas/HistoryGameSummary'
    ExportRating:
      description: Pilns reitinga ieraksts.
      allOf:
        - $ref: '#/components/schemas/RatingSummary'
        - type: object
          required:
            - rated
            - countedGames
            - updatedAt
          properties:
            rated:
              type: boolean
              description: Vai spēlētājs ir pametis fāzi, kurā reitings vēl nav noteikts.
            countedGames:
              type: integer
              minimum: 0
              description: Partijas, kas ieskaitītas reitingā.
            updatedAt:
              type: integer
              format: int64
              description: Kad ieraksts pēdējo reizi mainījās.
    RatingRefund:
      type: object
      required:
        - day
        - category
        - points
      properties:
        day:
          type: integer
          format: int64
          description: Dienas 00:00 UTC (Unix laiks, ms).
        category:
          type: string
        points:
          type: integer
          description: Tajā dienā šajā kategorijā atdotie punkti.
    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: Kad sesija tika izrakstīta, vai `null`.
        clientLabel:
          type:
            - string
            - 'null'
        ip:
          type:
            - string
            - 'null'
          description: Pierakstīšanās adrese, tiek izdzēsta pēc `RETENTION_IP_DAYS` dienām.
    SecurityEvent:
      type: object
      description: |-
        Drošības notikums. `ip` tiek norādīts tikai darbībām, kas veiktas pierakstītā sesijā, ar konta paroli (un otro faktoru) vai ar saiti, kas nosūtīta uz konta adresi: `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` un `account_exported`; visiem pārējiem veidiem ir `ip: null`, un `ip` tiek izdzēsts pēc `RETENTION_IP_DAYS` dienām.

        `detail` saglabā tikai šos laukus: `login`: `method`; `sso_login`, `sso_account_created`: `provider`; `sso_linked`: `provider` un `method` (`password` vai `password+totp`); `login_failed`: `failures`; `login_lockout`: `retryAfterMs`; `mfa_failed`: `attempts`; `recovery_code_used`: `remaining`; `reauth_failed`: `factor`; `session_revoked` un `sessions_revoked_all`: `reason`; `email_change_refused`: `reason`; `sanction_auto`: `kind`, `gameId`, `until`. Notikums `moderator_action` saglabā tikai `{ action }` darbībām `ban`, `unban`, `reset_mfa`, `verify_email` un `revoke_sessions` (pārējās moderatoru darbības netiek iekļautas). Visiem pārējiem veidiem ir `detail: null`. Notikumi `rating_refund` netiek iekļauti: to punkti ir laukā `ratingRefunds`.
      required:
        - kind
        - at
        - ip
        - detail
      properties:
        kind:
          type: string
          description: Notikuma veids (`login`, `password_changed`...).
        at:
          type: integer
          format: int64
          description: Kad tas notika.
        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: Spēlētāja, par kuru ziņots, pašreizējais publiskais vārds.
        category:
          type: string
          enum:
            - cheating
            - abuse
            - other
        comment:
          type:
            - string
            - 'null'
        createdAt:
          type: integer
          format: int64
        status:
          type: string
          enum:
            - open
            - closed
          description: Vai ziņojums joprojām ir atvērts (vai spēlētājam, par kuru ziņots, tika piemērota sankcija, netiek atklāts).
    LinkTokenForm:
      type: object
      additionalProperties: false
      required:
        - token
      properties:
        token:
          type: string
          minLength: 1
          maxLength: 128
          description: E-pasta saites marķieris (lapas veidlapas slēptais lauks).
    ResetPasswordForm:
      type: object
      additionalProperties: false
      required:
        - token
        - newPassword
        - confirmPassword
      properties:
        token:
          type: string
          minLength: 1
          maxLength: 128
          description: Atiestatīšanas saites marķieris (veidlapas slēptais lauks).
        newPassword:
          type: string
          minLength: 1
          maxLength: 1024
          description: Jaunā parole; piemēro reģistrēšanās paroles noteikumus.
        confirmPassword:
          type: string
          minLength: 1
          maxLength: 1024
          description: Jaunā parole vēlreiz; tai jābūt tādai pašai.
    HtmlDocument:
      type: string
      contentMediaType: text/html
      description: HTML lapa UTF-8 kodējumā (`text/html; charset=utf-8`) bez JavaScript un ārējiem resursiem.
