{"openapi":"3.1.0","info":{"title":"WordQuest API","version":"1.0.0","description":"Full API for the WordQuest Japanese learning platform. Covers 146 courses, 8,200+ vocabulary words, 500+ grammar patterns, AI tutoring (Sage), community features (Tavern), PvP duels, gacha shrine, squads, and the Neko Coin economy. Public learning endpoints require no authentication. All mutation endpoints require a session cookie, bearer token, or API key.","contact":{"name":"WordQuest","url":"https://wordquest.solanalink.jp"},"license":{"name":"Educational Use"},"x-logo":{"url":"https://wordquest.solanalink.jp/images/logo-wide.png","altText":"WordQuest","href":"https://wordquest.solanalink.jp"}},"servers":[{"url":"https://wordquest.solanalink.jp","description":"Production"},{"url":"http://localhost:3000","description":"Local Development"}],"externalDocs":{"description":"Full API & Platform Reference (LLM-optimized)","url":"https://wordquest.solanalink.jp/llms-full.txt"},"tags":[{"name":"Courses","x-displayName":"Courses (146 Japanese Courses)","description":"Japanese language course catalog covering JLPT N5 to N3. Each course contains 10-100 vocabulary words with professional audio."},{"name":"Vocabulary","description":"Vocabulary words with readings, meanings in 8 languages, and example sentences. 8,200+ words searchable by text, romaji, or translation."},{"name":"Grammar","description":"Grammar patterns with formulas, explanations, and example sentences. 500+ patterns organized by JLPT level."},{"name":"JLPT","description":"JLPT exam level information, statistics, and recommended study plans. Levels N5 (beginner) through N1 (advanced)."},{"name":"Platform","description":"Platform statistics, supported languages, API health, and feedback."},{"name":"Study","description":"Study history tracking, study strategies, and watchlist management."},{"name":"Quiz","description":"Quiz sessions — start, submit answers, and view history."},{"name":"Analytics","description":"Learning analytics: summary statistics, daily activity heatmaps, course progress, and quiz performance."},{"name":"Sage","x-displayName":"AI Tutor — Sage","description":"AI-powered Japanese tutor. Ask questions, get explanations, and practice conversation.\n\n**Billing:** 3 free questions/day, then 50 Neko Coins per question.\n\n**Models:** AWS Bedrock (Claude) and Google Gemini."},{"name":"Speaking","description":"AI speaking practice — conversational chat and pronunciation evaluation."},{"name":"Writing","description":"AI writing correction — submit Japanese text for grammar and style feedback."},{"name":"Auth","description":"Authentication: email/password login, registration, password management, and session handling."},{"name":"OAuth","description":"OAuth provider sign-in flows for Google, Apple, Twitter, Telegram, Discord, and LINE."},{"name":"NativeAuth","description":"Mobile-native OAuth deep link flows for iOS and Android apps."},{"name":"TwoFactor","x-displayName":"Two-Factor Authentication","description":"TOTP-based two-factor authentication setup, enable, and verification."},{"name":"ApiKeys","x-displayName":"API Keys","description":"API key management for CLI tool and AI agent access. Keys use the `wq_` prefix and are rate-limited at 300 req/min."},{"name":"Profile","description":"User profile management, chronicles, and avatar settings."},{"name":"Avatar","description":"Avatar upload, AI generation (DALL-E 3), and generation history."},{"name":"Tavern","description":"Community feed — create posts, cheer, comment, and tip with Neko Coins.\n\nPosts support Markdown content and can be sorted by Hot, New, or Top."},{"name":"TavernEvents","x-displayName":"Tavern Events","description":"Community events — creation (costs 50 Neko Coins), joining, participant management."},{"name":"Messages","x-displayName":"Messages (Secret Scrolls)","description":"Direct messaging between users. Includes blocking, reporting, and file uploads."},{"name":"Users","description":"Public user profiles and the follow/unfollow system."},{"name":"Duels","description":"PvP vocabulary duels with Neko Coin wagers.\n\n### Duel Lifecycle\n\n```\nCREATE → OPEN → JOIN → IN_PROGRESS → COMPLETED\n                 ↘ CANCELLED (by creator)\n```\n\n**Wager:** 10-1,000 Neko Coins. Held in escrow until settlement."},{"name":"Shrine","x-displayName":"Shrine (Gacha System)","description":"Gacha system — pull for cosmetic items using Neko Coins.\n\n**Rarities:** Common (C) → Rare (R) → Super Rare (SR) → SSR\n\n**Costs:** Single pull: 100 coins. 10-pull: 900 coins.\n\n**Pity:** SSR guaranteed at 100 pulls."},{"name":"Squads","description":"Team system — create squads, invite by code, voice chat (Agora RTC), and file sharing."},{"name":"Isekai","x-displayName":"Isekai (Tier Progression)","description":"XP/tier progression system.\n\n**Tiers:** Ronin → Ashigaru → Samurai → Hatamoto → Daimyo → Shogun"},{"name":"Economy","description":"Neko Coin economy — recharge packages (SOL/crypto), withdrawal, and transaction history."},{"name":"Referral","description":"Referral code system — validate, claim, and earn bonus Neko Coins."},{"name":"Notifications","description":"In-app notification management — list, count, mark as read."},{"name":"Push","description":"Push notification management — device tokens, preferences, and test sends."},{"name":"Admin","description":"Admin-only operations. Requires admin role."},{"name":"Cron","description":"Scheduled job endpoints. Server-to-server only, authenticated via CRON_SECRET."}],"x-tagGroups":[{"name":"Getting Started","tags":["Platform","ApiKeys"]},{"name":"Learning Content","tags":["Courses","Vocabulary","Grammar","JLPT"]},{"name":"Study Progress","tags":["Study","Quiz","Analytics"]},{"name":"AI Tutor (Sage)","tags":["Sage","Speaking","Writing"]},{"name":"Authentication","tags":["Auth","OAuth","NativeAuth","TwoFactor"]},{"name":"User Profile","tags":["Profile","Avatar"]},{"name":"Social Features","tags":["Tavern","TavernEvents","Messages","Users"]},{"name":"Gamification","tags":["Duels","Shrine","Squads","Isekai"]},{"name":"Economy","tags":["Economy","Referral"]},{"name":"Notifications","tags":["Notifications","Push"]},{"name":"Administration","tags":["Admin","Cron"]}],"paths":{"/api/v1/courses":{"get":{"operationId":"listCourses","summary":"List all Japanese language courses","description":"List all 146 Japanese language courses. Filter by JLPT level (N5=beginner, N3=intermediate). Each course contains 10-100 vocabulary words with professional audio. Use the \"jlpt\" query parameter to filter (e.g., ?jlpt=N4 for elementary courses). Returns course number, title, JLPT level, and vocabulary count. Paginated.","tags":["Courses"],"parameters":[{"$ref":"#/components/parameters/page"},{"$ref":"#/components/parameters/limit"},{"$ref":"#/components/parameters/jlptFilter"}],"x-rateLimit":{"tier":"publicRead","maxRequests":60,"windowSeconds":60},"x-codeSamples":[{"lang":"curl","label":"cURL","source":"curl -s 'https://wordquest.solanalink.jp/api/v1/courses?jlpt=N5&limit=10' | jq ."},{"lang":"JavaScript","label":"fetch","source":"const res = await fetch('https://wordquest.solanalink.jp/api/v1/courses?jlpt=N5');\nconst { data, pagination } = await res.json();"},{"lang":"Shell","label":"wq CLI","source":"wq courses list --level N5\nwq courses list --all -o json"}],"responses":{"200":{"description":"Paginated list of courses","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/PaginatedResponse"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/CourseSummary"}}}}]}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/v1/courses/{courseId}":{"get":{"operationId":"getCourseById","summary":"Get a specific course by ID","description":"Get details for a single course by its numeric ID (0-145). Course 0 is Kana (Hiragana & Katakana). Courses 1-48 cover grammar patterns. Courses 49-131 are JLPT N5/N4 vocabulary lists. Courses 132-145 are JLPT N3.","tags":["Courses"],"parameters":[{"$ref":"#/components/parameters/courseId"}],"x-rateLimit":{"tier":"publicRead","maxRequests":60,"windowSeconds":60},"responses":{"200":{"description":"Course details","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"$ref":"#/components/schemas/CourseSummary"}}}}}},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/v1/courses/{courseId}/vocabulary":{"get":{"operationId":"getCourseVocabulary","summary":"Get vocabulary words for a course","description":"Get all vocabulary words for a specific course. Each word includes Japanese text, romaji reading, meaning in the requested locale, part of speech, and an example sentence. Use the \"locale\" parameter to get meanings in different languages. Paginated — default 50 items per page.","tags":["Vocabulary"],"parameters":[{"$ref":"#/components/parameters/courseId"},{"$ref":"#/components/parameters/page"},{"$ref":"#/components/parameters/limitLarge"},{"$ref":"#/components/parameters/locale"}],"x-rateLimit":{"tier":"publicRead","maxRequests":60,"windowSeconds":60},"responses":{"200":{"description":"Paginated vocabulary list","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/PaginatedResponse"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/VocabularyWord"}}}}]}}}},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/v1/vocabulary/search":{"get":{"operationId":"searchVocabulary","summary":"Search vocabulary across all courses","description":"Full-text search across 8,200+ Japanese vocabulary words. Searches Japanese text (kanji/kana), romaji readings, Chinese translations, and English translations simultaneously. Query parameter \"q\" is required (minimum 1 character). Results include the source course number and JLPT level for each match.","tags":["Vocabulary"],"parameters":[{"name":"q","in":"query","required":true,"description":"Search query — matches against Japanese, romaji, Chinese, and English fields","schema":{"type":"string","minLength":1}},{"$ref":"#/components/parameters/page"},{"$ref":"#/components/parameters/limit"},{"$ref":"#/components/parameters/locale"}],"x-rateLimit":{"tier":"publicRead","maxRequests":60,"windowSeconds":60},"responses":{"200":{"description":"Paginated search results with course context","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/PaginatedResponse"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/VocabularySearchResult"}}}}]}}}},"400":{"$ref":"#/components/responses/BadRequest"}}}},"/api/v1/vocabulary/jlpt/{level}":{"get":{"operationId":"getVocabularyByJlptLevel","summary":"Get all vocabulary for a JLPT level","description":"Get all vocabulary words for a specific JLPT level (N5, N4, or N3). N5 contains ~4,000 beginner words, N4 ~2,500 elementary words, N3 ~1,500 intermediate words. Results include the source course number. Paginated — default 50 items per page.","tags":["Vocabulary"],"parameters":[{"$ref":"#/components/parameters/jlptLevel"},{"$ref":"#/components/parameters/page"},{"$ref":"#/components/parameters/limitLarge"},{"$ref":"#/components/parameters/locale"}],"x-rateLimit":{"tier":"publicRead","maxRequests":60,"windowSeconds":60},"responses":{"200":{"description":"Paginated vocabulary for the JLPT level"},"400":{"$ref":"#/components/responses/BadRequest"}}}},"/api/v1/grammar":{"get":{"operationId":"listGrammarPatterns","summary":"List grammar patterns","description":"List all Japanese grammar patterns (N5 and N4 currently available, ~500 total). Each pattern includes its formula, JLPT level, example count, and tags. Paginated.","tags":["Grammar"],"parameters":[{"$ref":"#/components/parameters/page"},{"$ref":"#/components/parameters/limit"},{"$ref":"#/components/parameters/jlptFilter"}],"x-rateLimit":{"tier":"publicRead","maxRequests":60,"windowSeconds":60},"responses":{"200":{"description":"Paginated grammar pattern list","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/PaginatedResponse"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/GrammarPatternSummary"}}}}]}}}}}}},"/api/v1/grammar/{patternId}":{"get":{"operationId":"getGrammarPatternById","summary":"Get a grammar pattern by ID","description":"Get full details for a grammar pattern including its formula, explanation, example sentences with Japanese, romaji, and Chinese translations, related patterns, and tags. Pattern IDs follow the format \"n5-pattern-1\" or \"n4-pattern-42\".","tags":["Grammar"],"parameters":[{"$ref":"#/components/parameters/patternId"}],"x-rateLimit":{"tier":"publicRead","maxRequests":60,"windowSeconds":60},"responses":{"200":{"description":"Full grammar pattern with examples","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"$ref":"#/components/schemas/GrammarPatternDetail"}}}}}},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/v1/jlpt/levels":{"get":{"operationId":"getJlptLevels","summary":"Get JLPT level overview","description":"Get an overview of all 5 JLPT levels (N5 through N1) with course counts, vocabulary counts, grammar pattern counts, difficulty labels, and availability status.","tags":["JLPT"],"x-rateLimit":{"tier":"publicRead","maxRequests":60,"windowSeconds":60},"responses":{"200":{"description":"Array of JLPT levels with statistics","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"type":"array","items":{"$ref":"#/components/schemas/JlptLevel"}}}}}}}}}},"/api/v1/jlpt/{level}/study-plan":{"get":{"operationId":"getJlptStudyPlan","summary":"Get recommended study plan for a JLPT level","description":"Get a recommended study plan for preparing for a specific JLPT level. Includes estimated weeks, recommended hours per week, 4-phase study approach, and counts of available courses and vocabulary for that level.","tags":["JLPT"],"parameters":[{"$ref":"#/components/parameters/jlptLevel"}],"x-rateLimit":{"tier":"publicRead","maxRequests":60,"windowSeconds":60},"responses":{"200":{"description":"Study plan with phases and time estimates","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"$ref":"#/components/schemas/StudyPlan"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"}}}},"/api/v1/stats":{"get":{"operationId":"getPlatformStats","summary":"Get platform statistics","description":"Get aggregate statistics for the WordQuest platform: total courses, vocabulary words, grammar patterns, available JLPT levels, supported locales, audio files, and flashcard images.","tags":["Platform"],"x-rateLimit":{"tier":"publicRead","maxRequests":60,"windowSeconds":60},"responses":{"200":{"description":"Platform statistics","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"$ref":"#/components/schemas/PlatformStats"}}}}}}}}},"/api/v1/languages":{"get":{"operationId":"getSupportedLanguages","summary":"Get supported languages","description":"Get all 8 supported languages with ISO codes, English names, and native names. The default locale is \"zh\" (Simplified Chinese). Use these codes as the \"locale\" parameter on vocabulary endpoints.","tags":["Platform"],"x-rateLimit":{"tier":"publicRead","maxRequests":60,"windowSeconds":60},"responses":{"200":{"description":"Array of supported languages","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"type":"array","items":{"$ref":"#/components/schemas/Language"}}}}}}}}}},"/api/v1/health":{"get":{"operationId":"getHealthCheck","summary":"API health check","description":"Returns API status, version, and current timestamp. Use this to verify the API is online before making other requests.","tags":["Platform"],"responses":{"200":{"description":"API is healthy","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"$ref":"#/components/schemas/HealthCheck"}}}}}}}}},"/api/v1/auth/api-keys":{"get":{"operationId":"listApiKeys","summary":"List your API keys","description":"List all API keys for the authenticated user. Returns key metadata (name, prefix, creation date, last used) but never the full key value. Keys use the \"wq_\" prefix.","tags":["ApiKeys"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"x-rateLimit":{"tier":"authenticatedRead","maxRequests":200,"windowSeconds":60},"responses":{"200":{"description":"List of API keys","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"type":"array","items":{"$ref":"#/components/schemas/ApiKeyInfo"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}},"post":{"operationId":"createApiKey","summary":"Create a new API key","description":"Create a new API key with an optional name. The full key value is returned ONLY in this response — store it securely. Keys are prefixed with \"wq_\" and rate-limited at the agentRead tier (300 req/min).","tags":["ApiKeys"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"x-rateLimit":{"tier":"authenticatedWrite","maxRequests":30,"windowSeconds":60},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"Friendly name for the key","maxLength":100}},"required":["name"]}}}},"responses":{"201":{"description":"API key created — full key shown once","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"$ref":"#/components/schemas/ApiKeyCreateResponse"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/v1/auth/api-keys/{id}":{"get":{"operationId":"getApiKey","summary":"Get API key details","description":"Get metadata for a specific API key by ID. Does not return the full key value.","tags":["ApiKeys"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"parameters":[{"$ref":"#/components/parameters/apiKeyId"}],"x-rateLimit":{"tier":"authenticatedRead","maxRequests":200,"windowSeconds":60},"responses":{"200":{"description":"API key details","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"$ref":"#/components/schemas/ApiKeyInfo"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}},"delete":{"operationId":"revokeApiKey","summary":"Revoke an API key","description":"Permanently revoke an API key. The key will immediately stop working. This cannot be undone.","tags":["ApiKeys"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"parameters":[{"$ref":"#/components/parameters/apiKeyId"}],"x-rateLimit":{"tier":"authenticatedWrite","maxRequests":30,"windowSeconds":60},"responses":{"200":{"description":"Key revoked","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/v1/auth/api-keys/{id}/rotate":{"post":{"operationId":"rotateApiKey","summary":"Rotate an API key","description":"Generate a new key value for an existing API key. The old key is immediately invalidated. The new full key value is returned ONLY in this response — store it securely.","tags":["ApiKeys"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"parameters":[{"$ref":"#/components/parameters/apiKeyId"}],"x-rateLimit":{"tier":"authenticatedWrite","maxRequests":30,"windowSeconds":60},"responses":{"200":{"description":"New key generated — full key shown once","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"$ref":"#/components/schemas/ApiKeyCreateResponse"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/auth/register":{"post":{"operationId":"register","summary":"Create a new account","description":"Register a new user with username, email, and password. Optionally include a referral code for bonus Neko Coins (+100).","tags":["Auth"],"security":[],"x-rateLimit":{"tier":"auth","maxRequests":10,"windowSeconds":900},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegisterRequest"}}}},"responses":{"201":{"description":"Account created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/auth/login":{"post":{"operationId":"login","summary":"Email/password login","description":"Authenticate with email and password. Returns a session cookie. If 2FA is enabled, returns a challenge requiring TOTP verification.","tags":["Auth"],"security":[],"x-rateLimit":{"tier":"auth","maxRequests":10,"windowSeconds":900},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginRequest"}}}},"responses":{"200":{"description":"Login successful (session cookie set)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DataResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/auth/logout":{"post":{"operationId":"logout","summary":"End session","description":"Log out the current user and invalidate the session cookie.","tags":["Auth"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"responses":{"200":{"description":"Logged out","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}}}}},"/api/auth/me":{"get":{"operationId":"getCurrentSession","summary":"Get current session","description":"Get the currently authenticated user session including profile data, tier, coins, and streak.","tags":["Auth"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"x-rateLimit":{"tier":"authenticatedRead","maxRequests":200,"windowSeconds":60},"responses":{"200":{"description":"Current user session","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"$ref":"#/components/schemas/UserSession"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/auth/change-password":{"post":{"operationId":"changePassword","summary":"Change password","description":"Change the current user password. Requires the current password for verification.","tags":["Auth"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"x-rateLimit":{"tier":"auth","maxRequests":10,"windowSeconds":900},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChangePasswordRequest"}}}},"responses":{"200":{"description":"Password changed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/auth/forgot-password":{"post":{"operationId":"forgotPassword","summary":"Request password reset","description":"Send a password reset email to the specified address. Always returns 200 to prevent email enumeration.","tags":["Auth"],"security":[],"x-rateLimit":{"tier":"auth","maxRequests":10,"windowSeconds":900},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ForgotPasswordRequest"}}}},"responses":{"200":{"description":"Reset email sent (if account exists)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}}}}},"/api/auth/reset-password":{"post":{"operationId":"resetPassword","summary":"Reset password with token","description":"Reset password using the token from the reset email.","tags":["Auth"],"security":[],"x-rateLimit":{"tier":"auth","maxRequests":10,"windowSeconds":900},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResetPasswordRequest"}}}},"responses":{"200":{"description":"Password reset","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"}}}},"/api/auth/verify-email":{"post":{"operationId":"verifyEmail","summary":"Verify email address","description":"Verify an email address using the token sent during registration.","tags":["Auth"],"security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["token"],"properties":{"token":{"type":"string"}}}}}},"responses":{"200":{"description":"Email verified","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"}}}},"/api/auth/delete-account":{"post":{"operationId":"deleteAccount","summary":"Delete account","description":"Permanently delete the authenticated user account and all associated data. This action cannot be undone.","tags":["Auth"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["confirmation"],"properties":{"confirmation":{"type":"string","description":"Must be \"DELETE\""}}}}}},"responses":{"200":{"description":"Account deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/auth/google-native":{"post":{"operationId":"googleNativeSignIn","summary":"Google native sign-in","description":"Authenticate using a Google ID token from native mobile OAuth flow.","tags":["NativeAuth"],"security":[],"x-rateLimit":{"tier":"auth","maxRequests":10,"windowSeconds":900},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthNativeRequest"}}}},"responses":{"200":{"description":"Authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DataResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/auth/apple-native":{"post":{"operationId":"appleNativeSignIn","summary":"Apple native sign-in","description":"Authenticate using an Apple ID token from native mobile OAuth flow.","tags":["NativeAuth"],"security":[],"x-rateLimit":{"tier":"auth","maxRequests":10,"windowSeconds":900},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthNativeRequest"}}}},"responses":{"200":{"description":"Authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DataResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/auth/twitter-native":{"post":{"operationId":"twitterNativeSignIn","summary":"Twitter native sign-in","description":"Initiate Twitter OAuth flow for native mobile app. Returns a redirect URL.","tags":["NativeAuth"],"security":[],"x-rateLimit":{"tier":"auth","maxRequests":10,"windowSeconds":900},"responses":{"200":{"description":"Redirect URL for Twitter OAuth","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DataResponse"}}}}}}},"/api/auth/twitter-native/callback":{"get":{"operationId":"twitterNativeCallback","summary":"Twitter OAuth callback","description":"Callback endpoint for Twitter OAuth flow. Redirects back to the mobile app.","tags":["NativeAuth"],"security":[],"responses":{"302":{"description":"Redirect to app with auth code"}}}},"/api/auth/telegram-native":{"get":{"operationId":"telegramNativeAuth","summary":"Telegram authentication","description":"Authenticate using Telegram widget data.","tags":["NativeAuth"],"security":[],"responses":{"200":{"description":"Authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DataResponse"}}}}}}},"/api/auth/callback/telegram":{"get":{"operationId":"telegramCallback","summary":"Telegram OAuth callback","description":"Callback endpoint for Telegram OAuth flow.","tags":["OAuth"],"security":[],"responses":{"302":{"description":"Redirect with session"}}}},"/api/auth/exchange-native-code":{"post":{"operationId":"exchangeNativeCode","summary":"Exchange one-time code for session","description":"Exchange a one-time authorization code (from native OAuth callback) for a session cookie.","tags":["NativeAuth"],"security":[],"x-rateLimit":{"tier":"auth","maxRequests":10,"windowSeconds":900},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["code"],"properties":{"code":{"type":"string"}}}}}},"responses":{"200":{"description":"Session established","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DataResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"}}}},"/api/auth/native-signin":{"post":{"operationId":"nativeSignIn","summary":"Native app sign-in","description":"Sign in from native mobile app with email/password. Returns tokens suitable for mobile storage.","tags":["NativeAuth"],"security":[],"x-rateLimit":{"tier":"auth","maxRequests":10,"windowSeconds":900},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginRequest"}}}},"responses":{"200":{"description":"Authenticated with tokens","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DataResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/auth/native-callback":{"get":{"operationId":"nativeCallback","summary":"Native OAuth callback handler","description":"Generic callback endpoint for native OAuth flows. Redirects back to the mobile app with auth code.","tags":["NativeAuth"],"security":[],"responses":{"302":{"description":"Redirect to app"}}}},"/api/2fa":{"get":{"operationId":"get2faStatus","summary":"Get 2FA status","description":"Check whether two-factor authentication is enabled for the current user.","tags":["TwoFactor"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"responses":{"200":{"description":"2FA status","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"type":"object","properties":{"enabled":{"type":"boolean"}}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}},"post":{"operationId":"setup2fa","summary":"Setup 2FA (get QR code)","description":"Generate a TOTP secret and QR code for setting up two-factor authentication in an authenticator app.","tags":["TwoFactor"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"responses":{"200":{"description":"QR code and secret","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"$ref":"#/components/schemas/TwoFactorSetup"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/2fa/enable":{"post":{"operationId":"enable2fa","summary":"Enable 2FA with TOTP code","description":"Enable two-factor authentication by verifying a TOTP code from the authenticator app.","tags":["TwoFactor"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["code"],"properties":{"code":{"type":"string","description":"6-digit TOTP code"}}}}}},"responses":{"200":{"description":"2FA enabled","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/2fa/verify":{"post":{"operationId":"verify2fa","summary":"Verify 2FA during login","description":"Complete the login flow by verifying a TOTP code. Required when 2FA is enabled.","tags":["TwoFactor"],"security":[],"x-rateLimit":{"tier":"auth","maxRequests":10,"windowSeconds":900},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["code","challengeToken"],"properties":{"code":{"type":"string"},"challengeToken":{"type":"string"}}}}}},"responses":{"200":{"description":"Login completed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DataResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/profile":{"get":{"operationId":"getProfile","summary":"Get own profile","description":"Get the full profile for the authenticated user including stats, settings, and tier information.","tags":["Profile"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"responses":{"200":{"description":"User profile","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"$ref":"#/components/schemas/UserProfile"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}},"put":{"operationId":"updateProfile","summary":"Update profile","description":"Update the authenticated user profile. Supports partial updates — only include fields to change.","tags":["Profile"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProfileUpdateRequest"}}}},"responses":{"200":{"description":"Profile updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/profile/chronicle":{"get":{"operationId":"getChronicle","summary":"Get user chronicle","description":"Get the user timeline/chronicle showing milestones, achievements, and activity history.","tags":["Profile"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"responses":{"200":{"description":"User chronicle","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"$ref":"#/components/schemas/Chronicle"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/profile/avatar":{"post":{"operationId":"uploadAvatar","summary":"Upload avatar image","description":"Upload a custom avatar image. Accepts JPEG, PNG, or WebP up to 5MB.","tags":["Avatar"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"file":{"type":"string","format":"binary"}},"required":["file"]}}}},"responses":{"200":{"description":"Avatar uploaded","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"type":"object","properties":{"imageUrl":{"type":"string"}}}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"}}},"delete":{"operationId":"removeAvatar","summary":"Remove avatar","description":"Remove the current avatar and revert to default.","tags":["Avatar"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"responses":{"200":{"description":"Avatar removed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/profile/avatar/ledger":{"get":{"operationId":"getAvatarLedger","summary":"Get avatar generation ledger","description":"Get the spending history for AI avatar generations.","tags":["Avatar"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"responses":{"200":{"description":"Avatar spending ledger","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DataResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/profile/ai-avatar":{"get":{"operationId":"getAiAvatar","summary":"Get current AI avatar","description":"Get the currently active AI-generated avatar.","tags":["Avatar"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"responses":{"200":{"description":"Current AI avatar","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DataResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}},"post":{"operationId":"setAiAvatar","summary":"Set AI avatar","description":"Set the AI-generated avatar as the active avatar.","tags":["Avatar"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"avatarId":{"type":"string"}},"required":["avatarId"]}}}},"responses":{"200":{"description":"Avatar set","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/profile/ai-avatar/generate":{"post":{"operationId":"generateAiAvatar","summary":"Generate AI avatar (DALL-E 3)","description":"Generate a new AI avatar using DALL-E 3. Costs 200 Neko Coins. Requires Hatamoto tier or above.","tags":["Avatar"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"x-nekoCoinCost":200,"x-tierRequired":"Hatamoto","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"prompt":{"type":"string","description":"Generation prompt","maxLength":500},"style":{"type":"string","enum":["anime","realistic","pixel"]}},"required":["prompt"]}}}},"responses":{"200":{"description":"Avatar generated","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"$ref":"#/components/schemas/AvatarHistory"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}}},"/api/profile/ai-avatar/history":{"get":{"operationId":"getAiAvatarHistory","summary":"Avatar generation history","description":"Get all previously generated AI avatars with prompts and timestamps.","tags":["Avatar"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"responses":{"200":{"description":"Avatar history","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"type":"array","items":{"$ref":"#/components/schemas/AvatarHistory"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/profile/ai-avatar/use":{"post":{"operationId":"useGeneratedAvatar","summary":"Use a previously generated avatar","description":"Set a previously generated AI avatar as the active avatar.","tags":["Avatar"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"avatarId":{"type":"string"}},"required":["avatarId"]}}}},"responses":{"200":{"description":"Avatar activated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/tavern/posts":{"get":{"operationId":"listTavernPosts","summary":"List tavern posts","description":"List community posts with optional sorting (hot, new, top) and pagination. Authenticated users see their own cheer status on each post.","tags":["Tavern"],"security":[],"x-rateLimit":{"tier":"publicRead","maxRequests":60,"windowSeconds":60},"parameters":[{"$ref":"#/components/parameters/page"},{"$ref":"#/components/parameters/limit"},{"$ref":"#/components/parameters/sort"}],"responses":{"200":{"description":"Paginated list of tavern posts","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/PaginatedResponse"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/TavernPost"}}}}]}}}},"429":{"$ref":"#/components/responses/RateLimited"}}},"post":{"operationId":"createTavernPost","summary":"Create a tavern post","description":"Create a new community post. Supports Markdown content. Posts can be categorized by type and tagged for discoverability.","tags":["Tavern"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"x-rateLimit":{"tier":"authenticatedWrite","maxRequests":20,"windowSeconds":60},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["title","content"],"properties":{"title":{"type":"string","maxLength":200},"content":{"type":"string","maxLength":10000},"type":{"type":"string","description":"Post category type"},"tags":{"type":"array","items":{"type":"string"},"maxItems":5}}}}}},"responses":{"201":{"description":"Post created","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"$ref":"#/components/schemas/TavernPost"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/tavern/posts/{id}":{"get":{"operationId":"getTavernPost","summary":"Get a tavern post","description":"Get full details of a single tavern post by ID. Authenticated users see their own cheer status.","tags":["Tavern"],"security":[],"parameters":[{"$ref":"#/components/parameters/postId"}],"responses":{"200":{"description":"Post details","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"$ref":"#/components/schemas/TavernPost"}}}}}},"404":{"$ref":"#/components/responses/NotFound"}}},"patch":{"operationId":"updateTavernPost","summary":"Update a tavern post","description":"Update the title, content, or tags of an existing post. Only the post author can update.","tags":["Tavern"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"parameters":[{"$ref":"#/components/parameters/postId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"title":{"type":"string","maxLength":200},"content":{"type":"string","maxLength":10000},"tags":{"type":"array","items":{"type":"string"},"maxItems":5}}}}}},"responses":{"200":{"description":"Post updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}},"delete":{"operationId":"deleteTavernPost","summary":"Delete a tavern post","description":"Delete a post. Only the post author or an admin can delete.","tags":["Tavern"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"parameters":[{"$ref":"#/components/parameters/postId"}],"responses":{"200":{"description":"Post deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/tavern/posts/{id}/cheer":{"post":{"operationId":"cheerTavernPost","summary":"Cheer a post","description":"Toggle a cheer (like) on a tavern post. Cheering the same post again removes the cheer.","tags":["Tavern"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"parameters":[{"$ref":"#/components/parameters/postId"}],"responses":{"200":{"description":"Cheer toggled","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"type":"object","properties":{"cheered":{"type":"boolean"},"cheers":{"type":"integer"}}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/tavern/posts/{id}/comments":{"get":{"operationId":"listPostComments","summary":"List comments on a post","description":"Get paginated comments for a specific tavern post, ordered by creation time.","tags":["Tavern"],"security":[],"parameters":[{"$ref":"#/components/parameters/postId"},{"$ref":"#/components/parameters/page"},{"$ref":"#/components/parameters/limit"}],"responses":{"200":{"description":"Paginated comments","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/PaginatedResponse"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/TavernComment"}}}}]}}}},"404":{"$ref":"#/components/responses/NotFound"}}},"post":{"operationId":"createPostComment","summary":"Add a comment to a post","description":"Add a comment to a tavern post. The comment author is the authenticated user.","tags":["Tavern"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"x-rateLimit":{"tier":"authenticatedWrite","maxRequests":30,"windowSeconds":60},"parameters":[{"$ref":"#/components/parameters/postId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["content"],"properties":{"content":{"type":"string","maxLength":2000}}}}}},"responses":{"201":{"description":"Comment created","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"$ref":"#/components/schemas/TavernComment"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/tavern/posts/{id}/related":{"get":{"operationId":"getRelatedPosts","summary":"Get related posts","description":"Get posts related to the specified post, based on tags and content similarity.","tags":["Tavern"],"security":[],"parameters":[{"$ref":"#/components/parameters/postId"}],"responses":{"200":{"description":"Related posts","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"type":"array","items":{"$ref":"#/components/schemas/TavernPost"}}}}}}},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/tavern/posts/{id}/tip":{"post":{"operationId":"tipPostAuthor","summary":"Tip post author with Neko Coins","description":"Send a Neko Coin tip to the author of a post. The amount is deducted from the tipper and credited to the author.","tags":["Tavern"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"x-nekoCoinCost":"variable","parameters":[{"$ref":"#/components/parameters/postId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["amount"],"properties":{"amount":{"type":"integer","minimum":1,"description":"Neko Coin amount to tip"}}}}}},"responses":{"200":{"description":"Tip sent","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/tavern/comments/{commentId}":{"delete":{"operationId":"deleteTavernComment","summary":"Delete a comment","description":"Delete a comment. Only the comment author or an admin can delete.","tags":["Tavern"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"parameters":[{"$ref":"#/components/parameters/commentId"}],"responses":{"200":{"description":"Comment deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/tavern/events":{"get":{"operationId":"listTavernEvents","summary":"List tavern events","description":"List community events with pagination. Authenticated users see their participation status on each event.","tags":["TavernEvents"],"security":[],"x-rateLimit":{"tier":"publicRead","maxRequests":60,"windowSeconds":60},"parameters":[{"$ref":"#/components/parameters/page"},{"$ref":"#/components/parameters/limit"}],"responses":{"200":{"description":"Paginated list of events","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/PaginatedResponse"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/TavernEvent"}}}}]}}}},"429":{"$ref":"#/components/responses/RateLimited"}}},"post":{"operationId":"createTavernEvent","summary":"Create a tavern event","description":"Create a new community event. Costs 50 Neko Coins. Events can have a maximum participant limit and a scheduled start time.","tags":["TavernEvents"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"x-nekoCoinCost":50,"x-rateLimit":{"tier":"authenticatedWrite","maxRequests":10,"windowSeconds":60},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["title","description","startTime"],"properties":{"title":{"type":"string","maxLength":200},"description":{"type":"string","maxLength":5000},"startTime":{"type":"string","format":"date-time"},"maxParticipants":{"type":"integer","minimum":2,"nullable":true}}}}}},"responses":{"201":{"description":"Event created","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"$ref":"#/components/schemas/TavernEvent"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/tavern/events/{id}":{"get":{"operationId":"getTavernEvent","summary":"Get a tavern event","description":"Get full details of a community event by ID including participant count and status.","tags":["TavernEvents"],"security":[],"parameters":[{"$ref":"#/components/parameters/eventId"}],"responses":{"200":{"description":"Event details","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"$ref":"#/components/schemas/TavernEvent"}}}}}},"404":{"$ref":"#/components/responses/NotFound"}}},"put":{"operationId":"updateTavernEvent","summary":"Update a tavern event","description":"Update event details. Only the event creator can update.","tags":["TavernEvents"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"parameters":[{"$ref":"#/components/parameters/eventId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"title":{"type":"string","maxLength":200},"description":{"type":"string","maxLength":5000},"startTime":{"type":"string","format":"date-time"},"maxParticipants":{"type":"integer","minimum":2,"nullable":true}}}}}},"responses":{"200":{"description":"Event updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}},"delete":{"operationId":"cancelTavernEvent","summary":"Cancel a tavern event","description":"Cancel an event. Only the event creator or an admin can cancel. Sets status to CANCELLED.","tags":["TavernEvents"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"parameters":[{"$ref":"#/components/parameters/eventId"}],"responses":{"200":{"description":"Event cancelled","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/tavern/events/{id}/join":{"post":{"operationId":"joinTavernEvent","summary":"Join a tavern event","description":"Join a community event as a participant. Fails if the event is full or already joined.","tags":["TavernEvents"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"parameters":[{"$ref":"#/components/parameters/eventId"}],"responses":{"200":{"description":"Joined event","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"}}},"delete":{"operationId":"leaveTavernEvent","summary":"Leave a tavern event","description":"Leave a community event that the user has previously joined.","tags":["TavernEvents"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"parameters":[{"$ref":"#/components/parameters/eventId"}],"responses":{"200":{"description":"Left event","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/tavern/events/{id}/participants":{"get":{"operationId":"listEventParticipants","summary":"List event participants","description":"Get the list of users participating in a community event.","tags":["TavernEvents"],"security":[],"parameters":[{"$ref":"#/components/parameters/eventId"}],"responses":{"200":{"description":"Participant list","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"type":"array","items":{"type":"object","properties":{"userId":{"type":"string"},"name":{"type":"string"},"image":{"type":"string","nullable":true},"joinedAt":{"type":"string","format":"date-time"}}}}}}}}},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/tavern/events/my":{"get":{"operationId":"getMyTavernEvents","summary":"Get own events","description":"Get events created by or joined by the authenticated user.","tags":["TavernEvents"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"responses":{"200":{"description":"User events","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"type":"array","items":{"$ref":"#/components/schemas/TavernEvent"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/tavern/events/generate-assets":{"post":{"operationId":"generateEventAssets","summary":"Generate AI event assets","description":"Generate AI-powered visual assets (banners, thumbnails) for a tavern event.","tags":["TavernEvents"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"x-rateLimit":{"tier":"aiGeneration","maxRequests":5,"windowSeconds":60},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["eventId"],"properties":{"eventId":{"type":"integer"},"style":{"type":"string","description":"Asset style preference"}}}}}},"responses":{"200":{"description":"Assets generated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DataResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/tavern/ledger":{"get":{"operationId":"getTavernLedger","summary":"Get tavern spending ledger","description":"Get the Neko Coin spending history for tavern activities (tips, event creation, etc.).","tags":["Tavern"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"parameters":[{"$ref":"#/components/parameters/page"},{"$ref":"#/components/parameters/limit"}],"responses":{"200":{"description":"Spending ledger","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DataResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/tavern/online":{"get":{"operationId":"getTavernOnlineCount","summary":"Get online users count","description":"Get the current number of users active in the tavern.","tags":["Tavern"],"security":[],"x-rateLimit":{"tier":"publicRead","maxRequests":120,"windowSeconds":60},"responses":{"200":{"description":"Online user count","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"type":"object","properties":{"count":{"type":"integer"}}}}}}}}}}},"/api/messages":{"get":{"operationId":"listConversations","summary":"List conversations","description":"Get all conversations for the authenticated user, ordered by most recent message. Each conversation includes the other user info, last message preview, and unread count.","tags":["Messages"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"x-rateLimit":{"tier":"authenticatedRead","maxRequests":60,"windowSeconds":60},"responses":{"200":{"description":"Conversation list","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"type":"array","items":{"$ref":"#/components/schemas/Conversation"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}},"post":{"operationId":"sendMessage","summary":"Send a message","description":"Send a direct message to another user. Creates a new conversation if one does not exist.","tags":["Messages"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"x-rateLimit":{"tier":"authenticatedWrite","maxRequests":30,"windowSeconds":60},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["recipientId","content"],"properties":{"recipientId":{"type":"string","description":"Recipient user ID (cuid)"},"content":{"type":"string","maxLength":5000}}}}}},"responses":{"201":{"description":"Message sent","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"$ref":"#/components/schemas/Message"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/messages/count":{"get":{"operationId":"getUnreadMessageCount","summary":"Get unread message count","description":"Get the total number of unread messages across all conversations.","tags":["Messages"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"x-rateLimit":{"tier":"authenticatedRead","maxRequests":120,"windowSeconds":60},"responses":{"200":{"description":"Unread count","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"type":"object","properties":{"count":{"type":"integer"}}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/messages/{userId}":{"get":{"operationId":"getConversationWithUser","summary":"Get conversation with a user","description":"Get the message history with a specific user, ordered by creation time. Marks messages from the other user as read.","tags":["Messages"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"x-rateLimit":{"tier":"authenticatedRead","maxRequests":60,"windowSeconds":60},"parameters":[{"$ref":"#/components/parameters/userId"},{"$ref":"#/components/parameters/page"},{"$ref":"#/components/parameters/limit"}],"responses":{"200":{"description":"Paginated message history","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/PaginatedResponse"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Message"}}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}},"post":{"operationId":"sendMessageToUser","summary":"Send a message to a specific user","description":"Send a direct message to the specified user in their existing conversation thread.","tags":["Messages"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"x-rateLimit":{"tier":"authenticatedWrite","maxRequests":30,"windowSeconds":60},"parameters":[{"$ref":"#/components/parameters/userId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["content"],"properties":{"content":{"type":"string","maxLength":5000}}}}}},"responses":{"201":{"description":"Message sent","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"$ref":"#/components/schemas/Message"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/messages/block":{"get":{"operationId":"listBlockedUsers","summary":"List blocked users","description":"Get the list of users blocked by the authenticated user.","tags":["Messages"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"responses":{"200":{"description":"Blocked user list","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"type":"array","items":{"type":"object","properties":{"userId":{"type":"string"},"name":{"type":"string"},"image":{"type":"string","nullable":true},"blockedAt":{"type":"string","format":"date-time"}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}},"post":{"operationId":"blockUser","summary":"Block a user","description":"Block a user from sending messages. Blocked users cannot initiate conversations.","tags":["Messages"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["userId"],"properties":{"userId":{"type":"string","description":"User ID to block (cuid)"}}}}}},"responses":{"200":{"description":"User blocked","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"}}},"delete":{"operationId":"unblockUser","summary":"Unblock a user","description":"Remove a user from the block list, allowing them to send messages again.","tags":["Messages"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["userId"],"properties":{"userId":{"type":"string","description":"User ID to unblock (cuid)"}}}}}},"responses":{"200":{"description":"User unblocked","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/messages/report":{"post":{"operationId":"reportMessage","summary":"Report a message","description":"Report a message for moderation review. Provide a reason for the report.","tags":["Messages"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"x-rateLimit":{"tier":"authenticatedWrite","maxRequests":10,"windowSeconds":60},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["messageId","reason"],"properties":{"messageId":{"type":"string","description":"ID of the message to report"},"reason":{"type":"string","maxLength":1000,"description":"Reason for reporting"}}}}}},"responses":{"200":{"description":"Message reported","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/messages/upload":{"post":{"operationId":"uploadMessageFile","summary":"Upload a file in a message","description":"Upload an image or file to attach to a message. Returns the file URL for inclusion in message content.","tags":["Messages"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"x-rateLimit":{"tier":"authenticatedWrite","maxRequests":10,"windowSeconds":60},"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["file"],"properties":{"file":{"type":"string","format":"binary","description":"File to upload (image, max 5MB)"}}}}}},"responses":{"200":{"description":"File uploaded","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"type":"object","properties":{"url":{"type":"string","format":"uri"},"type":{"type":"string"}}}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/users/{userId}":{"get":{"operationId":"getPublicProfile","summary":"Get public user profile","description":"Get the public profile for a user by ID. Includes display name, avatar, tier, streak, join date, and follower/following counts. Does not expose private data.","tags":["Users"],"security":[],"parameters":[{"$ref":"#/components/parameters/userId"}],"responses":{"200":{"description":"Public user profile","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"image":{"type":"string","nullable":true},"tier":{"type":"string"},"streak":{"type":"integer"},"joinedAt":{"type":"string","format":"date-time"},"followerCount":{"type":"integer"},"followingCount":{"type":"integer"}}}}}}}},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/users/{userId}/follow":{"get":{"operationId":"checkFollowStatus","summary":"Check follow status","description":"Check whether the authenticated user is following the specified user.","tags":["Users"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"parameters":[{"$ref":"#/components/parameters/userId"}],"responses":{"200":{"description":"Follow status","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"type":"object","properties":{"following":{"type":"boolean"}}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}},"post":{"operationId":"followUser","summary":"Follow a user","description":"Follow the specified user. The user will receive a notification.","tags":["Users"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"x-rateLimit":{"tier":"authenticatedWrite","maxRequests":30,"windowSeconds":60},"parameters":[{"$ref":"#/components/parameters/userId"}],"responses":{"200":{"description":"User followed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"}}},"delete":{"operationId":"unfollowUser","summary":"Unfollow a user","description":"Unfollow the specified user.","tags":["Users"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"parameters":[{"$ref":"#/components/parameters/userId"}],"responses":{"200":{"description":"User unfollowed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/users/{userId}/followers":{"get":{"operationId":"listFollowers","summary":"List followers","description":"Get a paginated list of users who follow the specified user.","tags":["Users"],"security":[],"parameters":[{"$ref":"#/components/parameters/userId"},{"$ref":"#/components/parameters/page"},{"$ref":"#/components/parameters/limit"}],"responses":{"200":{"description":"Paginated follower list","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/PaginatedResponse"},{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"image":{"type":"string","nullable":true},"tier":{"type":"string"}}}}}}]}}}},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/users/{userId}/following":{"get":{"operationId":"listFollowing","summary":"List following","description":"Get a paginated list of users that the specified user is following.","tags":["Users"],"security":[],"parameters":[{"$ref":"#/components/parameters/userId"},{"$ref":"#/components/parameters/page"},{"$ref":"#/components/parameters/limit"}],"responses":{"200":{"description":"Paginated following list","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/PaginatedResponse"},{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"image":{"type":"string","nullable":true},"tier":{"type":"string"}}}}}}]}}}},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/notifications":{"get":{"operationId":"listNotifications","summary":"List notifications","description":"Get paginated in-app notifications for the authenticated user. Includes follow alerts, cheer notifications, comment replies, duel results, and system messages.","tags":["Notifications"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"x-rateLimit":{"tier":"authenticatedRead","maxRequests":60,"windowSeconds":60},"parameters":[{"$ref":"#/components/parameters/page"},{"$ref":"#/components/parameters/limit"}],"responses":{"200":{"description":"Paginated notification list","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/PaginatedResponse"},{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string","description":"Notification type (follow, cheer, comment, duel, system)"},"title":{"type":"string"},"body":{"type":"string"},"read":{"type":"boolean"},"actionUrl":{"type":"string","nullable":true},"createdAt":{"type":"string","format":"date-time"}}}}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}},"post":{"operationId":"markNotificationsRead","summary":"Mark notifications as read","description":"Mark one or more notifications as read by providing their IDs.","tags":["Notifications"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["ids"],"properties":{"ids":{"type":"array","items":{"type":"string"},"minItems":1,"description":"Notification IDs to mark as read"}}}}}},"responses":{"200":{"description":"Notifications marked as read","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/notifications/count":{"get":{"operationId":"getUnreadNotificationCount","summary":"Get unread notification count","description":"Get the total number of unread notifications for the authenticated user.","tags":["Notifications"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"x-rateLimit":{"tier":"authenticatedRead","maxRequests":120,"windowSeconds":60},"responses":{"200":{"description":"Unread count","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"type":"object","properties":{"count":{"type":"integer"}}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/push/device-token":{"post":{"operationId":"registerDeviceToken","summary":"Register device for push notifications","description":"Register a device token (FCM or APNs) for receiving push notifications. Each user can register multiple devices.","tags":["Push"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["token","platform"],"properties":{"token":{"type":"string","description":"Device push token (FCM or APNs)"},"platform":{"type":"string","enum":["ios","android","web"],"description":"Device platform"}}}}}},"responses":{"200":{"description":"Device registered","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"}}},"delete":{"operationId":"unregisterDeviceToken","summary":"Unregister device from push notifications","description":"Remove a device token to stop receiving push notifications on that device.","tags":["Push"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["token","platform"],"properties":{"token":{"type":"string","description":"Device push token to remove"},"platform":{"type":"string","enum":["ios","android","web"],"description":"Device platform"}}}}}},"responses":{"200":{"description":"Device unregistered","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/push/preferences":{"patch":{"operationId":"updatePushPreferences","summary":"Update push notification preferences","description":"Update which types of push notifications the user wants to receive. Supports granular control over follow, cheer, comment, duel, and marketing notifications.","tags":["Push"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["settings"],"properties":{"settings":{"type":"object","description":"Notification preference settings","properties":{"follows":{"type":"boolean"},"cheers":{"type":"boolean"},"comments":{"type":"boolean"},"duels":{"type":"boolean"},"events":{"type":"boolean"},"marketing":{"type":"boolean"}}}}}}}},"responses":{"200":{"description":"Preferences updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/push/subscribe":{"post":{"operationId":"subscribeWebPush","summary":"Subscribe to web push notifications","description":"Subscribe the browser to web push notifications using a PushSubscription object from the Push API.","tags":["Push"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["subscription"],"properties":{"subscription":{"type":"object","description":"Web Push API PushSubscription object","properties":{"endpoint":{"type":"string","format":"uri"},"keys":{"type":"object","properties":{"p256dh":{"type":"string"},"auth":{"type":"string"}}}}}}}}}},"responses":{"200":{"description":"Subscribed to web push","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/push/test":{"post":{"operationId":"sendTestPush","summary":"Send a test push notification","description":"Send a test push notification to all registered devices for the authenticated user. Useful for verifying push setup.","tags":["Push"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"x-rateLimit":{"tier":"authenticatedWrite","maxRequests":5,"windowSeconds":60},"responses":{"200":{"description":"Test push sent","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/push/test-native":{"post":{"operationId":"sendTestNativePush","summary":"Send a test native push notification","description":"Send a test push notification specifically to native mobile devices (iOS/Android) for the authenticated user.","tags":["Push"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"x-rateLimit":{"tier":"authenticatedWrite","maxRequests":5,"windowSeconds":60},"responses":{"200":{"description":"Test native push sent","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/chat/auth":{"post":{"operationId":"getChatAuthToken","summary":"Get chat authentication token","description":"Exchange the current session for a short-lived chat authentication token. The token is used to establish a real-time chat connection (WebSocket/SSE).","tags":["Messages"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"x-rateLimit":{"tier":"authenticatedRead","maxRequests":30,"windowSeconds":60},"responses":{"200":{"description":"Chat auth token","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"type":"object","properties":{"token":{"type":"string","description":"Short-lived chat auth token"},"expiresAt":{"type":"string","format":"date-time"}}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/chat/conversations":{"get":{"operationId":"listChatConversations","summary":"List chat conversations","description":"Get all real-time chat conversations for the authenticated user. Includes participant info, last message preview, and unread counts.","tags":["Messages"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"x-rateLimit":{"tier":"authenticatedRead","maxRequests":60,"windowSeconds":60},"parameters":[{"$ref":"#/components/parameters/page"},{"$ref":"#/components/parameters/limit"}],"responses":{"200":{"description":"Paginated chat conversations","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/PaginatedResponse"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Conversation"}}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/chat/messages":{"get":{"operationId":"getChatMessages","summary":"Get chat messages","description":"Get paginated messages for a specific chat conversation. Requires the conversationId query parameter. Messages are ordered by creation time (newest first).","tags":["Messages"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"x-rateLimit":{"tier":"authenticatedRead","maxRequests":60,"windowSeconds":60},"parameters":[{"name":"conversationId","in":"query","required":true,"description":"ID of the chat conversation to fetch messages from","schema":{"type":"string"}},{"$ref":"#/components/parameters/page"},{"$ref":"#/components/parameters/limit"}],"responses":{"200":{"description":"Paginated chat messages","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/PaginatedResponse"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Message"}}}}]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/duels":{"get":{"operationId":"listDuels","summary":"List duels","description":"List all duels visible to the authenticated user. Includes open duels available to join, in-progress duels, and completed duels the user participated in.","tags":["Duels"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"parameters":[{"$ref":"#/components/parameters/page"},{"$ref":"#/components/parameters/limit"}],"responses":{"200":{"description":"Paginated list of duels","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"type":"array","items":{"$ref":"#/components/schemas/Duel"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}},"post":{"operationId":"createDuel","summary":"Create a new duel","description":"Create a new PvP vocabulary duel. The challenger selects a JLPT level, wager amount (10-1000 Neko Coins), and number of questions. The wager is held in escrow until the duel is settled.","tags":["Duels"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["jlptLevel","wager","questionCount"],"properties":{"jlptLevel":{"type":"string","enum":["N5","N4","N3","N2","N1"],"description":"JLPT level for duel questions"},"wager":{"type":"integer","minimum":10,"maximum":1000,"description":"Neko Coin wager amount (10-1000)"},"questionCount":{"type":"integer","description":"Number of vocabulary questions in the duel"}}}}}},"responses":{"201":{"description":"Duel created","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"$ref":"#/components/schemas/Duel"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/duels/{duelId}":{"get":{"operationId":"getDuel","summary":"Get duel details","description":"Get full details for a specific duel including participants, status, wager, scores, and winner.","tags":["Duels"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"parameters":[{"$ref":"#/components/parameters/duelId"}],"responses":{"200":{"description":"Duel details","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"$ref":"#/components/schemas/Duel"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/duels/{duelId}/join":{"post":{"operationId":"joinDuel","summary":"Join a duel","description":"Join an open duel as the opponent. The matching wager amount is held in escrow from the joining player.","tags":["Duels"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"parameters":[{"$ref":"#/components/parameters/duelId"}],"responses":{"200":{"description":"Successfully joined the duel","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"$ref":"#/components/schemas/Duel"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/duels/{duelId}/start":{"post":{"operationId":"startDuel","summary":"Start a duel","description":"Start a duel after both players have joined. Generates the vocabulary questions and begins the timer.","tags":["Duels"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"parameters":[{"$ref":"#/components/parameters/duelId"}],"responses":{"200":{"description":"Duel started","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"$ref":"#/components/schemas/Duel"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/duels/{duelId}/questions":{"get":{"operationId":"getDuelQuestions","summary":"Get duel questions","description":"Get the vocabulary questions for an in-progress duel. Each question includes Japanese text, multiple-choice options, and a time limit.","tags":["Duels"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"parameters":[{"$ref":"#/components/parameters/duelId"}],"responses":{"200":{"description":"Duel questions","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"type":"array","items":{"$ref":"#/components/schemas/DuelQuestion"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/duels/{duelId}/submit":{"post":{"operationId":"submitDuelAnswers","summary":"Submit duel answers","description":"Submit answers for all questions in an in-progress duel. Each answer maps a question to the selected option.","tags":["Duels"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"parameters":[{"$ref":"#/components/parameters/duelId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["answers"],"properties":{"answers":{"type":"array","items":{"type":"object","properties":{"questionId":{"type":"integer"},"selectedOption":{"type":"string"}}},"description":"Array of question answers"}}}}}},"responses":{"200":{"description":"Answers submitted","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"type":"object","properties":{"score":{"type":"integer"},"total":{"type":"integer"}}}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/duels/{duelId}/settle":{"post":{"operationId":"settleDuel","summary":"Settle duel results","description":"Settle a completed duel by comparing scores and distributing the wager to the winner. Both players must have submitted answers before settlement.","tags":["Duels"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"parameters":[{"$ref":"#/components/parameters/duelId"}],"responses":{"200":{"description":"Duel settled","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"$ref":"#/components/schemas/Duel"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/duels/{duelId}/cancel":{"post":{"operationId":"cancelDuel","summary":"Cancel a duel","description":"Cancel an open or in-progress duel. Wagers are refunded to both participants.","tags":["Duels"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"parameters":[{"$ref":"#/components/parameters/duelId"}],"responses":{"200":{"description":"Duel cancelled and wagers refunded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/duels/grok/join":{"post":{"operationId":"joinGrokDuel","summary":"Join a Grok AI duel","description":"Start a duel against the Grok AI opponent. The AI adapts to the player JLPT level and provides a challenging but fair vocabulary battle experience.","tags":["Duels"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"responses":{"200":{"description":"Grok AI duel started","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"$ref":"#/components/schemas/Duel"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/duels/grok/status":{"get":{"operationId":"getGrokDuelStatus","summary":"Get Grok AI duel status","description":"Check the status of the current Grok AI duel for the authenticated user, including scores and progress.","tags":["Duels"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"responses":{"200":{"description":"Grok duel status","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"$ref":"#/components/schemas/Duel"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/shrine/banner":{"get":{"operationId":"getShrineBanner","summary":"Get current gacha banner","description":"Get the currently active gacha banner including featured items, pull rates for each rarity tier, and the banner end date. Public endpoint — no authentication required.","tags":["Shrine"],"security":[],"responses":{"200":{"description":"Current banner details","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"$ref":"#/components/schemas/ShrineBanner"}}}}}}}}},"/api/shrine/pull":{"post":{"operationId":"pullShrine","summary":"Pull gacha items","description":"Perform a gacha pull on the current banner. Single pull costs 100 Neko Coins, 10-pull costs 900 Neko Coins (10% discount). Returns the pulled items and updated pity counter.","tags":["Shrine"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"x-nekoCoinCost":"100 (single) or 900 (10-pull)","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["count"],"properties":{"count":{"type":"integer","enum":[1,10],"description":"Number of pulls (1 = 100 coins, 10 = 900 coins)"}}}}}},"responses":{"200":{"description":"Pull results with items and pity counter","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"$ref":"#/components/schemas/ShrinePullResult"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/shrine/inventory":{"get":{"operationId":"getShrineInventory","summary":"Get owned shrine items","description":"Get all cosmetic items owned by the authenticated user, including equipped status and rarity.","tags":["Shrine"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"responses":{"200":{"description":"Owned items inventory","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"type":"array","items":{"$ref":"#/components/schemas/ShrineItem"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/shrine/equip":{"get":{"operationId":"getEquippedItems","summary":"Get currently equipped items","description":"Get the cosmetic items currently equipped by the authenticated user (frame, badge, title, background).","tags":["Shrine"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"responses":{"200":{"description":"Currently equipped items","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"type":"array","items":{"$ref":"#/components/schemas/ShrineItem"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}},"post":{"operationId":"equipShrineItem","summary":"Equip a shrine item","description":"Equip an owned cosmetic item. Only one item per type (frame, badge, title, background) can be equipped at a time.","tags":["Shrine"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["itemId"],"properties":{"itemId":{"type":"string","description":"ID of the shrine item to equip"}}}}}},"responses":{"200":{"description":"Item equipped","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}},"delete":{"operationId":"unequipShrineItem","summary":"Unequip a shrine item","description":"Unequip the currently equipped cosmetic item of the specified type.","tags":["Shrine"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"responses":{"200":{"description":"Item unequipped","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/shrine/history":{"get":{"operationId":"getShrineHistory","summary":"Get pull history","description":"Get the gacha pull history for the authenticated user showing all past pulls with timestamps and items received.","tags":["Shrine"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"responses":{"200":{"description":"Pull history","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"items":{"type":"array","items":{"$ref":"#/components/schemas/ShrineItem"}},"count":{"type":"integer"},"cost":{"type":"integer"},"createdAt":{"type":"string","format":"date-time"}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/shrine/ledger":{"get":{"operationId":"getShrineLedger","summary":"Get shrine spending ledger","description":"Get the detailed spending ledger for all shrine gacha transactions including pull costs and coin deductions.","tags":["Shrine"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"responses":{"200":{"description":"Shrine spending ledger","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DataResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/squads":{"get":{"operationId":"listSquads","summary":"List squads","description":"List all public squads. Optionally authenticated to show membership status for the current user.","tags":["Squads"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]},{}],"parameters":[{"$ref":"#/components/parameters/page"},{"$ref":"#/components/parameters/limit"}],"responses":{"200":{"description":"Paginated list of squads","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"type":"array","items":{"$ref":"#/components/schemas/Squad"}}}}}}}}},"post":{"operationId":"createSquad","summary":"Create a squad","description":"Create a new study squad. The creator becomes the squad leader. Generates a unique invite code.","tags":["Squads"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","description":"Squad name"},"description":{"type":"string","description":"Squad description","nullable":true}}}}}},"responses":{"201":{"description":"Squad created","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"$ref":"#/components/schemas/Squad"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/squads/my":{"get":{"operationId":"getMySquad","summary":"Get own squad","description":"Get the squad the authenticated user currently belongs to, including member list and squad details.","tags":["Squads"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"responses":{"200":{"description":"User squad details","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"$ref":"#/components/schemas/Squad"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/squads/join":{"post":{"operationId":"joinSquadByCode","summary":"Join squad by invite code","description":"Join a squad using its invite code. The user must not already belong to another squad.","tags":["Squads"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["code"],"properties":{"code":{"type":"string","description":"Squad invite code"}}}}}},"responses":{"200":{"description":"Joined squad","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"$ref":"#/components/schemas/Squad"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/squads/leave":{"post":{"operationId":"leaveSquad","summary":"Leave current squad","description":"Leave the squad the authenticated user currently belongs to. Leaders must transfer leadership before leaving.","tags":["Squads"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"responses":{"200":{"description":"Left squad","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/squads/{squadId}":{"get":{"operationId":"getSquad","summary":"Get squad details","description":"Get full details for a specific squad including members, leader, invite code, and creation date.","tags":["Squads"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"parameters":[{"$ref":"#/components/parameters/squadId"}],"responses":{"200":{"description":"Squad details","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"$ref":"#/components/schemas/Squad"},"members":{"type":"array","items":{"$ref":"#/components/schemas/SquadMember"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}},"delete":{"operationId":"disbandSquad","summary":"Disband a squad","description":"Disband a squad permanently. Only the squad leader can disband. All members are removed.","tags":["Squads"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"parameters":[{"$ref":"#/components/parameters/squadId"}],"responses":{"200":{"description":"Squad disbanded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/squads/{squadId}/join":{"post":{"operationId":"joinSquadById","summary":"Join a specific squad","description":"Join a squad by its ID. The squad must have available capacity and the user must not belong to another squad.","tags":["Squads"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"parameters":[{"$ref":"#/components/parameters/squadId"}],"responses":{"200":{"description":"Joined squad","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"$ref":"#/components/schemas/Squad"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/squads/{squadId}/transfer":{"post":{"operationId":"transferSquadLeadership","summary":"Transfer squad leadership","description":"Transfer squad leadership to another member. Only the current leader can perform this action.","tags":["Squads"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"parameters":[{"$ref":"#/components/parameters/squadId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["userId"],"properties":{"userId":{"type":"string","description":"User ID of the new leader"}}}}}},"responses":{"200":{"description":"Leadership transferred","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/squads/{squadId}/members/{memberId}":{"delete":{"operationId":"removeSquadMember","summary":"Remove a squad member","description":"Remove a member from the squad. Only the squad leader can remove members.","tags":["Squads"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"parameters":[{"$ref":"#/components/parameters/squadId"},{"$ref":"#/components/parameters/memberId"}],"responses":{"200":{"description":"Member removed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/squads/{squadId}/voice-token":{"get":{"operationId":"getSquadVoiceToken","summary":"Get Agora voice chat token","description":"Get a temporary Agora RTC token for joining the squad voice channel. Token expires after the session.","tags":["Squads"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"parameters":[{"$ref":"#/components/parameters/squadId"}],"responses":{"200":{"description":"Agora voice token","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"type":"object","properties":{"token":{"type":"string"},"channelName":{"type":"string"},"uid":{"type":"integer"},"expiresAt":{"type":"string","format":"date-time"}}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/squads/{squadId}/chat/upload":{"post":{"operationId":"uploadSquadChatFile","summary":"Upload file in squad chat","description":"Upload a file attachment for squad chat. Supports images and documents.","tags":["Squads"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"parameters":[{"$ref":"#/components/parameters/squadId"}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"file":{"type":"string","format":"binary"}},"required":["file"]}}}},"responses":{"200":{"description":"File uploaded","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"type":"object","properties":{"fileUrl":{"type":"string"},"fileName":{"type":"string"}}}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}}},"/api/squads/{squadId}/provision-chat":{"post":{"operationId":"provisionSquadChat","summary":"Provision squad chat","description":"Provision and initialize the chat channel for a squad. Creates the necessary messaging infrastructure.","tags":["Squads"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"parameters":[{"$ref":"#/components/parameters/squadId"}],"responses":{"200":{"description":"Chat provisioned","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/isekai/stats":{"get":{"operationId":"getIsekaiStats","summary":"Get isekai progression stats","description":"Get the authenticated user isekai progression stats including current tier, XP, level, Neko Coin balance, streak count, and XP required to reach the next tier.","tags":["Isekai"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"responses":{"200":{"description":"Isekai progression stats","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"$ref":"#/components/schemas/IsekaiStats"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/isekai/award-points":{"post":{"operationId":"awardIsekaiPoints","summary":"Award XP points","description":"Award experience points to the authenticated user for completing study activities. Points contribute to leveling up and advancing through isekai tiers.","tags":["Isekai"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["points","reason"],"properties":{"points":{"type":"integer","description":"Number of XP points to award"},"reason":{"type":"string","description":"Reason for the XP award (e.g., \"course_complete\", \"daily_login\")"}}}}}},"responses":{"200":{"description":"Points awarded with updated stats","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"$ref":"#/components/schemas/IsekaiStats"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/recharge/packages":{"get":{"operationId":"listRechargePackages","summary":"List recharge packages","description":"List all available Neko Coin recharge packages with pricing in USD and SOL. Public endpoint — no authentication required.","tags":["Economy"],"security":[],"responses":{"200":{"description":"Available recharge packages","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"type":"array","items":{"$ref":"#/components/schemas/RechargePackage"}}}}}}}}}},"/api/recharge/create-order":{"post":{"operationId":"createRechargeOrder","summary":"Create a recharge order","description":"Create a new recharge order for the selected package. Returns the order details with a blockchain payment address for completing the transaction.","tags":["Economy"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["packageId"],"properties":{"packageId":{"type":"string","description":"Recharge package ID to purchase"}}}}}},"responses":{"201":{"description":"Order created","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"$ref":"#/components/schemas/RechargeOrder"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/recharge/verify-tx":{"post":{"operationId":"verifyRechargeTransaction","summary":"Verify blockchain payment","description":"Verify a blockchain transaction hash against a pending recharge order. On success, credits the purchased Neko Coins to the user account.","tags":["Economy"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["orderId","txHash"],"properties":{"orderId":{"type":"string","description":"Recharge order ID"},"txHash":{"type":"string","description":"Blockchain transaction hash to verify"}}}}}},"responses":{"200":{"description":"Transaction verified and coins credited","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"$ref":"#/components/schemas/RechargeOrder"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/recharge/orders":{"get":{"operationId":"listRechargeOrders","summary":"Get recharge order history","description":"Get the paginated recharge order history for the authenticated user including order status and amounts.","tags":["Economy"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"parameters":[{"$ref":"#/components/parameters/page"},{"$ref":"#/components/parameters/limit"}],"responses":{"200":{"description":"Paginated order history","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/PaginatedResponse"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/RechargeOrder"}}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/withdrawal/eligibility":{"get":{"operationId":"checkWithdrawalEligibility","summary":"Check withdrawal eligibility","description":"Check whether the authenticated user is eligible to withdraw Neko Coins. Returns eligibility status and any requirements not yet met.","tags":["Economy"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"responses":{"200":{"description":"Withdrawal eligibility status","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"type":"object","properties":{"eligible":{"type":"boolean"},"reason":{"type":"string","nullable":true}}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/withdrawal/preview":{"get":{"operationId":"previewWithdrawal","summary":"Preview withdrawal amount","description":"Preview the withdrawal conversion including fees and estimated SOL amount for the specified coin count.","tags":["Economy"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"parameters":[{"name":"coins","in":"query","required":true,"description":"Number of Neko Coins to withdraw","schema":{"type":"integer","minimum":1}}],"responses":{"200":{"description":"Withdrawal preview","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"$ref":"#/components/schemas/WithdrawalPreview"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/withdrawal":{"post":{"operationId":"requestWithdrawal","summary":"Request coin withdrawal","description":"Request a withdrawal of Neko Coins to a Solana wallet address. Converts coins to SOL minus fees.","tags":["Economy"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["coins","walletAddress"],"properties":{"coins":{"type":"integer","minimum":1,"description":"Number of Neko Coins to withdraw"},"walletAddress":{"type":"string","description":"Solana wallet address to receive SOL"}}}}}},"responses":{"200":{"description":"Withdrawal request submitted","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"type":"object","properties":{"withdrawalId":{"type":"string"},"coins":{"type":"integer"},"estimatedSol":{"type":"number"},"status":{"type":"string"}}}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/coin-transactions":{"get":{"operationId":"listCoinTransactions","summary":"Get coin transaction history","description":"Get the paginated Neko Coin transaction history for the authenticated user. Includes earnings, spending, transfers, recharges, and withdrawals.","tags":["Economy"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"parameters":[{"$ref":"#/components/parameters/page"},{"$ref":"#/components/parameters/limit"}],"responses":{"200":{"description":"Paginated transaction history","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/PaginatedResponse"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/CoinTransaction"}}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/ai/mentor/ask":{"post":{"operationId":"askSage","summary":"Ask Sage a question","description":"Ask the AI tutor (Sage) a Japanese language question. Supports optional JLPT level targeting and conversation threading via conversationId. 3 free questions per day, then 50 Neko Coins each.","tags":["Sage"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"x-rateLimit":{"tier":"aiChat","maxRequests":10,"windowSeconds":60},"x-nekoCoinCost":50,"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["question"],"properties":{"question":{"type":"string","description":"Question to ask Sage"},"jlptLevel":{"type":"string","enum":["N5","N4","N3","N2","N1"],"description":"Target JLPT level for the response"},"conversationId":{"type":"string","description":"Continue an existing conversation"}}}}}},"responses":{"200":{"description":"Sage response","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"$ref":"#/components/schemas/SageResponse"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}}},"patch":{"operationId":"continueSageConversation","summary":"Continue Sage conversation","description":"Send a follow-up message in an existing Sage conversation. Requires a valid conversationId from a previous ask. Costs 50 Neko Coins after free daily quota is exhausted.","tags":["Sage"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"x-rateLimit":{"tier":"aiChat","maxRequests":10,"windowSeconds":60},"x-nekoCoinCost":50,"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["conversationId","followUp"],"properties":{"conversationId":{"type":"string","description":"Existing conversation ID"},"followUp":{"type":"string","description":"Follow-up message"}}}}}},"responses":{"200":{"description":"Follow-up response","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"$ref":"#/components/schemas/SageResponse"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/ai/sage/status":{"get":{"operationId":"getSageUsageStatus","summary":"Sage usage status","description":"Get the current Sage AI usage status including remaining free daily questions, total usage count, and coin balance.","tags":["Sage"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"responses":{"200":{"description":"Usage status","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"$ref":"#/components/schemas/SageUsageStatus"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/ai/sage/ledger":{"get":{"operationId":"getSageLedger","summary":"AI usage history","description":"Get paginated history of AI usage transactions including Sage questions, speaking sessions, and writing corrections.","tags":["Sage"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"parameters":[{"$ref":"#/components/parameters/page"},{"$ref":"#/components/parameters/limit"}],"responses":{"200":{"description":"Paginated AI usage ledger","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DataResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/ai/speaking/chat":{"post":{"operationId":"speakingChat","summary":"Speaking practice chat","description":"Start or continue a speaking practice conversation with the AI. The AI responds with natural Japanese dialogue appropriate for the user level. Use conversationId to maintain context across messages.","tags":["Speaking"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"x-rateLimit":{"tier":"aiChat","maxRequests":10,"windowSeconds":60},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["message"],"properties":{"message":{"type":"string","description":"User message in Japanese or English"},"conversationId":{"type":"string","description":"Continue an existing speaking session"}}}}}},"responses":{"200":{"description":"AI speaking response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DataResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/ai/speaking/evaluate":{"post":{"operationId":"evaluatePronunciation","summary":"Evaluate pronunciation","description":"Evaluate pronunciation quality from an audio URL or text transcription. Returns pronunciation score, fluency score, specific issues, and improvement suggestions.","tags":["Speaking"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"x-rateLimit":{"tier":"aiChat","maxRequests":10,"windowSeconds":60},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"audioUrl":{"type":"string","format":"uri","description":"URL of the audio recording to evaluate"},"text":{"type":"string","description":"Text transcription to evaluate (alternative to audioUrl)"}}}}}},"responses":{"200":{"description":"Pronunciation evaluation","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"$ref":"#/components/schemas/SpeakingResult"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/ai/writing/correct":{"post":{"operationId":"correctWriting","summary":"Writing correction","description":"Submit Japanese text for AI grammar and style correction. Returns the corrected text, a list of errors with explanations, improvement suggestions, and an overall writing score.","tags":["Writing"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"x-rateLimit":{"tier":"aiChat","maxRequests":10,"windowSeconds":60},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["text"],"properties":{"text":{"type":"string","description":"Japanese text to correct"},"jlptLevel":{"type":"string","enum":["N5","N4","N3","N2","N1"],"description":"Target JLPT level for feedback calibration"}}}}}},"responses":{"200":{"description":"Writing correction result","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"$ref":"#/components/schemas/WritingCorrection"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/ai/practice-session":{"post":{"operationId":"startAiPracticeSession","summary":"Start AI practice session","description":"Generate an AI-powered practice session with multiple-choice questions tailored to the specified JLPT level and practice type (vocabulary, grammar, reading, or mixed).","tags":["Sage"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"x-rateLimit":{"tier":"aiChat","maxRequests":10,"windowSeconds":60},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["jlptLevel","type"],"properties":{"jlptLevel":{"type":"string","enum":["N5","N4","N3","N2","N1"],"description":"JLPT level for questions"},"type":{"type":"string","enum":["vocabulary","grammar","reading","mixed"],"description":"Practice session type"}}}}}},"responses":{"200":{"description":"Practice session created","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"$ref":"#/components/schemas/AIPracticeSession"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/ai/study-plan":{"get":{"operationId":"getStudyPlan","summary":"Get active study plan","description":"Get the current AI-generated study plan for the authenticated user, including weekly goals, progress, and schedule.","tags":["Sage"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"responses":{"200":{"description":"Active study plan","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"$ref":"#/components/schemas/AIStudyPlan"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}},"post":{"operationId":"createStudyPlan","summary":"Create study plan","description":"Create a new AI-generated study plan targeting a specific JLPT level with a weekly hour goal. Replaces any existing active plan.","tags":["Sage"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["jlptLevel","weeklyGoalHours"],"properties":{"jlptLevel":{"type":"string","enum":["N5","N4","N3","N2","N1"],"description":"Target JLPT level"},"weeklyGoalHours":{"type":"integer","minimum":1,"maximum":40,"description":"Weekly study hours goal"}}}}}},"responses":{"201":{"description":"Study plan created","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"$ref":"#/components/schemas/AIStudyPlan"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"}}},"put":{"operationId":"updateStudyPlan","summary":"Update study plan","description":"Update the active study plan settings such as weekly goal hours or status.","tags":["Sage"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"weeklyGoalHours":{"type":"integer","minimum":1,"maximum":40},"status":{"type":"string","enum":["active","paused"]}}}}}},"responses":{"200":{"description":"Study plan updated","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"$ref":"#/components/schemas/AIStudyPlan"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"}}},"delete":{"operationId":"deleteStudyPlan","summary":"Delete study plan","description":"Delete the active study plan. This cannot be undone.","tags":["Sage"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"responses":{"200":{"description":"Study plan deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/ai/study-plan/progress":{"get":{"operationId":"getStudyPlanProgress","summary":"Study plan progress","description":"Get detailed progress for the active study plan including current week, completed lessons, and time spent.","tags":["Sage"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"responses":{"200":{"description":"Study plan progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DataResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/sage-client/sso/token":{"post":{"operationId":"getSageSsoToken","summary":"Get Sage SSO token","description":"Generate a single sign-on token for the Sage AI client interface. Used to authenticate the embedded Sage chat.","tags":["Sage"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"responses":{"200":{"description":"SSO token","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"type":"object","properties":{"token":{"type":"string"}}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/sage-client/billing/check":{"get":{"operationId":"checkSageBilling","summary":"Check Sage billing","description":"Check whether the user has sufficient balance or free quota remaining for a Sage query.","tags":["Sage"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"responses":{"200":{"description":"Billing check result","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"type":"object","properties":{"canQuery":{"type":"boolean"},"freeRemaining":{"type":"integer"},"coinBalance":{"type":"integer"}}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/sage-client/billing/deduct":{"post":{"operationId":"deductSageBilling","summary":"Deduct Sage billing","description":"Deduct Neko Coins for a Sage AI usage. Called internally by the Sage client after a query completes.","tags":["Sage"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["amount"],"properties":{"amount":{"type":"integer","minimum":1,"description":"Neko Coins to deduct"}}}}}},"responses":{"200":{"description":"Coins deducted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/spirit-vision":{"post":{"operationId":"spiritVision","summary":"Spirit vision AI feature","description":"Invoke the spirit vision AI feature for visual recognition and analysis of Japanese text in images.","tags":["Sage"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"responses":{"200":{"description":"Spirit vision result","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DataResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/study-history":{"get":{"operationId":"getStudyHistory","summary":"Get study history","description":"Get the authenticated user study history with timestamps, durations, course IDs, and words studied per session.","tags":["Study"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"parameters":[{"$ref":"#/components/parameters/page"},{"$ref":"#/components/parameters/limit"}],"responses":{"200":{"description":"Paginated study history","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DataResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}},"post":{"operationId":"recordStudySession","summary":"Record study session","description":"Record a completed study session with course, duration, and number of words studied. Used to track learning progress and update streak data.","tags":["Study"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["courseId","duration","wordsStudied"],"properties":{"courseId":{"type":"integer","minimum":0,"maximum":145,"description":"Course number studied"},"duration":{"type":"integer","minimum":1,"description":"Session duration in seconds"},"wordsStudied":{"type":"integer","minimum":0,"description":"Number of words studied in the session"}}}}}},"responses":{"201":{"description":"Study session recorded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/study-strategy":{"get":{"operationId":"getStudyStrategy","summary":"Get study strategy","description":"Get the current study strategy settings for the authenticated user, including preferred study methods and scheduling preferences.","tags":["Study"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"responses":{"200":{"description":"Study strategy","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DataResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}},"post":{"operationId":"createStudyStrategy","summary":"Create study strategy","description":"Create a new study strategy with preferred methods, daily goals, and scheduling settings.","tags":["Study"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"preferredMethod":{"type":"string","description":"Preferred study method"},"dailyGoalMinutes":{"type":"integer","minimum":5,"description":"Daily study goal in minutes"},"reminderTime":{"type":"string","description":"Preferred reminder time (HH:mm format)"}}}}}},"responses":{"201":{"description":"Strategy created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"}}},"put":{"operationId":"updateStudyStrategy","summary":"Update study strategy","description":"Update the existing study strategy settings. Supports partial updates.","tags":["Study"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"preferredMethod":{"type":"string"},"dailyGoalMinutes":{"type":"integer","minimum":5},"reminderTime":{"type":"string"}}}}}},"responses":{"200":{"description":"Strategy updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"}}},"delete":{"operationId":"deleteStudyStrategy","summary":"Delete study strategy","description":"Delete the current study strategy and revert to default settings.","tags":["Study"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"responses":{"200":{"description":"Strategy deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/quiz":{"get":{"operationId":"listQuizzes","summary":"List quizzes","description":"List available quizzes for the authenticated user, including completed and in-progress quizzes.","tags":["Quiz"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"responses":{"200":{"description":"Quiz list","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DataResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/quiz/start":{"post":{"operationId":"startQuiz","summary":"Start quiz","description":"Start a new quiz session. Optionally target a specific course or JLPT level, and control the number of questions.","tags":["Quiz"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"courseId":{"type":"integer","minimum":0,"maximum":145,"description":"Specific course to quiz on"},"jlptLevel":{"type":"string","enum":["N5","N4","N3","N2","N1"],"description":"JLPT level to quiz on"},"questionCount":{"type":"integer","minimum":5,"maximum":50,"default":10,"description":"Number of questions"}}}}}},"responses":{"201":{"description":"Quiz started","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DataResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/quiz/submit":{"post":{"operationId":"submitQuizAnswers","summary":"Submit quiz answers","description":"Submit answers for an active quiz session. Returns score, correct/incorrect breakdown, and XP earned.","tags":["Quiz"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["quizId","answers"],"properties":{"quizId":{"type":"string","description":"Quiz session ID"},"answers":{"type":"array","items":{"type":"object","required":["questionId","selectedIndex"],"properties":{"questionId":{"type":"string"},"selectedIndex":{"type":"integer"}}},"description":"Array of question answers"}}}}}},"responses":{"200":{"description":"Quiz results","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DataResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/quiz/history":{"get":{"operationId":"getQuizHistory","summary":"Quiz history","description":"Get paginated history of completed quizzes with scores, dates, and course information.","tags":["Quiz"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"parameters":[{"$ref":"#/components/parameters/page"},{"$ref":"#/components/parameters/limit"}],"responses":{"200":{"description":"Paginated quiz history","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DataResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/watchlist":{"get":{"operationId":"getWatchlist","summary":"Get watchlist","description":"Get the authenticated user vocabulary watchlist — words saved for focused review.","tags":["Study"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"responses":{"200":{"description":"Watchlist items","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DataResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}},"post":{"operationId":"addToWatchlist","summary":"Add to watchlist","description":"Add a vocabulary word to the watchlist for focused review. Requires the vocabulary ID and source course ID.","tags":["Study"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["vocabularyId","courseId"],"properties":{"vocabularyId":{"type":"integer","description":"Vocabulary word ID"},"courseId":{"type":"integer","minimum":0,"maximum":145,"description":"Source course number"}}}}}},"responses":{"201":{"description":"Added to watchlist","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"409":{"$ref":"#/components/responses/Conflict"}}},"put":{"operationId":"updateWatchlistItem","summary":"Update watchlist item","description":"Update a watchlist item, such as marking it as mastered or adjusting review priority.","tags":["Study"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["vocabularyId"],"properties":{"vocabularyId":{"type":"integer","description":"Vocabulary word ID to update"},"mastered":{"type":"boolean","description":"Mark as mastered"},"priority":{"type":"integer","minimum":1,"maximum":5,"description":"Review priority (1=low, 5=high)"}}}}}},"responses":{"200":{"description":"Watchlist item updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}},"delete":{"operationId":"removeFromWatchlist","summary":"Remove from watchlist","description":"Remove a vocabulary word from the watchlist.","tags":["Study"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["vocabularyId"],"properties":{"vocabularyId":{"type":"integer","description":"Vocabulary word ID to remove"}}}}}},"responses":{"200":{"description":"Removed from watchlist","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/analytics/summary":{"get":{"operationId":"getAnalyticsSummary","summary":"Overall learning statistics","description":"Get aggregate learning statistics for the authenticated user including total study time, words learned, courses completed, current streak, quiz average, and tier progress.","tags":["Analytics"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"responses":{"200":{"description":"Learning statistics summary","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"type":"object","properties":{"totalStudyMinutes":{"type":"integer"},"wordsLearned":{"type":"integer"},"coursesCompleted":{"type":"integer"},"currentStreak":{"type":"integer"},"quizAverage":{"type":"number"},"tier":{"type":"string"}}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/analytics/activity":{"get":{"operationId":"getActivityHeatmap","summary":"Daily activity heatmap","description":"Get daily study activity data for rendering a heatmap visualization. Returns an array of dates with study minutes and words studied. Defaults to the last 365 days if no date range is specified.","tags":["Analytics"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"parameters":[{"name":"startDate","in":"query","description":"Start date for the activity range (ISO 8601 date, e.g. 2025-01-01)","schema":{"type":"string","format":"date"}},{"name":"endDate","in":"query","description":"End date for the activity range (ISO 8601 date, e.g. 2025-12-31)","schema":{"type":"string","format":"date"}}],"responses":{"200":{"description":"Daily activity data","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"type":"array","items":{"type":"object","properties":{"date":{"type":"string","format":"date"},"studyMinutes":{"type":"integer"},"wordsStudied":{"type":"integer"}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/analytics/progress":{"get":{"operationId":"getCourseProgress","summary":"Course completion progress","description":"Get completion progress across all courses the user has started. Returns per-course vocabulary mastery percentage, quiz scores, and last study date.","tags":["Analytics"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"responses":{"200":{"description":"Course completion progress","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"type":"array","items":{"type":"object","properties":{"courseId":{"type":"integer"},"courseTitle":{"type":"string"},"jlptLevel":{"type":"string"},"completionPercent":{"type":"number"},"wordsLearned":{"type":"integer"},"totalWords":{"type":"integer"},"lastStudied":{"type":"string","format":"date-time"}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/analytics/quizzes":{"get":{"operationId":"getQuizPerformance","summary":"Quiz performance history","description":"Get quiz performance analytics including score trends, average accuracy by JLPT level, and weakest vocabulary categories. Useful for identifying areas that need more practice.","tags":["Analytics"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"responses":{"200":{"description":"Quiz performance analytics","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"type":"object","properties":{"totalQuizzes":{"type":"integer"},"averageScore":{"type":"number"},"bestScore":{"type":"number"},"recentScores":{"type":"array","items":{"type":"object","properties":{"date":{"type":"string","format":"date-time"},"score":{"type":"number"},"jlptLevel":{"type":"string"}}}},"weakCategories":{"type":"array","items":{"type":"string"}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/admin/generate-vocab-audio":{"post":{"operationId":"generateVocabAudio","summary":"Generate vocabulary audio","description":"Generate TTS audio files for vocabulary words that are missing audio. Admin-only batch operation.","tags":["Admin"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"x-internal":true,"responses":{"200":{"description":"Audio generation started","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DataResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}}},"/api/admin/kyc":{"get":{"operationId":"adminListKyc","summary":"List KYC submissions","description":"List all pending and processed KYC (Know Your Customer) verification submissions for admin review.","tags":["Admin"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"x-internal":true,"parameters":[{"$ref":"#/components/parameters/page"},{"$ref":"#/components/parameters/limit"}],"responses":{"200":{"description":"KYC submission list","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DataResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}},"post":{"operationId":"adminProcessKyc","summary":"Process KYC submission","description":"Approve or reject a KYC verification submission.","tags":["Admin"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"x-internal":true,"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["userId","action"],"properties":{"userId":{"type":"string","description":"User ID to process"},"action":{"type":"string","enum":["approve","reject"],"description":"Approval action"},"reason":{"type":"string","description":"Rejection reason (required for reject)"}}}}}},"responses":{"200":{"description":"KYC processed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}}},"/api/admin/withdrawal":{"get":{"operationId":"adminListWithdrawals","summary":"List withdrawal requests","description":"List all pending and processed Neko Coin withdrawal requests for admin review.","tags":["Admin"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"x-internal":true,"parameters":[{"$ref":"#/components/parameters/page"},{"$ref":"#/components/parameters/limit"}],"responses":{"200":{"description":"Withdrawal request list","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DataResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}},"post":{"operationId":"adminProcessWithdrawal","summary":"Process withdrawal request","description":"Approve or reject a Neko Coin withdrawal request. Approved withdrawals are sent on-chain.","tags":["Admin"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"x-internal":true,"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["withdrawalId","action"],"properties":{"withdrawalId":{"type":"string","description":"Withdrawal request ID"},"action":{"type":"string","enum":["approve","reject"],"description":"Approval action"},"reason":{"type":"string","description":"Rejection reason (required for reject)"}}}}}},"responses":{"200":{"description":"Withdrawal processed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}}},"/api/cron/anime-bot":{"post":{"operationId":"cronAnimeBot","summary":"Anime bot cron","description":"Trigger the anime bot to generate and post anime-themed Japanese learning content to the Tavern.","tags":["Cron"],"security":[{"cronSecret":[]}],"x-internal":true,"responses":{"200":{"description":"Anime bot executed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/cron/avatar-queue":{"post":{"operationId":"cronAvatarQueue","summary":"Avatar queue cron","description":"Process the queued AI avatar generation requests. Picks up pending requests and generates images via DALL-E 3.","tags":["Cron"],"security":[{"cronSecret":[]}],"x-internal":true,"responses":{"200":{"description":"Avatar queue processed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/cron/career-bot":{"post":{"operationId":"cronCareerBot","summary":"Career bot cron","description":"Trigger the career bot to post Japanese career and business vocabulary content to the Tavern.","tags":["Cron"],"security":[{"cronSecret":[]}],"x-internal":true,"responses":{"200":{"description":"Career bot executed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/cron/daily-reminders":{"post":{"operationId":"cronDailyReminders","summary":"Daily reminders cron","description":"Send daily study reminder notifications to users who have opted in. Checks user preferences and streak status.","tags":["Cron"],"security":[{"cronSecret":[]}],"x-internal":true,"responses":{"200":{"description":"Reminders sent","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/cron/duel-expire":{"post":{"operationId":"cronDuelExpire","summary":"Expire old duels","description":"Expire duels that have been open or in-progress beyond the time limit. Refunds escrowed Neko Coins to participants.","tags":["Cron"],"security":[{"cronSecret":[]}],"x-internal":true,"responses":{"200":{"description":"Expired duels processed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/cron/grok-post":{"post":{"operationId":"cronGrokPost","summary":"Grok post cron","description":"Generate and publish an AI-powered Japanese learning post using the Grok model.","tags":["Cron"],"security":[{"cronSecret":[]}],"x-internal":true,"responses":{"200":{"description":"Grok post created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/cron/grok-raid":{"post":{"operationId":"cronGrokRaid","summary":"Grok raid cron","description":"Trigger a Grok-powered raid event — a time-limited community challenge with bonus rewards.","tags":["Cron"],"security":[{"cronSecret":[]}],"x-internal":true,"responses":{"200":{"description":"Grok raid triggered","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/cron/sage-chain":{"post":{"operationId":"cronSageChain","summary":"Sage chain sync","description":"Synchronize Sage AI usage records with the on-chain ledger for transparency and auditing.","tags":["Cron"],"security":[{"cronSecret":[]}],"x-internal":true,"responses":{"200":{"description":"Chain sync completed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/cron/study-plan-advance":{"post":{"operationId":"cronStudyPlanAdvance","summary":"Advance study plans","description":"Advance all active study plans to the next week or phase based on elapsed time and completion status.","tags":["Cron"],"security":[{"cronSecret":[]}],"x-internal":true,"responses":{"200":{"description":"Study plans advanced","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/cron/tavern-guardian":{"post":{"operationId":"cronTavernGuardian","summary":"Content moderation","description":"Run the Tavern Guardian automated content moderation sweep. Checks recent posts and comments for policy violations.","tags":["Cron"],"security":[{"cronSecret":[]}],"x-internal":true,"responses":{"200":{"description":"Moderation sweep completed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/feedback":{"get":{"operationId":"getFeedback","summary":"Get submitted feedback","description":"Get feedback previously submitted by the authenticated user.","tags":["Platform"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"responses":{"200":{"description":"User feedback list","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DataResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}},"post":{"operationId":"submitFeedback","summary":"Submit feedback","description":"Submit feedback about the platform. Supports types such as bug report, feature request, and general feedback.","tags":["Platform"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["type","message"],"properties":{"type":{"type":"string","enum":["bug","feature","general"],"description":"Feedback type"},"message":{"type":"string","maxLength":2000,"description":"Feedback message"}}}}}},"responses":{"201":{"description":"Feedback submitted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/upload":{"post":{"operationId":"uploadFile","summary":"Upload file","description":"Upload a file (image, audio, or document). Returns the uploaded file URL. Max size varies by file type.","tags":["Platform"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["file"],"properties":{"file":{"type":"string","format":"binary","description":"File to upload"}}}}}},"responses":{"200":{"description":"File uploaded","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"type":"object","properties":{"url":{"type":"string","format":"uri"},"filename":{"type":"string"}}}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/download":{"get":{"operationId":"getAppDownload","summary":"App download redirect","description":"Redirect to the appropriate app store (iOS App Store or Google Play) based on the User-Agent. Falls back to the website.","tags":["Platform"],"security":[],"responses":{"302":{"description":"Redirect to app store or website"}}}},"/api/vocab-audio":{"get":{"operationId":"getVocabAudioUrl","summary":"Get vocab audio URL","description":"Get the audio pronunciation URL for a specific Japanese vocabulary word. Returns a signed URL to the audio file.","tags":["Platform"],"security":[],"parameters":[{"name":"word","in":"query","required":true,"description":"Japanese word to get audio for (kanji or kana)","schema":{"type":"string"}}],"responses":{"200":{"description":"Audio URL","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"type":"object","properties":{"audioUrl":{"type":"string","format":"uri"}}}}}}}},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/referral":{"get":{"operationId":"getReferralInfo","summary":"Get own referral info","description":"Get the authenticated user referral code, referral count, and earned bonus Neko Coins from referrals.","tags":["Referral"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"responses":{"200":{"description":"Referral information","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"type":"object","properties":{"code":{"type":"string"},"referralCount":{"type":"integer"},"totalEarned":{"type":"integer"}}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/referral/claim":{"post":{"operationId":"claimReferralBonus","summary":"Claim referral bonus","description":"Claim a referral bonus by providing a valid referral code. Awards +100 Neko Coins to both the referrer and the new user.","tags":["Referral"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["code"],"properties":{"code":{"type":"string","description":"Referral code to claim"}}}}}},"responses":{"200":{"description":"Referral bonus claimed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"409":{"$ref":"#/components/responses/Conflict"}}}},"/api/referral/validate":{"get":{"operationId":"validateReferralCode","summary":"Validate referral code","description":"Check whether a referral code is valid and has not been used by the requesting user.","tags":["Referral"],"security":[],"parameters":[{"name":"code","in":"query","required":true,"description":"Referral code to validate","schema":{"type":"string"}}],"responses":{"200":{"description":"Validation result","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"type":"object","properties":{"valid":{"type":"boolean"},"referrerName":{"type":"string"}}}}}}}}}}},"/api/reports":{"post":{"operationId":"reportContent","summary":"Report content","description":"Report a post, comment, or user for policy violations. Reports are reviewed by the Tavern Guardian moderation system.","tags":["Platform"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["type","targetId","reason"],"properties":{"type":{"type":"string","enum":["post","comment","user"],"description":"Type of content being reported"},"targetId":{"type":"string","description":"ID of the reported content or user"},"reason":{"type":"string","maxLength":1000,"description":"Description of the violation"}}}}}},"responses":{"201":{"description":"Report submitted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/kyc":{"get":{"operationId":"getKycStatus","summary":"KYC verification status","description":"Get the current KYC (Know Your Customer) verification status for the authenticated user. Required for withdrawals above a threshold.","tags":["Platform"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"responses":{"200":{"description":"KYC status","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"type":"object","properties":{"status":{"type":"string","enum":["none","pending","approved","rejected"]},"submittedAt":{"type":"string","format":"date-time","nullable":true}}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}},"post":{"operationId":"submitKyc","summary":"Submit KYC verification","description":"Submit identity documents for KYC verification. Required for large Neko Coin withdrawals.","tags":["Platform"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["documentType","frontImage"],"properties":{"documentType":{"type":"string","enum":["passport","id_card","drivers_license"]},"frontImage":{"type":"string","format":"binary","description":"Front of document"},"backImage":{"type":"string","format":"binary","description":"Back of document (if applicable)"}}}}}},"responses":{"201":{"description":"KYC submitted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/streak-protection":{"post":{"operationId":"buyStreakProtection","summary":"Buy streak protection","description":"Purchase streak protection to prevent losing your study streak for one day. Costs 50 Neko Coins.","tags":["Platform"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"x-nekoCoinCost":50,"responses":{"200":{"description":"Streak protection activated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/shop/buy/frozen-amulet":{"post":{"operationId":"buyFrozenAmulet","summary":"Buy frozen amulet","description":"Purchase a Frozen Amulet from the shop. This item freezes your streak counter, preventing streak loss for a set duration.","tags":["Platform"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"responses":{"200":{"description":"Frozen amulet purchased","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/user/{userId}/log":{"get":{"operationId":"getUserActivityLog","summary":"User activity log","description":"Get the activity log for a specific user. Shows recent actions such as study sessions, quiz completions, and social interactions.","tags":["Users"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"parameters":[{"$ref":"#/components/parameters/userId"}],"responses":{"200":{"description":"User activity log","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DataResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/user/{userId}/notes":{"get":{"operationId":"getUserAdminNotes","summary":"Get user admin notes","description":"Get admin notes attached to a specific user account. Used for moderation tracking and support history.","tags":["Users"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"parameters":[{"$ref":"#/components/parameters/userId"}],"responses":{"200":{"description":"Admin notes list","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DataResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}},"post":{"operationId":"addUserAdminNote","summary":"Add user admin note","description":"Add an admin note to a user account for moderation or support tracking.","tags":["Users"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"parameters":[{"$ref":"#/components/parameters/userId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["note"],"properties":{"note":{"type":"string","maxLength":2000,"description":"Admin note content"}}}}}},"responses":{"201":{"description":"Note added","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/user/{userId}/squad":{"get":{"operationId":"getUserSquad","summary":"User squad info","description":"Get the squad membership information for a specific user. Returns the squad name, role, and join date if the user belongs to a squad.","tags":["Users"],"security":[],"parameters":[{"$ref":"#/components/parameters/userId"}],"responses":{"200":{"description":"User squad information","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"type":"object","nullable":true,"properties":{"squadId":{"type":"string"},"squadName":{"type":"string"},"role":{"type":"string","enum":["leader","officer","member"]},"joinedAt":{"type":"string","format":"date-time"}}}}}}}},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/user/chain-address":{"get":{"operationId":"getChainAddress","summary":"Get blockchain address","description":"Get the linked blockchain wallet address for the authenticated user. Used for on-chain Neko Coin withdrawals.","tags":["Economy"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"responses":{"200":{"description":"Blockchain address","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"type":"object","properties":{"address":{"type":"string","nullable":true,"description":"Solana wallet address or null if not linked"},"linkedAt":{"type":"string","format":"date-time","nullable":true}}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}},"post":{"operationId":"linkChainAddress","summary":"Link blockchain address","description":"Link a Solana wallet address to the authenticated user account for on-chain Neko Coin withdrawals.","tags":["Economy"],"security":[{"sessionCookie":[]},{"bearerToken":[]},{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["address"],"properties":{"address":{"type":"string","description":"Solana wallet address to link"}}}}}},"responses":{"200":{"description":"Address linked","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"409":{"$ref":"#/components/responses/Conflict"}}}}},"components":{"parameters":{"page":{"name":"page","in":"query","description":"Page number (starts at 1)","schema":{"type":"integer","default":1,"minimum":1}},"limit":{"name":"limit","in":"query","description":"Items per page (max 100)","schema":{"type":"integer","default":20,"minimum":1,"maximum":100}},"limitLarge":{"name":"limit","in":"query","description":"Items per page (max 100, default 50 for vocabulary)","schema":{"type":"integer","default":50,"minimum":1,"maximum":100}},"locale":{"name":"locale","in":"query","description":"Response locale for translations","schema":{"type":"string","default":"en","enum":["en","zh","ja","ko","vi","id","zh-TW","zh-HK"]}},"courseId":{"name":"courseId","in":"path","required":true,"description":"Course number (0-145). Course 0 = Kana, 1-48 = Grammar, 49+ = Vocabulary Lists","schema":{"type":"integer","minimum":0,"maximum":145}},"jlptLevel":{"name":"level","in":"path","required":true,"description":"JLPT level (N5=beginner, N4=elementary, N3=intermediate, N2=upper-intermediate, N1=advanced)","schema":{"type":"string","enum":["N5","N4","N3","N2","N1"]}},"jlptFilter":{"name":"jlpt","in":"query","description":"Filter by JLPT level","schema":{"type":"string","enum":["N5","N4","N3","N2","N1"]}},"userId":{"name":"userId","in":"path","required":true,"description":"User ID (cuid)","schema":{"type":"string"}},"duelId":{"name":"duelId","in":"path","required":true,"description":"Duel ID","schema":{"type":"string"}},"squadId":{"name":"squadId","in":"path","required":true,"description":"Squad ID","schema":{"type":"string"}},"postId":{"name":"id","in":"path","required":true,"description":"Post ID","schema":{"type":"integer"}},"eventId":{"name":"id","in":"path","required":true,"description":"Event ID","schema":{"type":"integer"}},"sort":{"name":"sort","in":"query","description":"Sort order","schema":{"type":"string","enum":["hot","new","top"]}},"apiKeyId":{"name":"id","in":"path","required":true,"description":"API key ID","schema":{"type":"string"}},"commentId":{"name":"commentId","in":"path","required":true,"description":"Comment ID","schema":{"type":"integer"}},"memberId":{"name":"memberId","in":"path","required":true,"description":"Squad member user ID","schema":{"type":"string"}},"patternId":{"name":"patternId","in":"path","required":true,"description":"Grammar pattern ID (e.g., \"n5-pattern-1\")","schema":{"type":"string"}}},"schemas":{"Pagination":{"type":"object","properties":{"page":{"type":"integer"},"limit":{"type":"integer"},"total":{"type":"integer"},"totalPages":{"type":"integer"},"hasMore":{"type":"boolean"}}},"PaginatedResponse":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"pagination":{"$ref":"#/components/schemas/Pagination"}}},"SuccessResponse":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"message":{"type":"string"},"timestamp":{"type":"string","format":"date-time"}},"required":["success"]},"DataResponse":{"type":"object","description":"Standard success envelope wrapping a data field","properties":{"success":{"type":"boolean","enum":[true]},"data":{},"message":{"type":"string"},"timestamp":{"type":"string","format":"date-time"}},"required":["success"]},"ErrorResponse":{"type":"object","properties":{"success":{"type":"boolean","enum":[false]},"error":{"type":"string","description":"Human-readable error message"},"code":{"type":"integer","description":"Application error code (1000-9999)"},"request_id":{"type":"string","format":"uuid","description":"Request trace ID"},"is_retriable":{"type":"boolean","description":"Whether the client should retry"},"retry_after":{"type":"integer","description":"Seconds to wait before retrying"},"doc_url":{"type":"string","format":"uri","description":"Link to relevant documentation"},"alternative_action":{"type":"string","description":"Suggested workaround"},"details":{"type":"object","description":"Additional error context"},"timestamp":{"type":"string","format":"date-time"}},"required":["success","error"]},"CourseSummary":{"type":"object","properties":{"courseNumber":{"type":"integer","description":"Unique course identifier (0-145)"},"title":{"type":"string","description":"Course title"},"jlptLevel":{"type":"string","enum":["N5","N4","N3","N2","N1"]},"vocabularyCount":{"type":"integer","description":"Number of vocabulary words in this course"},"hasAudio":{"type":"boolean","description":"Whether audio pronunciation files are available"}}},"VocabularyWord":{"type":"object","properties":{"id":{"type":"integer","description":"Word ID within the course"},"japanese":{"type":"string","description":"Japanese text (kanji/kana)"},"romaji":{"type":"string","description":"Romanized pronunciation"},"meaning":{"type":"string","description":"Translation in requested locale"},"partOfSpeech":{"type":"string","nullable":true,"description":"Part of speech (noun, verb, adjective, etc.)"},"example":{"type":"object","properties":{"japanese":{"type":"string","description":"Example sentence in Japanese"},"romaji":{"type":"string","nullable":true},"meaning":{"type":"string","description":"Example translation in requested locale"}}}}},"VocabularySearchResult":{"allOf":[{"$ref":"#/components/schemas/VocabularyWord"},{"type":"object","properties":{"courseNumber":{"type":"integer","description":"Source course number"},"jlptLevel":{"type":"string","description":"JLPT level of the source course"}}}]},"GrammarPatternSummary":{"type":"object","properties":{"id":{"type":"string","description":"Pattern ID (e.g., \"n5-pattern-1\")"},"title":{"type":"string","description":"Pattern title in Japanese"},"formula":{"type":"string","description":"Grammar formula"},"level":{"type":"string","enum":["N5","N4","N3","N2","N1"]},"exampleCount":{"type":"integer","description":"Number of example sentences"},"tags":{"type":"array","items":{"type":"string"}}}},"GrammarPatternDetail":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"titleChinese":{"type":"string","description":"Title in Chinese"},"level":{"type":"string","enum":["N5","N4","N3","N2","N1"]},"formula":{"type":"string"},"explanation":{"type":"string","description":"Detailed explanation"},"courseRange":{"type":"string","description":"Applicable course range"},"tags":{"type":"array","items":{"type":"string"}},"examples":{"type":"array","items":{"type":"object","properties":{"japanese":{"type":"string"},"romaji":{"type":"string"},"chinese":{"type":"string"}}}},"relatedPatterns":{"type":"array","items":{"type":"string"}}}},"JlptLevel":{"type":"object","properties":{"level":{"type":"string","enum":["N5","N4","N3","N2","N1"]},"difficulty":{"type":"string"},"courses":{"type":"integer","description":"Number of courses at this level"},"vocabulary":{"type":"integer","description":"Total vocabulary words at this level"},"grammarPatterns":{"type":"integer","description":"Grammar patterns at this level"},"available":{"type":"boolean","description":"Whether content is available for this level"}}},"StudyPlan":{"type":"object","properties":{"level":{"type":"string"},"estimatedWeeks":{"type":"integer"},"hoursPerWeek":{"type":"integer"},"phases":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"weeks":{"type":"integer"},"focus":{"type":"string"}}}},"coursesAvailable":{"type":"integer"},"vocabularyCount":{"type":"integer"}}},"Language":{"type":"object","properties":{"code":{"type":"string"},"name":{"type":"string"},"nativeName":{"type":"string"},"isDefault":{"type":"boolean"}}},"PlatformStats":{"type":"object","properties":{"totalCourses":{"type":"integer"},"totalVocabulary":{"type":"integer"},"totalGrammar":{"type":"integer"},"jlptLevels":{"type":"integer"},"locales":{"type":"integer"},"audioFiles":{"type":"integer"},"flashcardImages":{"type":"integer"}}},"HealthCheck":{"type":"object","properties":{"status":{"type":"string","enum":["ok"]},"version":{"type":"string"},"timestamp":{"type":"string","format":"date-time"}}},"UserSession":{"type":"object","properties":{"id":{"type":"string","description":"User ID (cuid)"},"name":{"type":"string","description":"Display name"},"email":{"type":"string","format":"email"},"image":{"type":"string","nullable":true,"description":"Avatar URL"},"tier":{"type":"string","enum":["Ronin","Ashigaru","Samurai","Hatamoto","Daimyo","Shogun"]},"locale":{"type":"string"},"nekoCoins":{"type":"integer"},"xp":{"type":"integer"},"streak":{"type":"integer"},"twoFactorEnabled":{"type":"boolean"}}},"LoginRequest":{"type":"object","required":["email","password"],"properties":{"email":{"type":"string","format":"email"},"password":{"type":"string","minLength":8}}},"RegisterRequest":{"type":"object","required":["username","email","password"],"properties":{"username":{"type":"string","minLength":3,"maxLength":20},"email":{"type":"string","format":"email"},"password":{"type":"string","minLength":8},"locale":{"type":"string","enum":["en","zh","ja","ko","vi","id","zh-TW","zh-HK"]},"referralCode":{"type":"string","description":"Optional referral code for bonus coins"}}},"ApiKeyInfo":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"prefix":{"type":"string","description":"First 8 chars of the key (e.g., \"wq_abc12\")"},"createdAt":{"type":"string","format":"date-time"},"lastUsedAt":{"type":"string","format":"date-time","nullable":true}}},"ApiKeyCreateResponse":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"key":{"type":"string","description":"Full API key — shown only once. Store securely."}}},"TwoFactorSetup":{"type":"object","properties":{"qrCode":{"type":"string","description":"QR code data URL for authenticator app"},"secret":{"type":"string","description":"TOTP secret (manual entry)"},"backupCodes":{"type":"array","items":{"type":"string"},"description":"One-time backup codes"}}},"ChangePasswordRequest":{"type":"object","required":["currentPassword","newPassword"],"properties":{"currentPassword":{"type":"string"},"newPassword":{"type":"string","minLength":8}}},"ForgotPasswordRequest":{"type":"object","required":["email"],"properties":{"email":{"type":"string","format":"email"}}},"ResetPasswordRequest":{"type":"object","required":["token","password"],"properties":{"token":{"type":"string"},"password":{"type":"string","minLength":8}}},"OAuthNativeRequest":{"type":"object","required":["idToken"],"properties":{"idToken":{"type":"string","description":"ID token from the OAuth provider"},"platform":{"type":"string","enum":["ios","android","web"]}}},"UserProfile":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"email":{"type":"string","format":"email"},"image":{"type":"string","nullable":true},"bio":{"type":"string","nullable":true},"tier":{"type":"string","enum":["Ronin","Ashigaru","Samurai","Hatamoto","Daimyo","Shogun"]},"tierName":{"type":"string"},"level":{"type":"integer"},"exp":{"type":"integer"},"expToNextLevel":{"type":"integer"},"nekoCoins":{"type":"integer"},"currentStreak":{"type":"integer"},"jlptTarget":{"type":"string","nullable":true},"coursesCompleted":{"type":"integer"},"vocabularyStudied":{"type":"integer"},"locale":{"type":"string"},"createdAt":{"type":"string","format":"date-time"}}},"UserPublicProfile":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"image":{"type":"string","nullable":true},"bio":{"type":"string","nullable":true},"tier":{"type":"string"},"level":{"type":"integer"},"followerCount":{"type":"integer"},"followingCount":{"type":"integer"},"isFollowing":{"type":"boolean","description":"Whether the authenticated user follows this user"}}},"ProfileUpdateRequest":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":50},"bio":{"type":"string","maxLength":500},"locale":{"type":"string","enum":["en","zh","ja","ko","vi","id","zh-TW","zh-HK"]},"jlptTarget":{"type":"string","enum":["N5","N4","N3","N2","N1"]}}},"AvatarHistory":{"type":"object","properties":{"id":{"type":"string"},"imageUrl":{"type":"string"},"prompt":{"type":"string","description":"AI generation prompt used"},"createdAt":{"type":"string","format":"date-time"}}},"Chronicle":{"type":"object","properties":{"entries":{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"description":{"type":"string"},"date":{"type":"string","format":"date-time"}}}}}},"TavernPost":{"type":"object","properties":{"id":{"type":"integer"},"title":{"type":"string"},"content":{"type":"string"},"authorId":{"type":"string"},"author":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"image":{"type":"string"},"tier":{"type":"string"}}},"cheers":{"type":"integer"},"commentCount":{"type":"integer"},"type":{"type":"string"},"tags":{"type":"array","items":{"type":"string"}},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"TavernComment":{"type":"object","properties":{"id":{"type":"integer"},"content":{"type":"string"},"authorId":{"type":"string"},"author":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"image":{"type":"string"}}},"createdAt":{"type":"string","format":"date-time"}}},"TavernEvent":{"type":"object","properties":{"id":{"type":"integer"},"title":{"type":"string"},"description":{"type":"string"},"startTime":{"type":"string","format":"date-time"},"endTime":{"type":"string","format":"date-time","nullable":true},"location":{"type":"string","nullable":true},"maxParticipants":{"type":"integer","nullable":true},"participantCount":{"type":"integer"},"cost":{"type":"integer","description":"Neko Coin cost to create"},"status":{"type":"string","enum":["UPCOMING","ACTIVE","ENDED","CANCELLED"]},"createdBy":{"type":"string"},"createdAt":{"type":"string","format":"date-time"}}},"Message":{"type":"object","properties":{"id":{"type":"string"},"senderId":{"type":"string"},"receiverId":{"type":"string"},"content":{"type":"string"},"type":{"type":"string","enum":["text","image"]},"readAt":{"type":"string","format":"date-time","nullable":true},"createdAt":{"type":"string","format":"date-time"}}},"Conversation":{"type":"object","properties":{"userId":{"type":"string"},"userName":{"type":"string"},"userImage":{"type":"string","nullable":true},"lastMessage":{"type":"string"},"unreadCount":{"type":"integer"},"updatedAt":{"type":"string","format":"date-time"}}},"Duel":{"type":"object","properties":{"id":{"type":"string"},"challengerId":{"type":"string"},"challengerName":{"type":"string"},"opponentId":{"type":"string","nullable":true},"opponentName":{"type":"string","nullable":true},"wager":{"type":"integer","description":"Neko Coin wager 10-1000"},"jlptLevel":{"type":"string"},"questionCount":{"type":"integer"},"status":{"type":"string","enum":["OPEN","IN_PROGRESS","COMPLETED","CANCELLED"]},"winnerId":{"type":"string","nullable":true},"createdAt":{"type":"string","format":"date-time"}}},"DuelQuestion":{"type":"object","properties":{"id":{"type":"integer"},"japanese":{"type":"string"},"options":{"type":"array","items":{"type":"string"}},"timeLimit":{"type":"integer","description":"Seconds"}}},"ShrineItem":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"description":{"type":"string"},"rarity":{"type":"string","enum":["C","R","SR","SSR"]},"type":{"type":"string","enum":["frame","badge","title","background"]},"imageUrl":{"type":"string"},"equipped":{"type":"boolean"}}},"ShrineBanner":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"description":{"type":"string"},"featuredItems":{"type":"array","items":{"$ref":"#/components/schemas/ShrineItem"}},"endDate":{"type":"string","format":"date-time"},"rates":{"type":"object","properties":{"C":{"type":"number"},"R":{"type":"number"},"SR":{"type":"number"},"SSR":{"type":"number"}}}}},"ShrinePullResult":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/ShrineItem"}},"pityCount":{"type":"integer","description":"Current pity counter"}}},"Squad":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"code":{"type":"string","description":"Invite code"},"description":{"type":"string","nullable":true},"leaderId":{"type":"string"},"leaderName":{"type":"string"},"memberCount":{"type":"integer"},"maxMembers":{"type":"integer","default":10},"createdAt":{"type":"string","format":"date-time"}}},"SquadMember":{"type":"object","properties":{"userId":{"type":"string"},"name":{"type":"string"},"image":{"type":"string","nullable":true},"role":{"type":"string","enum":["leader","member"]},"joinedAt":{"type":"string","format":"date-time"}}},"IsekaiStats":{"type":"object","properties":{"tier":{"type":"string","enum":["Ronin","Ashigaru","Samurai","Hatamoto","Daimyo","Shogun"]},"tierName":{"type":"string"},"xp":{"type":"integer"},"xpToNext":{"type":"integer"},"nekoCoins":{"type":"integer"},"streak":{"type":"integer"},"level":{"type":"integer"}}},"SageResponse":{"type":"object","properties":{"answer":{"type":"string"},"conversationId":{"type":"string","nullable":true},"tokensUsed":{"type":"integer"},"model":{"type":"string"},"followUpSuggestions":{"type":"array","items":{"type":"string"}}}},"SpeakingResult":{"type":"object","properties":{"feedback":{"type":"string"},"pronunciation":{"type":"object","properties":{"score":{"type":"number"},"issues":{"type":"array","items":{"type":"string"}}}},"fluency":{"type":"object","properties":{"score":{"type":"number"},"suggestions":{"type":"array","items":{"type":"string"}}}},"overallScore":{"type":"number"}}},"WritingCorrection":{"type":"object","properties":{"corrected":{"type":"string","description":"Corrected Japanese text"},"errors":{"type":"array","items":{"type":"object","properties":{"original":{"type":"string"},"correction":{"type":"string"},"explanation":{"type":"string"}}}},"suggestions":{"type":"array","items":{"type":"string"}},"score":{"type":"number"}}},"AIPracticeSession":{"type":"object","properties":{"id":{"type":"string"},"questions":{"type":"array","items":{"type":"object","properties":{"japanese":{"type":"string"},"options":{"type":"array","items":{"type":"string"}},"correctIndex":{"type":"integer"}}}},"difficulty":{"type":"string"},"jlptLevel":{"type":"string"}}},"AIStudyPlan":{"type":"object","properties":{"id":{"type":"string"},"userId":{"type":"string"},"jlptLevel":{"type":"string"},"weeklyGoalHours":{"type":"integer"},"startDate":{"type":"string","format":"date-time"},"status":{"type":"string","enum":["active","paused","completed"]},"progress":{"type":"object","properties":{"currentWeek":{"type":"integer"},"totalWeeks":{"type":"integer"},"completedLessons":{"type":"integer"}}}}},"SageUsageStatus":{"type":"object","properties":{"dailyFreeRemaining":{"type":"integer"},"totalUsed":{"type":"integer"},"coinBalance":{"type":"integer"}}},"RechargePackage":{"type":"object","properties":{"id":{"type":"string"},"coins":{"type":"integer"},"priceUsd":{"type":"number"},"priceSol":{"type":"number","nullable":true},"bonus":{"type":"integer","description":"Bonus coins"},"popular":{"type":"boolean"}}},"RechargeOrder":{"type":"object","properties":{"id":{"type":"string"},"packageId":{"type":"string"},"coins":{"type":"integer"},"amount":{"type":"number"},"currency":{"type":"string"},"status":{"type":"string","enum":["pending","completed","failed"]},"txHash":{"type":"string","nullable":true},"createdAt":{"type":"string","format":"date-time"}}},"WithdrawalPreview":{"type":"object","properties":{"eligibleCoins":{"type":"integer"},"fee":{"type":"integer"},"netCoins":{"type":"integer"},"estimatedSol":{"type":"number"}}},"CoinTransaction":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string","enum":["earn","spend","transfer","recharge","withdrawal"]},"amount":{"type":"integer"},"balance":{"type":"integer"},"description":{"type":"string"},"source":{"type":"string","nullable":true},"createdAt":{"type":"string","format":"date-time"}}}},"responses":{"BadRequest":{"description":"Validation error (code 2001-2004). See details.fieldErrors for specifics.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"Unauthorized":{"description":"Authentication required (code 1001). Provide session cookie, bearer token, or API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"Forbidden":{"description":"Insufficient permissions (code 1002). Requires higher tier or admin role.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"NotFound":{"description":"Resource not found (code 3001). Verify the resource ID via the list endpoint.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"Conflict":{"description":"Resource conflict (code 3002-3003). Fetch existing resource then update.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"RateLimited":{"description":"Rate limit exceeded (code 6001). Check Retry-After header.","headers":{"X-RateLimit-Limit":{"schema":{"type":"integer"},"description":"Requests allowed per window"},"X-RateLimit-Remaining":{"schema":{"type":"integer"},"description":"Requests remaining"},"X-RateLimit-Reset":{"schema":{"type":"integer"},"description":"Window reset (Unix timestamp)"},"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"InternalError":{"description":"Server error (code 9001). Retriable — wait 10 seconds then retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"securitySchemes":{"sessionCookie":{"type":"apiKey","in":"cookie","name":"next-auth.session-token","description":"NextAuth v5 session cookie. Obtained via POST /api/auth/login or OAuth flow. HttpOnly, Secure, SameSite=Lax."},"bearerToken":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"Access token in Authorization header. Used by mobile apps and API clients. Obtain via login or OAuth native flow."},"apiKey":{"type":"apiKey","in":"header","name":"Authorization","description":"API key with \"Bearer wq_\" prefix. Created via POST /api/v1/auth/api-keys. Used by CLI tool and AI agents. Rate limited at agentRead tier (300 req/min)."},"cronSecret":{"type":"apiKey","in":"header","name":"Authorization","description":"Cron job secret token. Server-to-server only. Validates against CRON_SECRET env var. Not for external use."}}}}