openapi: 3.1.1
info:
  title: Scacelith सर्वर HTTPS API
  version: "1.1.0"
  summary: हर Scacelith सर्वर का HTTPS API, आधिकारिक सर्वर का भी और कम्युनिटी सर्वरों का भी।
  description: |-
    हर Scacelith सर्वर, चाहे आधिकारिक `caissa.scacelith.com` हो या कोई भी कम्युनिटी सर्वर, इस HTTPS API के अनुरोधों का जवाब देता है। गेम इसका इस्तेमाल बाज़ी के बाहर की हर चीज़ के लिए करता है: साइन-अप और साइन-इन (दो-चरणीय सत्यापन और Google समेत), खाते का पेज, बाज़ियों का इतिहास, बाज़ियों के PGN डाउनलोड और एनिमेटेड GIF, साइन-इन वाले डिवाइस, डेटा डाउनलोड, खाता हटाना, और शिकायतें।

    लाइव खेल (मैचमेकिंग, चुनौतियाँ, चालें, घड़ियाँ) उसी सर्वर के WebSocket (`wss://<host>/ws`) से होता है, जिसे इस API के सेशन टोकन से खोला जाता है; उसका विवरण `PROTOCOL.md` में है, यहाँ नहीं।

    नीचे दिए गए डिफ़ॉल्ट मान ऐसे सर्वर के हैं जिसका कॉन्फ़िगरेशन बदला नहीं गया है; कोई कम्युनिटी सर्वर इन्हें बदल सकता है (हर सेटिंग का नाम `CONFIG.md` में है)।

    ## बेस URL, पोर्ट और ट्रांसपोर्ट

    - आधिकारिक सर्वर: `https://caissa.scacelith.com/api/v1`, TCP पोर्ट 443।
    - कम्युनिटी सर्वर: `https://<SERVER_PUBLIC_HOST>[:<port>]/api/v1`। `API_PORT` डिफ़ॉल्ट रूप से 443 है; NAT या प्रॉक्सी के पीछे, खिलाड़ी जिस पोर्ट का इस्तेमाल करते हैं वह `PUBLIC_API_PORT` है।
    - गेम का WebSocket डिफ़ॉल्ट रूप से इसी पोर्ट पर होता है (`WS_PORT` उसे दूसरे पोर्ट पर ले जाता है; `GET /info` क्लाइंट को बताता है कि वह कहाँ है)।
    - TLS पर HTTP/1.1। `TLS_MODE=proxy` होने पर सर्वर के आगे लगा रिवर्स प्रॉक्सी TLS टर्मिनेट करता है। `TLS_MODE=off` (सादा HTTP) केवल लोकल डेवलपमेंट के लिए है।
    - ईमेल के लिंक से खुलने वाले HTML पेज (`/verify-email`, `/reset-password`, `/confirm-email-change`) सर्वर के रूट पर हैं, `/api/v1` के बाहर। हेल्थ एंडपॉइंट रूट पर भी जवाब देते हैं और `/api/v1` के तहत भी। Google साइन-इन का सर्वर पर कोई पेज नहीं है: Google ब्राउज़र को वापस ख़ुद गेम पर, `127.0.0.1` पर, भेजता है, या उस वेबसाइट के किसी पेज पर जिसने साइन-इन शुरू किया था (`GOOGLE_WEB_REDIRECT_URI`)।
    - अंत का स्लैश अनदेखा किया जाता है (`/api/v1/info/` वही है जो `/api/v1/info`), और पाथ पैरामीटर URL-डिकोड किए जाते हैं (जो पैरामीटर मान्य URL एन्कोडिंग में न हो, उस पर जवाब 400 `invalid_request` होता है)।

    ## अनुरोध

    - **JSON बॉडी।** `POST`, `PUT` और `DELETE` अनुरोधों में `Content-Type: application/json` के साथ एक JSON ऑब्जेक्ट होता है। UTF-8 के अलावा कोई और `charset` अस्वीकार किया जाता है, और कोई भी दूसरा कंटेंट टाइप भी (415 `unsupported_media_type`)। ख़ाली बॉडी को `{}` माना जाता है, और बिना पैरामीटर वाले एंडपॉइंट यही लेते हैं (`POST /auth/logout`, `POST /auth/logout-all`, `DELETE /auth/sessions/{id}`)।
    - **आकार और समय।** बॉडी की सीमा `HTTP_BODY_LIMIT` बाइट है (डिफ़ॉल्ट रूप से 16,384; `POST /gif` की अपनी सीमा 1,35,168 बाइट है): 413 `payload_too_large`। बॉडी को 10 सेकंड के भीतर पहुँचना चाहिए: 408 `request_timeout`। इन दोनों में से किसी भी त्रुटि के बाद सर्वर कनेक्शन बंद कर देता है।
    - **सख़्त स्कीमा।** जिस फ़ील्ड को एंडपॉइंट नहीं जानता, वह अस्वीकार किया जाता है; जो फ़ील्ड वैकल्पिक के रूप में चिह्नित नहीं है, वह ज़रूरी है; और टाइप तथा लंबाई जाँची जाती हैं। इनमें से किसी भी विफलता पर जवाब 400 `invalid_request` होता है, और `field` उस फ़ील्ड का नाम बताता है (नेस्टेड फ़ील्ड के लिए डॉट वाला नाम, जैसे `pow.nonce`)। स्ट्रिंग में कंट्रोल वर्ण नहीं हो सकते। लंबाई UTF-16 कोड यूनिट में गिनी जाती है, इसलिए बेसिक मल्टीलिंगुअल प्लेन के बाहर का वर्ण (जैसे कोई इमोजी) दो बार गिना जाता है। जो बॉडी JSON नहीं है, उस पर जवाब 400 `invalid_json` होता है। `POST /gif` और `POST /reports` अपनी बॉडी ख़ुद जाँचते हैं (हर एंडपॉइंट का विवरण देखें)।
    - **क्वेरी स्ट्रिंग।** किसी पैरामीटर की केवल पहली मौजूदगी गिनी जाती है, अज्ञात पैरामीटर अनदेखे किए जाते हैं, और `+` स्पेस में डिकोड होता है: टाइम कंट्रोल की श्रेणी लेने वाले एंडपॉइंट `3%2B2` और `3+2` दोनों स्वीकार करते हैं।
    - **मेथड।** `HEAD` बिना बॉडी वाला `GET` है। मौजूद पाथ पर `OPTIONS` का जवाब 204 होता है, `Allow` हेडर के साथ (और CORS प्रीफ़्लाइट के हेडर के साथ, नीचे देखें)। पाथ में जो मेथड नहीं है, ऐसे किसी भी दूसरे मेथड पर जवाब 405 `method_not_allowed` होता है, `Allow` के साथ।
    - **अनुरोध टारगेट।** यह 4096 वर्णों से ज़्यादा नहीं हो सकता (414 `uri_too_long`)। जो अनुरोध पार्स ही नहीं हो पाता, या जिसका हेड (अनुरोध पंक्ति और हेडर) बहुत बड़ा है या बहुत धीरे आता है, उसे ख़ाली 400, 431 या 408 जवाब मिलता है और कनेक्शन बंद कर दिया जाता है।
    - **CORS: एक अनुमति-सूची, डिफ़ॉल्ट रूप से बंद।** API गेम को सेवा देता है, और गेम कोई `Origin` नहीं भेजता। `CORS_ORIGINS` ख़ाली होने पर (डिफ़ॉल्ट) कोई भी `Access-Control-*` हेडर कभी नहीं भेजा जाता, इसलिए कोई भी वेब पेज जवाब नहीं पढ़ सकता। `CORS_ORIGINS` उन वेब ओरिजिन की सूची है जिनके पेज ब्राउज़र से API को कॉल कर सकते हैं, हर ओरिजिन ठीक वैसा ही जैसा ब्राउज़र उसे `Origin` में भेजते हैं: `https://host` या `https://host:port`, छोटे अक्षरों में, बिना पाथ, बिना अंत के स्लैश और बिना डिफ़ॉल्ट पोर्ट के; `http://` केवल `localhost`, `127.0.0.1` और `[::1]` के लिए (लोकल डेवलपमेंट); कोई वाइल्डकार्ड नहीं। आधिकारिक सर्वर अपनी वेबसाइट के लिए `CORS_ORIGINS=https://scacelith.com,https://www.scacelith.com` सेट करता है। तब:
      - हेल्थ एंडपॉइंट को छोड़कर `/api/v1` के तहत किसी भी पाथ के हर जवाब में, चाहे सफलता हो या त्रुटि (प्रति-पता लेयर का 429 भी), `Vary: Origin` होता है। जब अनुरोध का `Origin` बाइट-दर-बाइट सूची के किसी ओरिजिन के समान होता है, तो जवाब में `Access-Control-Allow-Origin: <that origin>` भी होता है और, प्रीफ़्लाइट के जवाब को छोड़कर, `Access-Control-Expose-Headers: Retry-After, Content-Disposition` भी, ताकि पेज त्रुटि कोड, `Retry-After` और PGN तथा GIF डाउनलोड के फ़ाइल नाम पढ़ सके।
      - सूची के किसी ओरिजिन से मौजूद पाथ पर आए प्रीफ़्लाइट (`Access-Control-Request-Method` के साथ `OPTIONS`) का जवाब 204 होता है, बिना बॉडी के, और `Allow`, `Access-Control-Allow-Origin`, `Access-Control-Allow-Methods` (`Allow` वाले मेथड), `Access-Control-Allow-Headers: Authorization, Content-Type`, `Access-Control-Max-Age: 600` और `Vary: Origin` के साथ। इसके लिए किसी टोकन की ज़रूरत नहीं होती और यह न किसी खाते के बजट में गिना जाता है, न किसी एंडपॉइंट लिमिट में; किसी भी अनुरोध की तरह, यह प्रति-पता लेयर का एक टोकन लेता है। ब्राउज़र किसी प्रीफ़्लाइट को 10 मिनट तक दोबारा इस्तेमाल करता है, लेकिन केवल उसी सटीक URL के लिए, क्वेरी स्ट्रिंग समेत। `Authorization` और JSON बॉडी के बिना वाले अनुरोध, जैसे `GET /info` या `GET /leaderboard`, को प्रीफ़्लाइट की ज़रूरत नहीं होती।
      - प्रीफ़्लाइट का कोई भी दूसरा जवाब विफल प्रीफ़्लाइट है, भले ही उसमें CORS हेडर हों: मौजूद न होने वाले पाथ के लिए 404, या प्रति-पता लेयर का 429। तब ब्राउज़र अनुरोध नहीं भेजता, और पेज को नेटवर्क त्रुटि दिखती है, बिना जवाब या उसके `Retry-After` के।
      - `Access-Control-Allow-Credentials` कभी नहीं भेजा जाता, और `Access-Control-Allow-Origin: *` भी नहीं: पेज अपना सेशन टोकन `Authorization: Bearer` में भेजता है, जैसे गेम भेजता है, और API कोई कुकी सेट नहीं करता। HTML पेजों, हेल्थ एंडपॉइंट और पढ़े न जा सकने वाले अनुरोधों के ख़ाली जवाबों में कोई CORS हेडर नहीं होता। WebSocket CORS का इस्तेमाल नहीं करता: जो पेज उसे खोल सकते हैं, उनकी सूची `WS_ALLOWED_ORIGINS` में होती है।
      - जो ओरिजिन सूची में नहीं है, उसका पेज कुछ भी नहीं पढ़ पाता, और उसके बदलाव करने वाले क्रॉस-साइट अनुरोध कभी भेजे ही नहीं जाते: JSON बॉडी और `Authorization` के लिए प्रीफ़्लाइट ज़रूरी है, और उसके जवाब में उस ओरिजिन के लिए कोई `Access-Control-Allow-Origin` नहीं होता। `Cross-Origin-Resource-Policy: same-origin` हर जवाब पर बना रहता है: यह केवल `no-cors` अनुरोधों पर लागू होता है (कोई दूसरी साइट किसी जवाब को एम्बेड करे), सूची वाले पेज के `cors` अनुरोधों पर कभी नहीं।

    ## जवाब और त्रुटियाँ

    - जवाब UTF-8 में JSON होते हैं, सिवाय PGN डाउनलोड (`application/x-chess-pgn`), एनिमेटेड GIF (`image/gif`) और HTML पेजों के। समय 1970-01-01 UTC से बीते मिलीसेकंड में दिए जाते हैं। आईडी पूर्णांक होती हैं। बाज़ी की आईडी में 16 अंक तक होते हैं, लेकिन वे 2^53 से कम रहती हैं, इसलिए JSON नंबर (एक double) उन्हें ठीक-ठीक रख सकता है।
    - API के हर जवाब में `Cache-Control: no-store`, `X-Content-Type-Options: nosniff`, `Referrer-Policy: no-referrer`, `X-Frame-Options: DENY`, `Cross-Origin-Resource-Policy: same-origin` और `Content-Security-Policy: default-src 'none'; frame-ancestors 'none'` होते हैं (HTML पेजों की अपनी अलग पॉलिसी है)। `TLS_MODE=native` होने पर उसमें `Strict-Transport-Security: max-age=31536000` भी होता है। जो जवाब लिसनर API से पहले ख़ुद लिखता है (हेल्थ एंडपॉइंट के `GET` और `HEAD`, प्रति-पता लेयर का 429, और API के पूरी तरह विफल होने पर 500 `internal_error`), उनमें `Cross-Origin-Resource-Policy` को छोड़कर यही हेडर होते हैं, और उनका `Content-Type` बिना charset के `application/json` होता है।
    - सर्वर के जवाब तैयार कर लेने के बाद उसे 60 सेकंड के भीतर पढ़ लिया जाना चाहिए, जो केवल बड़े जवाबों के लिए मायने रखता है (कोई GIF, डेटा एक्सपोर्ट, लंबा PGN)। जवाब तैयार करने में सर्वर जो समय लेता है, वह न इसमें गिना जाता है, न उन 30 सेकंड में जिनमें एक भी बाइट न आने और न जाने पर कनेक्शन बंद कर दिया जाता है।

    त्रुटियों का एक ही रूप होता है (`Error` स्कीमा):

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

    `message` लॉग के लिए और फ़ॉलबैक के रूप में है; क्लाइंट `error` के आधार पर तय करता है कि क्या दिखाना है। `retryAfter` (सेकंड) केवल उन अस्वीकृतियों में होता है जो समय बीतने पर ख़त्म हो जाती हैं, और तब जवाब में उसी मान वाला `Retry-After` हेडर भी होता है; एकमात्र अपवाद इतिहास, बाज़ी, खिलाड़ी और लीडरबोर्ड पढ़ने वाले अनुरोधों का 503 `busy` है, जिसमें केवल फ़ील्ड होता है। कुछ त्रुटियाँ अतिरिक्त फ़ील्ड जोड़ती हैं: `field` (अमान्य इनपुट), `reason` (`weak_password`, `pow_required`), `pow` (`pow_required`), `until` (`banned`), `line` और `column` (`invalid_pgn`)।

    त्रुटियाँ जो कोई भी एंडपॉइंट दे सकता है:

    | स्टेटस | `error` | कब |
    |---|---|---|
    | 400 | `invalid_request` | बॉडी एंडपॉइंट के स्कीमा का उल्लंघन करती है (`field` बताता है कहाँ), अनुरोध टारगेट या `Content-Length` ग़लत रूप में है, कोई पाथ पैरामीटर मान्य URL एन्कोडिंग में नहीं है, या बॉडी बीच में कट गई। |
    | 400 | `invalid_json` | बॉडी JSON नहीं है। |
    | 401 | `unauthorized` | सेशन माँगने वाले एंडपॉइंट पर कोई `Authorization` हेडर नहीं। |
    | 401 | `invalid_token` | टोकन ग़लत रूप में है, उसकी अवधि ख़त्म हो चुकी है, वह निरस्त हो चुका है या किसी हटाए गए खाते का है; उन एंडपॉइंट पर भी जहाँ सेशन वैकल्पिक है। |
    | 404 | `not_found` | ऐसा कोई एंडपॉइंट नहीं। कुछ एंडपॉइंट भी इसका इस्तेमाल करते हैं: ऐसी कोई बाज़ी, खिलाड़ी या सेशन नहीं। |
    | 405 | `method_not_allowed` | पाथ दूसरे मेथड के लिए मौजूद है (`Allow` देखें)। |
    | 408 | `request_timeout` | बॉडी 10 सेकंड के भीतर नहीं पहुँची। |
    | 413 | `payload_too_large` | बॉडी `HTTP_BODY_LIMIT` से बड़ी है (`POST /gif` के लिए 1,35,168 बाइट)। |
    | 414 | `uri_too_long` | अनुरोध टारगेट 4096 वर्णों से ज़्यादा है। |
    | 415 | `unsupported_media_type` | बॉडी `application/json` नहीं है, या उसका charset UTF-8 नहीं है। |
    | 429 | `rate_limited` | कोई रेट लिमिट (नीचे देखें): `retryAfter` और साथ में `Retry-After` हेडर। |
    | 500 | `internal_error` | अप्रत्याशित विफलता। सर्वर उसे लॉग करता है। |
    | 503 | `timeout` | सर्वर ने 30 सेकंड के भीतर जवाब नहीं दिया (एक्सपोर्ट के लिए 60 सेकंड, डिफ़ॉल्ट सेटिंग्स में GIF के लिए 45 सेकंड)। |
    | 503 | `server_busy` | सेशन लुकअप या खाते में बदलाव के दौरान डेटाबेस लॉक मिला (`retryAfter` 1), या पासवर्ड हैश कतार भरी हुई है (नीचे देखें)। साथ ही, 2 से 5 के `retryAfter` के साथ, जब सर्वर काफ़ी पीछे चल रहा हो: `POST`, `PUT`, `PATCH` या `DELETE` के लिए, डेटाबेस में बहुत सारे राइट प्रतीक्षा में हैं; किसी भी अनुरोध के लिए, पहले के बहुत सारे अनुरोध अपने टाइमआउट के बाद भी चल रहे हैं, या सेशन टोकन 5 सेकंड के भीतर जाँचा नहीं जा सका। अनुरोध प्रोसेस नहीं किया गया, और उसकी रेट लिमिट ने कुछ नहीं लिया। |

    पढ़ने वाले एंडपॉइंट (बाज़ियों का इतिहास, बाज़ियाँ, खिलाड़ी, लीडरबोर्ड) और एक्सपोर्ट, डेटाबेस लॉक रहने पर `retryAfter: 1` के साथ 503 `busy` जवाब देते हैं।

    पासवर्ड जाँचने या हैश करने वाले एंडपॉइंट इनमें से कोई एक जवाब भी दे सकते हैं, दोनों 5 से 15 सेकंड के रैंडम `retryAfter` के साथ:

    - 503 `server_busy`: सर्वर की पासवर्ड हैश कतार (`PASSWORD_HASH_QUEUE_MAX`) भरी हुई है, या प्रतीक्षा का समय ख़त्म हो गया।
    - 429 `rate_limited`: कतार आधी भर जाने के बाद, इस क्लाइंट (एक IPv4 पता या एक IPv6 /48) के पहले से `PASSWORD_HASH_WAITERS_PER_SOURCE` हैश प्रतीक्षा में हैं। यह 429 अनुरोध द्वारा लिए गए रेट लिमिट टोकन लौटा देता है।

    कुछ भी नहीं बदला गया और कोई विफल प्रयास नहीं गिना गया (`POST /auth/sso/google/link` को छोड़कर, जिसकी टिकट-कोशिश और खाते की विफलता हैश से पहले ही दर्ज हो चुकी थीं), और रीसेट लिंक मान्य रहता है।

    HTML पेज अपनी त्रुटियाँ उन्हीं स्टेटस कोड के साथ HTML पेजों के रूप में देते हैं।

    ## ऑथेंटिकेशन

    जिन एंडपॉइंट को सेशन चाहिए, वे `Authorization` हेडर में bearer टोकन लेते हैं (`bearerAuth` स्कीम):

    ```
    Authorization: Bearer sct_L_8GDd7uzfQ3QQWtqrsWXDTsFWzRwIvJcwIGHhjWPS8
    ```

    - **टोकन पाना।** टोकन (`sct_` और उसके बाद 43 base64url वर्ण) `POST /auth/login` से मिलता है, और दो-चरणीय सत्यापन चालू होने पर उसके बाद `POST /auth/login/mfa` से; `POST /auth/sso/google/finish` और `POST /auth/sso/google/link` (Google साइन-इन) से; और `POST /auth/sso/complete` से। इनमें से हर एक का जवाब `{ token, expiresAt, user }` होता है। सर्वर टोकन का केवल SHA-256 सहेजता है।
    - **ज़रूरी सेशन।** हेडर न होने पर जवाब 401 `unauthorized` होता है, `WWW-Authenticate: Bearer realm="scacelith"` के साथ। अमान्य टोकन पर जवाब 401 `invalid_token` होता है, `WWW-Authenticate: Bearer realm="scacelith", error="invalid_token"` के साथ। जिस टोकन पर `invalid_token` मिले, क्लाइंट को उसे भूल जाना चाहिए और फिर से साइन इन करना चाहिए।
    - **वैकल्पिक सेशन।** `GET /games/{id}`, `GET /games/{id}/pgn`, `GET /players/{username}` और `GET /players/{username}/games` पर सेशन वैकल्पिक है। हेडर के बिना ये सार्वजनिक व्यू लौटाते हैं; अगर हेडर भेजा जाए, तो उसका टोकन मान्य होना चाहिए।
    - **व्यस्त स्टोर।** टोकन की जाँच के दौरान डेटाबेस लॉक रहने पर, सेशन माँगने या स्वीकार करने वाला एंडपॉइंट `retryAfter: 1` (और `Retry-After`) के साथ 503 `server_busy` जवाब देता है; जाँच में 5 सेकंड से ज़्यादा लगने पर, वह 2 से 5 के `retryAfter` के साथ 503 `server_busy` जवाब देता है। दोनों ही स्थितियों में टोकन पर कोई फ़ैसला नहीं हुआ: उसे रखें और अनुरोध फिर से भेजें।
    - **अवधि।** सेशन इन दोनों में से जो पहले हो, तब ख़त्म होता है: साइन-इन के `SESSION_MAX_DAYS` (90) दिन बाद (यही `expiresAt` है), या `SESSION_IDLE_DAYS` (30) दिन तक इस्तेमाल न होने पर। हर इस्तेमाल निष्क्रियता सीमा को आगे बढ़ा देता है; सर्वर नया मान बैकग्राउंड में, अधिकतम हर 5 मिनट में एक बार लिखता है। जब उसके डेटाबेस में बहुत सारे राइट प्रतीक्षा में हों, तो वह इस राइट को किसी बाद के अनुरोध तक टाल देता है, सिवाय निष्क्रियता सीमा से पहले के आख़िरी घंटे के, ताकि इस्तेमाल में चल रहे सेशन की अवधि ख़त्म न हो। एक खाते के अधिकतम `MAX_SESSIONS_PER_USER` (10) सेशन रहते हैं: नया साइन-इन इस संख्या से ऊपर के सबसे पुराने सेशन को निरस्त कर देता है।
    - **निरस्तीकरण।** साइन आउट करना (`POST /auth/logout`, `POST /auth/logout-all`, `DELETE /auth/sessions/{id}`) सेशन निरस्त करता है; पासवर्ड बदलना (दूसरे सेशन), पासवर्ड रीसेट और खाता हटाना (हर सेशन), और कोई एडमिनिस्ट्रेटर भी सेशन निरस्त करते हैं। API से हुआ निरस्तीकरण तुरंत लागू होता है, और निरस्त सेशन से खोला गया WebSocket बंद कर दिया जाता है। पासवर्ड बदलना या रीसेट करना और खाता हटाना, ख़ुद उस बदलाव वाले ट्रांज़ैक्शन में ही सेशन निरस्त करते हैं: इनमें से कोई विफल हो, तो कोई सेशन निरस्त नहीं हुआ और बाकी भी कुछ नहीं बदला। सर्वर सेशन लुकअप को 30 सेकंड तक कैश करता है, इसलिए एडमिन कमांड (जो एक अलग प्रोसेस है) से हुआ निरस्तीकरण 30 सेकंड के भीतर लागू होता है।
    - **दायरा।** टोकन एक ही सर्वर का होता है और उसका WebSocket भी खोलता है। इसे कभी किसी दूसरे सर्वर पर न भेजें।

    ## रेट लिमिट और दूसरी पाबंदियाँ

    लिमिट दो में से किसी एक दायरे पर लागू होती हैं। **क्लाइंट:** एक IPv4 पता, या एक IPv6 /64 (प्रॉक्सी मोड में, पता `TRUSTED_PROXIES` के किसी पते से भेजे गए `X-Forwarded-For` से आता है); कुछ लिमिट हर IPv6 /48 को उसके हर /64 नेटवर्क के अलावा एक पूरी इकाई के रूप में भी गिनती हैं। **खिलाड़ी:** साइन-इन किया हुआ खाता, उसका पता चाहे जो हो; जो लिमिट वैकल्पिक सेशन वाले एंडपॉइंट पर प्रति खिलाड़ी गिनी जाती है, वह बिना टोकन वाले अनुरोध के लिए प्रति क्लाइंट गिनी जाती है।

    हर लिमिट एक टोकन बकेट है जिसमें `limit` अनुरोध समाते हैं और जो `limit / window` की दर से लगातार भरता रहता है; `retryAfter` अगले टोकन तक का समय है। *साझा* चिह्नित लिमिट उतनी ही लंबी स्लाइडिंग विंडो पर भी गिनी जाती हैं, ताकि किसी भी विंडो में `limit` से बहुत ज़्यादा अनुरोध न हों। हर लिमिट पूरे सर्वर के लिए गिनी जाती है। जब किसी एंडपॉइंट की कोई एक लिमिट अनुरोध अस्वीकार करती है, तो उस अनुरोध के लिए उसकी दूसरी लिमिट द्वारा लिए गए टोकन लौटा दिए जाते हैं। अस्वीकृति का जवाब 429 `rate_limited` होता है, `retryAfter` और `Retry-After` के साथ।

    - **प्रति-पता लेयर।** हर अनुरोध (कोई भी पाथ और मेथड, हेल्थ एंडपॉइंट और WebSocket अपग्रेड समेत) सबसे पहले अपने क्लाइंट के बजट से एक टोकन लेता है: प्रति मिनट `HTTP_RATE_PER_IP` (600), आधे मिनट के बर्स्ट के साथ, और एक IPv6 /48 के लिए `HTTP_RATE_PER_PREFIX` (4 x `HTTP_RATE_PER_IP`)। किसी क्लाइंट के एक साथ अधिकतम `IP_MAX_INFLIGHT` (32 x `WORKERS`) अनुरोध प्रगति में हो सकते हैं (इससे ज़्यादा पर `retryAfter` 1)। जो क्लाइंट अस्वीकृतियों के बाद भी अनुरोध भेजता रहता है, उसे ब्लॉक कर दिया जाता है: एक मिनट में `ABUSE_BLOCK_REFUSALS_PER_MIN` (600) अस्वीकृतियाँ उसे 1 मिनट के लिए ब्लॉक करती हैं, फिर 6 घंटे के भीतर हर नए ब्लॉक पर 4, 16 और 60 मिनट के लिए। `auth`, `auth_*` और `reauth` लिमिट की एक अस्वीकृति 5 गिनी जाती है; प्रति खिलाड़ी गिनी जाने वाली लिमिट की अस्वीकृतियाँ कभी नहीं गिनी जातीं। `ABUSE_EXEMPT` में दिए गए पते इस लेयर को छोड़ देते हैं, लेकिन खाते के बजट या एंडपॉइंट लिमिट को नहीं।
    - **खाते का बजट।** मान्य सेशन टोकन वाला हर अनुरोध उसके खाते के बजट में भी गिना जाता है: सभी एंडपॉइंट के लिए मिलाकर प्रति मिनट `USER_RATE_PER_MIN` (120), पता चाहे जो हो, आधे मिनट के बर्स्ट के साथ।
    - **एंडपॉइंट लिमिट:**

    | लिमिट | डिफ़ॉल्ट | किसके हिसाब से गिनी जाती है | एंडपॉइंट |
    |---|---|---|---|
    | `auth` | `AUTH_RATE_PER_IP` (20) / 10 मिनट, साझा | क्लाइंट, और प्रति IPv6 /48 `AUTH_RATE_PER_PREFIX` (5 x `AUTH_RATE_PER_IP`) | `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) / घंटा, साझा | क्लाइंट, प्रति IPv6 /48 उसका 3 गुना | `POST /auth/register` |
    | `auth_mail` | `AUTH_MAIL_PER_HOUR` (10) / घंटा, साझा | क्लाइंट, प्रति IPv6 /48 उसका 3 गुना | `POST /auth/verify-email/resend` |
    | `auth_forgot` | `AUTH_FORGOT_PER_HOUR` (3) / घंटा, साझा | क्लाइंट, प्रति IPv6 /48 उसका 3 गुना | `POST /auth/password/forgot` |
    | `auth_forgot_day` | `AUTH_FORGOT_PER_DAY` (10) / 24 घंटे, साझा | क्लाइंट, प्रति IPv6 /48 उसका 3 गुना | `POST /auth/password/forgot` |
    | `auth_reset` | `AUTH_RESET_PER_HOUR` (10) / घंटा, साझा | क्लाइंट, प्रति IPv6 /48 उसका 3 गुना | `POST /auth/password/reset`, `POST /reset-password` |
    | `reauth` | `auth` वाली संख्याएँ, अपने अलग बकेट के साथ, साझा | क्लाइंट और 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 मिनट, साझा | खिलाड़ी | `reauth` वाले ही एंडपॉइंट |
    | `account` | 60 / मिनट | खिलाड़ी | `GET /account/me`, `PUT /account/preferences` |
    | `account_games` | 60 / मिनट | खिलाड़ी | `GET /account/games` |
    | `account_export` | 5 / घंटा, साझा | खिलाड़ी | `POST /account/export` (`reauth` से पहले जाँची जाती है; हर प्रयास गिना जाता है) |
    | `sessions` | 60 / मिनट | खिलाड़ी | `POST /auth/logout`, `/auth/logout-all`, `GET /auth/sessions`, `DELETE /auth/sessions/{id}` |
    | `public_read` | 60 / मिनट | खिलाड़ी (बिना टोकन के: क्लाइंट) | `GET /players/{username}`, `/players/{username}/games`, `/games/{id}`, `/games/{id}/pgn` (चारों के लिए एक ही बकेट) |
    | `gif` | 30 / मिनट | खिलाड़ी | `GET /games/{id}/gif`, `POST /gif` (दोनों के लिए एक ही बकेट) |
    | `gif_user_min`, `gif_user_hour` | `GIF_USER_RENDERS_PER_MIN` (4) / मिनट और `GIF_USER_RENDERS_PER_HOUR` (30) / घंटा, साझा | खिलाड़ी | वही दोनों, केवल तब जब GIF बनाना पड़े (कैश से नहीं) |
    | `gif_ip_min`, `gif_ip_hour` | `GIF_IP_RENDERS_PER_MIN` (12) / मिनट और `GIF_IP_RENDERS_PER_HOUR` (120) / घंटा, साझा | क्लाइंट (उसके सभी खाते मिलाकर), प्रति IPv6 /48 उसका 3 गुना | वही, ऊपर की तरह |
    | `reports` | 30 / घंटा | खिलाड़ी | `POST /reports` |
    | `sso_start` | 30 / 10 मिनट, साझा | क्लाइंट, और प्रति IPv6 /48 90 | `POST /auth/sso/google/start`, `POST /auth/sso/google/web/start` (दोनों मिलाकर) |
    | `sso_finish` | 30 / मिनट | क्लाइंट | `POST /auth/sso/google/finish` |
    | `page` | 60 / मिनट | क्लाइंट | `GET /verify-email`, `/reset-password`, `/confirm-email-change` |

    `GET /info` और `GET /leaderboard` की अपनी कोई लिमिट नहीं है: केवल प्रति-पता लेयर। कई लिमिट वाला एंडपॉइंट उन्हें उसी क्रम में जाँचता है जो उसके विवरण में दिया गया है।

    सर्वर किसी अनुरोध को इस क्रम में संभालता है: प्रति-पता लेयर, फिर हेल्थ एंडपॉइंट; एंडपॉइंट का मिलान; यह जाँच कि सर्वर काफ़ी पीछे तो नहीं चल रहा (503 `server_busy`): पहले के बहुत सारे अनुरोध अपने टाइमआउट के बाद भी चल रहे हों, और `POST`, `PUT`, `PATCH` या `DELETE` के लिए, डेटाबेस का राइट बैकलॉग; ऑथेंटिकेशन, 5 सेकंड के भीतर (उससे ज़्यादा पर 503 `server_busy`); खाते का बजट, जब अनुरोध में सेशन हो; एंडपॉइंट की लिमिट; बॉडी; ख़ुद एंडपॉइंट (GIF एंडपॉइंट अपनी रेंडर लिमिट तभी लेते हैं जब उन्हें कोई GIF बनाना हो)। इसलिए जो अनुरोध अपने सेशन टोकन की वजह से अस्वीकार होता है, वह एंडपॉइंट का कोई टोकन ख़र्च नहीं करता, और अमान्य बॉडी वाला अनुरोध उन्हें ख़र्च करता है।

    दूसरी पाबंदियाँ, जिनका जवाब एंडपॉइंट ख़ुद देते हैं:

    - **एक लॉगिन नाम पर विफल साइन-इन** (उपयोगकर्ता नाम या ईमेल): `AUTH_FAILURES_PER_ACCOUNT` (5) विफलताएँ हो जाने के बाद से, हर प्रयास को पिछले प्रयास से दोगुना इंतज़ार करना होता है (2 सेकंड, 4 सेकंड, और इसी तरह 15 मिनट तक): `retryAfter` के साथ 429 `too_many_attempts`। एक घंटे तक कोई विफलता न होने पर काउंटर रीसेट हो जाता है।
    - **साइन-इन पर विफल दूसरा फ़ैक्टर:** खाते के पाँचवें ग़लत कोड से यही नियम। साइन-इन का एक चरण अधिकतम 5 ग़लत कोड स्वीकार करता है।
    - **एक खाते के दूसरे फ़ैक्टर वाले कोड:** हर 15 मिनट में अधिकतम `AUTH_MFA_PER_ACCOUNT` (10) कोड (ऑथेंटिकेटर या रिकवरी कोड, सही या ग़लत), किसी भी पते से, साइन-इन पर और री-ऑथेंटिकेशन में; इससे ज़्यादा पर कोड जाँचने से पहले ही 429 `too_many_attempts`, इसलिए रिकवरी कोड ख़र्च नहीं होता।
    - **किसी खाते के विफल री-ऑथेंटिकेशन** (ग़लत पासवर्ड या कोड): यही नियम (429 `too_many_attempts`), री-ऑथेंटिकेशन करने वाले सभी एंडपॉइंट के बीच साझा।
    - **ईमेल:** हर पते पर हर 5 मिनट में एक पुष्टि या रीसेट मेल (जवाब वही रहता है); हर पते पर हर घंटे एक "किसी ने आपका पता इस्तेमाल करने की कोशिश की" सूचना।
    - **शिकायतें:** प्रति खिलाड़ी 24 घंटे में `REPORTS_PER_DAY` (5): 429 `report_limit`।

    ## प्रूफ़ ऑफ़ वर्क

    `POW_REGISTER_BITS` के 0 से ज़्यादा होने पर `POST /auth/register` को हमेशा प्रूफ़ ऑफ़ वर्क चाहिए (डिफ़ॉल्ट रूप से 18; `GET /info` इसे `pow.register` के रूप में देता है)। `POST /auth/login` और `POST /auth/sso/google/link` (दोनों के लिए एक ही तरह का चैलेंज) को इसकी ज़रूरत केवल तब होती है जब सर्वर विफल साइन-इन की लहर देखता है, और उसके बाद 5 मिनट तक (`POW_LOGIN_TRIGGER_PER_MIN`, फिर `POW_LOGIN_BITS`); क्लाइंट को यह जवाब से पता चलता है।

    1. बिना प्रूफ़ वाले (या अस्वीकार किए गए प्रूफ़ वाले) अनुरोध का जवाब 428 `pow_required` होता है, `reason` और `pow` चैलेंज `{ challenge, bits, expiresAt }` के साथ।
    2. एक नॉन्स खोजें: अधिकतम 20 अंकों की ऐसी दशमलव स्ट्रिंग जिससे `SHA-256(challenge + ":" + nonce)` `bits` शून्य बिट से शुरू हो (पहले बाइट का सबसे महत्वपूर्ण बिट सबसे पहले)। 18 बिट के लिए औसतन लगभग 2,60,000 हैश लगते हैं।
    3. वही अनुरोध फिर से भेजें, बॉडी में `"pow": { "challenge": "...", "nonce": "123456" }` के साथ।

    चैलेंज 2 मिनट तक और केवल एक बार मान्य है, एक एंडपॉइंट और एक क्लाइंट नेटवर्क (एक IPv4 पता या IPv6 /64) के लिए। यह साइन किया हुआ होता है, इसलिए वापस आने तक सर्वर इसके बारे में कुछ भी नहीं रखता। `reason` बताता है कि प्रूफ़ क्यों अस्वीकार हुआ: `required`, `malformed`, `signature`, `endpoint`, `network`, `expired`, `bits`, `work` या `replayed`।

    ## री-ऑथेंटिकेशन

    खाते में बदलाव के लिए पासवर्ड दोबारा माँगा जाता है और, दो-चरणीय सत्यापन चालू होने पर, दूसरा फ़ैक्टर भी:

    - केवल पासवर्ड: `POST /account/password` और `POST /account/mfa/totp/setup`;
    - पासवर्ड और ऑथेंटिकेटर कोड, रिकवरी कोड अस्वीकार किया जाता है: `POST /account/mfa/recovery-codes`;
    - पासवर्ड और ऑथेंटिकेटर कोड या रिकवरी कोड: `POST /account/mfa/totp/disable`, `POST /account/email`, `POST /account/export` और `POST /account/delete`।

    बॉडी में, `code` में 6 अंकों का ऑथेंटिकेटर कोड होता है और `recoveryCode` में रिकवरी कोड (`xxxx-xxxx-xx`; छोटे-बड़े अक्षर, स्पेस और डैश से फ़र्क़ नहीं पड़ता)। जहाँ रिकवरी कोड स्वीकार किए जाते हैं, वहाँ रिकवरी कोड `code` में भी भेजा जा सकता है। हर कोड एक ही बार काम करता है: इस्तेमाल हो चुका ऑथेंटिकेटर कोड अगले 30 सेकंड के चरण तक अस्वीकार किया जाता है, और रिकवरी कोड एक बार इस्तेमाल होने के बाद ख़त्म हो जाता है।

    | स्टेटस | `error` | कब |
    |---|---|---|
    | 403 | `invalid_password` | ग़लत पासवर्ड, या अनुरोध चलते समय पासवर्ड रीसेट किया गया या बदला गया (कुछ नहीं बदला गया)। |
    | 403 | `mfa_code_required` | दो-चरणीय सत्यापन चालू है और न `code` भेजा गया, न `recoveryCode`। |
    | 403 | `invalid_code` | ग़लत या पहले से इस्तेमाल हो चुका कोड। |
    | 400 | `password_not_set` | केवल Google वाले खाते का अभी कोई पासवर्ड नहीं है ("पासवर्ड याद नहीं?" से एक पासवर्ड सेट होता है)। |
    | 429 | `too_many_attempts` | इस खाते पर बहुत ज़्यादा विफलताएँ, या बहुत ज़्यादा कोड आज़माए गए। |
    | 503 / 429 | `server_busy` / `rate_limited` | पासवर्ड हैश कतार व्यस्त है। |

    ग़लत पासवर्ड और कोड खाते के विफलता काउंटर में गिने जाते हैं और सुरक्षा घटनाओं के रूप में दर्ज किए जाते हैं।

    ## मेट्रिक्स पोर्ट

    API पोर्ट के अलावा, सर्वर मेट्रिक्स पोर्ट पर सादे HTTP में जवाब देता है (`METRICS_PORT`, 9464, `METRICS_BIND` पर बाउंड, डिफ़ॉल्ट रूप से 127.0.0.1; इसे निजी रखें)। यह इस API का हिस्सा नहीं है: `GET /healthz` का जवाब `ok` होता है; `GET /readyz` स्टार्ट-अप पूरा होने (हर शार्ड अपना जर्नल रीप्ले कर चुका है और लिसनर बाउंड हो चुके हैं) से लेकर शटडाउन शुरू होने तक `ready` जवाब देता है, वरना 503 `not ready`; `GET /metrics` Prometheus मेट्रिक्स देता है और, `METRICS_TOKEN` सेट होने पर, ठीक उसी टोकन के साथ `Authorization: Bearer <token>` माँगता है।
  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 सर्वर का सोर्स कोड और उसका संदर्भ दस्तावेज़ीकरण (API.md, PROTOCOL.md, CONFIG.md)।
  url: https://github.com/DarkCenobyte/scacelith-chess-server
servers:
  - url: https://caissa.scacelith.com/api/v1
    description: आधिकारिक सर्वर।
  - url: https://{host}:{port}/api/v1
    description: कोई भी Scacelith सर्वर, जैसे कोई कम्युनिटी सर्वर।
    variables:
      host:
        default: caissa.scacelith.com
        description: सर्वर का सार्वजनिक होस्ट नाम (`SERVER_PUBLIC_HOST`)।
      port:
        default: "443"
        description: सार्वजनिक API पोर्ट (`PUBLIC_API_PORT`, वरना `API_PORT`)।
tags:
  - name: server-info
    x-displayName: सर्वर की जानकारी
    description: साइन इन करने या कनेक्ट करने से पहले क्लाइंट को क्या जानना ज़रूरी है।
  - name: health
    x-displayName: हेल्थ
    description: |-
      मॉनिटरिंग के लिए सर्वर की लाइवनेस और रेडीनेस। ये एंडपॉइंट API पोर्ट पर ऑथेंटिकेशन और हर एंडपॉइंट लिमिट से पहले जवाब देते हैं, लेकिन किसी भी अनुरोध की तरह प्रति-पता लेयर का एक टोकन लेते हैं; मॉनिटरिंग होस्ट को `ABUSE_EXEMPT` में दर्ज किया जा सकता है। हर एंडपॉइंट के लिए दोनों पाथ काम करते हैं: सर्वर के रूट पर और `/api/v1` के तहत।

      मेट्रिक्स पोर्ट के अपने अलग हेल्थ एंडपॉइंट हैं, जिनका विवरण परिचय में है।
  - name: auth
    x-displayName: रजिस्ट्रेशन और साइन-इन
    description: |-
      खाता बनाना, पासवर्ड (और दूसरे फ़ैक्टर) से साइन इन करना, पुष्टि और पासवर्ड रीसेट के ईमेल।

      `POST /auth/login`, `POST /auth/login/mfa`, `POST /auth/sso/google/finish`, `POST /auth/sso/google/link` और `POST /auth/sso/complete` जवाब में एक सेशन `{ token, expiresAt, user }` देते हैं (या, शुरुआती एंडपॉइंट के मामले में, दूसरा चरण)।
  - name: google-sign-in
    x-displayName: Google साइन-इन
    description: |-
      तब उपलब्ध है जब `GET /info` में `sso.google: true` हो; वरना नीचे का हर एंडपॉइंट 404 `sso_disabled` जवाब देता है। `sso.googleWeb` के true होने पर कोई वेबसाइट अपने ख़ुद के क्लाइंट से इसी तरह साइन इन करती है (नीचे, **वेबसाइट पर**)। गेम सिस्टम ब्राउज़र के ज़रिए RFC 8252 के इंस्टॉल्ड-ऐप फ़्लो से साइन इन करता है (एक "Desktop app" क्लाइंट): Google ब्राउज़र को `127.0.0.1` पर गेम के एक लिसनर पर वापस भेजता है, इस सर्वर पर कभी नहीं, और गेम को कभी कोई Google क्रेडेंशियल नहीं दिखता।

      1. क्लाइंट `127.0.0.1:0` पर सुनता है (पोर्ट सिस्टम चुनता है) और एक PKCE जोड़ी बनाता है: `[A-Za-z0-9._~-]` के 43 से 128 वर्णों का `codeVerifier` और `codeChallenge = BASE64URL(SHA-256(codeVerifier))`, जिसमें 43 वर्ण होते हैं और कोई पैडिंग नहीं होती।
      2. चैलेंज और पोर्ट के साथ `POST /auth/sso/google/start` Google URL और प्रयास का `state` लौटाता है। क्लाइंट URL की जाँच करता है (नीचे देखें) और उसे ब्राउज़र में खोलता है।
      3. Google ब्राउज़र को `http://127.0.0.1:<port>/oauth2/google/<tag>?code=...&state=...` पर भेजता है। क्लाइंट केवल start वाले जवाब का `state` स्वीकार करता है, और `error=` रीडायरेक्ट के बाद कुछ नहीं भेजता।
      4. प्रयास की आईडी, `codeVerifier`, `state` और `code` (और Google ने `iss` भेजा हो तो वह भी) के साथ `POST /auth/sso/google/finish`।
      5. जवाब एक सेशन होता है, या दो-चरणीय सत्यापन का चरण (`POST /auth/login/mfa` से आगे बढ़ें), या नए खिलाड़ी के लिए `needsUsername` (`POST /auth/sso/complete` से आगे बढ़ें), या `needsPassword` जब पासवर्ड वाला कोई खाता उस पते का इस्तेमाल करता है (`POST /auth/sso/google/link` से आगे बढ़ें)।

      **ओरिजिन टैग।** रीडायरेक्ट URI में उस सर्वर का टैग होता है जिसे खिलाड़ी ने जोड़ा है, ताकि किसी दूसरे सर्वर को उसके अपने खिलाड़ियों के लिए मिला URL गेम अस्वीकार कर दे। ओरिजिन छोटे अक्षरों में होस्ट (IPv6 लिटरल ब्रैकेट में), `:` और दशमलव API पोर्ट है, जो हमेशा लिखा जाता है, 443 भी: सर्वर `SERVER_PUBLIC_HOST` और अपना सार्वजनिक API पोर्ट लेता है, और गेम वह पता जिससे वह कनेक्टेड है। टैग SHA-256(UTF-8 `"scacelith-sso-origin-v1\n"` + ओरिजिन) के base64url (बिना पैडिंग) के पहले 22 वर्ण हैं। रीडायरेक्ट URI `"http://127.0.0.1:" + port + "/oauth2/google/" + tag` है: हमेशा IPv4 लिटरल, और गेम के लिसनर का पोर्ट (1024 से 65535)। सर्वर क्लाइंट से कभी कोई URI, होस्ट या पाथ नहीं लेता।

      | ओरिजिन | टैग |
      |---|---|
      | `play.scacelith.example:443` | `IhcScoV7eDOzTEcSnqPUPt` |
      | `localhost:8443` | `TFGx7zQ_8QlGZW5zpqznCr` |
      | `[::1]:8443` | `XToJm0DG5PjciEVmZa9Cho` |
      | `127.0.0.1:50443` | `3r653wM5ZjYsHcAJljmCwY` |

      इसलिए Google साइन-इन केवल उन खिलाड़ियों के लिए काम करता है जिन्होंने सर्वर को ठीक `SERVER_PUBLIC_HOST` और उसके सार्वजनिक API पोर्ट के साथ जोड़ा है।

      **ब्राउज़र खोलने से पहले गेम क्या जाँचता है।** `authUrl` ठीक `https://accounts.google.com/o/oauth2/v2/auth?` से शुरू होता है और 4096 वर्णों से कम का प्रिंट होने योग्य ASCII है, और उसकी क्वेरी में ठीक एक `response_type=code`, ठीक एक `redirect_uri` जो उस URI के बराबर है जिसे गेम अपने पोर्ट और अपने ओरिजिन टैग से निकालता है, ठीक एक `state` जो जवाब के `state` के बराबर है, `code_challenge_method=S256` और 43 वर्णों का `code_challenge` होता है। वरना गेम अपना लिसनर बंद कर देता है और कुछ नहीं खोलता। वह `finish` और `link` केवल उसी सर्वर को भेजता है जिसने `start` का जवाब दिया था।

      **वेबसाइट पर।** कोई वेबसाइट (आधिकारिक वेबसाइट: <https://scacelith.com/>) उसी Google प्रोजेक्ट के "Web application" क्लाइंट (`GOOGLE_WEB_CLIENT_ID`) से साइन इन करती है। Google ब्राउज़र को साइट के उस एक पेज पर वापस भेजता है जिसे ऑपरेटर ने कॉन्फ़िगर किया है (`GOOGLE_WEB_REDIRECT_URI`, जो क्लाइंट के "Authorized redirect URIs" में दर्ज है), और वह पेज कोड को `finish` पर भेजता है; यहाँ भी सर्वर क्लाइंट से कोई रीडायरेक्ट URI नहीं लेता।

      1. पेज ऊपर की तरह एक PKCE जोड़ी बनाता है, वेरिफ़ायर को टैब में (`sessionStorage`) रखता है, और चैलेंज के साथ `POST /auth/sso/google/web/start` भेजता है।
      2. वह गेम की तरह `authUrl` की जाँच करता है, सिवाय इसके कि उसका `redirect_uri` उसके अपने ओरिजिन का पेज होना चाहिए, फिर उस पर नेविगेट करता है। कोई Google स्क्रिप्ट लोड नहीं की जाती।
      3. Google ब्राउज़र को `GOOGLE_WEB_REDIRECT_URI?code=...&state=...` पर भेजता है। पेज केवल start वाले जवाब का `state` स्वीकार करता है और `error=` रीडायरेक्ट के बाद कुछ नहीं भेजता।
      4. `finish`, `link` और `complete` गेम की तरह ही होते हैं। प्रयास याद रखता है कि उसे वेबसाइट ने शुरू किया था: कोड वेबसाइट के क्लाइंट और पेज के साथ एक्सचेंज किया जाता है, और ID टोकन में उसी क्लाइंट का नाम होना चाहिए (गेम के क्लाइंट का कोड या ID टोकन 502 `sso_failed` के साथ विफल होता है, और इसका उल्टा भी)।

      एक Google खाता गेम से भी और वेबसाइट से भी उसी Scacelith खाते तक पहुँचता है: जुड़ाव में Google का subject (`sub`) सहेजा होता है, जो क्लाइंट चाहे जो हो, Google खाते की पहचान करता है। वेबसाइट का ओरिजिन `CORS_ORIGINS` में होना चाहिए (वरना `check-config` चेतावनी देता है)।

      **Google साइन-इन किस खाते तक पहुँचता है:** उस Google खाते से पहले से जुड़ा खाता; वरना Google द्वारा पुष्ट किए गए पते वाला सक्रिय खाता (पासवर्ड वाला हो तो `finish` का जवाब `needsPassword` होता है और जुड़ाव तभी सहेजा जाता है जब उसका पासवर्ड, और फिर चालू होने पर उसका दूसरा फ़ैक्टर, सही निकले; बिना पासवर्ड वाला हो तो 409 `sso_account_exists`); वरना एक नया खाता (`needsUsername`)। कोई Google खाता केवल पते के आधार पर किसी मौजूदा खाते से कभी नहीं जोड़ा जाता। जब Google साइन-इन कोई खाता बनाता है और जब उसे किसी मौजूदा खाते में जोड़ा जाता है, तब खाते के पते पर एक मेल जाता है।
  - name: sessions
    x-displayName: सेशन
    description: खाते के साइन-इन वाले डिवाइस, और साइन आउट करना।
  - name: account
    x-displayName: खाता
    description: खाते का व्यू, उसकी वरीयताएँ और उसका पासवर्ड।
  - name: two-step-verification
    x-displayName: दो-चरणीय सत्यापन
    description: ऑथेंटिकेटर ऐप (TOTP, RFC 6238), SHA-1, 6 अंकों, 30 सेकंड के चरणों और दोनों ओर एक चरण की छूट के साथ, और एक बार इस्तेमाल होने वाले रिकवरी कोड।
  - name: email-change
    x-displayName: ईमेल पता बदलना
    description: खाते का पता बदलना, जिसकी पुष्टि नए पते पर भेजे गए लिंक से होती है।
  - name: data-export
    x-displayName: डेटा एक्सपोर्ट
    description: सर्वर खाते के बारे में जो कुछ भी रखता है, वह सब एक JSON फ़ाइल के रूप में।
  - name: account-deletion
    x-displayName: खाता हटाना
    description: खाते को हमेशा के लिए हटाना।
  - name: game-history
    x-displayName: बाज़ियों का इतिहास
    description: साइन-इन किए हुए खिलाड़ी की अपनी बाज़ियाँ, फ़िल्टर की हुई और पेजों में बँटी हुई।
  - name: games
    x-displayName: बाज़ियाँ और PGN
    description: |-
      बाज़ियों के रिकॉर्ड, उनकी चालों और घड़ियों के साथ, और उनकी PGN फ़ाइलें।

      **बाज़ी के कोड।** `status`: 1 `WhiteWins`, 2 `BlackWins`, 3 `Draw`, 4 `Aborted` (केवल ख़त्म हो चुकी बाज़ियाँ सहेजी जाती हैं)। `result`: `1-0`, `0-1`, `1/2-1/2`, या `*` (रद्द)। `reason`, अपने नाम (`termination`) और उन शब्दों के साथ जिनसे PGN फ़ाइल का चालों वाला टेक्स्ट ख़त्म होता है (फ़ाइल में अंग्रेज़ी में, नीचे कोष्ठक में उनका अनुवाद); कोड 7, 14 और 21 ड्रॉ हैं (जिस खिलाड़ी का समय ख़त्म हुआ, जिसने हार मानी या जिसने बाज़ी छोड़ी, उसके प्रतिद्वंद्वी के लिए मात देना संभव नहीं था: FIDE 6.9 और 5.1.2; नियम `DESIGN.md` 6.3 में है), और सर्वर कभी कोड 4 और 13 से बाज़ी ख़त्म नहीं करता (ये कारणों की साझा सूची का हिस्सा हैं):

      | कोड | `termination` | PGN में शब्द |
      |---|---|---|
      | 1 | `Checkmate` | Checkmate (मात) |
      | 2 | `Resignation` | Resignation (हार मान ली गई) |
      | 3 | `Timeout` | Loss on time (समय ख़त्म होने से हार) |
      | 4 | `IllegalMoves` | Second illegal move (forfeit) (दूसरी अवैध चाल, हार घोषित) |
      | 5 | `Stalemate` | Stalemate (स्टेलमेट) |
      | 6 | `InsufficientMaterial` | Dead position (insufficient material) (मृत स्थिति, अपर्याप्त मटीरियल) |
      | 7 | `TimeoutVsInsufficient` | Flag fall, but the opponent cannot checkmate (समय ख़त्म, पर प्रतिद्वंद्वी के लिए मात देना संभव नहीं) |
      | 8 | `FivefoldRepetition` | Fivefold repetition (पाँच बार दोहराव) |
      | 9 | `SeventyFiveMoves` | 75-move rule (75 चालों का नियम) |
      | 10 | `ThreefoldClaim` | Threefold repetition (claimed) (तीन बार दोहराव, दावा किया गया) |
      | 11 | `FiftyMoveClaim` | 50-move rule (claimed) (50 चालों का नियम, दावा किया गया) |
      | 12 | `Agreement` | Draw by agreement (आपसी सहमति से ड्रॉ) |
      | 13 | `IllegalMovesVsInsufficient` | Second illegal move, but the opponent cannot checkmate (दूसरी अवैध चाल, पर प्रतिद्वंद्वी के लिए मात देना संभव नहीं) |
      | 14 | `ResignationVsInsufficient` | Resignation, but the opponent cannot checkmate (हार मान ली गई, पर प्रतिद्वंद्वी के लिए मात देना संभव नहीं) |
      | 20 | `Abandonment` | Abandoned (disconnected for too long) (बाज़ी छोड़ दी गई, बहुत देर तक कनेक्शन टूटा रहा) |
      | 21 | `AbandonmentVsInsufficient` | Abandoned, but the opponent cannot checkmate (बाज़ी छोड़ दी गई, पर प्रतिद्वंद्वी के लिए मात देना संभव नहीं) |
      | 22 | `Aborted` | Game aborted (बाज़ी रद्द) |
      | 23 | `NoShow` | Aborted: first move not played in time (रद्द: पहली चाल समय पर नहीं चली गई) |
      | 24 | `Forfeit` | Forfeit (fair play violation) (हार घोषित, फ़ेयर प्ले का उल्लंघन) |
      | 25 | `ServerAborted` | Aborted by the server (सर्वर द्वारा रद्द) |
      | 26 | `BothDisconnected` | Aborted: both players disconnected (रद्द: दोनों खिलाड़ियों का कनेक्शन टूट गया) |

      कोड 14 प्रोटोकॉल के माइनर संस्करण 1 में नया है: उससे पहले लिखे गए क्लाइंट को अज्ञात कोड को उसके `result` के साथ बाज़ी के सामान्य अंत के रूप में दिखाना चाहिए।
  - name: gifs
    x-displayName: एनिमेटेड GIF
    description: |-
      एनिमेटेड GIF के रूप में एक बाज़ी, रखने या शेयर करने के लिए: ऊपर से दिखता बोर्ड, शुरुआत से अंतिम स्थिति तक हर स्थिति का एक फ़्रेम, बोर्ड के ऊपर खिलाड़ियों के नाम और रेटिंग, उसके नीचे आख़िरी चाल, और आख़िरी फ़्रेम पर परिणाम और यह कि बाज़ी किस तरह ख़त्म हुई।

      **लागत, कैश और कोटा।** GIF अपने अलग रेंडरिंग थ्रेड पर बनता है, बाज़ियाँ चलाने वाले थ्रेड पर कभी नहीं, और सबसे कम CPU प्राथमिकता पर: `GIF_THREADS` थ्रेड (डिफ़ॉल्ट रूप से `WORKERS`), जो पहले GIF के साथ शुरू होते हैं और एक मिनट तक कोई GIF न बनने पर बंद हो जाते हैं। अधिकतम `GIF_QUEUE_MAX` (4 x `WORKERS`) GIF किसी थ्रेड का इंतज़ार करते हैं, हर एक अधिकतम `GIF_QUEUE_TIMEOUT_MS` (10 सेकंड); एक रेंडर `GIF_RENDER_TIMEOUT_MS` (30 सेकंड) तक चल सकता है। सर्वर अपने बनाए GIF को `GIF_CACHE_MB` MB (32 x `WORKERS`) के कैश में रखता है, जिसमें सबसे कम हाल में इस्तेमाल हुआ GIF सबसे पहले हटता है; कैश से मिलने वाला GIF, या किसी दूसरे अनुरोध के लिए बन रहा GIF, कोई रेंडर ख़र्च नहीं करता। कैश की कुंजी में वह सब कुछ शामिल है जो चित्र को बदलता है, नाम भी।

      हर अनुरोध `gif` में गिना जाता है (दोनों एंडपॉइंट के लिए मिलाकर प्रति खिलाड़ी प्रति मिनट 30)। जिस GIF को बनाना पड़ता है, वह रेंडर लिमिट में भी गिना जाता है: प्रति खिलाड़ी प्रति मिनट `GIF_USER_RENDERS_PER_MIN` (4) और प्रति घंटा `GIF_USER_RENDERS_PER_HOUR` (30); प्रति क्लाइंट, उसके सभी खाते मिलाकर, प्रति मिनट `GIF_IP_RENDERS_PER_MIN` (12) और प्रति घंटा `GIF_IP_RENDERS_PER_HOUR` (120), और प्रति IPv6 /48 उसका 3 गुना। क्लाइंट को डाउनलोड की गई फ़ाइल रख लेनी चाहिए, उसे दोबारा नहीं माँगना चाहिए, और 429 या 503 के बाद `retryAfter` तक इंतज़ार करना चाहिए।

      आकार: `small` (32 px के खाने: 284 x 350 पिक्सेल), `medium` (48 px: 424 x 515), `large` (72 px: 628 x 762); निर्देशांकों के बिना 268 x 342, 400 x 503 और 600 x 748। शुरुआती स्थिति कम से कम 1 सेकंड दिखती है (डिले इससे लंबा हो तो डिले जितनी देर), अंतिम स्थिति 3 सेकंड, और GIF लूप में चलता है; पहले फ़्रेम के बाद, चित्र का केवल वही हिस्सा सहेजा जाता है जो बदलता है। 40 चालों की बाज़ी लगभग 135, 205 और 325 KiB लेती है (small, medium, large), 150 चालों की बाज़ी 0.5, 0.8 और 1.2 MiB।
  - name: players
    x-displayName: खिलाड़ी
    description: सार्वजनिक प्रोफ़ाइल और हाल की बाज़ियाँ। केवल सार्वजनिक डेटा, कभी भी ईमेल पता, सेशन, दंड या इंटीग्रिटी लेवल नहीं। हटाए गए खाते की कोई प्रोफ़ाइल नहीं होती।
  - name: leaderboard
    x-displayName: लीडरबोर्ड
    description: हर आधिकारिक श्रेणी के शीर्ष खिलाड़ी।
  - name: reports
    x-displayName: शिकायतें
    description: हाल की किसी बाज़ी के प्रतिद्वंद्वी की शिकायत मॉडरेटरों से करना।
  - name: pages
    x-displayName: HTML पेज
    description: |-
      ब्राउज़र के लिए पेज, जो ईमेल के लिंक से खुलते हैं। उनके लिंक `/api/v1` के बाहर `https://<SERVER_PUBLIC_HOST>` (443 न होने पर `:<PUBLIC_API_PORT>` के साथ) की ओर ले जाते हैं।

      - पेज कोई JavaScript नहीं चलाते और कोई बाहरी रिसोर्स लोड नहीं करते। वे `Content-Security-Policy: default-src 'none'; style-src 'unsafe-inline'; form-action 'self'; frame-ancestors 'none'; base-uri 'none'` के साथ सर्व किए जाते हैं।
      - `GET` केवल एक बटन या फ़ॉर्म दिखाता है, ताकि लिंक खोलने वाला कोई मेल स्कैनर उसे ख़र्च न कर दे। बदलाव `POST` पर होता है: `application/x-www-form-urlencoded` के रूप में भेजा गया फ़ॉर्म (JSON बॉडी भी स्वीकार की जाती है), जिसके एक छिपे फ़ील्ड में लिंक का टोकन होता है। दो बार दिया गया फ़ील्ड अस्वीकार किया जाता है।
      - त्रुटियाँ (रेट लिमिट, अमान्य फ़ील्ड) भी HTML पेज होती हैं, जिनका शीर्षक "Request refused" (अनुरोध अस्वीकार) या, 500 और उससे ऊपर, "Server error" (सर्वर त्रुटि) होता है। केवल पेज के मिलान से पहले पाई गई विफलताओं (बहुत लंबा या ग़लत रूप वाला अनुरोध टारगेट, प्रति-पता लेयर) का जवाब JSON में होता है।
x-tagGroups:
  - name: server
    x-displayName: सर्वर
    tags:
      - server-info
      - health
  - name: accounts
    x-displayName: खाते
    tags:
      - auth
      - google-sign-in
      - sessions
      - account
      - two-step-verification
      - email-change
      - data-export
      - account-deletion
  - name: games
    x-displayName: बाज़ियाँ
    tags:
      - game-history
      - games
      - gifs
  - name: community
    x-displayName: खिलाड़ी और शिकायतें
    tags:
      - players
      - leaderboard
      - reports
  - name: browser-pages
    x-displayName: ब्राउज़र पेज
    tags:
      - pages
paths:
  /info:
    get:
      operationId: getServerInfo
      tags:
        - server-info
      summary: सर्वर का नाम, संस्करण, पोर्ट और साइन-अप के नियम पाएँ
      description: |-
        साइन इन करने या कनेक्ट करने से पहले क्लाइंट को जो चाहिए: सर्वर का नाम और आईडी, WebSocket प्रोटोकॉल के संस्करण और WebSocket कहाँ है, साइन-अप के नियम, आधिकारिक टाइम कंट्रोल और वे सीमाएँ जिन्हें क्लाइंट कोई फ़ॉर्म भेजने से पहले जाँच सकता है।

        **लिमिट:** केवल प्रति-पता लेयर।
      security: []
      responses:
        '200':
          description: सर्वर का विवरण।
          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`: प्रति-पता लेयर।'
        '500':
          $ref: '#/components/responses/InternalError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
          description: '`timeout`। सर्वर के काफ़ी पीछे चलने पर (परिचय देखें), `retryAfter` 2 से 5 के साथ `server_busy` भी: अनुरोध प्रोसेस नहीं किया गया।'
  /auth/register:
    post:
      operationId: registerAccount
      tags:
        - auth
      summary: खाता बनाएँ
      description: |-
        खाता बनाता है: उसके ईमेल पते की पुष्टि उस पर भेजे गए लिंक से हो जाने के बाद, या ईमेल पुष्टि न होने पर तुरंत।

        **ईमेल पुष्टि के साथ** (`REQUIRE_EMAIL_VERIFICATION`, डिफ़ॉल्ट): 202 `verification_sent`। अभी कोई खाता नहीं बनता: साइन-अप 24 घंटे तक प्रतीक्षा करता है और इस बीच अपना उपयोगकर्ता नाम रोके रखता है। उन 24 घंटों तक मान्य एक लिंक उस पते पर जाता है, हर पते पर हर 5 मिनट में अधिकतम एक (`POST /auth/verify-email/resend` नया लिंक भेजता है); लिंक इस्तेमाल होने पर (`/verify-email` पेज का बटन) खाता बनता है, उसके पते की पुष्टि के साथ, और फिर उससे साइन इन किया जा सकता है। तब तक उस उपयोगकर्ता नाम से साइन-इन का जवाब `invalid_credentials` होता है, जैसा अज्ञात खाते के लिए, और सार्वजनिक प्रोफ़ाइल मौजूद नहीं होती। उसी पते से किया गया नया साइन-अप प्रतीक्षारत साइन-अप की जगह ले लेता है। जब कोई दूसरा खाता पहले से उस पते का इस्तेमाल करता है, तब भी जवाब यही होता है: तब कोई लिंक नहीं भेजा जाता, उसके बजाय उस खाते के मालिक को एक सूचना मिलती है (प्रति घंटा अधिकतम एक), और उपयोगकर्ता नाम इसी तरह रोका जाता है, ताकि किसी भी चीज़ से यह पता न चले कि उस पते का कोई खाता है या नहीं। जिस साइन-अप का लिंक इस्तेमाल नहीं हुआ, वह 24 घंटे बाद हटा दिया जाता है, और उसका उपयोगकर्ता नाम फिर से उपलब्ध हो जाता है।

        **ईमेल पुष्टि के बिना** (`REQUIRE_EMAIL_VERIFICATION=false`): 201 `ready`, खाता तुरंत बनता है और उससे साइन इन किया जा सकता है।

        **नियम।** `username`: `USERNAME_MIN` से `USERNAME_MAX` वर्ण (3 से 20), अक्षर, अंक, `_` और `-`, जो किसी अक्षर या अंक से शुरू हो (`GET /info` इन्हें `limits` के रूप में देता है); आरक्षित नाम (`admin`, `moderator`, `deleted`...) और कुछ प्रीफ़िक्स अस्वीकार किए जाते हैं; छोटे-बड़े अक्षरों का भेद किए बिना अद्वितीय। `email`: आगे-पीछे के स्पेस हटाकर छोटे अक्षरों में सहेजा जाता है, सादा ASCII, डॉट वाले डोमेन के साथ। `password`: कम से कम `PASSWORD_MIN_LENGTH` (10) वर्ण और UTF-8 में अधिकतम 256 बाइट; इसमें उपयोगकर्ता नाम या ईमेल का लोकल पार्ट नहीं होना चाहिए, और यह कोई आम पासवर्ड नहीं होना चाहिए।

        **त्रुटियाँ**, इस क्रम में जाँची जाती हैं: 403 `registration_closed`; 400 `invalid_username`; 400 `invalid_email`; 400 `weak_password`; 409 `username_taken`; 428 `pow_required`; पासवर्ड हैश कतार की त्रुटियाँ; 409 `email_taken` (केवल ईमेल पुष्टि के बिना)।

        **लिमिट:** `auth`, फिर `auth_register` (प्रति क्लाइंट प्रति घंटा 10 रजिस्ट्रेशन)। **प्रूफ़ ऑफ़ वर्क:** हमेशा, जब `POW_REGISTER_BITS` 0 से ज़्यादा हो।
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RegisterRequest'
            examples:
              first:
                summary: पहला प्रयास, प्रूफ़ ऑफ़ वर्क के बिना
                value:
                  username: alice
                  email: alice@example.org
                  password: correct horse battery
              withPow:
                summary: प्रूफ़ ऑफ़ वर्क के साथ दोबारा भेजा गया
                value:
                  username: alice
                  email: alice@example.org
                  password: correct horse battery
                  pow:
                    challenge: eyJ2IjoxLCJlIjoicmVnaXN0ZXIiLCJiIjoxOCwieCI6MTc5MDg4Mjk5MTIwMH0.6Ku_D7kw3S69OYxj-9KxsXszRCwz6ECnOQ-B6-v2erM
                    nonce: '123456'
      responses:
        '201':
          description: खाता बन गया है और उससे साइन इन किया जा सकता है (इस सर्वर पर ईमेल पुष्टि नहीं होती)।
          headers:
            Cache-Control:
              $ref: '#/components/headers/CacheControl'
          content:
            application/json:
              schema:
                type: object
                required:
                  - status
                properties:
                  status:
                    const: ready
              example:
                status: ready
        '202':
          description: साइन-अप अपने पुष्टि लिंक की प्रतीक्षा में है (या उस पते का पहले से कोई खाता है; जवाब यह नहीं बताता)।
          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` (`field` के साथ), `invalid_json`, `invalid_username`, `invalid_email`, या `reason` के साथ `weak_password`: `too_short`, `too_long`, `contains_username`, `contains_email` या `too_common`।'
        '403':
          $ref: '#/components/responses/Forbidden'
          description: '`registration_closed`: इस सर्वर पर रजिस्ट्रेशन बंद है (`GET /info` में `registration: closed` है)।'
        '408':
          $ref: '#/components/responses/RequestTimeout'
        '409':
          $ref: '#/components/responses/Conflict'
          description: '`username_taken`: किसी खाते के पास यह उपयोगकर्ता नाम है, या किसी दूसरे पते का प्रतीक्षारत साइन-अप उसे रोके हुए है। `email_taken`: कोई दूसरा खाता इस पते का इस्तेमाल करता है (केवल ईमेल पुष्टि के बिना)।'
        '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`: `auth` या `auth_register` लिमिट, या पासवर्ड हैश कतार।'
        '500':
          $ref: '#/components/responses/InternalError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
          description: '`server_busy` (पासवर्ड हैश कतार, या डेटाबेस लॉक रहा), `timeout`। सर्वर के काफ़ी पीछे चलने पर (परिचय देखें), `retryAfter` 2 से 5 के साथ `server_busy` भी: अनुरोध प्रोसेस नहीं किया गया।'
  /auth/login:
    post:
      operationId: logIn
      tags:
        - auth
      summary: पासवर्ड से साइन इन करें
      description: |-
        उपयोगकर्ता नाम या ईमेल पते और पासवर्ड से साइन इन करता है। दो संभावित जवाब हैं, दोनों स्टेटस 200 के साथ: एक सेशन, या, दो-चरणीय सत्यापन चालू होने पर, एक दूसरा चरण जिसे 5 मिनट के भीतर `POST /auth/login/mfa` से पूरा करना होता है।

        अज्ञात खाते, ग़लत पासवर्ड और बिना पासवर्ड वाले खाते को एक ही जवाब मिलता है, उतने ही समय बाद: 401 `invalid_credentials`। खाते की जाँचें (`banned`, `email_unverified`) सही पासवर्ड के बाद ही होती हैं। एक लॉगिन नाम पर `AUTH_FAILURES_PER_ACCOUNT` (5) विफलताएँ हो जाने के बाद से, हर प्रयास को इंतज़ार करना होता है (429 `too_many_attempts`)।

        **लिमिट:** `auth`। **प्रूफ़ ऑफ़ वर्क:** केवल विफल साइन-इन की लहर के दौरान (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: एक सेशन, या दो-चरणीय सत्यापन वाले साइन-इन का दूसरा चरण।
          headers:
            Cache-Control:
              $ref: '#/components/headers/CacheControl'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LoginAnswer'
              examples:
                session:
                  summary: साइन इन हो गया
                  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: दो-चरणीय सत्यापन चालू है
                  value:
                    mfaRequired: true
                    mfaToken: mfa_m4wXAVhYCMWG0PX7MLcIpBeccBFg0oxPvoRujzGWjO8
                    expiresIn: 300
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
          description: '`invalid_credentials`: अज्ञात खाता, ग़लत पासवर्ड, या बिना पासवर्ड वाला खाता।'
        '403':
          $ref: '#/components/responses/Forbidden'
          description: 'केवल सही पासवर्ड के बाद: `until` के साथ `banned` (epoch से मिलीसेकंड, स्थायी निलंबन के लिए `null`), या `email_unverified` (कोई पुराना खाता जिसके पते की अभी पुष्टि नहीं हुई है)।'
        '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` (इस लॉगिन नाम की विफलताओं के बाद की प्रतीक्षा), या `rate_limited` (`auth` लिमिट, पासवर्ड हैश कतार)।'
        '500':
          $ref: '#/components/responses/InternalError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
          description: '`server_busy` (पासवर्ड हैश कतार, या डेटाबेस लॉक रहा), `timeout`। सर्वर के काफ़ी पीछे चलने पर (परिचय देखें), `retryAfter` 2 से 5 के साथ `server_busy` भी: अनुरोध प्रोसेस नहीं किया गया।'
  /auth/login/mfa:
    post:
      operationId: logInWithSecondFactor
      tags:
        - auth
      summary: दूसरे फ़ैक्टर से साइन-इन पूरा करें
      description: |-
        दो-चरणीय सत्यापन वाले साइन-इन का दूसरा चरण, जब `POST /auth/login` या Google साइन-इन (`POST /auth/sso/google/finish` या `POST /auth/sso/google/link`) ने `mfaRequired` जवाब दिया हो। `code` (6 अंकों का ऑथेंटिकेटर कोड, या रिकवरी कोड) या `recoveryCode` भेजें। यहाँ इस्तेमाल हुआ रिकवरी कोड ख़त्म हो जाता है।

        एक चरण अधिकतम 5 ग़लत कोड स्वीकार करता है; पासवर्ड रीसेट होने या बदले जाने पर भी चरण ख़त्म हो जाता है। Google जुड़ाव के पासवर्ड वाले चरण के बाद, जुड़ाव तभी सहेजा जाता है जब यहाँ कोड सही निकले।

        **लिमिट:** `auth`, और खाते के लिए हर 15 मिनट में अधिकतम `AUTH_MFA_PER_ACCOUNT` (10) कोड, किसी भी पते से; इससे ज़्यादा पर कोड जाँचा नहीं जाता, इसलिए रिकवरी कोड ख़र्च नहीं होता।
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MfaLoginRequest'
            examples:
              authenticator:
                summary: ऑथेंटिकेटर कोड
                value:
                  mfaToken: mfa_m4wXAVhYCMWG0PX7MLcIpBeccBFg0oxPvoRujzGWjO8
                  code: '123456'
              recovery:
                summary: रिकवरी कोड
                value:
                  mfaToken: mfa_m4wXAVhYCMWG0PX7MLcIpBeccBFg0oxPvoRujzGWjO8
                  recoveryCode: j7v5-3ezx-zn
      responses:
        '200':
          description: साइन इन हो गया।
          headers:
            Cache-Control:
              $ref: '#/components/headers/CacheControl'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SessionAnswer'
        '400':
          $ref: '#/components/responses/BadRequest'
          description: '`invalid_request`: न `code` भेजा गया, न `recoveryCode`, या बॉडी स्कीमा का उल्लंघन करती है; `invalid_json`।'
        '401':
          $ref: '#/components/responses/Unauthorized'
          description: '`invalid_mfa_token`: चरण की अवधि ख़त्म हो गई, उसका इस्तेमाल हो चुका है, या 5 ग़लत कोड के बाद वह ख़त्म हो गया, या पहले चरण के बाद पासवर्ड रीसेट किया गया या बदला गया (फिर से साइन इन करें)। `invalid_code`: ग़लत या पहले से इस्तेमाल हो चुका कोड।'
        '403':
          $ref: '#/components/responses/Forbidden'
          description: '`banned` (`until` के साथ), `email_unverified`।'
        '408':
          $ref: '#/components/responses/RequestTimeout'
        '409':
          $ref: '#/components/responses/Conflict'
          description: '`sso_already_linked`: केवल Google जुड़ाव के पासवर्ड वाले चरण के बाद, इस बीच Google खाता किसी दूसरे खाते से जोड़ दिया गया।'
        '410':
          $ref: '#/components/responses/Gone'
          description: '`sso_expired`: केवल Google जुड़ाव के पासवर्ड वाले चरण के बाद, इस बीच खाता बदल गया; साइन-इन फिर से शुरू करें।'
        '413':
          $ref: '#/components/responses/PayloadTooLarge'
        '414':
          $ref: '#/components/responses/UriTooLong'
        '415':
          $ref: '#/components/responses/UnsupportedMediaType'
        '429':
          $ref: '#/components/responses/TooManyRequests'
          description: '`too_many_attempts`: खाते की विफलताओं के बाद की प्रतीक्षा, या पिछले 15 मिनट के उसके `AUTH_MFA_PER_ACCOUNT` कोड ख़त्म हो चुके हैं। `rate_limited`: `auth` लिमिट।'
        '500':
          $ref: '#/components/responses/InternalError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
          description: '`retryAfter: 1` के साथ `server_busy`: डेटाबेस लॉक रहा (Google जुड़ाव के चरण के बाद, कुछ भी नहीं जोड़ा गया: साइन-इन फिर से शुरू करें)। `timeout`। सर्वर के काफ़ी पीछे चलने पर (परिचय देखें), `retryAfter` 2 से 5 के साथ `server_busy` भी: अनुरोध प्रोसेस नहीं किया गया।'
  /auth/logout:
    post:
      operationId: logOut
      tags:
        - sessions
      summary: इस सेशन से साइन आउट करें
      description: |-
        इस्तेमाल किए गए टोकन का सेशन निरस्त करता है। उससे खोला गया WebSocket बंद कर दिया जाता है। कोई बॉडी नहीं (या `{}`)।

        **लिमिट:** `sessions`।
      security:
        - bearerAuth: []
      responses:
        '200':
          description: साइन आउट हो गया।
          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`: `{}` के अलावा कोई और बॉडी; `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`: `sessions` लिमिट या खाते का बजट।'
        '500':
          $ref: '#/components/responses/InternalError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
          description: '`server_busy` (डेटाबेस लॉक रहा), `timeout`। सर्वर के काफ़ी पीछे चलने पर (परिचय देखें), `retryAfter` 2 से 5 के साथ `server_busy` भी: अनुरोध प्रोसेस नहीं किया गया।'
  /auth/logout-all:
    post:
      operationId: logOutEverywhere
      tags:
        - sessions
      summary: हर सेशन से साइन आउट करें
      description: |-
        खाते का हर सेशन निरस्त करता है, यह सेशन भी। उनसे खोले गए WebSocket बंद कर दिए जाते हैं। कोई बॉडी नहीं (या `{}`)।

        **लिमिट:** `sessions`।
      security:
        - bearerAuth: []
      responses:
        '200':
          description: हर सेशन से साइन आउट हो गया।
          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`: `{}` के अलावा कोई और बॉडी; `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`: `sessions` लिमिट या खाते का बजट।'
        '500':
          $ref: '#/components/responses/InternalError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
          description: '`server_busy` (डेटाबेस लॉक रहा), `timeout`। सर्वर के काफ़ी पीछे चलने पर (परिचय देखें), `retryAfter` 2 से 5 के साथ `server_busy` भी: अनुरोध प्रोसेस नहीं किया गया।'
  /auth/verify-email/resend:
    post:
      operationId: resendVerificationEmail
      tags:
        - auth
      summary: पुष्टि लिंक दोबारा भेजें
      description: |-
        ईमेल पुष्टि का लिंक दोबारा भेजता है। पता चाहे जो हो, जवाब 202 `accepted` होता है, ताकि इससे कभी यह पता न चले कि कोई खाता या साइन-अप उस पते का इस्तेमाल करता है या नहीं।

        हर पते के लिए अनुरोध हर 5 मिनट में अधिकतम एक बार असर करता है। उस पते वाले प्रतीक्षारत साइन-अप को फिर से पूरे 24 घंटे मिल जाते हैं, चाहे कोई दूसरा खाता उस पते का इस्तेमाल करता हो या नहीं, ताकि दोनों ही मामलों में उसका उपयोगकर्ता नाम उतनी ही देर रुका रहे। लिंक केवल दो स्थितियों में भेजा जाता है: उस साइन-अप के लिए, जब उस पते का कोई खाता न हो (24 घंटे तक मान्य नया लिंक पिछले लिंक की जगह ले लेता है), या उस पते वाले किसी सक्रिय, अपुष्ट खाते के लिए।

        **लिमिट:** `auth`, फिर `auth_mail` (प्रति क्लाइंट प्रति घंटा 10)।
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/EmailRequest'
            example:
              email: alice@example.org
      responses:
        '202':
          description: स्वीकार किया गया (पता चाहे जो हो)।
          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`: `auth` या `auth_mail` लिमिट (पता चाहे जो हो)।'
        '500':
          $ref: '#/components/responses/InternalError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
          description: '`retryAfter: 1` के साथ `server_busy`: खाता खोजते समय या किसी अपुष्ट खाते का लिंक सहेजते समय डेटाबेस लॉक रहा (प्रतीक्षारत साइन-अप का नवीनीकरण यथासंभव किया जाता है और उससे अनुरोध कभी विफल नहीं होता); पते के 5 मिनट फिर भी शुरू हो जाते हैं, इसलिए उनके भीतर आया नया अनुरोध कुछ नहीं करता। `timeout`। सर्वर के काफ़ी पीछे चलने पर (परिचय देखें), `retryAfter` 2 से 5 के साथ `server_busy` भी: अनुरोध प्रोसेस नहीं किया गया।'
  /auth/password/forgot:
    post:
      operationId: requestPasswordReset
      tags:
        - auth
      summary: पासवर्ड रीसेट लिंक भेजें
      description: |-
        एक घंटे तक मान्य पासवर्ड रीसेट लिंक भेजता है, जो `/reset-password` पेज खोलता है। पता चाहे जो हो, जवाब 202 `accepted` होता है। लिंक केवल सक्रिय खाते को जाता है, हर पते पर हर 5 मिनट में अधिकतम एक बार। केवल Google वाला खाता इसी तरह अपना पहला पासवर्ड सेट करता है।

        पासवर्ड रिकवरी की लिमिट API में सबसे सख़्त हैं, और सभी पूरे सर्वर के लिए गिनी जाती हैं: प्रति क्लाइंट (एक IPv4 पता या एक IPv6 /64) प्रति घंटा 3 अनुरोध (`AUTH_FORGOT_PER_HOUR`) और प्रति 24 घंटे 10 (`AUTH_FORGOT_PER_DAY`), प्रति IPv6 /48 इन संख्याओं का 3 गुना, और इनके ऊपर `auth` लिमिट (प्रति 10 मिनट 20) और हर पते पर हर 5 मिनट में एक मेल। न 202 से पता चलता है, न 429 से, कि कोई खाता उस पते का इस्तेमाल करता है या नहीं। नया पासवर्ड सेट करने की अपनी अलग लिमिट है (`auth_reset`)।

        **लिमिट:** `auth`, `auth_forgot`, फिर `auth_forgot_day`।
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/EmailRequest'
            example:
              email: alice@example.org
      responses:
        '202':
          description: स्वीकार किया गया (पता चाहे जो हो)।
          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`: `auth`, `auth_forgot` या `auth_forgot_day` लिमिट (पता चाहे जो हो)।'
        '500':
          $ref: '#/components/responses/InternalError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
          description: '`retryAfter: 1` के साथ `server_busy`: खाता खोजते समय या लिंक सहेजते समय डेटाबेस लॉक रहा; पते के 5 मिनट फिर भी शुरू हो जाते हैं, इसलिए उनके भीतर आया नया अनुरोध कोई लिंक नहीं भेजता। `timeout`। सर्वर के काफ़ी पीछे चलने पर (परिचय देखें), `retryAfter` 2 से 5 के साथ `server_busy` भी: अनुरोध प्रोसेस नहीं किया गया।'
  /auth/password/reset:
    post:
      operationId: resetPassword
      tags:
        - auth
      summary: रीसेट लिंक के टोकन से नया पासवर्ड सेट करें
      description: |-
        रीसेट लिंक के टोकन से नया पासवर्ड सेट करता है (`/reset-password` पेज भी यही करता है)। नया पासवर्ड रजिस्ट्रेशन के नियमों का पालन करता है।

        रीसेट के ये असर होते हैं, एक ही ट्रांज़ैक्शन में (या तो सभी, या कोई नहीं): यह हर सेशन निरस्त करता है और लंबित ईमेल बदलाव को रद्द करता है; खाते के दूसरे रीसेट लिंक काम करना बंद कर देते हैं; पते को पुष्ट माना जाता है (लिंक ने यह साबित कर दिया); मालिक को एक मेल मिलता है। दो-चरणीय सत्यापन को छुआ नहीं जाता। पासवर्ड हैश कतार की त्रुटि, 503 या 500 के बाद, कुछ नहीं बदला: पुराना पासवर्ड और सेशन बने रहते हैं, और लिंक मान्य रहता है।

        **लिमिट:** `auth`, फिर `auth_reset` (प्रति क्लाइंट प्रति घंटा 10, प्रति IPv6 /48 30, पेज के साथ साझा: हर प्रयास एक पासवर्ड हैश करता है)।
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PasswordResetRequest'
            example:
              token: A0ATfmXCDKFC8UkSxLJCD85K7au5nMIrZ2P3K-1VWQg
              newPassword: a much better passphrase
      responses:
        '200':
          description: पासवर्ड बदल दिया गया है और हर सेशन से साइन आउट हो गया है।
          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`: लिंक अमान्य है, इस्तेमाल हो चुका है या उसकी अवधि ख़त्म हो चुकी है, या वह ऐसे पते पर भेजा गया था जो अब खाते का नहीं है। `weak_password` (`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`: `auth` या `auth_reset` लिमिट, या पासवर्ड हैश कतार।'
        '500':
          $ref: '#/components/responses/InternalError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
          description: '`server_busy`: पासवर्ड हैश कतार, या डेटाबेस लॉक रहा (`retryAfter: 1`, कुछ नहीं बदला)। `timeout`। सर्वर के काफ़ी पीछे चलने पर (परिचय देखें), `retryAfter` 2 से 5 के साथ `server_busy` भी: अनुरोध प्रोसेस नहीं किया गया।'
  /auth/sso/google/start:
    post:
      operationId: startGoogleSignIn
      tags:
        - google-sign-in
      summary: Google साइन-इन शुरू करें
      description: |-
        PKCE चैलेंज और `127.0.0.1` पर गेम के लिसनर के पोर्ट के लिए Google साइन-इन का एक प्रयास शुरू करता है। जवाब में सिस्टम ब्राउज़र में खोलने के लिए Google URL और प्रयास का `state` होता है; प्रयास 10 मिनट तक मान्य रहता है।

        `authUrl` में `client_id`, `redirect_uri` (`redirectPort` और सर्वर के ओरिजिन टैग से बना), `response_type=code`, `scope=openid email profile`, `state`, `nonce`, `code_challenge_method=S256` के साथ `code_challenge` (Google के लिए सर्वर के अपने वेरिफ़ायर का S256), और `prompt=select_account` होते हैं। गेम इसे खोलने से पहले इसकी जाँच करता है (अनुभाग का विवरण देखें)।

        **लिमिट:** `sso_start`।
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SsoStartRequest'
            example:
              codeChallenge: 6e7diXEYxG7OTYw7STfNOltEeLxAilPthC_txzaE0xA
              redirectPort: 51234
      responses:
        '200':
          description: प्रयास।
          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`: इस सर्वर पर Google साइन-इन उपलब्ध नहीं है।'
        '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`: `sso_start` लिमिट।'
        '500':
          $ref: '#/components/responses/InternalError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
          description: '`server_busy` (डेटाबेस लॉक रहा), `timeout`। सर्वर के काफ़ी पीछे चलने पर (परिचय देखें), `retryAfter` 2 से 5 के साथ `server_busy` भी: अनुरोध प्रोसेस नहीं किया गया।'
  /auth/sso/google/web/start:
    post:
      operationId: startGoogleWebSignIn
      tags:
        - google-sign-in
      summary: वेबसाइट पर Google साइन-इन शुरू करें
      description: |-
        किसी वेबसाइट के पेज के PKCE चैलेंज के लिए उस वेबसाइट का Google साइन-इन प्रयास शुरू करता है। जवाब वही होता है जो गेम के लिए: जिस Google URL पर नेविगेट करना है, और प्रयास का `state`; प्रयास 10 मिनट तक मान्य रहता है। तब उपलब्ध है जब `GET /info` में `sso.googleWeb: true` हो।

        `authUrl` में वेबसाइट का `client_id` और `redirect_uri=GOOGLE_WEB_REDIRECT_URI` होते हैं, और बाकी पैरामीटर वही होते हैं जो गेम के लिए। पेज उस पर नेविगेट करने से पहले उसकी जाँच करता है (अनुभाग के विवरण में **वेबसाइट पर** देखें)।

        **लिमिट:** `sso_start`, गेम के start के साथ साझा।
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SsoWebStartRequest'
            example:
              codeChallenge: 6e7diXEYxG7OTYw7STfNOltEeLxAilPthC_txzaE0xA
      responses:
        '200':
          description: प्रयास।
          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`: `codeChallenge` मौजूद नहीं है या ग़लत रूप में है, या कोई दूसरा फ़ील्ड भेजा गया (`redirectPort` भी)।'
        '404':
          $ref: '#/components/responses/NotFound'
          description: '`sso_disabled`: इस सर्वर पर Google साइन-इन उपलब्ध नहीं है, या वेबसाइटों के लिए उपलब्ध नहीं है।'
        '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`: `sso_start` लिमिट (गेम के start के साथ साझा)।'
        '500':
          $ref: '#/components/responses/InternalError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
          description: '`server_busy` (डेटाबेस लॉक रहा), `timeout`। सर्वर के काफ़ी पीछे चलने पर (परिचय देखें), `retryAfter` 2 से 5 के साथ `server_busy` भी: अनुरोध प्रोसेस नहीं किया गया।'
  /auth/sso/google/finish:
    post:
      operationId: finishGoogleSignIn
      tags:
        - google-sign-in
      summary: Google का जवाब सर्वर को सौंपें
      description: |-
        Google ने गेम के लिसनर या वेबसाइट के पेज को जो `code` और `state` भेजे, उन्हें प्रयास की आईडी और PKCE वेरिफ़ायर के साथ सर्वर को सौंपता है। वेरिफ़ायर के बिना प्रयास की आईडी बेकार है, और सर्वर के अपने PKCE वेरिफ़ायर और क्लाइंट सीक्रेट के बिना कोड बेकार है।

        सर्वर प्रयास की जाँच करता है (अज्ञात, इस्तेमाल हो चुका या अवधि ख़त्म: 410), फिर वेरिफ़ायर की (ग़लत वेरिफ़ायर होने पर प्रयास इस्तेमाल योग्य बना रहता है), फिर प्रयास को एक बार इस्तेमाल करता है, `state` और `iss` जाँचता है, Google के साथ कोड एक्सचेंज करता है और ID टोकन सत्यापित करता है। जवाब (200) इनमें से एक होता है:

        - `{ token, expiresAt, user }`: जुड़े हुए खाते में साइन इन हो गया;
        - `{ mfaRequired, mfaToken, expiresIn }`: `POST /auth/login/mfa` से आगे बढ़ें;
        - `{ needsUsername, ssoTicket, suggestedUsername }`: नया खाता; 10 मिनट के भीतर `POST /auth/sso/complete` से आगे बढ़ें। `suggestedUsername` Google नाम या पते से आता है, और कुछ भी उपयुक्त न होने पर `""` होता है;
        - `{ needsPassword, linkTicket, username, expiresIn }`: उस पते वाले खाते का पासवर्ड है; `POST /auth/sso/google/link` से आगे बढ़ें। अभी कुछ भी जोड़ा नहीं गया है।

        **लिमिट:** `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: एक सेशन, दूसरा चरण, या पहली बार के Google साइन-इन का अगला चरण।
          headers:
            Cache-Control:
              $ref: '#/components/headers/CacheControl'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SsoFinishAnswer'
              examples:
                session:
                  summary: जुड़े हुए खाते में साइन इन हो गया
                  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: दो-चरणीय सत्यापन चालू है
                  value:
                    mfaRequired: true
                    mfaToken: mfa_m4wXAVhYCMWG0PX7MLcIpBeccBFg0oxPvoRujzGWjO8
                    expiresIn: 300
                needsUsername:
                  summary: नया खिलाड़ी
                  value:
                    needsUsername: true
                    ssoTicket: sso_gYIKKhwnz1AqWfFxOC3R-ZvCACw06q74fEMtGEOJQWU
                    suggestedUsername: alice
                needsPassword:
                  summary: पासवर्ड वाला कोई खाता इस पते का इस्तेमाल करता है
                  value:
                    needsPassword: true
                    linkTicket: sso_ZfEI6iZjePTHbnbevupaD9O7GjBCzcL-GJu2XGOdFGc
                    username: alice
                    expiresIn: 600
        '400':
          $ref: '#/components/responses/BadRequest'
        '403':
          $ref: '#/components/responses/Forbidden'
          description: '`invalid_verifier`: वेरिफ़ायर प्रयास के चैलेंज से मेल नहीं खाता (प्रयास इस्तेमाल योग्य बना रहता है)। `sso_email_unverified`: Google ने पते की पुष्टि नहीं की है। `registration_closed`। `account_disabled`। केवल जुड़े हुए खाते के लिए: `banned` (`until` के साथ), `email_unverified`।'
        '404':
          $ref: '#/components/responses/NotFound'
          description: '`sso_disabled`: इस सर्वर पर Google साइन-इन उपलब्ध नहीं है।'
        '408':
          $ref: '#/components/responses/RequestTimeout'
        '409':
          $ref: '#/components/responses/Conflict'
          description: '`sso_account_exists`: बिना पासवर्ड वाला कोई सक्रिय खाता इस पते का इस्तेमाल करता है (उसका नाम नहीं बताया जाता)।'
        '410':
          $ref: '#/components/responses/Gone'
          description: '`sso_expired`: अज्ञात प्रयास, इस्तेमाल हो चुका प्रयास, या ऐसा प्रयास जिसकी अवधि ख़त्म हो चुकी है।'
        '413':
          $ref: '#/components/responses/PayloadTooLarge'
        '414':
          $ref: '#/components/responses/UriTooLong'
        '415':
          $ref: '#/components/responses/UnsupportedMediaType'
        '429':
          $ref: '#/components/responses/TooManyRequests'
          description: '`rate_limited`: `sso_finish` लिमिट।'
        '500':
          $ref: '#/components/responses/InternalError'
        '502':
          $ref: '#/components/responses/BadGateway'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
          description: '`server_busy` (डेटाबेस लॉक रहा), `timeout`। सर्वर के काफ़ी पीछे चलने पर (परिचय देखें), `retryAfter` 2 से 5 के साथ `server_busy` भी: अनुरोध प्रोसेस नहीं किया गया।'
  /auth/sso/google/link:
    post:
      operationId: linkGoogleAccount
      tags:
        - google-sign-in
      summary: पासवर्ड के ज़रिए Google को मौजूदा खाते से जोड़ें
      description: |-
        `finish` के `needsPassword` जवाब देने के बाद: गेम में टाइप किए गए मौजूदा खाते के पासवर्ड से Google को उस खाते से जोड़ता है। जवाब (200) एक सेशन होता है (जुड़ाव सहेज लिया जाता है और साइन-इन हो जाता है), या, दो-चरणीय सत्यापन चालू होने पर, `{ mfaRequired, mfaToken, expiresIn }`: `POST /auth/login/mfa` से आगे बढ़ें, और जुड़ाव तभी सहेजा जाता है जब वहाँ कोई कोड सही निकले।

        ग़लत पासवर्ड होने पर टिकट इस्तेमाल योग्य बना रहता है, कुल 5 कोशिशों तक। पासवर्ड जाँचे जाने से ठीक पहले एक कोशिश ली जाती है: 429 `too_many_attempts` या 428 `pow_required` कोई कोशिश नहीं लेते, जबकि पासवर्ड हैश कतार की अस्वीकृति (503 `server_busy`, 429 `rate_limited`) एक कोशिश ले चुकी होती है और खाते की विफलता के रूप में गिनी जाती है। विफलताओं के बाद की प्रतीक्षा के लिए वही काउंटर इस्तेमाल होता है जो `POST /auth/login` के लिए।

        **लिमिट:** `auth` (उसकी IPv6 /48 गिनती के साथ)। **प्रूफ़ ऑफ़ वर्क:** `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: एक सेशन, या साइन-इन का दूसरा चरण।
          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`: ग़लत पासवर्ड; टिकट बना रहता है, कुल 5 कोशिशों तक।'
        '403':
          $ref: '#/components/responses/Forbidden'
          description: '`banned` (`until` के साथ), केवल सही पासवर्ड के बाद।'
        '404':
          $ref: '#/components/responses/NotFound'
          description: '`sso_disabled`: इस सर्वर पर Google साइन-इन उपलब्ध नहीं है।'
        '408':
          $ref: '#/components/responses/RequestTimeout'
        '409':
          $ref: '#/components/responses/Conflict'
          description: '`sso_already_linked`: इस बीच Google खाता किसी दूसरे खाते से जोड़ दिया गया।'
        '410':
          $ref: '#/components/responses/Gone'
          description: '`sso_expired`: टिकट अज्ञात है, इस्तेमाल हो चुका है या उसकी अवधि ख़त्म हो चुकी है; पाँचवाँ ग़लत पासवर्ड; ऐसा खाता जिसकी स्थिति या पता `finish` के बाद बदल गया; या ऐसा खाता जिसकी स्थिति, पता, पासवर्ड या दो-चरणीय सत्यापन तब बदल गया जब जुड़ाव सहेजा जा रहा था। साइन-इन फिर से शुरू करें।'
        '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` (खाते की विफलताओं के बाद की प्रतीक्षा), या `rate_limited` (`auth` लिमिट, पासवर्ड हैश कतार)।'
        '500':
          $ref: '#/components/responses/InternalError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
          description: '`server_busy`: पासवर्ड हैश कतार, या डेटाबेस लॉक रहा (`retryAfter: 1`, कुछ भी नहीं जोड़ा गया)। `timeout`। सर्वर के काफ़ी पीछे चलने पर (परिचय देखें), `retryAfter` 2 से 5 के साथ `server_busy` भी: अनुरोध प्रोसेस नहीं किया गया।'
  /auth/sso/complete:
    post:
      operationId: completeGoogleSignUp
      tags:
        - google-sign-in
      summary: पहले Google साइन-इन का खाता बनाएँ
      description: |-
        `finish` के `needsUsername` जवाब देने के बाद: चुने गए उपयोगकर्ता नाम से खाता बनाता है (रजिस्ट्रेशन के नियम लागू होते हैं) और उसे साइन इन करता है। खाते का कोई पासवर्ड नहीं होता: `hasPassword` `false` होता है, और "पासवर्ड याद नहीं?" (`POST /auth/password/forgot`) से उसे पासवर्ड मिलता है।

        **लिमिट:** `auth` (IPv6 /48 की गिनती सहित)।
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SsoCompleteRequest'
            example:
              ssoTicket: sso_gYIKKhwnz1AqWfFxOC3R-ZvCACw06q74fEMtGEOJQWU
              username: alice
              clientLabel: Scacelith 1.4 (Windows)
      responses:
        '200':
          description: खाता बन गया है और साइन इन हो गया है।
          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`: इस सर्वर पर Google साइन-इन उपलब्ध नहीं है।'
        '408':
          $ref: '#/components/responses/RequestTimeout'
        '409':
          $ref: '#/components/responses/Conflict'
          description: '`username_taken` (किसी खाते के पास यह उपयोगकर्ता नाम है, या किसी दूसरे पते का प्रतीक्षारत साइन-अप उसे रोके हुए है), `sso_already_linked`, `email_taken`।'
        '410':
          $ref: '#/components/responses/Gone'
          description: '`sso_expired`: टिकट अज्ञात है, इस्तेमाल हो चुका है या उसकी अवधि ख़त्म हो चुकी है।'
        '413':
          $ref: '#/components/responses/PayloadTooLarge'
        '414':
          $ref: '#/components/responses/UriTooLong'
        '415':
          $ref: '#/components/responses/UnsupportedMediaType'
        '429':
          $ref: '#/components/responses/TooManyRequests'
          description: '`rate_limited`: `auth` लिमिट।'
        '500':
          $ref: '#/components/responses/InternalError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
          description: '`server_busy` (डेटाबेस लॉक रहा), `timeout`। सर्वर के काफ़ी पीछे चलने पर (परिचय देखें), `retryAfter` 2 से 5 के साथ `server_busy` भी: अनुरोध प्रोसेस नहीं किया गया।'
  /auth/sessions:
    get:
      operationId: listSessions
      tags:
        - sessions
      summary: साइन-इन वाले डिवाइस की सूची देखें
      description: |-
        खाते के सक्रिय सेशन (साइन-इन वाले डिवाइस), सबसे हाल में इस्तेमाल हुआ सबसे पहले। `current` उस सेशन को चिह्नित करता है जिससे अनुरोध किया जा रहा है। `lastSeenAt` अधिक से अधिक हर 5 मिनट में अपडेट होता है (सर्वर के काफ़ी पीछे चलने पर इससे भी देर से); `expiresAt` निश्चित अंत है, और निष्क्रियता सीमा सेशन को उससे पहले भी ख़त्म कर सकती है।

        **लिमिट:** `sessions`।
      security:
        - bearerAuth: []
      responses:
        '200':
          description: सक्रिय सेशन।
          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`: `sessions` लिमिट या खाते का बजट।'
        '500':
          $ref: '#/components/responses/InternalError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
          description: '`server_busy` (डेटाबेस लॉक रहा), `timeout`। सर्वर के काफ़ी पीछे चलने पर (परिचय देखें), `retryAfter` 2 से 5 के साथ `server_busy` भी: अनुरोध प्रोसेस नहीं किया गया।'
  /auth/sessions/{id}:
    delete:
      operationId: revokeSession
      tags:
        - sessions
      summary: एक डिवाइस को साइन आउट करें
      description: |-
        खाते के एक सेशन को साइन आउट करता है; मौजूदा सेशन को भी साइन आउट किया जा सकता है। उस सेशन से खोला गया WebSocket बंद कर दिया जाता है। कोई बॉडी नहीं (या `{}`)।

        **लिमिट:** `sessions`।
      security:
        - bearerAuth: []
      parameters:
        - $ref: '#/components/parameters/SessionId'
      responses:
        '200':
          description: सेशन साइन आउट हो गया है।
          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`: `{}` के अलावा कोई और बॉडी, या ऐसा `id` जो मान्य URL एन्कोडिंग नहीं है; `invalid_json`।'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
          description: '`not_found`: इस खाते पर इस आईडी वाला कोई सक्रिय सेशन नहीं है।'
        '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`: `sessions` लिमिट या खाते का बजट।'
        '500':
          $ref: '#/components/responses/InternalError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
          description: '`server_busy` (डेटाबेस लॉक रहा), `timeout`। सर्वर के काफ़ी पीछे चलने पर (परिचय देखें), `retryAfter` 2 से 5 के साथ `server_busy` भी: अनुरोध प्रोसेस नहीं किया गया।'
  /account/me:
    get:
      operationId: getAccount
      tags:
        - account
      summary: खाता, उसकी रेटिंग और सक्रिय दंड पाएँ
      description: |-
        खाता, जैसा उसका खिलाड़ी उसे देखता है: खाते का व्यू (हर साइन-इन जवाब का `user` भी यही है), हर उस श्रेणी का एक रेटिंग रिकॉर्ड जिसमें खिलाड़ी ने रेटेड बाज़ियाँ खेली हैं, सक्रिय दंड और निलंबन। एंटी-चीट का इंटीग्रिटी लेवल कभी नहीं दिखाया जाता।

        **लिमिट:** `account`।
      security:
        - bearerAuth: []
      responses:
        '200':
          description: खाता।
          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`, या `invalid_token` (खाता हटा दिए जाने पर भी)।'
        '414':
          $ref: '#/components/responses/UriTooLong'
        '429':
          $ref: '#/components/responses/TooManyRequests'
          description: '`rate_limited`: `account` लिमिट या खाते का बजट।'
        '500':
          $ref: '#/components/responses/InternalError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
          description: '`server_busy` (डेटाबेस लॉक रहा), `timeout`। सर्वर के काफ़ी पीछे चलने पर (परिचय देखें), `retryAfter` 2 से 5 के साथ `server_busy` भी: अनुरोध प्रोसेस नहीं किया गया।'
  /account/preferences:
    put:
      operationId: updatePreferences
      tags:
        - account
      summary: सीधी चुनौतियाँ स्वीकार या अस्वीकार करें
      description: |-
        तय करता है कि दूसरे खिलाड़ी इस खिलाड़ी को नाम से चुनौती दे सकते हैं या नहीं। `none` होने पर सीधी चुनौतियाँ अस्वीकार कर दी जाती हैं: चुनौती देने वाले को बताया जाता है कि खिलाड़ी उपलब्ध नहीं है। कोई री-ऑथेंटिकेशन नहीं।

        **लिमिट:** `account`।
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/Preferences'
            example:
              acceptChallenges: none
      responses:
        '200':
          description: अब लागू वरीयताएँ।
          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`: `account` लिमिट या खाते का बजट।'
        '500':
          $ref: '#/components/responses/InternalError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
          description: '`server_busy` (डेटाबेस लॉक रहा), `timeout`। सर्वर के काफ़ी पीछे चलने पर (परिचय देखें), `retryAfter` 2 से 5 के साथ `server_busy` भी: अनुरोध प्रोसेस नहीं किया गया।'
  /account/password:
    post:
      operationId: changePassword
      tags:
        - account
      summary: पासवर्ड बदलें
      description: |-
        पासवर्ड बदलता है। मौजूदा पासवर्ड ज़रूरी है, पर दूसरा फ़ैक्टर नहीं, दो-चरणीय सत्यापन चालू होने पर भी नहीं। नया पासवर्ड रजिस्ट्रेशन के नियमों का पालन करता है (मौजूदा पासवर्ड के बाद जाँचा जाता है)।

        इस बदलाव के ये असर होते हैं, एक ही ट्रांज़ैक्शन में (या तो सभी, या कोई नहीं): यह बाक़ी सभी सेशन निरस्त करता है (यह सेशन साइन इन रहता है), लंबित ईमेल बदलाव रद्द करता है, खाते के पासवर्ड रीसेट लिंक बेकार कर देता है, और खाते के मालिक को ईमेल भेजता है।

        **री-ऑथेंटिकेशन:** केवल पासवर्ड। **लिमिट:** `reauth`, फिर `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: पासवर्ड बदल दिया गया है।
          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` (केवल Google वाला खाता), `weak_password` (`reason` के साथ), `invalid_request`, `invalid_json`।'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
          description: '`invalid_password`: ग़लत मौजूदा पासवर्ड, या ऐसा पासवर्ड रीसेट या बदलाव जो अनुरोध की जाँच के दौरान हो गया।'
        '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` (खाते के विफल री-ऑथेंटिकेशन), या `rate_limited` (`reauth` या `reauth_user` लिमिट, खाते का बजट, पासवर्ड हैश कतार)।'
        '500':
          $ref: '#/components/responses/InternalError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
          description: '`server_busy`: पासवर्ड हैश कतार, या डेटाबेस लॉक रहा (`retryAfter: 1`, कुछ नहीं बदला)। `timeout`। सर्वर के काफ़ी पीछे चलने पर (परिचय देखें), `retryAfter` 2 से 5 के साथ `server_busy` भी: अनुरोध प्रोसेस नहीं किया गया।'
  /account/mfa/totp/setup:
    post:
      operationId: startTotpSetup
      tags:
        - two-step-verification
      summary: दो-चरणीय सत्यापन चालू करना शुरू करें
      description: |-
        एक नया लंबित ऑथेंटिकेटर सीक्रेट सहेजता है, जो पहले के किसी भी लंबित सीक्रेट की जगह लेता है, और उसे ऑथेंटिकेटर ऐप के लिए लौटाता है (टेक्स्ट के रूप में, और QR कोड के रूप में दिखाने के लिए `otpauth://` URI के रूप में)। दो-चरणीय सत्यापन अभी चालू नहीं है: `POST /account/mfa/totp/enable` इस सीक्रेट के एक कोड से उसे चालू करता है।

        **री-ऑथेंटिकेशन:** केवल पासवर्ड। **लिमिट:** `reauth`, फिर `reauth_user`।
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PasswordOnlyRequest'
            example:
              password: correct horse battery
      responses:
        '200':
          description: लंबित सीक्रेट।
          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` (केवल 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` (पासवर्ड से पहले जाँचा जाता है)।'
        '413':
          $ref: '#/components/responses/PayloadTooLarge'
        '414':
          $ref: '#/components/responses/UriTooLong'
        '415':
          $ref: '#/components/responses/UnsupportedMediaType'
        '429':
          $ref: '#/components/responses/TooManyRequests'
          description: '`too_many_attempts` (खाते के विफल री-ऑथेंटिकेशन), या `rate_limited` (`reauth` या `reauth_user` लिमिट, खाते का बजट, पासवर्ड हैश कतार)।'
        '500':
          $ref: '#/components/responses/InternalError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
          description: '`server_busy` (पासवर्ड हैश कतार, या डेटाबेस लॉक रहा), `timeout`। सर्वर के काफ़ी पीछे चलने पर (परिचय देखें), `retryAfter` 2 से 5 के साथ `server_busy` भी: अनुरोध प्रोसेस नहीं किया गया।'
  /account/mfa/totp/enable:
    post:
      operationId: enableTotp
      tags:
        - two-step-verification
      summary: दो-चरणीय सत्यापन चालू करना पूरा करें और रिकवरी कोड पाएँ
      description: |-
        लंबित सीक्रेट के एक कोड (ठीक 6 अंक) से दो-चरणीय सत्यापन चालू करता है। यहाँ पासवर्ड नहीं माँगा जाता; वह सेटअप के समय दिया जा चुका है। जवाब में 10 रिकवरी कोड होते हैं, जो केवल इसी एक बार दिखाए जाते हैं।

        **लिमिट:** `reauth`, फिर `reauth_user`; ग़लत कोड खाते की री-ऑथेंटिकेशन विफलताओं में गिना जाता है।
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/TotpEnableRequest'
            example:
              code: '123456'
      responses:
        '200':
          description: दो-चरणीय सत्यापन चालू है।
          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` ठीक 6 अंकों का नहीं है, या बॉडी स्कीमा का उल्लंघन करती है; `invalid_json`।'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
          description: '`invalid_code`: ग़लत कोड (डिवाइस का समय जाँचें)।'
        '408':
          $ref: '#/components/responses/RequestTimeout'
        '409':
          $ref: '#/components/responses/Conflict'
          description: '`mfa_already_enabled`; `mfa_setup_required`: कोई लंबित सीक्रेट नहीं (पहले `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` (खाते के विफल री-ऑथेंटिकेशन), या `rate_limited` (`reauth` या `reauth_user` लिमिट, खाते का बजट)।'
        '500':
          $ref: '#/components/responses/InternalError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
          description: '`server_busy` (डेटाबेस लॉक रहा), `timeout`। सर्वर के काफ़ी पीछे चलने पर (परिचय देखें), `retryAfter` 2 से 5 के साथ `server_busy` भी: अनुरोध प्रोसेस नहीं किया गया।'
  /account/mfa/totp/disable:
    post:
      operationId: disableTotp
      tags:
        - two-step-verification
      summary: दो-चरणीय सत्यापन बंद करें
      description: |-
        दो-चरणीय सत्यापन बंद करता है। सीक्रेट और रिकवरी कोड हटा दिए जाते हैं, और खाते के मालिक को ईमेल भेजा जाता है।

        **री-ऑथेंटिकेशन:** पासवर्ड, और ऑथेंटिकेटर कोड या रिकवरी कोड (`code` या `recoveryCode` में से एक ज़रूरी है)। **लिमिट:** `reauth`, फिर `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: दो-चरणीय सत्यापन बंद है।
          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` (केवल Google वाला खाता), `invalid_request`, `invalid_json`।'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
          description: '`mfa_code_required` (न `code` है, न `recoveryCode`; पासवर्ड से पहले जाँचा जाता है), `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` (विफल री-ऑथेंटिकेशन, या इस खाते पर बहुत ज़्यादा कोड आज़माए गए), या `rate_limited` (`reauth` या `reauth_user` लिमिट, खाते का बजट, पासवर्ड हैश कतार)।'
        '500':
          $ref: '#/components/responses/InternalError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
          description: '`server_busy` (पासवर्ड हैश कतार, या डेटाबेस लॉक रहा), `timeout`। सर्वर के काफ़ी पीछे चलने पर (परिचय देखें), `retryAfter` 2 से 5 के साथ `server_busy` भी: अनुरोध प्रोसेस नहीं किया गया।'
  /account/mfa/recovery-codes:
    post:
      operationId: regenerateRecoveryCodes
      tags:
        - two-step-verification
      summary: रिकवरी कोड बदलें
      description: |-
        रिकवरी कोड की जगह 10 नए कोड बनाता है; पुराने कोड काम करना बंद कर देते हैं।

        **री-ऑथेंटिकेशन:** पासवर्ड, और `code` में ऑथेंटिकेटर कोड (रिकवरी कोड 403 `invalid_code` के साथ अस्वीकार किया जाता है)। **लिमिट:** `reauth`, फिर `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: नए रिकवरी कोड, जो केवल इसी एक बार दिखाए जाते हैं।
          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` (केवल 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'
        '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` (विफल री-ऑथेंटिकेशन, या इस खाते पर बहुत ज़्यादा कोड आज़माए गए), या `rate_limited` (`reauth` या `reauth_user` लिमिट, खाते का बजट, पासवर्ड हैश कतार)।'
        '500':
          $ref: '#/components/responses/InternalError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
          description: '`server_busy` (पासवर्ड हैश कतार, या डेटाबेस लॉक रहा), `timeout`। सर्वर के काफ़ी पीछे चलने पर (परिचय देखें), `retryAfter` 2 से 5 के साथ `server_busy` भी: अनुरोध प्रोसेस नहीं किया गया।'
  /account/email:
    post:
      operationId: changeEmail
      tags:
        - email-change
      summary: ईमेल पता बदलें
      description: |-
        खाते का ईमेल पता बदलता है। `newEmail` के आगे-पीछे के ख़ाली स्थान हटाए जाते हैं और उसे छोटे अक्षरों में बदला जाता है, फिर उसे रजिस्ट्रेशन के पते की तरह जाँचा जाता है।

        **ईमेल पुष्टि के साथ** (डिफ़ॉल्ट): 202 `verification_sent`। 24 घंटे तक मान्य एक लिंक नए पते पर जाता है; वह `/confirm-email-change` खोलता है, और पता तभी बदलता है जब खिलाड़ी उस पेज का बटन दबाता है। तब तक `GET /account/me` नए पते को `pendingEmail` के रूप में दिखाता है। नया अनुरोध लंबित अनुरोध की जगह ले लेता है; पासवर्ड बदलने या रीसेट होने पर वह रद्द हो जाता है। किसी एक नए पते पर हर 5 मिनट में अधिक से अधिक एक लिंक जाता है, चाहे कोई भी माँगे (पहले से लंबित बदलाव के लिए किया गया अनुरोध पहले भेजा गया लिंक ही रखता है, जो मान्य रहता है); जवाब वही रहता है। मौजूदा पते को सूचना मिलती है कि आंशिक रूप से छिपाए गए एक पते (`a***@example.org`) पर बदलाव का अनुरोध किया गया है। जब कोई दूसरा खाता पहले से नया पता इस्तेमाल करता है, तब भी जवाब और `pendingEmail` वही रहते हैं: तब कोई लिंक नहीं भेजा जाता, इसलिए वह बदलाव कभी पूरा नहीं होता, और उसकी जगह उस पते के मालिक को सूचना मिलती है (प्रति घंटे अधिक से अधिक एक)।

        लिंक की पुष्टि होने पर पता बदल जाता है और पुष्ट माना जाता है, डिवाइस साइन इन रहते हैं, पहले भेजे गए लिंक (पुष्टि, पासवर्ड रीसेट, दूसरे बदलाव) काम करना बंद कर देते हैं, और पुराने पते को सूचना दी जाती है, जिसमें नया पता आंशिक रूप से छिपा होता है।

        **ईमेल पुष्टि के बिना** (`REQUIRE_EMAIL_VERIFICATION=false`): पता तुरंत बदल जाता है (200 `email_changed`), और पुराने पते को सूचना दी जाती है। अगर कोई दूसरा खाता वह पता इस्तेमाल करता है, तो जवाब 409 `email_taken` होता है, और उस पते के मालिक को सूचना मिलती है।

        **री-ऑथेंटिकेशन:** पासवर्ड, और दो-चरणीय सत्यापन होने पर ऑथेंटिकेटर कोड या रिकवरी कोड। `invalid_email` और `same_email` पासवर्ड से पहले जाँचे जाते हैं, इसलिए उनसे कोई विफलता नहीं गिनी जाती। **लिमिट:** `reauth`, फिर `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: पता तुरंत बदल गया (इस सर्वर पर ईमेल पुष्टि नहीं होती)।
          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: नया पता, जैसा सहेजा गया।
              example:
                status: email_changed
                email: alice.new@example.org
        '202':
          description: नए पते पर पुष्टि का लिंक भेजा गया (या पता किसी दूसरे खाते का है; जवाब यह नहीं बताता)।
          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` (खाते का मौजूदा पता), `password_not_set` (केवल 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'
        '409':
          $ref: '#/components/responses/Conflict'
          description: '`email_taken`: केवल ईमेल पुष्टि के बिना।'
        '413':
          $ref: '#/components/responses/PayloadTooLarge'
        '414':
          $ref: '#/components/responses/UriTooLong'
        '415':
          $ref: '#/components/responses/UnsupportedMediaType'
        '429':
          $ref: '#/components/responses/TooManyRequests'
          description: '`too_many_attempts` (विफल री-ऑथेंटिकेशन, या इस खाते पर बहुत ज़्यादा कोड आज़माए गए), या `rate_limited` (`reauth` या `reauth_user` लिमिट, खाते का बजट, पासवर्ड हैश कतार)।'
        '500':
          $ref: '#/components/responses/InternalError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
          description: '`server_busy`: पासवर्ड हैश कतार, या डेटाबेस लॉक रहा (`retryAfter: 1`: कुछ नहीं बदला, और वही अनुरोध फिर से भेजा जा सकता है)। `timeout`। सर्वर के काफ़ी पीछे चलने पर (परिचय देखें), `retryAfter` 2 से 5 के साथ `server_busy` भी: अनुरोध प्रोसेस नहीं किया गया।'
  /account/export:
    post:
      operationId: exportAccountData
      tags:
        - data-export
      summary: खाते का डेटा डाउनलोड करें
      description: |-
        सर्वर खाते के बारे में जो कुछ भी रखता है, वह सब, सहेजने के लिए एक JSON फ़ाइल के रूप में (`format` `scacelith-account-export`, `version` 1)। एक्सपोर्ट एक सुरक्षा घटना (`account_exported`) दर्ज करता है। दस्तावेज़ का `notes` ऐरे खिलाड़ी को सरल अंग्रेज़ी में बताता है कि क्या छोड़ा गया है।

        **एक्सपोर्ट में कभी नहीं:** पासवर्ड हैश, दो-चरणीय सत्यापन का सीक्रेट और रिकवरी कोड; कोई भी सेशन या लिंक टोकन, या उसका हैश; एंटी-चीट का डेटा (इंटीग्रिटी लेवल और स्कोर, विसंगतियाँ, बाज़ियों का विश्लेषण, किसी शिकायत का वज़न); दूसरे खिलाड़ियों द्वारा इस खिलाड़ी के बारे में की गई शिकायतें; मॉडरेटरों की पहचान; दूसरे खिलाड़ियों का निजी डेटा (प्रतिद्वंद्वी अपने सार्वजनिक नाम और रेटिंग से दिखते हैं, कुछ भी यह नहीं बताता कि किसी दूसरे खिलाड़ी को दंड मिला या नहीं, और ऐसा कोई IP पता शामिल नहीं होता जो किसी दूसरे व्यक्ति का हो सकता है)।

        **री-ऑथेंटिकेशन:** पासवर्ड, और दो-चरणीय सत्यापन होने पर ऑथेंटिकेटर कोड या रिकवरी कोड। **लिमिट:** `account_export` (प्रति खिलाड़ी प्रति घंटे 5, पूरे सर्वर के लिए; हर प्रयास गिना जाता है, विफल प्रयास भी; सबसे पहले जाँची जाती है), फिर `reauth` और `reauth_user`। **समय सीमा:** 60 सेकंड।
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ReauthCredentials'
            example:
              password: correct horse battery
              code: '123456'
      responses:
        '200':
          description: एक्सपोर्ट दस्तावेज़, अटैचमेंट के रूप में।
          headers:
            Cache-Control:
              $ref: '#/components/headers/CacheControl'
            Content-Disposition:
              description: '`attachment; filename="scacelith-account-<username>.json"`। उपयोगकर्ता नाम में अक्षरों, अंकों, `_`, `.` और `-` के अलावा बाक़ी वर्ण `_` बन जाते हैं।'
              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` (केवल 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` (विफल री-ऑथेंटिकेशन, या इस खाते पर बहुत ज़्यादा कोड आज़माए गए), या `rate_limited` (`account_export`, `reauth` या `reauth_user` लिमिट, खाते का बजट, पासवर्ड हैश कतार)।'
        '500':
          $ref: '#/components/responses/InternalError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
          description: '`busy` (डेटाबेस लॉक रहा, `Retry-After: 1` हेडर के साथ), `server_busy` (पासवर्ड हैश कतार), `timeout` (60 सेकंड)। सर्वर के काफ़ी पीछे चलने पर (परिचय देखें), `retryAfter` 2 से 5 के साथ `server_busy` भी: अनुरोध प्रोसेस नहीं किया गया।'
  /account/delete:
    post:
      operationId: deleteAccount
      tags:
        - account-deletion
      summary: खाता हटाएँ
      description: |-
        खाता हटाता है; इसे पूर्ववत नहीं किया जा सकता।

        - हर सेशन तुरंत निरस्त हो जाता है: उसके बाद टोकन को 401 `invalid_token` मिलता है।
        - उपयोगकर्ता नाम `deleted#<id>` बन जाता है, खाते में और बाज़ी के हर रिकॉर्ड में।
        - ये मिटा दिए जाते हैं: ईमेल पता, पासवर्ड हैश, दो-चरणीय सत्यापन का सीक्रेट और रिकवरी कोड, सेशन और लिंक टोकन, Google से जुड़ाव, एंटी-चीट का इंटीग्रिटी रिकॉर्ड, और सुरक्षा घटनाओं के साथ सहेजे गए IP पते।
        - रेटिंग और बाज़ियाँ रखी जाती हैं। बाज़ियाँ गुमनाम नाम के तहत पढ़ी जा सकती हैं, और `GET /players/{username}` पुराने नाम के लिए 404 जवाब देता है।

        **री-ऑथेंटिकेशन:** पासवर्ड, और दो-चरणीय सत्यापन होने पर ऑथेंटिकेटर कोड या रिकवरी कोड। **लिमिट:** `reauth`, फिर `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: खाता हटा दिया गया है।
          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` (केवल 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` (विफल री-ऑथेंटिकेशन, या इस खाते पर बहुत ज़्यादा कोड आज़माए गए), या `rate_limited` (`reauth` या `reauth_user` लिमिट, खाते का बजट, पासवर्ड हैश कतार)।'
        '500':
          $ref: '#/components/responses/InternalError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
          description: '`server_busy` (पासवर्ड हैश कतार, या डेटाबेस लॉक रहा), `timeout`। सर्वर के काफ़ी पीछे चलने पर (परिचय देखें), `retryAfter` 2 से 5 के साथ `server_busy` भी: अनुरोध प्रोसेस नहीं किया गया।'
  /account/games:
    get:
      operationId: listAccountGames
      tags:
        - game-history
      summary: खिलाड़ी की बाज़ियों की सूची देखें, फ़िल्टर और पेजिंग के साथ
      description: |-
        साइन इन किए हुए खिलाड़ी की बाज़ियाँ, सबसे नई पहले, फ़िल्टर और पेजिंग के साथ, और फ़िल्टर से मेल खाने वाली बाज़ियों की संख्या के साथ। हर क्वेरी पैरामीटर वैकल्पिक है, और ख़ाली मान को अनुपस्थित माना जाता है।

        पेजिंग: पिछले पेज का `next` `before` के रूप में भेजें; आख़िरी पेज पर `next` `null` होता है। `total` सभी पेजों में फ़िल्टर से मेल खाने वाली बाज़ियाँ गिनता है।

        **लिमिट:** `account_games`।
      security:
        - bearerAuth: []
      parameters:
        - $ref: '#/components/parameters/BeforeQuery'
        - $ref: '#/components/parameters/LimitQuery'
        - name: category
          in: query
          required: false
          description: आधिकारिक श्रेणी की आईडी (`3+2`, या `3%2B2`), या किसी दूसरे टाइम कंट्रोल वाली हर बाज़ी के लिए `custom`।
          schema:
            type: string
            pattern: '^\s*([0-9]+[+ ][0-9]+|custom)\s*$'
          example: '3+2'
        - name: rated
          in: query
          required: false
          description: केवल रेटेड (`true`) या कैज़ुअल (`false`) बाज़ियाँ।
          schema:
            type: string
            enum:
              - 'true'
              - 'false'
        - name: result
          in: query
          required: false
          description: केवल जीती, हारी या ड्रॉ रही बाज़ियाँ, खिलाड़ी की ओर से। रद्द बाज़ियाँ केवल इस फ़िल्टर के बिना दिखती हैं।
          schema:
            type: string
            enum:
              - win
              - loss
              - draw
      responses:
        '200':
          description: इतिहास का एक पेज।
          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` बाज़ी की आईडी नहीं है), `invalid_limit`, या `invalid_filter` (`category`, `rated` या `result`), हर एक `field` के साथ, जो पैरामीटर का नाम बताता है।'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '414':
          $ref: '#/components/responses/UriTooLong'
        '429':
          $ref: '#/components/responses/TooManyRequests'
          description: '`rate_limited`: `account_games` लिमिट या खाते का बजट।'
        '500':
          $ref: '#/components/responses/InternalError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
          description: '`busy` (डेटाबेस लॉक रहा; बॉडी में `retryAfter: 1`, कोई `Retry-After` हेडर नहीं), `server_busy` (सेशन की जाँच में डेटाबेस लॉक मिला), `timeout`। सर्वर के काफ़ी पीछे चलने पर (परिचय देखें), `retryAfter` 2 से 5 के साथ `server_busy` भी: अनुरोध प्रोसेस नहीं किया गया।'
  /games/{id}:
    get:
      operationId: getGame
      tags:
        - games
      summary: चालों और घड़ियों के साथ बाज़ी का रिकॉर्ड पाएँ
      description: |-
        बाज़ी का एक रिकॉर्ड, उसकी चालों और घड़ियों के साथ। टोकन के बिना, या ऐसे खिलाड़ी के टोकन के साथ जिसने यह बाज़ी नहीं खेली, जवाब सार्वजनिक वाला होता है। जब टोकन वाले खिलाड़ी ने यह बाज़ी खेली हो, तो जवाब में `you` और `reportable` जुड़ जाते हैं।

        **लिमिट:** `public_read` (टोकन होने पर प्रति खिलाड़ी, टोकन के बिना प्रति क्लाइंट)।
      security:
        - {}
        - bearerAuth: []
      parameters:
        - $ref: '#/components/parameters/GameId'
      responses:
        '200':
          description: बाज़ी का रिकॉर्ड।
          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`: अधिकतम 16 अंकों का धनात्मक पूर्णांक (2^53 से कम) नहीं है; `invalid_request`: मान्य URL एन्कोडिंग नहीं है।'
        '401':
          $ref: '#/components/responses/Unauthorized'
          description: '`invalid_token`: टोकन भेजा गया, पर वह मान्य नहीं है।'
        '404':
          $ref: '#/components/responses/NotFound'
          description: '`not_found`: ऐसी कोई बाज़ी नहीं है।'
        '414':
          $ref: '#/components/responses/UriTooLong'
        '429':
          $ref: '#/components/responses/TooManyRequests'
          description: '`rate_limited`: `public_read` लिमिट या खाते का बजट।'
        '500':
          $ref: '#/components/responses/InternalError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
          description: '`busy` (डेटाबेस लॉक रहा; बॉडी में `retryAfter: 1`, कोई `Retry-After` हेडर नहीं), `server_busy` (सेशन की जाँच में डेटाबेस लॉक मिला), `timeout`। सर्वर के काफ़ी पीछे चलने पर (परिचय देखें), `retryAfter` 2 से 5 के साथ `server_busy` भी: अनुरोध प्रोसेस नहीं किया गया।'
  /games/{id}/pgn:
    get:
      operationId: getGamePgn
      tags:
        - games
      summary: बाज़ी को PGN फ़ाइल के रूप में डाउनलोड करें
      description: |-
        वही बाज़ी PGN फ़ाइल के रूप में: `\n` लाइन एंडिंग वाली एक बाज़ी, और चालों का टेक्स्ट 80 कॉलम से छोटी पंक्तियों में। टोकन के साथ या उसके बिना, जवाब एक जैसा होता है।

        टैग, इसी क्रम में: `Event` (`<SERVER_NAME> rated <category>` या `<SERVER_NAME> casual <category>`), `Site` (`SERVER_PUBLIC_HOST`), `Date` (UTC में शुरुआत की तारीख़), `Round`, `White`, `Black`, `Result` (रद्द बाज़ी के लिए `*`), `UTCDate` और `UTCTime` (शुरुआत), `WhiteElo` और `BlackElo` (शुरुआत की रेटिंग, या `-`), `WhiteRatingDiff` और `BlackRatingDiff` (बदलाव, जैसे `+10` और `-10`, हर रेटेड बाज़ी में; जब रेटिंग के नियमों के अनुसार रेटिंग नहीं बदलती, तब `+0`; कैज़ुअल, कस्टम या रद्द बाज़ी में दोनों में से कोई नहीं होता), `TimeControl` (सेकंड में), `Termination` (PGN मानक का एक मान: `normal`; `time forfeit`, यानी फ़्लैग गिरना, तब भी जब बाज़ी ड्रॉ पर ख़त्म हो; `abandoned`; `rules infraction`, यानी दूसरी अवैध चाल या फ़ेयर-प्ले के कारण हार; `unterminated`, यानी रद्द बाज़ी), `PlyCount`, `ScacelithGameId` (दशमलव आईडी)।

        हर चाल के साथ `{[%clk h:mm:ss.f] [%emt h:mm:ss.f]}` होता है: चाल चलने वाले की घड़ी चाल के बाद, और उस चाल के लिए गिना गया समय, सेकंड के दसवें हिस्से में (काटकर, पूर्णांकन के बिना)। रिकॉर्ड में कोई मान न हो, तो उसे छोड़ दिया जाता है। आख़िरी चाल के बाद समाप्ति का कारण शब्दों में आता है, फिर परिणाम।

        **लिमिट:** `public_read` (टोकन होने पर प्रति खिलाड़ी, टोकन के बिना प्रति क्लाइंट)।
      security:
        - {}
        - bearerAuth: []
      parameters:
        - $ref: '#/components/parameters/GameId'
      responses:
        '200':
          description: PGN फ़ाइल।
          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 टेक्स्ट, UTF-8 में (`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`: अधिकतम 16 अंकों का धनात्मक पूर्णांक (2^53 से कम) नहीं है; `invalid_request`: मान्य URL एन्कोडिंग नहीं है।'
        '401':
          $ref: '#/components/responses/Unauthorized'
          description: '`invalid_token`: टोकन भेजा गया, पर वह मान्य नहीं है।'
        '404':
          $ref: '#/components/responses/NotFound'
          description: '`not_found`: ऐसी कोई बाज़ी नहीं है।'
        '414':
          $ref: '#/components/responses/UriTooLong'
        '429':
          $ref: '#/components/responses/TooManyRequests'
          description: '`rate_limited`: `public_read` लिमिट या खाते का बजट।'
        '500':
          $ref: '#/components/responses/InternalError'
          description: '`internal_error`: अन्य कारणों के साथ, तब भी जब सहेजी गई चालों को दोबारा चलाकर सहेजे गए अंत तक नहीं पहुँचा जा सकता (सर्वर इसे लॉग करता है)।'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
          description: '`busy` (डेटाबेस लॉक रहा; बॉडी में `retryAfter: 1`, कोई `Retry-After` हेडर नहीं), `server_busy` (सेशन की जाँच में डेटाबेस लॉक मिला), `timeout`। सर्वर के काफ़ी पीछे चलने पर (परिचय देखें), `retryAfter` 2 से 5 के साथ `server_busy` भी: अनुरोध प्रोसेस नहीं किया गया।'
  /games/{id}/gif:
    get:
      operationId: getGameGif
      tags:
        - gifs
      summary: इस सर्वर की किसी बाज़ी को एनिमेटेड GIF के रूप में डाउनलोड करें
      description: |-
        बाज़ी, एनिमेटेड GIF के रूप में। नाम और रेटिंग बाज़ी के रिकॉर्ड से लिए जाते हैं (शुरुआत की रेटिंग, हटाया गया खाता `deleted#<id>` के रूप में), और परिणाम व समाप्ति (`Resignation`, `Loss on time`...) भी।

        जाँच इस क्रम में होती है: `gif_disabled`, चित्र के विकल्प (`size`, `orientation`, `delay`, `coords`), बाज़ी की आईडी, बाज़ी, उसकी लंबाई।

        **सेशन ज़रूरी:** कोटा प्रति खाता गिना जाता है। **लिमिट:** हर अनुरोध पर `gif`; `gif_user_min`, `gif_user_hour`, `gif_ip_min` और `gif_ip_hour` केवल तब, जब GIF बनाना पड़े (अनुभाग का विवरण देखें)। **समय सीमा:** `GIF_QUEUE_TIMEOUT_MS` + `GIF_RENDER_TIMEOUT_MS` + 5 सेकंड (डिफ़ॉल्ट रूप से 45 सेकंड)।
      security:
        - bearerAuth: []
      parameters:
        - $ref: '#/components/parameters/GameId'
        - name: size
          in: query
          required: false
          description: चित्र का आकार, `small` (32 px के खाने), `medium` (48 px) या `large` (72 px)।
          schema:
            type: string
            enum:
              - small
              - medium
              - large
            default: medium
        - name: orientation
          in: query
          required: false
          description: बोर्ड पर नीचे की ओर दिखने वाला पक्ष।
          schema:
            type: string
            enum:
              - white
              - black
            default: white
        - name: delay
          in: query
          required: false
          description: प्रति चाल मिलीसेकंड (1 से 6 दशमलव अंक)।
          schema:
            type: integer
            minimum: 100
            maximum: 3000
            default: 500
        - name: coords
          in: query
          required: false
          description: बोर्ड के चारों ओर फ़ाइल के अक्षर और रैंक की संख्याएँ बनाई जाएँ (`1`) या नहीं (`0`)।
          schema:
            type: string
            enum:
              - '1'
              - '0'
            default: '1'
      responses:
        '200':
          $ref: '#/components/responses/GifFile'
        '400':
          $ref: '#/components/responses/BadRequest'
          description: '`invalid_option`, `field` के साथ (`size`, `orientation`, `delay` या `coords`): अनुमत मानों से बाहर का मान। `invalid_game_id`। `invalid_request`: मान्य URL एन्कोडिंग नहीं है।'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
          description: '`not_found`: ऐसी कोई बाज़ी नहीं है। `gif_disabled`: सर्वर ने GIF बंद कर रखे हैं (`GIF_ENABLED=false`; `gif` का टोकन लौटा दिया जाता है)।'
        '414':
          $ref: '#/components/responses/UriTooLong'
        '422':
          $ref: '#/components/responses/UnprocessableContent'
        '429':
          $ref: '#/components/responses/TooManyRequests'
          description: '`rate_limited`: `gif` लिमिट, कोई रेंडर लिमिट, या खाते का बजट।'
        '500':
          $ref: '#/components/responses/InternalError'
          description: '`render_failed`: GIF नहीं बन सका (सर्वर कारण लॉग करता है)। `internal_error`।'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
          description: '`server_busy`, `retryAfter` (3 से 10 सेकंड) और `Retry-After` के साथ: रेंडरिंग कतार भरी है, या GIF ने ख़ाली थ्रेड के लिए `GIF_QUEUE_TIMEOUT_MS` (10 सेकंड) इंतज़ार किया; अनुरोध ने लिमिट के जो भी टोकन लिए थे, सब लौटा दिए जाते हैं। `busy`: डेटाबेस लॉक रहा (`retryAfter: 1` केवल बॉडी में)। `timeout`। सर्वर के काफ़ी पीछे चलने पर (परिचय देखें), `retryAfter` 2 से 5 के साथ `server_busy` भी: अनुरोध प्रोसेस नहीं किया गया।'
  /gif:
    post:
      operationId: renderPgnGif
      tags:
        - gifs
      summary: PGN के रूप में भेजी गई किसी भी बाज़ी का एनिमेटेड GIF बनाएँ
      description: |-
        `GET /games/{id}/gif` जैसा ही चित्र, PGN टेक्स्ट के रूप में भेजी गई किसी भी बाज़ी के लिए: गेम में सहेजी गई बाज़ी, किसी दूसरी साइट का एक्सपोर्ट, हाथ से टाइप की गई बाज़ी। टेक्स्ट की केवल पहली बाज़ी इस्तेमाल होती है।

        - PGN रीडर वह सब स्वीकार करता है जो गेम का अपना रीडर स्वीकार करता है: यह सर्वर जो भी PGN लिखता है, और दूसरी साइटों के सामान्य एक्सपोर्ट (टिप्पणियाँ, वेरिएशन, NAG और घड़ी के एनोटेशन छोड़ दिए जाते हैं; चाल संख्याएँ और SAN लचीलेपन से पढ़े जाते हैं; `FEN` टैग शुरुआती स्थिति देता है, जब तक `SetUp` `"0"` न हो)।
        - नाम और रेटिंग `White`, `Black`, `WhiteElo` और `BlackElo` टैग से आते हैं। एक्सेंट वाले अक्षरों के एक्सेंट हटा दिए जाते हैं, प्रिंट होने योग्य ASCII से बाहर के बाक़ी वर्ण `?` बन जाते हैं, और लंबे नाम काट दिए जाते हैं (48 वर्ण)। परिणाम `Result` टैग से आता है, नहीं तो चालों के टेक्स्ट के अंत से। `Termination` टैग दिखाया जाता है, जब तक वह `normal` न हो: तब अंतिम स्थिति ही सब बता देती है (मात, स्टेलमेट)।
        - यह एंडपॉइंट अपनी बॉडी ख़ुद जाँचता है: अज्ञात फ़ील्ड पर, या `pgn` के न होने या स्ट्रिंग न होने पर जवाब `field` के साथ 400 `invalid_request` होता है; ग़लत विकल्प पर जवाब 400 `invalid_option` होता है (`delayMs` JSON संख्या होनी चाहिए, `coords` बूलियन; `null` अस्वीकार किया जाता है)।

        **सेशन ज़रूरी:** कोटा प्रति खाता गिना जाता है। **लिमिट:** `GET /games/{id}/gif` की तरह। **बॉडी की सीमा:** 1,35,168 बाइट, `HTTP_BODY_LIMIT` चाहे जो हो (JSON स्ट्रिंग के रूप में PGN, उसके एस्केप सहित)। **समय सीमा:** डिफ़ॉल्ट रूप से 45 सेकंड।
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/GifRequest'
            examples:
              short:
                summary: टाइप की गई एक छोटी बाज़ी, निर्देशांकों के बिना
                value:
                  pgn: 1. f3 e5 2. g4 Qh4# 0-1
                  coords: false
              options:
                summary: हर विकल्प के साथ एक PGN फ़ाइल
                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`, `field` के साथ (अज्ञात फ़ील्ड, या `pgn` मौजूद नहीं या स्ट्रिंग नहीं; बॉडी ऑब्जेक्ट न होने पर `field` के बिना); `invalid_json`; `invalid_option`, `field` के साथ (`size`, `orientation`, `delayMs` या `coords`); `invalid_pgn`, `line` और `column` (1 से शुरू, कॉलम वर्णों में) और रीडर के `message` के साथ: अवैध या अस्पष्ट चाल, टूटा हुआ टैग, अज्ञात वेरिएंट, 65,536 बाइट से ज़्यादा।'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
          description: '`gif_disabled`: सर्वर ने GIF बंद कर रखे हैं (`GIF_ENABLED=false`; `gif` का टोकन लौटा दिया जाता है)।'
        '408':
          $ref: '#/components/responses/RequestTimeout'
        '413':
          $ref: '#/components/responses/PayloadTooLarge'
          description: '`payload_too_large`: 1,35,168 बाइट से बड़ी बॉडी।'
        '414':
          $ref: '#/components/responses/UriTooLong'
        '415':
          $ref: '#/components/responses/UnsupportedMediaType'
        '422':
          $ref: '#/components/responses/UnprocessableContent'
        '429':
          $ref: '#/components/responses/TooManyRequests'
          description: '`rate_limited`: `gif` लिमिट, कोई रेंडर लिमिट, या खाते का बजट।'
        '500':
          $ref: '#/components/responses/InternalError'
          description: '`render_failed`: GIF नहीं बन सका (सर्वर कारण लॉग करता है)। `internal_error`।'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
          description: '`server_busy`, `retryAfter` (3 से 10 सेकंड) और `Retry-After` के साथ: रेंडरिंग कतार भरी है, या GIF ने ख़ाली थ्रेड के लिए बहुत देर इंतज़ार किया; अनुरोध ने लिमिट के जो भी टोकन लिए थे, सब लौटा दिए जाते हैं। `timeout`। सर्वर के काफ़ी पीछे चलने पर (परिचय देखें), `retryAfter` 2 से 5 के साथ `server_busy` भी: अनुरोध प्रोसेस नहीं किया गया।'
  /players/{username}:
    get:
      operationId: getPlayer
      tags:
        - players
      summary: खिलाड़ी की सार्वजनिक प्रोफ़ाइल पाएँ
      description: |-
        खिलाड़ी की सार्वजनिक प्रोफ़ाइल: आधिकारिक श्रेणियों में रेटिंग (सर्वर के क्रम में) और बाज़ियों की गिनती। `games.total` हर सहेजी गई बाज़ी गिनता है, कैज़ुअल और रद्द बाज़ियों सहित; `games.rated`, `wins`, `draws` और `losses` रेटिंग रिकॉर्ड से जोड़कर निकाले जाते हैं, इसलिए केवल रेटेड बाज़ियाँ गिनी जाती हैं। टोकन के साथ या उसके बिना, जवाब एक जैसा होता है।

        **लिमिट:** `public_read` (टोकन होने पर प्रति खिलाड़ी, टोकन के बिना प्रति क्लाइंट)।
      security:
        - {}
        - bearerAuth: []
      parameters:
        - $ref: '#/components/parameters/Username'
      responses:
        '200':
          description: प्रोफ़ाइल।
          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`: `[A-Za-z0-9_.-]` के 2 से 24 वर्णों का नहीं है।'
        '401':
          $ref: '#/components/responses/Unauthorized'
          description: '`invalid_token`: टोकन भेजा गया, पर वह मान्य नहीं है।'
        '404':
          $ref: '#/components/responses/NotFound'
          description: '`not_found`: ऐसा कोई खिलाड़ी नहीं है, या खाता हटाया जा चुका है।'
        '414':
          $ref: '#/components/responses/UriTooLong'
        '429':
          $ref: '#/components/responses/TooManyRequests'
          description: '`rate_limited`: `public_read` लिमिट या खाते का बजट।'
        '500':
          $ref: '#/components/responses/InternalError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
          description: '`busy` (डेटाबेस लॉक रहा; बॉडी में `retryAfter: 1`, कोई `Retry-After` हेडर नहीं), `server_busy` (सेशन की जाँच में डेटाबेस लॉक मिला), `timeout`। सर्वर के काफ़ी पीछे चलने पर (परिचय देखें), `retryAfter` 2 से 5 के साथ `server_busy` भी: अनुरोध प्रोसेस नहीं किया गया।'
  /players/{username}/games:
    get:
      operationId: listPlayerGames
      tags:
        - players
      summary: खिलाड़ी की हाल की बाज़ियों की सूची देखें
      description: |-
        खिलाड़ी की हाल की बाज़ियाँ, सबसे नई पहले, इतिहास की तरह पेजिंग के साथ, पर फ़िल्टर या कुल संख्या के बिना। `color` इस खिलाड़ी का पक्ष है। जब भी पेज भरा हो, `next` आख़िरी बाज़ी की आईडी होता है, इसलिए अगला पेज ख़ाली हो सकता है। खिलाड़ी को सबसे पहले खोजा जाता है: अज्ञात खिलाड़ी पर जवाब 404 होता है, क्वेरी चाहे जो कहे।

        **लिमिट:** `public_read` (टोकन होने पर प्रति खिलाड़ी, टोकन के बिना प्रति क्लाइंट)।
      security:
        - {}
        - bearerAuth: []
      parameters:
        - $ref: '#/components/parameters/Username'
        - $ref: '#/components/parameters/BeforeQuery'
        - $ref: '#/components/parameters/LimitQuery'
      responses:
        '200':
          description: खिलाड़ी की बाज़ियों का एक पेज।
          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` बाज़ी की आईडी नहीं है), `invalid_limit` (ये दोनों `field` के बिना)।'
        '401':
          $ref: '#/components/responses/Unauthorized'
          description: '`invalid_token`: टोकन भेजा गया, पर वह मान्य नहीं है।'
        '404':
          $ref: '#/components/responses/NotFound'
          description: '`not_found`: ऐसा कोई खिलाड़ी नहीं है, या खाता हटाया जा चुका है।'
        '414':
          $ref: '#/components/responses/UriTooLong'
        '429':
          $ref: '#/components/responses/TooManyRequests'
          description: '`rate_limited`: `public_read` लिमिट या खाते का बजट।'
        '500':
          $ref: '#/components/responses/InternalError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
          description: '`busy` (डेटाबेस लॉक रहा; बॉडी में `retryAfter: 1`, कोई `Retry-After` हेडर नहीं), `server_busy` (सेशन की जाँच में डेटाबेस लॉक मिला), `timeout`। सर्वर के काफ़ी पीछे चलने पर (परिचय देखें), `retryAfter` 2 से 5 के साथ `server_busy` भी: अनुरोध प्रोसेस नहीं किया गया।'
  /leaderboard:
    get:
      operationId: getLeaderboard
      tags:
        - leaderboard
      summary: किसी श्रेणी के शीर्ष खिलाड़ी देखें
      description: |-
        किसी आधिकारिक श्रेणी के शीर्ष 100 रेटेड रिकॉर्ड, जिनमें कम से कम `minGames` (`PROVISIONAL_GAMES`) गिनी गई बाज़ियाँ हों; हटाए गए खाते और पुष्ट धोखेबाज़ इसमें शामिल नहीं होते। सर्वर हर लीडरबोर्ड की गणना अधिक से अधिक हर 10 सेकंड में एक बार दोबारा करता है; `updatedAt` बताता है कि कब। किसी श्रेणी की एक समय में एक ही गणना चलती है: इस बीच बाक़ी अनुरोधों को पिछली सूची मिलती है, या, अगर अभी कोई सूची नहीं है, तो वे गणना पूरी होने का इंतज़ार करते हैं।

        **लिमिट:** केवल प्रति-पता लेयर।
      security: []
      parameters:
        - name: category
          in: query
          required: true
          description: आधिकारिक श्रेणी की आईडी (`3+2`, या `3%2B2`)।
          schema:
            type: string
            pattern: '^\s*[0-9]+[+ ][0-9]+\s*$'
          example: '3+2'
        - name: limit
          in: query
          required: false
          description: कितने खिलाड़ी, 1 से 100। 3 अंकों तक की बड़ी संख्या 100 मानी जाती है; ख़ाली मान को अनुपस्थित माना जाता है।
          schema:
            type: integer
            minimum: 1
            maximum: 999
            default: 100
      responses:
        '200':
          description: लीडरबोर्ड।
          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` (मौजूद नहीं, या आधिकारिक श्रेणी नहीं), `invalid_limit`।'
        '414':
          $ref: '#/components/responses/UriTooLong'
        '429':
          $ref: '#/components/responses/TooManyRequests'
          description: '`rate_limited`: प्रति-पता लेयर।'
        '500':
          $ref: '#/components/responses/InternalError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
          description: '`busy` (डेटाबेस लॉक रहा; बॉडी में `retryAfter: 1`, कोई `Retry-After` हेडर नहीं), `timeout`। सर्वर के काफ़ी पीछे चलने पर (परिचय देखें), `retryAfter` 2 से 5 के साथ `server_busy` भी: अनुरोध प्रोसेस नहीं किया गया।'
  /reports:
    post:
      operationId: reportPlayer
      tags:
        - reports
      summary: हाल की किसी बाज़ी के प्रतिद्वंद्वी की शिकायत करें
      description: |-
        खिलाड़ी की अपनी उन बाज़ियों में से किसी एक के प्रतिद्वंद्वी की शिकायत करता है जो पिछले 7 दिनों में ख़त्म हुई हों। कोई शिकायत अपने आप में कभी रेटिंग, दंड या इंटीग्रिटी लेवल नहीं बदलती। वह मॉडरेटरों को दिखने वाली समीक्षा प्राथमिकता बढ़ाती है, और `abuse` को छोड़कर, बाज़ी के इंजन विश्लेषण का अनुरोध करती है।

        उसी बाज़ी के लिए उसी प्रतिद्वंद्वी की शिकायत को वही जवाब मिलता है और उससे कुछ नहीं बदलता; जवाब शिकायत किए गए खाते के बारे में कभी कुछ नहीं बताता। `GET /games/{id}` बाज़ी के खिलाड़ियों को पहले से बता देता है कि शिकायत ली जाएगी या नहीं (`reportable`)।

        यह एंडपॉइंट अपनी बॉडी ख़ुद जाँचता है, इस क्रम में: `gameId`, `reported`, `category`, `comment`; विफलता पर जवाब `field` के बिना 400 `invalid_request` होता है। जिन फ़ील्ड को यह नहीं जानता, उन्हें अनदेखा किया जाता है।

        **लिमिट:** `reports` (प्रति खिलाड़ी), फिर प्रति खिलाड़ी 24 घंटे में `REPORTS_PER_DAY` (5) शिकायतें (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: शिकायत मिल गई है (या पहले ही दर्ज की जा चुकी थी)।
          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` (`field` के बिना), `invalid_json`।'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
          description: '`report_not_allowed`: पिछले 7 दिनों में ख़त्म हुई किसी बाज़ी में शिकायत करने वाले का प्रतिद्वंद्वी नहीं है।'
        '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`, `Retry-After` हेडर के साथ): पिछले 24 घंटों में `REPORTS_PER_DAY` शिकायतें। `rate_limited`: `reports` लिमिट या खाते का बजट।'
        '500':
          $ref: '#/components/responses/InternalError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
          description: '`server_busy` (सेशन की जाँच में डेटाबेस लॉक मिला), `timeout`। सर्वर के काफ़ी पीछे चलने पर (परिचय देखें), `retryAfter` 2 से 5 के साथ `server_busy` भी: अनुरोध प्रोसेस नहीं किया गया।'
  /verify-email:
    servers:
      - url: https://caissa.scacelith.com
        description: आधिकारिक सर्वर (पेज रूट पर हैं, `/api/v1` के बाहर)।
      - url: https://{host}:{port}
        description: कोई भी Scacelith सर्वर (पेज रूट पर हैं, `/api/v1` के बाहर)।
        variables:
          host:
            default: caissa.scacelith.com
            description: सर्वर का सार्वजनिक होस्ट नाम (`SERVER_PUBLIC_HOST`)।
          port:
            default: "443"
            description: सार्वजनिक API पोर्ट (`PUBLIC_API_PORT`, नहीं तो `API_PORT`)।
    get:
      operationId: showVerifyEmailPage
      tags:
        - pages
      summary: ईमेल पुष्टि का पेज दिखाएँ
      description: |-
        ईमेल पुष्टि लिंक का पेज (रजिस्ट्रेशन, या दोबारा भेजा गया पुष्टि ईमेल)। यह केवल एक "Confirm my e-mail address" (मेरे ईमेल पते की पुष्टि करें) बटन दिखाता है, ताकि लिंक खोलने वाला कोई मेल स्कैनर उसे इस्तेमाल न कर दे; बटन फ़ॉर्म को `POST /verify-email` पर भेजता है।

        **लिमिट:** `page`।
      security: []
      parameters:
        - $ref: '#/components/parameters/LinkTokenQuery'
      responses:
        '200':
          $ref: '#/components/responses/HtmlPage'
          description: पुष्टि बटन वाला पेज।
        '400':
          $ref: '#/components/responses/HtmlBadRequest'
          description: लिंक अमान्य है या उसकी अवधि ख़त्म हो चुकी है (HTML पेज)।
        '414':
          $ref: '#/components/responses/UriTooLong'
        '429':
          $ref: '#/components/responses/HtmlTooManyRequests'
          description: '`page` लिमिट (HTML पेज), या प्रति-पता लेयर (JSON `rate_limited`)।'
        '500':
          $ref: '#/components/responses/HtmlInternalError'
        '503':
          $ref: '#/components/responses/HtmlServiceUnavailable'
    post:
      operationId: submitVerifyEmailPage
      tags:
        - pages
      summary: ईमेल पते की पुष्टि करें
      description: |-
        पुष्टि पेज का फ़ॉर्म। यह पते की पुष्टि करता है; नए साइन-अप के लिए यह उसी समय खाता बनाता है, और खिलाड़ी साइन इन कर सकता है।

        **लिमिट:** `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: पते की पुष्टि हो गई है (और नए साइन-अप का खाता बन गया है)।
        '400':
          $ref: '#/components/responses/HtmlBadRequest'
          description: लिंक अमान्य है, इस्तेमाल हो चुका है या उसकी अवधि ख़त्म हो चुकी है, या फ़ॉर्म अमान्य है (HTML पेज)।
        '408':
          $ref: '#/components/responses/HtmlRequestTimeout'
        '409':
          $ref: '#/components/responses/HtmlConflict'
          description: ऐसे नए साइन-अप का लिंक, जिसका उपयोगकर्ता नाम या पता इस बीच किसी दूसरे खाते ने ले लिया; कोई खाता नहीं बनाया जाता (HTML पेज)।
        '413':
          $ref: '#/components/responses/HtmlPayloadTooLarge'
        '414':
          $ref: '#/components/responses/UriTooLong'
        '415':
          $ref: '#/components/responses/HtmlUnsupportedMediaType'
        '429':
          $ref: '#/components/responses/HtmlTooManyRequests'
          description: '`auth` लिमिट (HTML पेज), या प्रति-पता लेयर (JSON `rate_limited`)।'
        '500':
          $ref: '#/components/responses/HtmlInternalError'
        '503':
          $ref: '#/components/responses/HtmlServiceUnavailable'
          description: 'डेटाबेस लॉक रहा (`Retry-After: 1`), या सर्वर काफ़ी पीछे चल रहा है (`Retry-After` 2 से 5, परिचय देखें): कुछ नहीं बदला और लिंक अब भी काम करता है। हैंडलर टाइमआउट भी।'
  /reset-password:
    servers:
      - url: https://caissa.scacelith.com
        description: आधिकारिक सर्वर (पेज रूट पर हैं, `/api/v1` के बाहर)।
      - url: https://{host}:{port}
        description: कोई भी Scacelith सर्वर (पेज रूट पर हैं, `/api/v1` के बाहर)।
        variables:
          host:
            default: caissa.scacelith.com
            description: सर्वर का सार्वजनिक होस्ट नाम (`SERVER_PUBLIC_HOST`)।
          port:
            default: "443"
            description: सार्वजनिक API पोर्ट (`PUBLIC_API_PORT`, नहीं तो `API_PORT`)।
    get:
      operationId: showResetPasswordPage
      tags:
        - pages
      summary: पासवर्ड रीसेट फ़ॉर्म दिखाएँ
      description: |-
        पासवर्ड रीसेट लिंक का पेज: नए पासवर्ड का फ़ॉर्म (पासवर्ड दो बार)। फ़ॉर्म `POST /reset-password` पर भेजा जाता है।

        **लिमिट:** `page`।
      security: []
      parameters:
        - $ref: '#/components/parameters/LinkTokenQuery'
      responses:
        '200':
          $ref: '#/components/responses/HtmlPage'
          description: नए पासवर्ड का फ़ॉर्म।
        '400':
          $ref: '#/components/responses/HtmlBadRequest'
          description: लिंक अमान्य है (तब भी, जब वह ऐसे पते पर भेजा गया था जो अब खाते का पता नहीं है)।
        '414':
          $ref: '#/components/responses/UriTooLong'
        '429':
          $ref: '#/components/responses/HtmlTooManyRequests'
          description: '`page` लिमिट (HTML पेज), या प्रति-पता लेयर (JSON `rate_limited`)।'
        '500':
          $ref: '#/components/responses/HtmlInternalError'
        '503':
          $ref: '#/components/responses/HtmlServiceUnavailable'
    post:
      operationId: submitResetPasswordPage
      tags:
        - pages
      summary: रीसेट फ़ॉर्म से नया पासवर्ड सेट करें
      description: |-
        रीसेट पेज का फ़ॉर्म; यह वही करता है जो `POST /auth/password/reset` करता है, एक ही ट्रांज़ैक्शन में: हर डिवाइस साइन आउट हो जाता है, लंबित ईमेल बदलाव रद्द हो जाता है, बाक़ी रीसेट लिंक काम करना बंद कर देते हैं, पता पुष्ट माना जाता है और खाते के मालिक को ईमेल भेजा जाता है।

        पहले लिंक जाँचा जाता है, फिर यह कि दोनों पासवर्ड एक जैसे हैं, फिर पासवर्ड के नियम। सर्वर व्यस्त होने पर फ़ॉर्म `Retry-After` के साथ वापस आता है और लिंक मान्य रहता है।

        **लिमिट:** `auth`, फिर `auth_reset` (`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: पासवर्ड बदल दिया गया है, और हर डिवाइस साइन आउट हो गया है।
        '400':
          $ref: '#/components/responses/HtmlBadRequest'
          description: त्रुटि के साथ फ़ॉर्म दोबारा (पासवर्ड अलग-अलग हैं, कमज़ोर पासवर्ड), लिंक अमान्य है, या फ़ॉर्म अमान्य है (HTML पेज)।
        '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: '`auth` या `auth_reset` लिमिट (HTML पेज), इस क्लाइंट के बहुत ज़्यादा पासवर्ड हैश प्रतीक्षा में होने पर `Retry-After` के साथ फ़ॉर्म दोबारा (लिंक मान्य रहता है), या प्रति-पता लेयर (JSON `rate_limited`)।'
        '500':
          $ref: '#/components/responses/HtmlInternalError'
        '503':
          $ref: '#/components/responses/HtmlServiceUnavailable'
          description: 'सर्वर व्यस्त होने पर (पासवर्ड हैश कतार, या डेटाबेस लॉक रहा) `Retry-After` के साथ फ़ॉर्म दोबारा; लिंक मान्य रहता है। सर्वर के काफ़ी पीछे चलने पर (परिचय देखें), इसकी जगह `Retry-After` (2 से 5) के साथ "Server error" (सर्वर त्रुटि) पेज: कुछ भी प्रोसेस नहीं किया गया और लिंक मान्य रहता है। हैंडलर टाइमआउट भी।'
  /confirm-email-change:
    servers:
      - url: https://caissa.scacelith.com
        description: आधिकारिक सर्वर (पेज रूट पर हैं, `/api/v1` के बाहर)।
      - url: https://{host}:{port}
        description: कोई भी Scacelith सर्वर (पेज रूट पर हैं, `/api/v1` के बाहर)।
        variables:
          host:
            default: caissa.scacelith.com
            description: सर्वर का सार्वजनिक होस्ट नाम (`SERVER_PUBLIC_HOST`)।
          port:
            default: "443"
            description: सार्वजनिक API पोर्ट (`PUBLIC_API_PORT`, नहीं तो `API_PORT`)।
    get:
      operationId: showConfirmEmailChangePage
      tags:
        - pages
      summary: ईमेल बदलाव की पुष्टि का पेज दिखाएँ
      description: |-
        ईमेल बदलाव लिंक का पेज: यह नया पता और खाते का नाम दिखाता है, साथ में एक "Use this e-mail address" (यह ईमेल पता इस्तेमाल करें) बटन, जो फ़ॉर्म को `POST /confirm-email-change` पर भेजता है।

        **लिमिट:** `page`।
      security: []
      parameters:
        - $ref: '#/components/parameters/LinkTokenQuery'
      responses:
        '200':
          $ref: '#/components/responses/HtmlPage'
          description: नए पते और उसके पुष्टि बटन वाला पेज।
        '400':
          $ref: '#/components/responses/HtmlBadRequest'
          description: लिंक अमान्य है या उसकी अवधि ख़त्म हो चुकी है (तब भी, जब अनुरोध के बाद खाते का पता बदल गया हो)।
        '414':
          $ref: '#/components/responses/UriTooLong'
        '429':
          $ref: '#/components/responses/HtmlTooManyRequests'
          description: '`page` लिमिट (HTML पेज), या प्रति-पता लेयर (JSON `rate_limited`)।'
        '500':
          $ref: '#/components/responses/HtmlInternalError'
        '503':
          $ref: '#/components/responses/HtmlServiceUnavailable'
    post:
      operationId: submitConfirmEmailChangePage
      tags:
        - pages
      summary: नए ईमेल पते की पुष्टि करें
      description: |-
        ईमेल बदलाव पेज का फ़ॉर्म। पता बदल जाता है और पुष्ट माना जाता है; डिवाइस साइन इन रहते हैं; पहले भेजे गए लिंक (पुष्टि, पासवर्ड रीसेट, दूसरे बदलाव) काम करना बंद कर देते हैं; पुराने पते को सूचना दी जाती है, जिसमें नया पता आंशिक रूप से छिपा होता है।

        **लिमिट:** `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: पता बदल दिया गया है।
        '400':
          $ref: '#/components/responses/HtmlBadRequest'
          description: लिंक अमान्य है, इस्तेमाल हो चुका है या उसकी अवधि ख़त्म हो चुकी है, या फ़ॉर्म अमान्य है (HTML पेज)।
        '408':
          $ref: '#/components/responses/HtmlRequestTimeout'
        '409':
          $ref: '#/components/responses/HtmlConflict'
          description: इस बीच किसी दूसरे खाते ने यह पता ले लिया (HTML पेज)।
        '413':
          $ref: '#/components/responses/HtmlPayloadTooLarge'
        '414':
          $ref: '#/components/responses/UriTooLong'
        '415':
          $ref: '#/components/responses/HtmlUnsupportedMediaType'
        '429':
          $ref: '#/components/responses/HtmlTooManyRequests'
          description: '`auth` लिमिट (HTML पेज), या प्रति-पता लेयर (JSON `rate_limited`)।'
        '500':
          $ref: '#/components/responses/HtmlInternalError'
        '503':
          $ref: '#/components/responses/HtmlServiceUnavailable'
          description: 'डेटाबेस लॉक रहा (`Retry-After: 1`), या सर्वर काफ़ी पीछे चल रहा है (`Retry-After` 2 से 5, परिचय देखें): कुछ नहीं बदला और लिंक अब भी काम करता है। हैंडलर टाइमआउट भी।'
  /healthz:
    servers:
      - url: https://caissa.scacelith.com
        description: आधिकारिक सर्वर, रूट पर।
      - url: https://caissa.scacelith.com/api/v1
        description: आधिकारिक सर्वर, `/api/v1` के अंतर्गत।
      - url: https://{host}:{port}
        description: कोई भी Scacelith सर्वर, रूट पर।
        variables:
          host:
            default: caissa.scacelith.com
            description: सर्वर का सार्वजनिक होस्ट नाम (`SERVER_PUBLIC_HOST`)।
          port:
            default: "443"
            description: सार्वजनिक API पोर्ट (`PUBLIC_API_PORT`, नहीं तो `API_PORT`)।
      - url: https://{host}:{port}/api/v1
        description: कोई भी Scacelith सर्वर, `/api/v1` के अंतर्गत।
        variables:
          host:
            default: caissa.scacelith.com
            description: सर्वर का सार्वजनिक होस्ट नाम (`SERVER_PUBLIC_HOST`)।
          port:
            default: "443"
            description: सार्वजनिक API पोर्ट (`PUBLIC_API_PORT`, नहीं तो `API_PORT`)।
    get:
      operationId: getLiveness
      tags:
        - health
      summary: जाँचें कि प्रोसेस चल रहा है
      description: |-
        जब तक प्रोसेस चल रहा है, 200 `ok` जवाब देता है। `HEAD` भी काम करता है। कोई भी दूसरा मेथड (`OPTIONS` सहित) `Allow: GET, HEAD` के साथ 405 `method_not_allowed` जवाब देता है।

        **लिमिट:** केवल प्रति-पता लेयर।
      security: []
      responses:
        '200':
          description: प्रोसेस चल रहा है।
          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`: प्रति-पता लेयर।'
  /readyz:
    servers:
      - url: https://caissa.scacelith.com
        description: आधिकारिक सर्वर, रूट पर।
      - url: https://caissa.scacelith.com/api/v1
        description: आधिकारिक सर्वर, `/api/v1` के अंतर्गत।
      - url: https://{host}:{port}
        description: कोई भी Scacelith सर्वर, रूट पर।
        variables:
          host:
            default: caissa.scacelith.com
            description: सर्वर का सार्वजनिक होस्ट नाम (`SERVER_PUBLIC_HOST`)।
          port:
            default: "443"
            description: सार्वजनिक API पोर्ट (`PUBLIC_API_PORT`, नहीं तो `API_PORT`)।
      - url: https://{host}:{port}/api/v1
        description: कोई भी Scacelith सर्वर, `/api/v1` के अंतर्गत।
        variables:
          host:
            default: caissa.scacelith.com
            description: सर्वर का सार्वजनिक होस्ट नाम (`SERVER_PUBLIC_HOST`)।
          port:
            default: "443"
            description: सार्वजनिक API पोर्ट (`PUBLIC_API_PORT`, नहीं तो `API_PORT`)।
    get:
      operationId: getReadiness
      tags:
        - health
      summary: जाँचें कि सर्वर खिलाड़ियों को स्वीकार कर रहा है
      description: |-
        जब सर्वर खिलाड़ियों को स्वीकार कर रहा हो, तब 200 `ready` जवाब देता है, और उसके शुरू या बंद होते समय 503 `not_ready`। `HEAD` भी काम करता है। कोई भी दूसरा मेथड (`OPTIONS` सहित) `Allow: GET, HEAD` के साथ 405 `method_not_allowed` जवाब देता है।

        **लिमिट:** केवल प्रति-पता लेयर।
      security: []
      responses:
        '200':
          description: सर्वर खिलाड़ियों को स्वीकार कर रहा है।
          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`: प्रति-पता लेयर।'
        '503':
          description: सर्वर शुरू या बंद हो रहा है।
          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: |-
        साइन-इन जवाब से मिला सेशन टोकन (`sct_` के बाद 43 base64url वर्ण), `Authorization` हेडर में: `Authorization: Bearer <token>`। प्रीफ़िक्स ठीक `Bearer` (केस-सेंसिटिव) और एक स्पेस है; जिस मान में यह न हो, या जो टोकन 1 से 512 प्रिंट होने योग्य ASCII वर्णों का न हो, उस पर किसी भी दूसरे अमान्य टोकन की तरह जवाब 401 `invalid_token` होता है। ख़ाली हेडर को हेडर न होने जैसा माना जाता है।
  parameters:
    GameId:
      name: id
      in: path
      required: true
      description: बाज़ी की आईडी, अधिकतम 16 अंकों का धनात्मक दशमलव पूर्णांक, शुरुआती शून्यों के बिना, 2^53 से कम।
      schema:
        type: integer
        format: int64
        minimum: 1
        maximum: 9007199254740991
      example: 4100000000001
    SessionId:
      name: id
      in: path
      required: true
      description: सेशन की आईडी, जैसी `GET /auth/sessions` देता है, शुरुआती शून्यों के बिना लिखी हुई (`03` सेशन 3 नहीं है)।
      schema:
        type: integer
        format: int64
        minimum: 1
      example: 3
    Username:
      name: username
      in: path
      required: true
      description: खिलाड़ी का उपयोगकर्ता नाम, छोटे-बड़े अक्षरों का भेद किए बिना। सर्वर `[A-Za-z0-9_.-]` के 2 से 24 वर्णों वाला कोई भी नाम स्वीकार करता है (अब तक कभी अनुमत सबसे व्यापक नियम)।
      schema:
        type: string
        pattern: '^[A-Za-z0-9_.-]{2,24}$'
      example: alice
    BeforeQuery:
      name: before
      in: query
      required: false
      description: बाज़ी की आईडी; केवल उससे पुरानी बाज़ियाँ सूची में आती हैं। पिछले पेज का `next` भेजें। ख़ाली मान को अनुपस्थित माना जाता है।
      schema:
        type: integer
        format: int64
        minimum: 1
        maximum: 9007199254740991
      example: 4100000000002
    LimitQuery:
      name: limit
      in: query
      required: false
      description: पेज का आकार, 1 से 50। 3 अंकों तक की बड़ी संख्या 50 मानी जाती है; ख़ाली मान को अनुपस्थित माना जाता है।
      schema:
        type: integer
        minimum: 1
        maximum: 999
        default: 20
    LinkTokenQuery:
      name: token
      in: query
      required: false
      description: ईमेल लिंक का टोकन (43 base64url वर्ण)। टोकन न होने या ग़लत होने पर "link invalid or expired" (लिंक अमान्य या अवधि ख़त्म) पेज दिखता है।
      schema:
        type: string
        pattern: '^[A-Za-z0-9_-]{43}$'
      example: asfioO4P0r_Zn5DxWzcQuF6Drgxu7wXGjCyS-UFbTaw
  headers:
    CacheControl:
      description: सर्वर का हर जवाब कैशिंग की मनाही करता है।
      schema:
        type: string
        const: no-store
    RetryAfter:
      description: दोबारा कोशिश करने से पहले इंतज़ार के सेकंड; बॉडी के `retryAfter` जैसा ही मान।
      schema:
        type: integer
        minimum: 1
      example: 30
    WwwAuthenticate:
      description: बेयरर चैलेंज, अमान्य टोकन के लिए `error="invalid_token"` के साथ।
      schema:
        type: string
        enum:
          - Bearer realm="scacelith"
          - Bearer realm="scacelith", error="invalid_token"
    ConnectionClose:
      description: इस जवाब के बाद सर्वर कनेक्शन बंद कर देता है।
      schema:
        type: string
        const: close
  responses:
    BadRequest:
      description: '`invalid_request` (जब बॉडी का कोई फ़ील्ड ग़लत हो, तो `field` के साथ) या `invalid_json`।'
      headers:
        Cache-Control:
          $ref: '#/components/headers/CacheControl'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          examples:
            invalidRequest:
              summary: कोई फ़ील्ड स्कीमा का उल्लंघन करता है
              value:
                error: invalid_request
                message: '"password" is required'
                field: password
            invalidJson:
              summary: बॉडी JSON नहीं है
              value:
                error: invalid_json
                message: The body is not valid JSON.
            weakPassword:
              summary: कमज़ोर पासवर्ड
              value:
                error: weak_password
                message: The password must have at least 10 characters.
                reason: too_short
            invalidPgn:
              summary: ऐसा PGN जो पढ़ा नहीं जा सकता
              value:
                error: invalid_pgn
                message: illegal move 'Ke3'
                line: 1
                column: 13
    Unauthorized:
      description: '`unauthorized` (कोई `Authorization` हेडर नहीं) या `invalid_token` (टोकन ग़लत रूप में है, उसकी अवधि ख़त्म हो चुकी है, वह निरस्त है, या किसी हटाए गए खाते का है)।'
      headers:
        Cache-Control:
          $ref: '#/components/headers/CacheControl'
        WWW-Authenticate:
          $ref: '#/components/headers/WwwAuthenticate'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          examples:
            unauthorized:
              summary: कोई सेशन नहीं
              value:
                error: unauthorized
                message: Log in first.
            invalidToken:
              summary: अमान्य टोकन
              value:
                error: invalid_token
                message: The session is invalid or has expired; log in again.
    Forbidden:
      description: अनुरोध अस्वीकार कर दिया गया है।
      headers:
        Cache-Control:
          $ref: '#/components/headers/CacheControl'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          examples:
            invalidPassword:
              summary: री-ऑथेंटिकेशन में ग़लत पासवर्ड
              value:
                error: invalid_password
                message: Wrong password.
            banned:
              summary: निलंबित खाता
              value:
                error: banned
                message: This account is banned.
                until: 1791487639708
    NotFound:
      description: '`not_found`, या ऐसी सुविधा जिसे इस सर्वर ने बंद कर रखा है।'
      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`: बॉडी 10 सेकंड के भीतर नहीं पहुँची। सर्वर कनेक्शन बंद कर देता है।'
      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: अनुरोध सर्वर की स्थिति से टकराता है।
      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`: Google साइन-इन का प्रयास या टिकट अज्ञात है, इस्तेमाल हो चुका है या उसकी अवधि ख़त्म हो चुकी है; साइन-इन फिर से शुरू करें।'
      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`: बॉडी `HTTP_BODY_LIMIT` (डिफ़ॉल्ट रूप से 16,384 बाइट) से बड़ी है। सर्वर कनेक्शन बंद कर देता है।'
      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`: अनुरोध टारगेट 4096 वर्णों से लंबा है।'
      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`: बॉडी `application/json` नहीं है, या उसका charset 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`: बाज़ी में `GIF_MAX_PLIES` (600) से ज़्यादा अर्ध-चालें हैं।'
      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`: `pow` चैलेंज हल करें और वही अनुरोध `pow: { challenge, nonce }` के साथ दोबारा भेजें। `reason` कारण बताता है: `required`, `malformed`, `signature`, `endpoint`, `network`, `expired`, `bits`, `work` या `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` (कोई रेट लिमिट, खाते का बजट, या पासवर्ड हैश कतार) या `too_many_attempts` (खाते पर लगी कोई पाबंदी), `retryAfter` और `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: रेट लिमिट
              value:
                error: rate_limited
                message: Too many requests; try again later.
                retryAfter: 30
            tooManyAttempts:
              summary: इस खाते पर बहुत ज़्यादा विफलताएँ
              value:
                error: too_many_attempts
                message: Too many attempts; wait before trying again.
                retryAfter: 4
    InternalError:
      description: '`internal_error`: अनपेक्षित विफलता। सर्वर इसे लॉग करता है।'
      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`: ग़लत `state` या `iss`, या Google ने कोड अस्वीकार कर दिया या ऐसा ID टोकन भेजा जिसका सत्यापन नहीं होता (दूसरे क्लाइंट, यानी गेम या वेबसाइट, का कोड या ID टोकन भी)। इसमें Google का टेक्स्ट कभी नहीं होता।'
      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` या `timeout`: `retryAfter` दिया गया हो, तो उसके बाद फिर कोशिश करें।'
      headers:
        Cache-Control:
          $ref: '#/components/headers/CacheControl'
        Retry-After:
          $ref: '#/components/headers/RetryAfter'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          examples:
            serverBusy:
              summary: सर्वर व्यस्त है
              value:
                error: server_busy
                message: The server is busy; try again in a few seconds.
                retryAfter: 9
            busy:
              summary: डेटाबेस लॉक रहा (पढ़ने का अनुरोध)
              value:
                error: busy
                message: Try again shortly.
                retryAfter: 1
            timeout:
              summary: सर्वर ने बहुत देर लगाई
              value:
                error: timeout
                message: The server took too long to answer; try again.
    GifFile:
      description: एनिमेटेड GIF, अटैचमेंट के रूप में।
      headers:
        Cache-Control:
          $ref: '#/components/headers/CacheControl'
        Content-Disposition:
          description: 'इस सर्वर की बाज़ी के लिए `attachment; filename="scacelith-<id>.gif"`, `POST /gif` से भेजे गए PGN के लिए `attachment; filename="scacelith-game.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: फ़ाइल का आकार, बाइट में।
          schema:
            type: integer
            minimum: 1
      content:
        image/gif:
          schema:
            type: string
            format: binary
    HtmlPage:
      description: एक HTML पेज।
      headers:
        Cache-Control:
          $ref: '#/components/headers/CacheControl'
      content:
        text/html:
          schema:
            $ref: '#/components/schemas/HtmlDocument'
    HtmlBadRequest:
      description: अस्वीकार का कारण समझाने वाला HTML पेज।
      headers:
        Cache-Control:
          $ref: '#/components/headers/CacheControl'
      content:
        text/html:
          schema:
            $ref: '#/components/schemas/HtmlDocument'
    HtmlRequestTimeout:
      description: फ़ॉर्म 10 सेकंड के भीतर नहीं पहुँचा (HTML पेज)। सर्वर कनेक्शन बंद कर देता है।
      headers:
        Cache-Control:
          $ref: '#/components/headers/CacheControl'
        Connection:
          $ref: '#/components/headers/ConnectionClose'
      content:
        text/html:
          schema:
            $ref: '#/components/schemas/HtmlDocument'
    HtmlConflict:
      description: टकराव समझाने वाला HTML पेज।
      headers:
        Cache-Control:
          $ref: '#/components/headers/CacheControl'
      content:
        text/html:
          schema:
            $ref: '#/components/schemas/HtmlDocument'
    HtmlPayloadTooLarge:
      description: फ़ॉर्म `HTTP_BODY_LIMIT` से बड़ा है (HTML पेज)। सर्वर कनेक्शन बंद कर देता है।
      headers:
        Cache-Control:
          $ref: '#/components/headers/CacheControl'
        Connection:
          $ref: '#/components/headers/ConnectionClose'
      content:
        text/html:
          schema:
            $ref: '#/components/schemas/HtmlDocument'
    HtmlUnsupportedMediaType:
      description: बॉडी न फ़ॉर्म (`application/x-www-form-urlencoded`) है, न JSON, या उसका charset UTF-8 नहीं है (HTML पेज)।
      headers:
        Cache-Control:
          $ref: '#/components/headers/CacheControl'
      content:
        text/html:
          schema:
            $ref: '#/components/schemas/HtmlDocument'
    HtmlTooManyRequests:
      description: रेट लिमिट (HTML पेज), `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: अनपेक्षित विफलता ("Server error" (सर्वर त्रुटि) शीर्षक वाला HTML पेज)। सर्वर इसे लॉग करता है।
      headers:
        Cache-Control:
          $ref: '#/components/headers/CacheControl'
      content:
        text/html:
          schema:
            $ref: '#/components/schemas/HtmlDocument'
    HtmlServiceUnavailable:
      description: सर्वर व्यस्त है (डेटाबेस लॉक रहा, या सर्वर काफ़ी पीछे चल रहा है; `Retry-After` के साथ) या उसने बहुत देर लगाई ("Server error" (सर्वर त्रुटि) शीर्षक वाला HTML पेज)।
      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: JSON API के हर त्रुटि जवाब का एकमात्र स्वरूप।
      required:
        - error
        - message
      properties:
        error:
          type: string
          pattern: '^[a-z][a-z0-9_]*$'
          description: '`snake_case` त्रुटि कोड। क्लाइंट इसी के आधार पर तय करता है कि क्या दिखाना है।'
          examples:
            - rate_limited
        message:
          type: string
          description: अंग्रेज़ी का एक वाक्य, लॉग के लिए और फ़ॉलबैक के रूप में।
        retryAfter:
          type: integer
          minimum: 1
          description: दोबारा कोशिश करने से पहले इंतज़ार के सेकंड, केवल उन अस्वीकारों में मौजूद जो समय बीतने पर ख़त्म हो जाते हैं। तब जवाब में उसी मान वाला `Retry-After` हेडर भी होता है, सिवाय इतिहास, बाज़ी, खिलाड़ी और लीडरबोर्ड पढ़ने वाले अनुरोधों के 503 `busy` के।
        field:
          type: string
          description: ग़लत फ़ील्ड या क्वेरी पैरामीटर (`invalid_request`, `invalid_option`, और `GET /account/games` के `invalid_cursor`, `invalid_limit` और `invalid_filter`)। नेस्टेड फ़ील्ड बिंदु से लिखा जाता है (`pow.nonce`)।
        reason:
          type: string
          description: 'कारण: वह नियम जिसे `weak_password` तोड़ता है, या प्रूफ़ ऑफ़ वर्क क्यों माँगा या अस्वीकार किया गया (`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: 'केवल `banned`: निलंबन का अंत (epoch से मिलीसेकंड), स्थायी निलंबन के लिए `null`।'
        line:
          type: integer
          minimum: 1
          description: 'केवल `invalid_pgn`: त्रुटि की पंक्ति, 1 से शुरू।'
        column:
          type: integer
          minimum: 1
          description: 'केवल `invalid_pgn`: त्रुटि का कॉलम, 1 से शुरू, वर्णों में।'
    PowChallenge:
      type: object
      description: प्रूफ़ ऑफ़ वर्क चैलेंज (428 `pow_required` का `pow`)।
      required:
        - challenge
        - bits
        - expiresAt
      properties:
        challenge:
          type: string
          pattern: '^[A-Za-z0-9_-]{16,400}\.[A-Za-z0-9_-]{43}$'
          description: हस्ताक्षरित चैलेंज, जिसे जैसा है वैसा ही वापस भेजना है।
        bits:
          type: integer
          minimum: 1
          maximum: 26
          description: शुरुआती शून्य बिट की वह संख्या जो `SHA-256(challenge + ":" + nonce)` में होनी चाहिए।
        expiresAt:
          type: integer
          format: int64
          description: चैलेंज की अवधि कब ख़त्म होती है (epoch से मिलीसेकंड), जारी होने के 2 मिनट बाद।
    PowAnswer:
      type: object
      description: प्रूफ़ ऑफ़ वर्क चैलेंज का जवाब।
      additionalProperties: false
      required:
        - challenge
        - nonce
      properties:
        challenge:
          type: string
          minLength: 16
          maxLength: 512
          description: 428 जवाब का `challenge`, जैसा दिया गया था।
        nonce:
          type: string
          pattern: '^[0-9]{1,20}$'
          description: अधिकतम 20 अंकों की दशमलव स्ट्रिंग, ऐसी कि `SHA-256(challenge + ":" + nonce)` `bits` शून्य बिट से शुरू हो।
    ServerInfo:
      type: object
      description: साइन इन करने या कनेक्ट होने से पहले क्लाइंट को जो चाहिए।
      required:
        - name
        - serverId
        - motd
        - protocol
        - wsPort
        - wsPath
        - registration
        - emailVerification
        - sso
        - mfa
        - pow
        - categories
        - limits
      properties:
        name:
          type: string
          description: सर्वर का नाम (`SERVER_NAME`)।
        serverId:
          type:
            - string
            - 'null'
          format: uuid
          description: एक UUID, जो डेटाबेस को पहली बार शुरू होने पर मिलता है। रीस्टार्ट के बाद भी यह वही रहता है। जब डेटाबेस इसे नहीं दे पाता, तब यह `null` होता है।
        motd:
          type: string
          description: आज का संदेश (`SERVER_MOTD`), ख़ाली भी हो सकता है।
        protocol:
          type: object
          description: WebSocket प्रोटोकॉल (`PROTOCOL.md`)।
          required:
            - min
            - max
            - schema
            - subprotocol
          properties:
            min:
              type: integer
              minimum: 1
              description: प्रोटोकॉल का सबसे पुराना संस्करण जिसे सर्वर समझता है।
            max:
              type: integer
              minimum: 1
              description: प्रोटोकॉल का सबसे नया संस्करण जिसे सर्वर समझता है।
            schema:
              type: integer
              format: int64
              minimum: 0
              maximum: 4294967295
              description: 'प्रोटोकॉल स्कीमा का फ़िंगरप्रिंट (उसके कैनोनिकल रूप के SHA-256 के पहले 4 बाइट, अनसाइन्ड पूर्णांक के रूप में: प्रोटोकॉल के माइनर संस्करण 2 के लिए `0xc649140f`, 3326678031); केवल जानकारी के लिए, इसकी कभी तुलना नहीं की जाती।'
            subprotocol:
              type: string
              description: WebSocket सबप्रोटोकॉल टोकन (`Sec-WebSocket-Protocol`)।
        wsPort:
          type: integer
          minimum: 0
          maximum: 65535
          description: खिलाड़ियों द्वारा इस्तेमाल किया जाने वाला WebSocket पोर्ट (`PUBLIC_WS_PORT`, नहीं तो `WS_PORT`, नहीं तो `API_PORT`)।
        wsPath:
          type: string
          const: /ws
          description: WebSocket का पाथ।
        registration:
          type: string
          enum:
            - open
            - closed
          description: क्या नए खाते बनाए जा सकते हैं।
        emailVerification:
          type: boolean
          description: क्या नए खाते अपने पते की पुष्टि करते हैं (`REQUIRE_EMAIL_VERIFICATION`)।
        sso:
          type: object
          required:
            - google
            - googleWeb
          properties:
            google:
              type: boolean
              description: क्या गेम के लिए Google साइन-इन उपलब्ध है।
            googleWeb:
              type: boolean
              description: क्या किसी वेबसाइट के लिए भी Google साइन-इन उपलब्ध है (`POST /auth/sso/google/web/start`)।
        mfa:
          type: boolean
          const: true
          description: दो-चरणीय सत्यापन हमेशा उपलब्ध है।
        pow:
          type: object
          required:
            - register
          properties:
            register:
              type: integer
              minimum: 0
              maximum: 26
              description: रजिस्ट्रेशन के लिए ज़रूरी प्रूफ़ ऑफ़ वर्क बिट (0 हो तो कोई नहीं)।
        categories:
          type: array
          description: आधिकारिक (रेटेड) टाइम कंट्रोल (`RATED_CATEGORIES`), सर्वर के क्रम में। कोई भी दूसरा टाइम कंट्रोल `custom` है।
          items:
            $ref: '#/components/schemas/Category'
        limits:
          type: object
          description: वे नियम जिन्हें क्लाइंट फ़ॉर्म भेजने से पहले जाँच सकता है।
          required:
            - usernameMin
            - usernameMax
            - usernamePattern
            - passwordMinLength
            - passwordMaxBytes
            - customTimeControls
            - reportsPerDay
            - wsMaxMessageBytes
          properties:
            usernameMin:
              type: integer
              description: उपयोगकर्ता नाम की न्यूनतम लंबाई (`USERNAME_MIN`)।
            usernameMax:
              type: integer
              description: उपयोगकर्ता नाम की अधिकतम लंबाई (`USERNAME_MAX`)।
            usernamePattern:
              type: string
              description: वह रेगुलर एक्सप्रेशन जिससे नया उपयोगकर्ता नाम मेल खाना चाहिए।
            passwordMinLength:
              type: integer
              description: पासवर्ड की न्यूनतम लंबाई, वर्णों में (`PASSWORD_MIN_LENGTH`)।
            passwordMaxBytes:
              type: integer
              description: पासवर्ड की अधिकतम लंबाई, UTF-8 के बाइट में।
            customTimeControls:
              type: boolean
              description: क्या चुनौतियों और निजी बाज़ियों में कस्टम टाइम कंट्रोल इस्तेमाल हो सकते हैं।
            reportsPerDay:
              type: integer
              description: एक खिलाड़ी 24 घंटे में कितनी शिकायतें दर्ज कर सकता है (`REPORTS_PER_DAY`)।
            wsMaxMessageBytes:
              type: integer
              description: क्लाइंट द्वारा भेजा जा सकने वाला सबसे बड़ा WebSocket संदेश, बाइट में।
      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: एक आधिकारिक टाइम कंट्रोल।
      required:
        - id
        - baseSec
        - incSec
      properties:
        id:
          type: string
          pattern: '^[0-9]+\+[0-9]+$'
          description: श्रेणी की आईडी, मिनट और इंक्रीमेंट के सेकंड (`3+2`)।
        baseSec:
          type: number
          minimum: 0
          description: मूल समय, सेकंड में।
        incSec:
          type: number
          minimum: 0
          description: प्रति चाल इंक्रीमेंट, सेकंड में।
    AccountView:
      type: object
      description: खाता, जैसा उसका खिलाड़ी उसे देखता है (साइन-इन जवाबों और `GET /account/me` का `user`)।
      required:
        - id
        - username
        - email
        - emailVerified
        - mfaEnabled
        - googleLinked
        - hasPassword
        - acceptChallenges
        - createdAt
        - lastLoginAt
        - pendingEmail
      properties:
        id:
          type: integer
          format: int64
          description: खाते की आईडी।
        username:
          type: string
          description: उपयोगकर्ता नाम।
        email:
          type: string
          format: email
          description: ईमेल पता।
        emailVerified:
          type: boolean
          description: क्या पते की पुष्टि हो चुकी है।
        mfaEnabled:
          type: boolean
          description: क्या दो-चरणीय सत्यापन चालू है।
        googleLinked:
          type: boolean
          description: क्या कोई Google खाता जुड़ा है।
        hasPassword:
          type: boolean
          description: 'Google के ज़रिए बने ऐसे खाते के लिए `false`, जिसने अभी तक पासवर्ड सेट नहीं किया है।'
        acceptChallenges:
          type: string
          enum:
            - all
            - none
          description: क्या नाम से दी गई सीधी चुनौतियाँ स्वीकार की जाती हैं (`PUT /account/preferences` देखें)।
        createdAt:
          type: integer
          format: int64
          description: खाता कब बनाया गया (epoch से मिलीसेकंड)।
        lastLoginAt:
          type:
            - integer
            - 'null'
          format: int64
          description: पिछला साइन-इन (epoch से मिलीसेकंड), या `null`।
        pendingEmail:
          type:
            - string
            - 'null'
          format: email
          description: लिंक की प्रतीक्षा कर रहे ईमेल बदलाव का नया पता, या `null`।
    SessionAnswer:
      type: object
      description: एक नया सेशन।
      required:
        - token
        - expiresAt
        - user
      properties:
        token:
          type: string
          pattern: '^sct_[A-Za-z0-9_-]{43}$'
          description: सेशन टोकन, `Authorization` हेडर और WebSocket के लिए।
        expiresAt:
          type: integer
          format: int64
          description: सेशन का निश्चित अंत (epoch से मिलीसेकंड); निष्क्रियता सीमा उसे इससे पहले भी ख़त्म कर सकती है।
        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: दो-चरणीय सत्यापन वाले साइन-इन का दूसरा चरण; `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: इस चरण का टोकन, `POST /auth/login/mfa` के लिए।
        expiresIn:
          type: integer
          const: 300
          description: चरण पूरा करने के लिए बचे सेकंड।
    LoginAnswer:
      description: एक सेशन, या दो-चरणीय सत्यापन वाले साइन-इन का दूसरा चरण।
      oneOf:
        - $ref: '#/components/schemas/SessionAnswer'
        - $ref: '#/components/schemas/MfaChallenge'
    SsoNeedsUsername:
      type: object
      description: पहला Google साइन-इन; `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: '`POST /auth/sso/complete` के लिए टिकट, 10 मिनट तक मान्य।'
        suggestedUsername:
          type: string
          description: Google नाम या पते से बना उपयोगकर्ता नाम, या कुछ भी उपयुक्त न हो तो `""`।
    SsoNeedsPassword:
      type: object
      description: पासवर्ड वाला एक खाता यह पता इस्तेमाल करता है; `POST /auth/sso/google/link` से आगे बढ़ें। अभी कुछ भी जोड़ा नहीं गया है।
      required:
        - needsPassword
        - linkTicket
        - username
        - expiresIn
      properties:
        needsPassword:
          type: boolean
          const: true
        linkTicket:
          type: string
          pattern: '^sso_[A-Za-z0-9_-]{43}$'
          description: '`POST /auth/sso/google/link` के लिए टिकट।'
        username:
          type: string
          description: उस खाते का उपयोगकर्ता नाम (इसे केवल वही देखता है जिसने Google के सामने साबित किया है कि यह पता उसका है)।
        expiresIn:
          type: integer
          const: 600
          description: टिकट कितने सेकंड तक मान्य रहता है।
    SsoFinishAnswer:
      description: '`POST /auth/sso/google/finish` का जवाब।'
      oneOf:
        - $ref: '#/components/schemas/SessionAnswer'
        - $ref: '#/components/schemas/MfaChallenge'
        - $ref: '#/components/schemas/SsoNeedsUsername'
        - $ref: '#/components/schemas/SsoNeedsPassword'
    SsoStartAnswer:
      type: object
      description: Google साइन-इन का एक प्रयास।
      required:
        - attemptId
        - authUrl
        - state
        - expiresIn
      properties:
        attemptId:
          type: string
          pattern: '^sso_[A-Za-z0-9_-]{43}$'
          description: प्रयास, `POST /auth/sso/google/finish` के लिए।
        authUrl:
          type: string
          format: uri
          description: सिस्टम ब्राउज़र में खोलने के लिए Google URL, जाँचने के बाद।
        state:
          type: string
          pattern: '^[A-Za-z0-9_-]{43}$'
          description: वह `state` जिसे Google को वापस भेजना होगा।
        expiresIn:
          type: integer
          const: 600
          description: प्रयास कितने सेकंड तक मान्य रहता है।
    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: उपयोगकर्ता नाम; फिर सर्वर के नियम लागू होते हैं (विवरण देखें)।
        email:
          type: string
          minLength: 1
          maxLength: 254
          description: ईमेल पता। आगे-पीछे के ख़ाली स्थान हटाकर छोटे अक्षरों में सहेजा जाता है।
        password:
          type: string
          minLength: 1
          maxLength: 1024
          description: पासवर्ड; फिर पासवर्ड के नियम लागू होते हैं।
        pow:
          $ref: '#/components/schemas/PowAnswer'
    LoginRequest:
      type: object
      additionalProperties: false
      required:
        - login
        - password
      properties:
        login:
          type: string
          minLength: 1
          maxLength: 254
          description: उपयोगकर्ता नाम, या ईमेल पता (`@` वाला कोई भी टेक्स्ट)।
        password:
          type: string
          minLength: 1
          maxLength: 1024
        clientLabel:
          $ref: '#/components/schemas/ClientLabel'
        pow:
          $ref: '#/components/schemas/PowAnswer'
    ClientLabel:
      type: string
      maxLength: 64
      description: वैकल्पिक। साइन-इन वाले डिवाइस की सूची में दिखाया जाता है (जैसे `Scacelith 1.4 (Windows)`)।
    MfaLoginRequest:
      type: object
      additionalProperties: false
      required:
        - mfaToken
      properties:
        mfaToken:
          type: string
          minLength: 1
          maxLength: 64
          description: साइन-इन जवाब (या Google साइन-इन के जवाब) का `mfaToken`।
        code:
          type: string
          maxLength: 32
          description: वैकल्पिक। 6 अंकों का ऑथेंटिकेटर कोड, या रिकवरी कोड।
        recoveryCode:
          type: string
          maxLength: 32
          description: वैकल्पिक। रिकवरी कोड। `code` और `recoveryCode` में से एक ज़रूरी है।
    EmailRequest:
      type: object
      additionalProperties: false
      required:
        - email
      properties:
        email:
          type: string
          minLength: 1
          maxLength: 254
          description: ईमेल पता।
    PasswordResetRequest:
      type: object
      additionalProperties: false
      required:
        - token
        - newPassword
      properties:
        token:
          type: string
          minLength: 1
          maxLength: 128
          description: रीसेट लिंक का `token` पैरामीटर।
        newPassword:
          type: string
          minLength: 1
          maxLength: 1024
          description: नया पासवर्ड; रजिस्ट्रेशन के पासवर्ड नियम लागू होते हैं।
    SsoStartRequest:
      type: object
      additionalProperties: false
      required:
        - codeChallenge
        - redirectPort
      properties:
        codeChallenge:
          type: string
          pattern: '^[A-Za-z0-9_-]{43}$'
          description: क्लाइंट के `codeVerifier` का S256 PKCE चैलेंज (`BASE64URL(SHA-256(codeVerifier))`)।
        redirectPort:
          type: integer
          minimum: 1024
          maximum: 65535
          description: '`127.0.0.1` पर क्लाइंट के लिसनर का पोर्ट।'
    SsoWebStartRequest:
      type: object
      additionalProperties: false
      required:
        - codeChallenge
      properties:
        codeChallenge:
          type: string
          pattern: '^[A-Za-z0-9_-]{43}$'
          description: पेज के `codeVerifier` का S256 PKCE चैलेंज (`BASE64URL(SHA-256(codeVerifier))`)।
    SsoFinishRequest:
      type: object
      additionalProperties: false
      required:
        - attemptId
        - codeVerifier
        - state
        - code
      properties:
        attemptId:
          type: string
          minLength: 1
          maxLength: 64
          description: '`POST /auth/sso/google/start` का `attemptId`।'
        codeVerifier:
          type: string
          pattern: '^[A-Za-z0-9._~-]{43,128}$'
          description: PKCE वेरिफ़ायर; इसका SHA-256 `start` को दिए गए चैलेंज से मेल खाना चाहिए।
        state:
          type: string
          pattern: '^[A-Za-z0-9_-]{43}$'
          description: '`state`, जैसा Google ने वापस भेजा।'
        code:
          type: string
          minLength: 1
          maxLength: 2048
          pattern: '^[\x21-\x7E]+$'
          description: '`code`, जैसा Google ने वापस भेजा (स्पेस के बिना प्रिंट होने योग्य ASCII)।'
        iss:
          type: string
          minLength: 1
          maxLength: 256
          description: वैकल्पिक। `iss`, जैसा Google ने वापस भेजा, अगर भेजा हो।
        clientLabel:
          $ref: '#/components/schemas/ClientLabel'
    SsoLinkRequest:
      type: object
      additionalProperties: false
      required:
        - linkTicket
        - password
      properties:
        linkTicket:
          type: string
          minLength: 1
          maxLength: 64
          description: '`finish` का `linkTicket`, 10 मिनट तक मान्य।'
        password:
          type: string
          minLength: 1
          maxLength: 1024
          description: खाते का पासवर्ड।
        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: '`finish` का `ssoTicket`, 10 मिनट तक मान्य।'
        username:
          type: string
          minLength: 1
          maxLength: 64
          description: उपयोगकर्ता नाम; रजिस्ट्रेशन के नियम लागू होते हैं।
        clientLabel:
          $ref: '#/components/schemas/ClientLabel'
    SessionList:
      type: object
      required:
        - sessions
      properties:
        sessions:
          type: array
          description: सक्रिय सेशन, सबसे हाल में इस्तेमाल हुआ सबसे पहले।
          items:
            $ref: '#/components/schemas/SessionEntry'
    SessionEntry:
      type: object
      description: एक सक्रिय सेशन (साइन-इन वाला डिवाइस)।
      required:
        - id
        - createdAt
        - lastSeenAt
        - expiresAt
        - clientLabel
        - current
      properties:
        id:
          type: integer
          format: int64
          description: सेशन की आईडी, `DELETE /auth/sessions/{id}` के लिए।
        createdAt:
          type: integer
          format: int64
          description: साइन-इन का समय (epoch से मिलीसेकंड)।
        lastSeenAt:
          type: integer
          format: int64
          description: पिछला इस्तेमाल (epoch से मिलीसेकंड), अधिक से अधिक हर 5 मिनट में अपडेट होता है (सर्वर के काफ़ी पीछे चलने पर इससे भी देर से)।
        expiresAt:
          type: integer
          format: int64
          description: निश्चित अंत (epoch से मिलीसेकंड); निष्क्रियता सीमा सेशन को इससे पहले भी ख़त्म कर सकती है।
        clientLabel:
          type:
            - string
            - 'null'
          description: साइन-इन का `clientLabel`, या अगर उसने कोई नहीं भेजा तो `null`।
        current:
          type: boolean
          description: क्या यही वह सेशन है जिससे अनुरोध किया जा रहा है।
    AccountMe:
      type: object
      required:
        - user
        - ratings
        - sanctions
        - ban
      properties:
        user:
          $ref: '#/components/schemas/AccountView'
        ratings:
          type: array
          description: हर उस श्रेणी का एक रिकॉर्ड जिसमें खिलाड़ी ने रेटेड बाज़ियाँ खेली हैं।
          items:
            $ref: '#/components/schemas/RatingSummary'
        sanctions:
          type: array
          description: सक्रिय दंड।
          items:
            $ref: '#/components/schemas/ActiveSanction'
        ban:
          description: 'निलंबन के दौरान `{ until }`, नहीं तो `null`।'
          oneOf:
            - $ref: '#/components/schemas/Ban'
            - type: 'null'
    RatingSummary:
      type: object
      description: एक श्रेणी का रेटिंग रिकॉर्ड।
      required:
        - category
        - rating
        - games
        - wins
        - draws
        - losses
        - peak
        - provisional
      properties:
        category:
          type: string
          description: श्रेणी की आईडी।
        rating:
          type: integer
          description: रेटिंग।
        games:
          type: integer
          minimum: 0
          description: इस श्रेणी में खेली गई बाज़ियाँ (गिनी गई हों या नहीं)।
        wins:
          type: integer
          minimum: 0
        draws:
          type: integer
          minimum: 0
        losses:
          type: integer
          minimum: 0
        peak:
          type: integer
          description: अब तक की सबसे ऊँची रेटिंग।
        provisional:
          type: boolean
          description: 'जब तक रेटिंग अनरेटेड है या उसमें `PROVISIONAL_GAMES` से कम गिनी गई बाज़ियाँ हैं, तब तक `true` (गेम इसे `1510?` के रूप में दिखाता है)।'
    ActiveSanction:
      type: object
      required:
        - kind
        - reason
        - startsAt
        - endsAt
      properties:
        kind:
          type: string
          enum:
            - ban
            - mm_block
            - warning
          description: दंड का प्रकार।
        reason:
          type:
            - string
            - 'null'
          description: बताया गया कारण, अगर कोई हो।
        startsAt:
          type: integer
          format: int64
          description: शुरुआत (epoch से मिलीसेकंड)।
        endsAt:
          type:
            - integer
            - 'null'
          format: int64
          description: अंत (epoch से मिलीसेकंड), दंड स्थायी होने पर `null`।
    Ban:
      type: object
      required:
        - until
      properties:
        until:
          type:
            - integer
            - 'null'
          format: int64
          description: निलंबन का अंत (epoch से मिलीसेकंड), स्थायी होने पर `null`।
    Preferences:
      type: object
      additionalProperties: false
      required:
        - acceptChallenges
      properties:
        acceptChallenges:
          type: string
          enum:
            - all
            - none
          description: 'नाम से दी गई सीधी चुनौतियाँ स्वीकार करने के लिए `all`, अस्वीकार करने के लिए `none`।'
    PasswordChangeRequest:
      type: object
      additionalProperties: false
      required:
        - currentPassword
        - newPassword
      properties:
        currentPassword:
          type: string
          minLength: 1
          maxLength: 1024
          description: मौजूदा पासवर्ड।
        newPassword:
          type: string
          minLength: 1
          maxLength: 1024
          description: नया पासवर्ड; रजिस्ट्रेशन के पासवर्ड नियम लागू होते हैं।
    PasswordOnlyRequest:
      type: object
      additionalProperties: false
      required:
        - password
      properties:
        password:
          type: string
          minLength: 1
          maxLength: 1024
          description: खाते का पासवर्ड।
    TotpEnableRequest:
      type: object
      additionalProperties: false
      required:
        - code
      properties:
        code:
          type: string
          pattern: '^[0-9]{6}$'
          description: लंबित सीक्रेट का एक कोड, ठीक 6 अंक।
    ReauthCredentials:
      type: object
      additionalProperties: false
      required:
        - password
      properties:
        password:
          type: string
          minLength: 1
          maxLength: 1024
          description: खाते का पासवर्ड।
        code:
          type: string
          minLength: 1
          maxLength: 32
          description: वैकल्पिक; दो-चरणीय सत्यापन होने पर ज़रूरी (या `recoveryCode`)। ऑथेंटिकेटर कोड, या रिकवरी कोड।
        recoveryCode:
          type: string
          minLength: 1
          maxLength: 32
          description: वैकल्पिक। रिकवरी कोड (`xxxx-xxxx-xx`; छोटे-बड़े अक्षर, स्पेस और डैश से फ़र्क़ नहीं पड़ता)।
    RecoveryCodesRequest:
      type: object
      additionalProperties: false
      required:
        - password
        - code
      properties:
        password:
          type: string
          minLength: 1
          maxLength: 1024
          description: खाते का पासवर्ड।
        code:
          type: string
          minLength: 1
          maxLength: 32
          description: ऑथेंटिकेटर कोड (रिकवरी कोड अस्वीकार किया जाता है)।
    EmailChangeRequest:
      type: object
      additionalProperties: false
      required:
        - newEmail
        - password
      properties:
        newEmail:
          type: string
          minLength: 1
          maxLength: 254
          description: नया पता; आगे-पीछे के ख़ाली स्थान हटाकर और छोटे अक्षरों में बदलकर, फिर रजिस्ट्रेशन के पते की तरह जाँचा जाता है।
        password:
          type: string
          minLength: 1
          maxLength: 1024
          description: खाते का पासवर्ड।
        code:
          type: string
          minLength: 1
          maxLength: 32
          description: वैकल्पिक; दो-चरणीय सत्यापन होने पर ज़रूरी (या `recoveryCode`)। ऑथेंटिकेटर कोड, या रिकवरी कोड।
        recoveryCode:
          type: string
          minLength: 1
          maxLength: 32
          description: वैकल्पिक। रिकवरी कोड।
    TotpSetup:
      type: object
      description: ऑथेंटिकेटर ऐप के लिए लंबित सीक्रेट।
      required:
        - secret
        - uri
        - algorithm
        - digits
        - period
      properties:
        secret:
          type: string
          pattern: '^[A-Z2-7]+$'
          description: base32 में सीक्रेट, ऐप में टाइप करने के लिए।
        uri:
          type: string
          format: uri
          description: '`otpauth://totp/...` URI, QR कोड के रूप में दिखाने के लिए।'
        algorithm:
          type: string
          const: SHA1
        digits:
          type: integer
          const: 6
        period:
          type: integer
          const: 30
          description: प्रति चरण सेकंड।
    RecoveryCodeList:
      type: array
      description: 10 रिकवरी कोड, जो केवल इसी एक बार दिखाए जाते हैं। हर कोड एक बार काम करता है।
      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: बाज़ी का एक पक्ष।
      required:
        - name
        - rating
        - ratingAfter
        - ratingDiff
      properties:
        name:
          type: string
          description: बाज़ी के रिकॉर्ड में नाम (हटाए गए खाते के लिए `deleted#<id>`)।
        rating:
          type:
            - integer
            - 'null'
          description: शुरुआत में रेटिंग, अज्ञात होने पर `null`।
        ratingAfter:
          type:
            - integer
            - 'null'
          description: बाज़ी के बाद की रेटिंग। रेटेड बाज़ी में यह हमेशा होती है; `null` केवल उस बाज़ी के लिए जो रेटिंग में नहीं गिनी जाती (कैज़ुअल, कस्टम, रद्द)।
        ratingDiff:
          type:
            - integer
            - 'null'
          description: रेटिंग में बदलाव; जब रेटिंग के नियमों के अनुसार रेटिंग नहीं बदलती, तब `0` (FIDE का शून्य स्कोर, अनरेटेड प्रतिद्वंद्वी, किसी अनरेटेड खिलाड़ी की शुरुआती बाज़ियाँ); `ratingAfter` की तरह `null`।
    GameSummary:
      type: object
      description: सहेजी गई बाज़ी का सारांश।
      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: बाज़ी की आईडी।
        category:
          type: string
          description: आधिकारिक श्रेणी की आईडी (`3+2`), या `custom`।
        rated:
          type: boolean
          description: क्या बाज़ी रेटिंग में गिनी जाती है।
        timeControl:
          type: string
          pattern: '^[0-9]+\+[0-9]+$'
          description: सेकंड में टाइम कंट्रोल (`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` (बाज़ी के कोड देखें)।'
        reason:
          type: integer
          minimum: 0
          maximum: 255
          description: समाप्ति के कारण का कोड (बाज़ी के कोड देखें)।
        result:
          type: string
          enum:
            - 1-0
            - 0-1
            - 1/2-1/2
            - '*'
          description: परिणाम, रद्द बाज़ी के लिए `*`।
        termination:
          $ref: '#/components/schemas/Termination'
        plies:
          type: integer
          minimum: 0
          description: अर्ध-चालों की संख्या।
        startedAt:
          type: integer
          format: int64
          description: शुरुआत (epoch से मिलीसेकंड)।
        endedAt:
          type: integer
          format: int64
          description: अंत (epoch से मिलीसेकंड)।
    Termination:
      type: string
      description: समाप्ति के कारण का नाम (बाज़ी के कोड देखें); ऐसे कोड के लिए `Unknown`, जिसे प्रोटोकॉल नहीं जानता।
      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: एक खिलाड़ी की ओर से बाज़ी का सारांश।
      allOf:
        - $ref: '#/components/schemas/GameSummary'
        - type: object
          required:
            - color
          properties:
            color:
              type: string
              enum:
                - white
                - black
              description: खिलाड़ी का पक्ष।
    HistoryGameSummary:
      description: साइन इन किए हुए खिलाड़ी के इतिहास की एक बाज़ी।
      allOf:
        - $ref: '#/components/schemas/PlayerGameSummary'
        - type: object
          required:
            - baseMs
            - incMs
            - outcome
          properties:
            baseMs:
              type: integer
              format: int64
              minimum: 0
              description: मूल समय, मिलीसेकंड में।
            incMs:
              type: integer
              format: int64
              minimum: 0
              description: इंक्रीमेंट, मिलीसेकंड में।
            outcome:
              type: string
              enum:
                - win
                - loss
                - draw
                - aborted
              description: खिलाड़ी की ओर से नतीजा।
    HistoryPage:
      type: object
      required:
        - games
        - next
        - total
      properties:
        games:
          type: array
          description: इस पेज की बाज़ियाँ, सबसे नई पहले।
          items:
            $ref: '#/components/schemas/HistoryGameSummary'
        next:
          type:
            - integer
            - 'null'
          format: int64
          description: अगले पेज के लिए `before` के रूप में भेजी जाने वाली आईडी, या आख़िरी पेज पर `null`।
        total:
          type: integer
          minimum: 0
          description: सभी पेजों में फ़िल्टर से मेल खाने वाली बाज़ियों की संख्या।
    MoveRecord:
      type: object
      description: बाज़ी की एक अर्ध-चाल।
      required:
        - uci
        - spentMs
        - clockMs
      properties:
        uci:
          type: string
          pattern: '^[a-h][1-8][a-h][1-8][nbrq]?$'
          description: UCI नोटेशन में चाल, प्रमोशन के लिए `n`, `b`, `r` या `q` जोड़कर।
        spentMs:
          type:
            - integer
            - 'null'
          minimum: 0
          description: चाल के लिए गिना गया समय (ms), रिकॉर्ड में न हो तो `null`।
        clockMs:
          type:
            - integer
            - 'null'
          minimum: 0
          description: चाल के बाद चाल चलने वाले की घड़ी (ms), रिकॉर्ड में न हो तो `null`।
    PgnTagsView:
      type: object
      description: मुख्य PGN टैग, दिखाने के लिए। यहाँ `Termination` समाप्ति के कारण का नाम है; PGN फ़ाइल PGN मानक के मान इस्तेमाल करती है।
      required:
        - Event
        - Site
        - Date
        - Round
        - White
        - Black
        - Result
        - WhiteElo
        - BlackElo
        - TimeControl
        - Termination
        - PlyCount
      properties:
        Event:
          type: string
          description: '`<SERVER_NAME> rated <category>` या `<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: 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: शुरुआत में रेटिंग, या `"-"`।
          oneOf:
            - type: integer
            - type: string
              const: '-'
        BlackElo:
          description: शुरुआत में रेटिंग, या `"-"`।
          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: बाज़ी का रिकॉर्ड, उसकी चालों और घड़ियों के साथ। इसमें बाज़ी के सारांश के फ़ील्ड होते हैं, `color` और `outcome` के बिना।
      allOf:
        - $ref: '#/components/schemas/GameSummary'
        - type: object
          required:
            - baseMs
            - incMs
            - statusName
            - rematchOf
            - moves
            - pgn
          properties:
            baseMs:
              type: integer
              format: int64
              minimum: 0
              description: मूल समय, मिलीसेकंड में।
            incMs:
              type: integer
              format: int64
              minimum: 0
              description: इंक्रीमेंट, मिलीसेकंड में।
            statusName:
              type: string
              enum:
                - WhiteWins
                - BlackWins
                - Draw
                - Aborted
              description: '`status` का नाम।'
            rematchOf:
              type:
                - integer
                - 'null'
              format: int64
              description: उस बाज़ी की आईडी जिसका यह रीमैच है, या `null`।
            moves:
              type: array
              description: हर अर्ध-चाल के लिए एक प्रविष्टि।
              items:
                $ref: '#/components/schemas/MoveRecord'
            pgn:
              $ref: '#/components/schemas/PgnTagsView'
            you:
              type: string
              enum:
                - white
                - black
              description: केवल तब, जब टोकन वाले खिलाड़ी ने यह बाज़ी खेली हो; उसका पक्ष।
            reportable:
              type: boolean
              description: केवल तब, जब टोकन वाले खिलाड़ी ने यह बाज़ी खेली हो; `true` जब `POST /reports` अभी इस बाज़ी के लिए प्रतिद्वंद्वी की शिकायत स्वीकार करेगा (बाज़ी 7 दिनों के भीतर ख़त्म हुई, दैनिक कोटा पूरा नहीं हुआ है, और इस बाज़ी के लिए प्रतिद्वंद्वी की शिकायत पहले से नहीं की गई है)।
    PlayerProfile:
      type: object
      required:
        - username
        - createdAt
        - ratings
        - games
      properties:
        username:
          type: string
          description: उपयोगकर्ता नाम, जैसा खिलाड़ी ने लिखा।
        createdAt:
          type: integer
          format: int64
          description: खाता कब बनाया गया (epoch से मिलीसेकंड)।
        ratings:
          type: array
          description: केवल आधिकारिक श्रेणियाँ, सर्वर के क्रम में।
          items:
            $ref: '#/components/schemas/RatingSummary'
        games:
          type: object
          required:
            - total
            - rated
            - wins
            - draws
            - losses
          properties:
            total:
              type: integer
              minimum: 0
              description: हर सहेजी गई बाज़ी, कैज़ुअल और रद्द बाज़ियों सहित।
            rated:
              type: integer
              minimum: 0
              description: रेटेड बाज़ियाँ, रेटिंग रिकॉर्ड से जोड़कर।
            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: उपयोगकर्ता नाम, जैसा खिलाड़ी ने लिखा।
        games:
          type: array
          description: इस पेज की बाज़ियाँ, सबसे नई पहले।
          items:
            $ref: '#/components/schemas/PlayerGameSummary'
        next:
          type:
            - integer
            - 'null'
          format: int64
          description: जब भी पेज भरा हो, आख़िरी बाज़ी की आईडी (तब अगला पेज ख़ाली हो सकता है), नहीं तो `null`।
    Leaderboard:
      type: object
      required:
        - category
        - minGames
        - updatedAt
        - players
      properties:
        category:
          type: string
          description: श्रेणी की आईडी।
        minGames:
          type: integer
          minimum: 0
          description: सूची में आने के लिए किसी रिकॉर्ड को जितनी गिनी गई बाज़ियाँ चाहिए (`PROVISIONAL_GAMES`)।
        updatedAt:
          type: integer
          format: int64
          description: लीडरबोर्ड की गणना कब हुई (epoch से मिलीसेकंड)।
        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 टेक्स्ट, UTF-8 के अधिकतम 65,536 बाइट। केवल पहली बाज़ी इस्तेमाल होती है।
        size:
          type: string
          enum:
            - small
            - medium
            - large
          default: medium
          description: वैकल्पिक। चित्र का आकार।
        orientation:
          type: string
          enum:
            - white
            - black
          default: white
          description: वैकल्पिक। बोर्ड पर नीचे की ओर दिखने वाला पक्ष।
        delayMs:
          type: integer
          minimum: 100
          maximum: 3000
          default: 500
          description: वैकल्पिक। प्रति चाल मिलीसेकंड, पूर्णांक मान वाली JSON संख्या।
        coords:
          type: boolean
          default: true
          description: वैकल्पिक। क्या बोर्ड के चारों ओर फ़ाइल के अक्षर और रैंक की संख्याएँ बनाई जाएँ।
    ReportRequest:
      type: object
      required:
        - gameId
        - reported
        - category
      properties:
        gameId:
          description: बाज़ी, पूर्णांक के रूप में या 1 से 16 अंकों की स्ट्रिंग के रूप में।
          oneOf:
            - type: integer
              format: int64
              minimum: 1
              maximum: 9007199254740991
            - type: string
              pattern: '^[0-9]{1,16}$'
        reported:
          type: string
          minLength: 1
          maxLength: 24
          description: प्रतिद्वंद्वी का उपयोगकर्ता नाम, जैसा बाज़ी के रिकॉर्ड में है या जैसा अभी है, छोटे-बड़े अक्षरों का भेद किए बिना। यह ख़ाली नहीं हो सकता।
        category:
          type: string
          enum:
            - cheating
            - abuse
            - other
          description: शिकायत किस बारे में है।
        comment:
          type:
            - string
            - 'null'
          description: वैकल्पिक। कंट्रोल वर्ण (टैब और लाइन फ़ीड को छोड़कर) और टेक्स्ट के आगे-पीछे के ख़ाली स्थान हटाने के बाद अधिकतम 500 वर्ण।
    AccountExport:
      type: object
      description: सर्वर खाते के बारे में जो कुछ भी रखता है (समय epoch से मिलीसेकंड में)।
      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: एक्सपोर्ट कब बनाया गया।
        server:
          type: object
          required:
            - name
            - host
          properties:
            name:
              type: string
              description: '`SERVER_NAME`।'
            host:
              type: string
              description: '`SERVER_PUBLIC_HOST`।'
        notes:
          type: array
          description: फ़ाइल में क्या है और क्या छोड़ा गया है, खिलाड़ी के लिए सरल अंग्रेज़ी में।
          items:
            type: string
        account:
          description: '`GET /account/me` वाला खाते का व्यू, `googleEmail` के साथ।'
          allOf:
            - $ref: '#/components/schemas/AccountView'
            - type: object
              required:
                - googleEmail
              properties:
                googleEmail:
                  type:
                    - string
                    - 'null'
                  description: जुड़े हुए Google खाते का पता, या `null`।
        ratings:
          type: array
          description: पूरे रेटिंग रिकॉर्ड।
          items:
            $ref: '#/components/schemas/ExportRating'
        ratingRefunds:
          type: array
          description: किसी प्रतिद्वंद्वी के धोखाधड़ी करते पाए जाने के बाद लौटाए गए रेटिंग अंक, हर UTC दिन और श्रेणी के हिसाब से जोड़कर, सबसे नए पहले। न बाज़ियाँ बताई जाती हैं, न धोखेबाज़ों के नाम।
          items:
            $ref: '#/components/schemas/RatingRefund'
        sessions:
          type: array
          description: हर सहेजा गया सेशन, सबसे नया पहले, बिना किसी टोकन के। रिटेंशन की सफ़ाई अवधि ख़त्म हो चुके सेशन को हटा देती है, और साइन आउट हुए सेशन को साइन-आउट के एक दिन बाद।
          items:
            $ref: '#/components/schemas/ExportSession'
        securityEvents:
          type: array
          description: सुरक्षा घटनाएँ, सबसे नई पहले, `RETENTION_SECURITY_DAYS` तक रखी जाती हैं।
          items:
            $ref: '#/components/schemas/SecurityEvent'
        sanctions:
          type: array
          description: हर दंड, हटाए जा चुके दंडों सहित। मॉडरेटर का नाम कभी शामिल नहीं होता।
          items:
            $ref: '#/components/schemas/ExportSanction'
        conduct:
          type: array
          description: खिलाड़ी की छोड़ी गई, रद्द और "समय पर पहली चाल नहीं" वाली बाज़ियों के लिए दर्ज आचरण घटनाएँ, 30 दिन तक रखी जाती हैं।
          items:
            $ref: '#/components/schemas/ConductEvent'
        reportsFiled:
          type: array
          description: खिलाड़ी द्वारा की गई शिकायतें।
          items:
            $ref: '#/components/schemas/FiledReport'
        games:
          type: object
          required:
            - total
            - list
          properties:
            total:
              type: integer
              minimum: 0
              description: बाज़ियों की संख्या।
            list:
              type: array
              description: हर बाज़ी, सबसे नई पहले, `GET /account/games` के सारांशों के रूप में। चालें `GET /games/{id}` पर हैं और PGN `GET /games/{id}/pgn` पर।
              items:
                $ref: '#/components/schemas/HistoryGameSummary'
    ExportRating:
      description: पूरा रेटिंग रिकॉर्ड।
      allOf:
        - $ref: '#/components/schemas/RatingSummary'
        - type: object
          required:
            - rated
            - countedGames
            - updatedAt
          properties:
            rated:
              type: boolean
              description: क्या खिलाड़ी अनरेटेड चरण से बाहर आ चुका है।
            countedGames:
              type: integer
              minimum: 0
              description: रेटिंग में गिनी गई बाज़ियाँ।
            updatedAt:
              type: integer
              format: int64
              description: रिकॉर्ड आख़िरी बार कब बदला।
    RatingRefund:
      type: object
      required:
        - day
        - category
        - points
      properties:
        day:
          type: integer
          format: int64
          description: उस दिन का 00:00 UTC (epoch से मिलीसेकंड)।
        category:
          type: string
        points:
          type: integer
          description: उस दिन उस श्रेणी में लौटाए गए अंक।
    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: सेशन कब साइन आउट हुआ, या `null`।
        clientLabel:
          type:
            - string
            - 'null'
        ip:
          type:
            - string
            - 'null'
          description: साइन-इन का पता, `RETENTION_IP_DAYS` के बाद मिटा दिया जाता है।
    SecurityEvent:
      type: object
      description: |-
        एक सुरक्षा घटना। `ip` केवल उन कामों के लिए दिया जाता है जो साइन इन रहते हुए, खाते के पासवर्ड (और दूसरे फ़ैक्टर) से, या खाते के पते पर भेजे गए लिंक से किए गए: `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` और `account_exported`; हर दूसरे प्रकार में `ip: null` होता है, और `ip` `RETENTION_IP_DAYS` के बाद मिटा दिया जाता है।

        `detail` में केवल ये फ़ील्ड रहते हैं: `login`: `method`; `sso_login`, `sso_account_created`: `provider`; `sso_linked`: `provider` और `method` (`password`, या `password+totp`); `login_failed`: `failures`; `login_lockout`: `retryAfterMs`; `mfa_failed`: `attempts`; `recovery_code_used`: `remaining`; `reauth_failed`: `factor`; `session_revoked` और `sessions_revoked_all`: `reason`; `email_change_refused`: `reason`; `sanction_auto`: `kind`, `gameId`, `until`। `moderator_action` घटना में केवल `{ action }` रहता है, `ban`, `unban`, `reset_mfa`, `verify_email` और `revoke_sessions` कार्रवाइयों के लिए (मॉडरेटर की बाक़ी कार्रवाइयाँ छोड़ दी जाती हैं)। हर दूसरे प्रकार में `detail: null` होता है। `rating_refund` घटनाएँ छोड़ दी जाती हैं: उनके अंक `ratingRefunds` में हैं।
      required:
        - kind
        - at
        - ip
        - detail
      properties:
        kind:
          type: string
          description: घटना का प्रकार (`login`, `password_changed`...)।
        at:
          type: integer
          format: int64
          description: यह कब हुआ।
        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: जिस खिलाड़ी की शिकायत की गई, उसका मौजूदा सार्वजनिक नाम।
        category:
          type: string
          enum:
            - cheating
            - abuse
            - other
        comment:
          type:
            - string
            - 'null'
        createdAt:
          type: integer
          format: int64
        status:
          type: string
          enum:
            - open
            - closed
          description: क्या शिकायत अभी भी खुली है (यह नहीं बताया जाता कि जिस खिलाड़ी की शिकायत की गई, उसे दंड मिला या नहीं)।
    LinkTokenForm:
      type: object
      additionalProperties: false
      required:
        - token
      properties:
        token:
          type: string
          minLength: 1
          maxLength: 128
          description: ईमेल लिंक का टोकन (पेज के फ़ॉर्म का छिपा हुआ फ़ील्ड)।
    ResetPasswordForm:
      type: object
      additionalProperties: false
      required:
        - token
        - newPassword
        - confirmPassword
      properties:
        token:
          type: string
          minLength: 1
          maxLength: 128
          description: रीसेट लिंक का टोकन (फ़ॉर्म का छिपा हुआ फ़ील्ड)।
        newPassword:
          type: string
          minLength: 1
          maxLength: 1024
          description: नया पासवर्ड; रजिस्ट्रेशन के पासवर्ड नियम लागू होते हैं।
        confirmPassword:
          type: string
          minLength: 1
          maxLength: 1024
          description: नया पासवर्ड दोबारा; यह एक जैसा होना चाहिए।
    HtmlDocument:
      type: string
      contentMediaType: text/html
      description: UTF-8 में HTML पेज (`text/html; charset=utf-8`), JavaScript या बाहरी संसाधनों के बिना।
