{"openapi":"3.1.0","info":{"title":"Stellara API","description":"Astrology app backend","version":"0.1.0"},"paths":{"/api/auth/register":{"post":{"tags":["auth"],"summary":"Register","description":"Register a new user with email and password.\n\nConcurrency note: the pre-check `SELECT … WHERE email=%s` exists so\nthe *common* path returns a clean 409 without a transaction round-trip.\nBut two simultaneous register POSTs to the same email both pass the\npre-check, both reach `INSERT`, and the second one raises\n``UniqueViolation`` from the schema-level `users.email UNIQUE NOT NULL`\nconstraint. Without the catch below, FastAPI surfaces the IntegrityError\nas a 500 (real concurrency bug, audited 2026-04-25). Catch it and\nre-raise as the same 409 the pre-check would have produced.","operationId":"register_api_auth_register_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegisterRequest"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TokenResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/auth/login":{"post":{"tags":["auth"],"summary":"Login","description":"Log in with email and password.","operationId":"login_api_auth_login_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TokenResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/auth/refresh":{"post":{"tags":["auth"],"summary":"Refresh","description":"Refresh an access token using a refresh token.","operationId":"refresh_api_auth_refresh_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RefreshRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TokenResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/auth/google":{"post":{"tags":["auth"],"summary":"Google Auth","description":"Authenticate via Google Sign-In ID token (Android Web Client OR iOS Client).\n\nThe `aud` claim differs per platform:\n  - Android sends a token issued for ``GOOGLE_CLIENT_ID`` (Web OAuth Client).\n  - iOS sends a token issued for ``GOOGLE_IOS_CLIENT_ID`` (from\n    ``GoogleService-Info.plist::CLIENT_ID``).\n\nWe verify the token WITHOUT passing ``audience=`` (the google library\nwould only accept a single value) and then assert the decoded ``aud`` is\nin our allow-list. This keeps Android-only deploys backward-compatible:\nwhen ``GOOGLE_IOS_CLIENT_ID`` is empty, the allow-list collapses to the\nWeb Client ID and behaviour is identical to the previous single-audience\ncheck.","operationId":"google_auth_api_auth_google_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GoogleAuthRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TokenResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/auth/apple":{"post":{"tags":["auth"],"summary":"Apple Auth","description":"Authenticate via Apple Sign-In identity_token (iOS-only flow).\n\nLookup priority (this order is load-bearing — see CLAUDE.md\n\"Apple Sign-In\" section + S5 in docs/STELLARA_IOS_APP_STORE_SETUP.md):\n\n  1. ``users.apple_sub`` exact match — STABLE across sign-ins; this\n     is the only reliable identifier for \"same Apple ID, second\n     sign-in\" since Apple omits ``email`` + ``fullName`` after the\n     first authorization for a given (App ID, Apple ID) pair.\n\n  2. ``users.email`` exact match (only when the JWT carries a\n     VERIFIED ``email`` claim — ``body.email`` is never consulted,\n     see ``_verified_email_from_claims``) — links Apple Sign-In to\n     a pre-existing email/password OR Google account on the same\n     email. Persists ``apple_sub`` on the existing row so step 1\n     wins on subsequent sign-ins.\n\n  3. INSERT a new user row with both ``email`` and ``apple_sub`` if\n     neither lookup succeeded. ``email`` MUST be present here —\n     otherwise we hit the S5 edge case (user revoked the OAuth grant\n     on iPhone Settings, tries again, Apple returns a brand new\n     ``sub`` with NULL email) and return 400 ``APPLE_NEW_SUB_NO_EMAIL``\n     so the client can show the \"Delete Stellara from Apple ID\n     settings + retry\" guidance.\n\nAfter the user row is resolved, opportunistically exchange\n``authorization_code`` for a ``refresh_token`` (Apple ``/auth/token``)\nso a future ``DELETE /api/users/me`` can revoke the OAuth grant.\nFailure is logged but does NOT fail this auth request — the user\nsigns in successfully even if Apple's token endpoint is down.","operationId":"apple_auth_api_auth_apple_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppleAuthRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TokenResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/auth/logout":{"post":{"tags":["auth"],"summary":"Logout","description":"Log out the current user — deactivate FCM tokens.\n\nFCM tokens live in the dedicated `fcm_tokens` table (one-to-many per user\nfor multi-device support), NOT as a column on `user_settings`. The previous\nimplementation tried to UPDATE a non-existent column on user_settings,\nwhich raised a SQL error on every logout in production.","operationId":"logout_api_auth_logout_post","responses":{"204":{"description":"Successful Response"}},"security":[{"HTTPBearer":[]}]}},"/api/auth/request-reset":{"post":{"tags":["auth"],"summary":"Request Password Reset","description":"Request a password reset code. Always returns 200 to avoid email enumeration.\n\nPrivacy invariants:\n- Same response for known + unknown emails (timing differences are\n  noise; we don't sleep to equalize because that's both ineffective\n  against patient attackers and bad UX for legit users).\n- Same response when the email send fails (SMTP down, bad creds,\n  unverified alias, network blip). The privacy-preserving \"If this\n  email is registered...\" message must NEVER hint at delivery success.","operationId":"request_password_reset_api_auth_request_reset_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RequestPasswordResetRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/auth/reset-password":{"post":{"tags":["auth"],"summary":"Reset Password","description":"Reset password using the 6-digit code from ``/request-reset``.","operationId":"reset_password_api_auth_reset_password_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResetPasswordRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/users/me":{"get":{"tags":["users"],"summary":"Get Profile","description":"Get the current user's profile.","operationId":"get_profile_api_users_me_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProfileResponse"}}}}},"security":[{"HTTPBearer":[]}]},"put":{"tags":["users"],"summary":"Update Profile","description":"Update the current user's profile.\n\nRate-limited to 60/minute per IP. A user editing fields with auto-save\n(each birth_place edit triggers a Nominatim geocode round-trip) can\nrealistically hit double-digit PUTs in a debug session — 60/minute\nleaves room for that without shielding bots that hammer the endpoint.","operationId":"update_profile_api_users_me_put","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProfileUpdateRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProfileResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]},"delete":{"tags":["users"],"summary":"Delete Account","description":"Permanently delete the current user's account and all associated data.\n\nRate-limited to 3/hour per IP. Account deletion is the most destructive\naction a user can take through the API — this cap blocks accidental\nrapid-fire deletes (a leaked JWT could otherwise nuke an account in a\nsingle request) while leaving room for \"delete + recreate\" support\nflows. A leaked JWT is still a problem; a re-auth challenge is the\nproper long-term fix (Step 9b in the audit plan).\n\nBefore deletion:\n1. (iOS) Best-effort revoke of Apple Sign-In OAuth grant — Apple\n   Guideline 5.1.1(v). Does NOT block deletion if Apple is down.\n2. (Android) Cancel active Google Play subscription. BLOCKS deletion\n   on persistent failure — we MUST NOT delete a user account while\n   Google Play would keep charging them. iOS subscriptions cannot\n   be server-side-cancelled (Apple's design); user must cancel via\n   iPhone Settings → Subscriptions. The Apple revoke above also\n   does not stop billing — only the user can stop iOS auto-renewal.\n3. Send a push notification confirming account deletion.\n4. Cascade-delete all user data.","operationId":"delete_account_api_users_me_delete","responses":{"204":{"description":"Successful Response"}},"security":[{"HTTPBearer":[]}]}},"/api/users/me/settings":{"get":{"tags":["users"],"summary":"Get Settings","description":"Get current user's settings.","operationId":"get_settings_api_users_me_settings_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserSettingsResponse"}}}}},"security":[{"HTTPBearer":[]}]},"put":{"tags":["users"],"summary":"Update Settings","description":"Update current user's settings.","operationId":"update_settings_api_users_me_settings_put","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserSettingsUpdateRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserSettingsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/users/me/transactions":{"get":{"tags":["users"],"summary":"List My Transactions","description":"Paginated coin-transaction journal for the current user.\n\nOrdered by ``created_at DESC, id DESC`` (the second tiebreaker\nmatters when ``spend_coins`` and ``refund_coins`` write within the\nsame statement_timestamp — without it the frontend can see an item\nflicker between pages on infinite scroll).\n\n``total_earned`` / ``total_spent`` are computed under the SAME\nfilter as the list — switching the chip strip from \"Все\" to \"Траты\"\nre-flips the header counters so the user always sees a summary\nconsistent with the visible rows.\n\nEntry shape mirrors the DB row verbatim so Flutter can switch on\n``operation`` to pick localized labels without the backend having\nto know every feature's ARB key.","operationId":"list_my_transactions_api_users_me_transactions_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}},{"name":"operation","in":"query","required":false,"schema":{"enum":["all","spend","earn","refund","subscription"],"type":"string","default":"all","title":"Operation"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserTransactionsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/horoscopes/daily":{"get":{"tags":["horoscopes"],"summary":"Get Daily Horoscope","description":"Get daily horoscope for the current user.","operationId":"get_daily_horoscope_api_horoscopes_daily_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"date","in":"query","required":false,"schema":{"anyOf":[{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"},{"type":"null"}],"description":"YYYY-MM-DD, defaults to today","title":"Date"},"description":"YYYY-MM-DD, defaults to today"},{"name":"category","in":"query","required":false,"schema":{"anyOf":[{"type":"string","pattern":"^(general|love|finance|health|career)$"},{"type":"null"}],"description":"general, love, finance, health, career","title":"Category"},"description":"general, love, finance, health, career"},{"name":"force","in":"query","required":false,"schema":{"type":"boolean","description":"Bypass cache and always generate a fresh horoscope (costs coins).","default":false,"title":"Force"},"description":"Bypass cache and always generate a fresh horoscope (costs coins)."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HoroscopeResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/horoscopes/weekly":{"get":{"tags":["horoscopes"],"summary":"Get Weekly Horoscope","description":"Get weekly horoscope for the current user.","operationId":"get_weekly_horoscope_api_horoscopes_weekly_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"week_start","in":"query","required":false,"schema":{"anyOf":[{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"},{"type":"null"}],"description":"YYYY-MM-DD (Monday)","title":"Week Start"},"description":"YYYY-MM-DD (Monday)"},{"name":"category","in":"query","required":false,"schema":{"anyOf":[{"type":"string","pattern":"^(general|love|finance|health|career)$"},{"type":"null"}],"title":"Category"}},{"name":"force","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Force"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HoroscopeResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/horoscopes/monthly":{"get":{"tags":["horoscopes"],"summary":"Get Monthly Horoscope","description":"Get monthly horoscope for the current user.","operationId":"get_monthly_horoscope_api_horoscopes_monthly_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"month","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","maximum":12,"minimum":1},{"type":"null"}],"title":"Month"}},{"name":"year","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","maximum":2100,"minimum":2020},{"type":"null"}],"title":"Year"}},{"name":"category","in":"query","required":false,"schema":{"anyOf":[{"type":"string","pattern":"^(general|love|finance|health|career)$"},{"type":"null"}],"title":"Category"}},{"name":"force","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Force"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HoroscopeResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/horoscopes/yearly":{"get":{"tags":["horoscopes"],"summary":"Get Yearly Horoscope","description":"Get yearly horoscope for the current user.","operationId":"get_yearly_horoscope_api_horoscopes_yearly_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"year","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","maximum":2100,"minimum":2020},{"type":"null"}],"title":"Year"}},{"name":"force","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Force"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HoroscopeResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/compatibility/check":{"post":{"tags":["compatibility"],"summary":"Check Compatibility","description":"Check compatibility with a partner based on birth data.\n\nCosts coins (cost_compatibility_check setting).\nUses Vertex AI (Gemini) for analysis.","operationId":"check_compatibility_api_compatibility_check_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CompatibilityCheckRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CompatibilityCheckResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/compatibility/history":{"get":{"tags":["compatibility"],"summary":"Get Compatibility History","description":"Get the history of compatibility checks for the current user (paginated).","operationId":"get_compatibility_history_api_compatibility_history_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"description":"Page number","default":1,"title":"Page"},"description":"Page number"},{"name":"per_page","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"description":"Items per page","default":20,"title":"Per Page"},"description":"Items per page"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CompatibilityHistoryResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/palmistry/analyze":{"post":{"tags":["palmistry"],"summary":"Analyze Palm","description":"Analyze a single palm photo using Vertex AI (Gemini).\n\nAccepts multipart form with image file, hand_type, and dominant_hand.","operationId":"analyze_palm_api_palmistry_analyze_post","requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_analyze_palm_api_palmistry_analyze_post"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PalmistryReadingResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/palmistry/analyze-dual":{"post":{"tags":["palmistry"],"summary":"Analyze Palm Dual","description":"Analyze both palms in a single session for full reading.\n\nSends both images to Gemini Vision for comparative analysis.\nCost is read live from ``app_settings.cost_palmistry_analyze``\n(admin-tunable, set to 8 in production by migration 040). Single-\nand dual-hand share the same cost key — the dual call's higher\nVertex spend is folded into the same per-feature price.","operationId":"analyze_palm_dual_api_palmistry_analyze_dual_post","requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_analyze_palm_dual_api_palmistry_analyze_dual_post"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PalmistryReadingResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/palmistry/history":{"get":{"tags":["palmistry"],"summary":"Get Palmistry History","description":"Get paginated history of palmistry analyses for the current user.\n\nBounds (P0-3, Cycle 9): without ``Query(ge=, le=)`` an authenticated\nattacker could request ``?limit=10000000`` and saturate the small\n(10) connection pool while psycopg materialised gigabytes of palmistry\nrows. Same DDoS surface Cycle 5 PR2 closed for tarot. Pydantic\nrejects out-of-range values with HTTP 422 below the handler.","operationId":"get_palmistry_history_api_palmistry_history_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":20,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","maximum":10000,"minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PalmistryHistoryResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/palmistry/compare":{"get":{"tags":["palmistry"],"summary":"Compare Readings","description":"Compare two palmistry readings side by side.\n\nReturns both readings with a diff of changes between them.","operationId":"compare_readings_api_palmistry_compare_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"id1","in":"query","required":false,"schema":{"type":"integer","default":0,"title":"Id1"}},{"name":"id2","in":"query","required":false,"schema":{"type":"integer","default":0,"title":"Id2"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PalmistryCompareResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/palmistry/{reading_id}":{"get":{"tags":["palmistry"],"summary":"Get Palmistry Reading","description":"Return a specific palmistry reading with full analysis details.","operationId":"get_palmistry_reading_api_palmistry__reading_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"reading_id","in":"path","required":true,"schema":{"type":"integer","title":"Reading Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PalmistryReadingResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["palmistry"],"summary":"Delete Palmistry Reading","description":"Delete a palmistry reading.","operationId":"delete_palmistry_reading_api_palmistry__reading_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"reading_id","in":"path","required":true,"schema":{"type":"integer","title":"Reading Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PalmistryDeleteResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/payments/verify-purchase":{"post":{"tags":["payments"],"summary":"Verify Purchase","description":"Verify a purchase and grant access/coins.\n\nRoutes to the platform-specific verifier:\n  - ``platform=\"android\"``: Google Play Developer API (legacy default).\n  - ``platform=\"ios\"``: App Store Server API + JWS verification.\n\nBoth branches share the same ``_upsert_subscription`` /\n``_atomic_coin_grant`` helpers below — the only platform-specific\nwork is the verification call itself + which set of credentials we\nuse to talk to the store.","operationId":"verify_purchase_api_payments_verify_purchase_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VerifyPurchaseRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VerifyPurchaseResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/payments/subscription":{"get":{"tags":["payments"],"summary":"Get Subscription","description":"Get current subscription status.","operationId":"get_subscription_api_payments_subscription_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubscriptionInfo"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/payments/restore":{"post":{"tags":["payments"],"summary":"Restore Purchases","description":"Restore subscription by re-verifying with Google Play.","operationId":"restore_purchases_api_payments_restore_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RestoreRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RestorePurchaseResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/payments/cancel":{"post":{"tags":["payments"],"summary":"Cancel Subscription Endpoint","description":"Cancel the current user's subscription on Google Play.\n\nUser keeps access until the current billing period ends.","operationId":"cancel_subscription_endpoint_api_payments_cancel_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubscriptionInfo"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/payments/webhook/google":{"post":{"tags":["payments"],"summary":"Google Play Webhook","description":"Handle RTDN push notifications from Google Play via Pub/Sub.\n\nCRITICAL: Always return 200 to prevent Pub/Sub infinite retries.\nAuth: verifies shared secret from query param or RTDN_WEBHOOK_SECRET env var.","operationId":"google_play_webhook_api_payments_webhook_google_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/payments/webhook/apple":{"post":{"tags":["payments"],"summary":"Apple App Store Webhook","description":"Handle App Store Server Notifications V2 from Apple.\n\nMirrors `google_play_webhook` shape but for Apple's signed-JWS\nnotification model. The supported transitions are documented in\n`core.app_store.map_apple_notification_status` — events that don't\nencode a status transition (TEST, CONSUMPTION_REQUEST,\nPRICE_INCREASE) are logged and swallowed.\n\nRace-handling: we take FOR UPDATE on user_subscriptions in the same\ntransaction shape as `google_play_webhook` so a stale ASSN can't\noverwrite a fresh `/verify-purchase` write that happened to land at\nthe same instant.","operationId":"apple_app_store_webhook_api_payments_webhook_apple_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/payments/refresh-coins":{"post":{"tags":["payments"],"summary":"Refresh Coins","description":"Daily cron: grant next 600-coin cycle to every Pro user whose last\ngrant is >= 30 days old.\n\nReuses ``NOTIFICATION_CRON_SECRET`` to avoid adding a second secret to\nSecret Manager / Cloud Run. Timing-safe comparison via ``hmac.compare_digest``.\n\nScheduler job: ``stellara-coin-refresh`` (``0 0 * * *`` UTC). Idempotent —\nre-running inside the same 30-day window is a noop for every user.","operationId":"refresh_coins_api_payments_refresh_coins_post","parameters":[{"name":"X-Cron-Secret","in":"header","required":true,"schema":{"type":"string","title":"X-Cron-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RefreshCoinsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/payments/retry-acknowledge":{"post":{"tags":["payments"],"summary":"Retry Acknowledge","description":"Cron: retry Google Play acknowledge for any subscription whose\ninitial ack failed (transient Google API error, throttle, etc).\n\nGoogle auto-refunds unacknowledged subs after ~72h, so the cron must\nfire often enough to win that race. Recommended schedule:\n``*/30 * * * *`` UTC via Cloud Scheduler job\n``stellara-acknowledge-retry`` reusing ``notification-cron-secret``.\n\nIdempotent — Google's acknowledge endpoint is itself idempotent, and\nrows that come back ``ok`` get ``acknowledged=TRUE`` so the next tick\nskips them.","operationId":"retry_acknowledge_api_payments_retry_acknowledge_post","parameters":[{"name":"X-Cron-Secret","in":"header","required":true,"schema":{"type":"string","title":"X-Cron-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RetryAcknowledgeResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/gifts/redeem":{"post":{"tags":["gifts"],"summary":"Redeem Gift","description":"Redeem a prepaid Stellara Premium gift code.","operationId":"redeem_gift_api_gifts_redeem_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GiftRedeemRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GiftRedeemResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/notifications/register-token":{"post":{"tags":["notifications"],"summary":"Register Fcm Token","description":"Register or update an FCM token for push notifications.\n\nUNIQUE(user_id, token) — upsert: reactivate if already exists.","operationId":"register_fcm_token_api_notifications_register_token_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegisterTokenRequest"}}},"required":true},"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/notifications/process":{"post":{"tags":["notifications"],"summary":"Process Notifications","description":"Process pending notifications (called by Cloud Scheduler).\n\nCRITICAL: Verify cron secret with ``hmac.compare_digest()``.\nPer-token claims serialize concurrent crons; notification_log is reporting.\nFCM delivery is at least once across a crash before the success commit.","operationId":"process_notifications_api_notifications_process_post","parameters":[{"name":"force","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Force"}},{"name":"X-Cron-Secret","in":"header","required":true,"schema":{"type":"string","title":"X-Cron-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotificationProcessResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/notifications/settings":{"get":{"tags":["notifications"],"summary":"Get Notification Settings","description":"Return user's notification preferences.","operationId":"get_notification_settings_api_notifications_settings_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotificationSettingsResponse"}}}}},"security":[{"HTTPBearer":[]}]},"put":{"tags":["notifications"],"summary":"Update Notification Settings","description":"Update notification preferences (upsert into user_settings).","operationId":"update_notification_settings_api_notifications_settings_put","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateNotificationSettingsRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotificationSettingsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/notifications/test":{"post":{"tags":["notifications"],"summary":"Send Test Push","description":"Send a test push notification to the current user's devices.","operationId":"send_test_push_api_notifications_test_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TestPushResponse"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/dreams/start":{"post":{"tags":["dreams"],"summary":"Start Dream Session","operationId":"start_dream_session_api_dreams_start_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DreamCreateRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DreamStartResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/dreams/reply":{"post":{"tags":["dreams"],"summary":"Reply To Dream Session","operationId":"reply_to_dream_session_api_dreams_reply_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DreamMessageRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DreamReplyResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/dreams/{session_id}/interpret":{"post":{"tags":["dreams"],"summary":"Interpret Dream","operationId":"interpret_dream_api_dreams__session_id__interpret_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"session_id","in":"path","required":true,"schema":{"type":"integer","title":"Session Id"}}],"requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/DreamInterpretRequest"},{"type":"null"}],"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DreamInterpretResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/dreams/operations/{request_id}":{"get":{"tags":["dreams"],"summary":"Get Dream Operation","operationId":"get_dream_operation_api_dreams_operations__request_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"request_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Request Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DreamOperationResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/dreams/history":{"get":{"tags":["dreams"],"summary":"Get Dream History","description":"Get user's dream interpretation history.","operationId":"get_dream_history_api_dreams_history_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":20,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","maximum":10000,"minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DreamHistoryResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/dreams/{session_id}":{"get":{"tags":["dreams"],"summary":"Get Dream Detail","description":"Get full dream session with messages and interpretation.","operationId":"get_dream_detail_api_dreams__session_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"session_id","in":"path","required":true,"schema":{"type":"integer","title":"Session Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DreamDetailResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/natal/chart":{"get":{"tags":["natal"],"summary":"Get Natal Chart","description":"Get user's natal chart. Returns cached version if available (free).\n\nFirst generation costs coins (cost_natal_chart setting).","operationId":"get_natal_chart_api_natal_chart_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NatalChartResponse"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/natal/chart/regenerate":{"post":{"tags":["natal"],"summary":"Regenerate Natal Chart","description":"Force-regenerate the natal chart. Always costs coins.","operationId":"regenerate_natal_chart_api_natal_chart_regenerate_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NatalChartResponse"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/natal/transits":{"get":{"tags":["natal"],"summary":"Get Current Transits","description":"Get current planetary transits and their impact on the user's chart.\n\nCache behaviour (migration 037):\n- First call for (user, today): ephemeris + Gemini, charge coins,\n  store in `transit_readings`.\n- Subsequent calls same day: serve the cached row for free.\n- `?force=true`: bypass cache, regenerate, charge coins.\n- Edited birth data → profile_hash mismatch → cache miss → regen.","operationId":"get_current_transits_api_natal_transits_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"force","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Force"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TransitsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/tarot/daily":{"get":{"tags":["tarot"],"summary":"Draw Daily Card","description":"Draw today's tarot card. One draw per day; returns cached result if already drawn.","operationId":"draw_daily_card_api_tarot_daily_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TarotDrawResponse"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/tarot/redraw":{"post":{"tags":["tarot"],"summary":"Redraw Card","description":"Redraw tarot card (always costs coins, replaces today's card).","operationId":"redraw_card_api_tarot_redraw_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TarotDrawResponse"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/tarot/history":{"get":{"tags":["tarot"],"summary":"Get Tarot History","description":"Get user's past tarot draws with card details.","operationId":"get_tarot_history_api_tarot_history_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":20,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","maximum":10000,"minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TarotHistoryResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/tarot/cards":{"get":{"tags":["tarot"],"summary":"List All Cards","description":"List all 78 tarot cards (for info/collection screen). No auth required.\n\nThe 78 cards are static reference data (last touched in migration 004,\nnever re-seeded). Adds an aggressive Cache-Control so CDN / mobile HTTP\nclients can serve repeat hits from cache instead of hitting Cloud Run.\nMitigates the no-auth + no-rate-limit DDoS surface that would otherwise\nrequire a global slowapi rewrite (deferred to the Cloud Run XFF cycle).\n(Cycle 5 audit P1 partial mitigation.)","operationId":"list_all_cards_api_tarot_cards_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TarotCardListResponse"}}}}}}},"/api/admin/gift-codes":{"post":{"tags":["admin"],"summary":"Create Gift Code Batch","description":"Create raw gift codes for manual sales, printing, or partner batches.","operationId":"create_gift_code_batch_api_admin_gift_codes_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GiftCodeCreateRequest"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminGiftCodeCreateResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/admin/gift-codes/revoke":{"post":{"tags":["admin"],"summary":"Revoke Gift Code Batch","description":"Revoke unredeemed gift codes after a Stripe refund/dispute.\n\nActivated gift codes remain active by policy: refunds are only allowed\nbefore activation, and a buyer action must not remove Pro from a recipient.","operationId":"revoke_gift_code_batch_api_admin_gift_codes_revoke_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GiftCodeRevokeRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GiftCodeRevokeResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/admin/settings":{"get":{"tags":["admin"],"summary":"Get All Settings","description":"Get all app settings.","operationId":"get_all_settings_api_admin_settings_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminSettingsResponse"}}}}},"security":[{"HTTPBearer":[]}]},"put":{"tags":["admin"],"summary":"Update Setting","description":"Update a single app setting.","operationId":"update_setting_api_admin_settings_put","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SettingUpdate"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminSettingUpdateResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/admin/feature-flags":{"get":{"tags":["admin"],"summary":"Get Feature Flags","description":"Return all app_settings where key starts with ``feature_``.","operationId":"get_feature_flags_api_admin_feature_flags_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminFeatureFlagsResponse"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/admin/feature-flags/{key}":{"put":{"tags":["admin"],"summary":"Update Feature Flag","description":"Toggle a feature flag on/off. Key must start with ``feature_``.","operationId":"update_feature_flag_api_admin_feature_flags__key__put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"key","in":"path","required":true,"schema":{"type":"string","title":"Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FeatureFlagUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FeatureFlagItem"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/settings/batch":{"put":{"tags":["admin"],"summary":"Update Settings Batch","description":"Update multiple settings at once.\n\nPR3 audit fix: enforces the same `_is_admin_writable` whitelist as\n`update_setting` so the batch path can't write to system-only keys\n(e.g. JWT_SECRET if it ever lands in app_settings). Each row also\nwrites to `admin_audit_log` so the forensic trail is identical to\nthe single-update path.","operationId":"update_settings_batch_api_admin_settings_batch_put","requestBody":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/SettingUpdate"},"type":"array","title":"Body"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminSettingsBatchResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/admin/prompts":{"get":{"tags":["admin"],"summary":"Get All Prompts","description":"Get all AI prompt templates.","operationId":"get_all_prompts_api_admin_prompts_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminPromptsResponse"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/admin/prompts/{prompt_key}":{"get":{"tags":["admin"],"summary":"Get Prompt","description":"Get a single AI prompt template.","operationId":"get_prompt_api_admin_prompts__prompt_key__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"prompt_key","in":"path","required":true,"schema":{"type":"string","title":"Prompt Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminPromptResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"tags":["admin"],"summary":"Update Prompt","description":"Update an AI prompt template.","operationId":"update_prompt_api_admin_prompts__prompt_key__put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"prompt_key","in":"path","required":true,"schema":{"type":"string","title":"Prompt Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PromptUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminPromptResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/users":{"get":{"tags":["admin"],"summary":"List Users","description":"List all users with their coin balances and subscription status.","operationId":"list_users_api_admin_users_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminUsersResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/users/{user_id}":{"get":{"tags":["admin"],"summary":"Get User Detail","description":"Get detailed user info including coin history.","operationId":"get_user_detail_api_admin_users__user_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"integer","title":"User Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminUserDetailResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/coins/grant":{"post":{"tags":["admin"],"summary":"Grant Coins","description":"Grant coins to a user.\n\nWrites an admin_audit_log row capturing both the delta and the\ntarget user's ID — essential for chargebacks / abuse investigations\n(pre-2026-04-23 this wrote to `coin_transactions` but NOT to\n`admin_audit_log`, so the admin tab's audit export missed every\nmanual coin grant and couldn't answer \"which admin granted user X\ntheir current balance\").","operationId":"grant_coins_api_admin_coins_grant_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CoinGrantRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminCoinGrantResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/admin/stats":{"get":{"tags":["admin"],"summary":"Get Stats","description":"Get overall app statistics.","operationId":"get_stats_api_admin_stats_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminStatsResponse"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/admin/stats/detailed":{"get":{"tags":["admin"],"summary":"Get Detailed Stats","description":"Comprehensive app statistics with DAU/WAU/MAU and feature breakdown.","operationId":"get_detailed_stats_api_admin_stats_detailed_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"period","in":"query","required":false,"schema":{"type":"string","pattern":"^(7d|30d|90d|all)$","default":"30d","title":"Period"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminDetailedStatsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/coin-costs":{"get":{"tags":["admin"],"summary":"Get Coin Costs","description":"Structured coin costs with display names.","operationId":"get_coin_costs_api_admin_coin_costs_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminCoinCostsResponse"}}}}},"security":[{"HTTPBearer":[]}]},"put":{"tags":["admin"],"summary":"Update Coin Costs","description":"Batch-update coin costs with audit trail.","operationId":"update_coin_costs_api_admin_coin_costs_put","requestBody":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/CoinCostUpdateItem"},"type":"array","title":"Body"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminSettingsBatchResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/admin/economics":{"get":{"tags":["admin"],"summary":"Get Economics","description":"ROI calculation: coins charged vs estimated Vertex AI costs.\n\nTwo source tables are joined:\n- ``coin_transactions`` — authoritative revenue journal. Includes\n  non-AI operations like ``export_pdf`` that spend coins without\n  hitting Vertex AI.\n- ``ai_usage_log`` — authoritative token/USD cost journal, keyed by\n  ``prompt_key``. A single user operation may span multiple prompts\n  (e.g. ``dream_interpret`` uses both ``dream_clarify`` and\n  ``dream_interpret``), and sub-operation prompts like\n  ``tts_summarize`` bill under the parent operation ``audio_tts``.\n\nPreviously revenue was read exclusively from ``ai_usage_log``, so\n``export_pdf`` spend was silently missing from the admin dashboard\n(revenue appeared as 0). ``dream_clarify`` and ``dream_interpret``\nalso showed as two separate rows with a misleading 0-coin entry for\nthe ``dream_interpret`` prompt (coin_cost=0 is correct telemetry —\ncharge happens once on ``dream_clarify`` — but admin saw \"0 монет\"\nper the per-prompt grouping and mistook the operation for free).\n\nThis query groups both tables by the stable ``operation`` identifier\n(same key used in ``cost_<op>`` and ``coin_transactions``), merging\nmulti-prompt and non-AI operations into one row each. ``group_by`` =\nmodel or day keeps the legacy AI-only view since non-AI ops have no\nmodel or per-day AI token breakdown.","operationId":"get_economics_api_admin_economics_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"period","in":"query","required":false,"schema":{"type":"string","pattern":"^(7d|30d|90d)$","default":"30d","title":"Period"}},{"name":"group_by","in":"query","required":false,"schema":{"type":"string","pattern":"^(feature|model|day)$","default":"feature","title":"Group By"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminEconomicsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/token-prices":{"get":{"tags":["admin"],"summary":"Get Token Prices","description":"List all token price entries.","operationId":"get_token_prices_api_admin_token_prices_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminTokenPricesResponse"}}}}},"security":[{"HTTPBearer":[]}]},"post":{"tags":["admin"],"summary":"Create Token Price","description":"Add a new token price entry.","operationId":"create_token_price_api_admin_token_prices_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TokenPriceCreate"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TokenPriceRow"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/admin/aggregate":{"post":{"tags":["admin"],"summary":"Aggregate Daily","description":"Aggregate ai_usage_log into ai_usage_daily_agg for a given date.\n\nAccessible to admins via JWT OR to Cloud Scheduler via\n`X-Cron-Secret`. Before 2026-04-23 this endpoint was locked behind\n`Depends(require_admin)` — FastAPI resolved that dep BEFORE the\nbody ran, so a cron-secret-only call always got 403 and the daily\naggregation was silently stale until a human admin manually\ninvoked the endpoint.","operationId":"aggregate_daily_api_admin_aggregate_post","parameters":[{"name":"date","in":"query","required":true,"schema":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$","title":"Date"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-Cron-Secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cron-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminAggregateResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/purge-old-transactions":{"post":{"tags":["admin"],"summary":"Purge Old Transactions Endpoint","description":"Delete coin_transactions older than the retention window\n(default 5 years, see app_settings.coin_tx_retention_years).\n\nThe user-facing «История трат» footer says \"История хранится 5 лет\"\n— this endpoint is what enforces that promise. Cloud Scheduler job\n`stellara-purge-tx` runs it nightly at 03:00 UTC; an admin can also\ntrigger it manually from the admin panel.\n\nIdempotent — re-running on the same day after a sweep returns\n`deleted=0`.","operationId":"purge_old_transactions_endpoint_api_admin_purge_old_transactions_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"X-Cron-Secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cron-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PurgeOldTransactionsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/users/search":{"get":{"tags":["admin"],"summary":"Search Users","description":"Search users with filtering, sorting, and pagination.","operationId":"search_users_api_admin_users_search_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"q","in":"query","required":false,"schema":{"type":"string","maxLength":200,"default":"","title":"Q"}},{"name":"tier","in":"query","required":false,"schema":{"type":"string","pattern":"^(all|free|premium)$","default":"all","title":"Tier"}},{"name":"sort","in":"query","required":false,"schema":{"type":"string","pattern":"^(created_at|name|coin_balance)$","default":"created_at","title":"Sort"}},{"name":"order","in":"query","required":false,"schema":{"type":"string","pattern":"^(asc|desc)$","default":"desc","title":"Order"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminUsersResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/users/export":{"get":{"tags":["admin"],"summary":"Export Users Csv","description":"Export users as CSV file with optional search/tier filter.","operationId":"export_users_csv_api_admin_users_export_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"q","in":"query","required":false,"schema":{"type":"string","maxLength":200,"default":"","title":"Q"}},{"name":"tier","in":"query","required":false,"schema":{"type":"string","pattern":"^(all|free|premium)$","default":"all","title":"Tier"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/users/{user_id}/ban":{"post":{"tags":["admin"],"summary":"Ban User","description":"Ban a user.","operationId":"ban_user_api_admin_users__user_id__ban_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"integer","title":"User Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BanRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminUserBanResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/users/{user_id}/unban":{"post":{"tags":["admin"],"summary":"Unban User","description":"Unban a user.","operationId":"unban_user_api_admin_users__user_id__unban_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"integer","title":"User Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminUserBanResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/users/bulk-grant":{"post":{"tags":["admin"],"summary":"Bulk Grant Coins","description":"Grant coins to multiple users at once (max 100).","operationId":"bulk_grant_coins_api_admin_users_bulk_grant_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkGrantRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminBulkGrantResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/admin/audit-log":{"get":{"tags":["admin"],"summary":"Get Audit Log","description":"View admin action audit trail.","operationId":"get_audit_log_api_admin_audit_log_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}},{"name":"target_type","in":"query","required":false,"schema":{"type":"string","maxLength":50,"default":"","title":"Target Type"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminAuditLogResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/api-keys":{"post":{"tags":["admin"],"summary":"Issue Api Key","description":"Mint a ``sk_stellara_…`` key. The plaintext is in this response and\nnowhere else — store only the sha256 hash.","operationId":"issue_api_key_api_admin_api_keys_post","security":[{"HTTPBearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeyIssueRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminApiKeyIssueResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["admin"],"summary":"List Api Keys","description":"Keys with today's (UTC) usage. The hash column is never selected.","operationId":"list_api_keys_api_admin_api_keys_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"email","in":"query","required":false,"schema":{"type":"string","maxLength":255,"default":"","title":"Email"}},{"name":"status","in":"query","required":false,"schema":{"type":"string","pattern":"^(all|active|revoked)$","default":"all","title":"Status"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminApiKeysResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/api-keys/{key_id}/revoke":{"post":{"tags":["admin"],"summary":"Revoke Api Key","description":"Revoke an active key. Already-revoked / unknown ids are a 404 so the\ncaller can tell \"nothing changed\" from \"done\".","operationId":"revoke_api_key_api_admin_api_keys__key_id__revoke_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"key_id","in":"path","required":true,"schema":{"type":"integer","title":"Key Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminApiKeyRevokeResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/lunar/today":{"get":{"tags":["lunar"],"summary":"Lunar Today","description":"Get the current moon phase, right now, in UTC.\n\nWe intentionally use `datetime.now(UTC)` instead of `date.today()`\nso the phase tracks the real illumination fraction at the moment\nof the request. `date.today()` on Cloud Run is UTC midnight of the\nserver's wall clock — for a Moscow user checking at 01:00 local\n(22:00 UTC the previous day), that meant they saw the previous UTC\nday's phase even though astronomically the moon had already moved\nforward by ~13° of apparent longitude.","operationId":"lunar_today_api_lunar_today_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LunarDayResponse"}}}}}}},"/api/lunar/month":{"get":{"tags":["lunar"],"summary":"Lunar Month","description":"Get moon phases for an entire month.","operationId":"lunar_month_api_lunar_month_get","parameters":[{"name":"year","in":"query","required":true,"schema":{"type":"integer","maximum":2100,"minimum":1900,"description":"Year","title":"Year"},"description":"Year"},{"name":"month","in":"query","required":true,"schema":{"type":"integer","maximum":12,"minimum":1,"description":"Month (1-12)","title":"Month"},"description":"Month (1-12)"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LunarMonthResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/lunar/date/{date_str}":{"get":{"tags":["lunar"],"summary":"Lunar Date","description":"Get moon phase for a specific date (YYYY-MM-DD).","operationId":"lunar_date_api_lunar_date__date_str__get","parameters":[{"name":"date_str","in":"path","required":true,"schema":{"type":"string","title":"Date Str"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LunarDayResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/affirmations/today":{"get":{"tags":["affirmations"],"summary":"Get Today Affirmation","description":"Get today's personalized affirmation. Free (0 coins).\n\nCache invariant: the stored `profile_hash` must match the current\nprofile state before a cached row is served. Without this check a\nuser who switched UI language (or text_language) after this morning's\ngeneration would be stuck reading the old-language affirmation for\nthe rest of the day — which is exactly the bug the screenshot showed.\nMirrors the pattern already in horoscope / natal / tarot / compat.","operationId":"get_today_affirmation_api_affirmations_today_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AffirmationResponse"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/affirmations/history":{"get":{"tags":["affirmations"],"summary":"Get Affirmation History","description":"Get past affirmations for the current user.","operationId":"get_affirmation_history_api_affirmations_history_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":30,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AffirmationHistoryResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/export/horoscope-pdf":{"post":{"tags":["export"],"summary":"Export Horoscope Pdf","description":"Export latest horoscope as structured data for PDF generation.\n\nThe Flutter client renders the returned payload into a PDF using the\n``pdf`` Dart package.  Coins are deducted before the export.","operationId":"export_horoscope_pdf_api_export_horoscope_pdf_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"period_type","in":"query","required":true,"schema":{"type":"string","pattern":"^(daily|weekly|monthly|yearly)$","title":"Period Type"}},{"name":"category","in":"query","required":false,"schema":{"type":"string","pattern":"^(general|love|finance|health|career)$","default":"general","title":"Category"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExportHoroscopeResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/audio/tts":{"post":{"tags":["audio"],"summary":"Generate Tts","description":"Generate audio from text using Google Cloud TTS.\n\nReturns MP3 audio bytes directly. Sets `X-TTS-Truncated: 1` and\n`X-TTS-Char-Count` headers so Flutter can react to long inputs.","operationId":"generate_tts_api_audio_tts_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TTSRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/chat/sessions":{"post":{"tags":["chat"],"summary":"Create Or Get Session","description":"Create a new chat session or return the existing active one.","operationId":"create_or_get_session_api_chat_sessions_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChatSessionResponse"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/chat/sessions/{session_id}/close":{"post":{"tags":["chat"],"summary":"Close Session","description":"Close a chat session so a new one can be created.","operationId":"close_session_api_chat_sessions__session_id__close_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"session_id","in":"path","required":true,"schema":{"type":"integer","title":"Session Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/chat/sessions/{session_id}/messages":{"post":{"tags":["chat"],"summary":"Send Message","description":"Send a message and receive an AI assistant reply.","operationId":"send_message_api_chat_sessions__session_id__messages_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"session_id","in":"path","required":true,"schema":{"type":"integer","title":"Session Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChatMessageRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChatMessageResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["chat"],"summary":"Get Messages","description":"Load chat message history for a session.","operationId":"get_messages_api_chat_sessions__session_id__messages_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"session_id","in":"path","required":true,"schema":{"type":"integer","title":"Session Id"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChatHistoryResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/astrology/natal":{"post":{"tags":["astrology-api"],"summary":"Natal Chart","description":"Natal chart: 14 points, 12 house cusps, 4 angles, exact aspects.","operationId":"natal_chart_api_v1_astrology_natal_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NatalRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AstrologyNatalResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/astrology/transits":{"post":{"tags":["astrology-api"],"summary":"Transits","description":"Transiting positions at ``target_datetime`` + aspects to the natal\npoints, with applying / separating and exact orbs.","operationId":"transits_api_v1_astrology_transits_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TransitsRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AstrologyTransitsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/astrology/synastry":{"post":{"tags":["astrology-api"],"summary":"Synastry","description":"Inter-chart aspects between two people + house overlays.","operationId":"synastry_api_v1_astrology_synastry_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SynastryRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AstrologySynastryResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/astrology/resolve-place":{"post":{"tags":["astrology-api"],"summary":"Resolve Place","description":"Place name -> coordinates + IANA timezone, and (optionally) a naive\nlocal birth time -> tz-aware ``birth_datetime`` with the historically\ncorrect UTC offset. Lets an agent go from \"Warsaw, 14:30\" to a valid\nnatal request without guessing coordinates or DST rules.","operationId":"resolve_place_api_v1_astrology_resolve_place_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResolvePlaceRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AstrologyResolvePlaceResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/billing/api-keys/request":{"post":{"tags":["billing"],"summary":"Request Free Key","description":"Issue a ``free`` key and e-mail it. The response is identical whether\nor not a key was issued (privacy + anti-enumeration), like password\nreset. Gate: one ACTIVE key per e-mail per 24 h; a key whose e-mail\ncould not be delivered is revoked immediately so the requester is not\nlocked out for a day.","operationId":"request_free_key_api_billing_api_keys_request_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FreeKeyRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BillingStatusResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/billing/checkout":{"post":{"tags":["billing"],"summary":"Start Checkout","description":"Create a Stripe Checkout Session; the landing redirects to ``url``.","operationId":"start_checkout_api_billing_checkout_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckoutRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BillingUrlResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/billing/portal":{"post":{"tags":["billing"],"summary":"Open Portal","description":"Authorised by the API key itself (``Authorization: Bearer sk_stellara_…``\nor ``X-API-Key``) — never by e-mail alone, otherwise anyone who knows a\ncustomer's address could cancel their subscription.","operationId":"open_portal_api_billing_portal_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BillingUrlResponse"}}}}}}},"/health":{"get":{"tags":["system"],"summary":"Health Check","description":"Health check endpoint for Cloud Run / load balancers.","operationId":"health_check_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}}},"components":{"schemas":{"AdminAggregateResponse":{"properties":{"date":{"type":"string","title":"Date"},"rows_upserted":{"type":"integer","title":"Rows Upserted"}},"type":"object","required":["date","rows_upserted"],"title":"AdminAggregateResponse"},"AdminApiKeyIssueResponse":{"properties":{"id":{"type":"integer","title":"Id"},"key":{"type":"string","title":"Key"},"key_prefix":{"type":"string","title":"Key Prefix"},"email":{"type":"string","title":"Email"},"tier":{"type":"string","title":"Tier"},"daily_limit":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Daily Limit"},"created_at":{"title":"Created At"}},"type":"object","required":["id","key","key_prefix","email","tier"],"title":"AdminApiKeyIssueResponse","description":"The plaintext ``key`` is returned here exactly once and never again\n(only its sha256 hash is stored)."},"AdminApiKeyItem":{"properties":{"id":{"type":"integer","title":"Id"},"key_prefix":{"type":"string","title":"Key Prefix"},"email":{"type":"string","title":"Email"},"tier":{"type":"string","title":"Tier"},"status":{"type":"string","title":"Status"},"daily_limit":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Daily Limit"},"stripe_customer_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Stripe Customer Id"},"stripe_subscription_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Stripe Subscription Id"},"usage_today":{"type":"integer","title":"Usage Today","default":0},"created_at":{"title":"Created At"},"revoked_at":{"title":"Revoked At"},"last_used_at":{"title":"Last Used At"}},"type":"object","required":["id","key_prefix","email","tier","status"],"title":"AdminApiKeyItem"},"AdminApiKeyRevokeResponse":{"properties":{"id":{"type":"integer","title":"Id"},"key_prefix":{"type":"string","title":"Key Prefix"},"email":{"type":"string","title":"Email"},"status":{"type":"string","title":"Status"},"revoked_at":{"title":"Revoked At"}},"type":"object","required":["id","key_prefix","email","status"],"title":"AdminApiKeyRevokeResponse"},"AdminApiKeysResponse":{"properties":{"items":{"items":{"$ref":"#/components/schemas/AdminApiKeyItem"},"type":"array","title":"Items","default":[]},"total":{"type":"integer","title":"Total","default":0}},"type":"object","title":"AdminApiKeysResponse"},"AdminAuditLogResponse":{"properties":{"entries":{"items":{"$ref":"#/components/schemas/AuditLogEntry"},"type":"array","title":"Entries","default":[]},"total":{"type":"integer","title":"Total","default":0}},"type":"object","title":"AdminAuditLogResponse"},"AdminBulkGrantResponse":{"properties":{"granted_count":{"type":"integer","title":"Granted Count"},"total_amount":{"type":"integer","title":"Total Amount"}},"type":"object","required":["granted_count","total_amount"],"title":"AdminBulkGrantResponse"},"AdminCoinCostsResponse":{"properties":{"costs":{"items":{"$ref":"#/components/schemas/CoinCostItem"},"type":"array","title":"Costs","default":[]},"economy_settings":{"additionalProperties":{"type":"integer"},"type":"object","title":"Economy Settings","default":{}}},"type":"object","title":"AdminCoinCostsResponse"},"AdminCoinGrantResponse":{"properties":{"user_id":{"type":"integer","title":"User Id"},"amount":{"type":"integer","title":"Amount"},"new_balance":{"title":"New Balance"}},"type":"object","required":["user_id","amount","new_balance"],"title":"AdminCoinGrantResponse"},"AdminDetailedStatsResponse":{"properties":{"users_total":{"type":"integer","title":"Users Total","default":0},"users_premium":{"type":"integer","title":"Users Premium","default":0},"coins_in_circulation":{"type":"integer","title":"Coins In Circulation","default":0},"dau":{"type":"integer","title":"Dau","default":0},"wau":{"type":"integer","title":"Wau","default":0},"mau":{"type":"integer","title":"Mau","default":0},"feature_usage":{"additionalProperties":{"type":"integer"},"type":"object","title":"Feature Usage","default":{}},"total_generations":{"type":"integer","title":"Total Generations","default":0},"successful_generations":{"type":"integer","title":"Successful Generations","default":0},"failed_generations":{"type":"integer","title":"Failed Generations","default":0},"success_rate":{"type":"number","title":"Success Rate","default":0.0},"avg_latency_ms":{"type":"number","title":"Avg Latency Ms","default":0.0},"new_users_by_day":{"items":{"$ref":"#/components/schemas/NewUsersByDayItem"},"type":"array","title":"New Users By Day","default":[]},"model_usage":{"items":{"$ref":"#/components/schemas/ModelUsageItem"},"type":"array","title":"Model Usage","default":[]}},"type":"object","title":"AdminDetailedStatsResponse"},"AdminEconomicsResponse":{"properties":{"period_days":{"type":"integer","title":"Period Days"},"group_by":{"type":"string","title":"Group By"},"rows":{"items":{"$ref":"#/components/schemas/EconomicsRow"},"type":"array","title":"Rows","default":[]},"totals":{"anyOf":[{"$ref":"#/components/schemas/EconomicsRow"},{"type":"null"}]},"pricing_as_of":{"type":"string","title":"Pricing As Of","default":""},"models_missing_price":{"items":{"type":"string"},"type":"array","title":"Models Missing Price","default":[]}},"type":"object","required":["period_days","group_by"],"title":"AdminEconomicsResponse"},"AdminFeatureFlagsResponse":{"properties":{"flags":{"items":{"$ref":"#/components/schemas/FeatureFlagItem"},"type":"array","title":"Flags","default":[]}},"type":"object","title":"AdminFeatureFlagsResponse"},"AdminGiftCodeCreateResponse":{"properties":{"codes":{"items":{"type":"string"},"type":"array","title":"Codes"},"count":{"type":"integer","title":"Count"},"duration_days":{"type":"integer","title":"Duration Days"},"campaign_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Campaign Name"}},"type":"object","required":["codes","count","duration_days"],"title":"AdminGiftCodeCreateResponse"},"AdminPromptResponse":{"properties":{"id":{"type":"integer","title":"Id"},"key":{"type":"string","title":"Key"},"title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Title"},"prompt_template":{"type":"string","title":"Prompt Template"},"model":{"type":"string","title":"Model"},"max_output_tokens":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Max Output Tokens"},"temperature":{"anyOf":[{},{"type":"null"}],"title":"Temperature"},"is_active":{"type":"boolean","title":"Is Active","default":true},"updated_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Updated By"},"updated_at":{"anyOf":[{},{"type":"null"}],"title":"Updated At"}},"additionalProperties":true,"type":"object","required":["id","key","prompt_template","model"],"title":"AdminPromptResponse","description":"Single AI prompt template returned by GET/PUT /api/admin/prompts/{key}."},"AdminPromptsResponse":{"properties":{"prompts":{"items":{},"type":"array","title":"Prompts"}},"type":"object","required":["prompts"],"title":"AdminPromptsResponse"},"AdminSettingUpdateResponse":{"properties":{"key":{"type":"string","title":"Key"},"value":{"type":"string","title":"Value"}},"type":"object","required":["key","value"],"title":"AdminSettingUpdateResponse"},"AdminSettingsBatchResponse":{"properties":{"updated":{"type":"integer","title":"Updated"}},"type":"object","required":["updated"],"title":"AdminSettingsBatchResponse"},"AdminSettingsResponse":{"properties":{"settings":{"items":{},"type":"array","title":"Settings"}},"type":"object","required":["settings"],"title":"AdminSettingsResponse"},"AdminStatsResponse":{"properties":{"users_total":{"type":"integer","title":"Users Total","default":0},"users_premium":{"type":"integer","title":"Users Premium","default":0},"coins_in_circulation":{"type":"integer","title":"Coins In Circulation","default":0},"horoscopes_generated":{"type":"integer","title":"Horoscopes Generated","default":0},"dreams_interpreted":{"type":"integer","title":"Dreams Interpreted","default":0}},"type":"object","title":"AdminStatsResponse"},"AdminTokenPricesResponse":{"properties":{"prices":{"items":{"$ref":"#/components/schemas/TokenPriceRow"},"type":"array","title":"Prices","default":[]}},"type":"object","title":"AdminTokenPricesResponse"},"AdminUserBanResponse":{"properties":{"user_id":{"type":"integer","title":"User Id"},"is_active":{"type":"boolean","title":"Is Active"},"banned_at":{"title":"Banned At"}},"type":"object","required":["user_id","is_active"],"title":"AdminUserBanResponse"},"AdminUserDetailResponse":{"properties":{"user":{"title":"User"},"transactions":{"items":{},"type":"array","title":"Transactions"}},"type":"object","required":["user","transactions"],"title":"AdminUserDetailResponse"},"AdminUsersResponse":{"properties":{"users":{"items":{},"type":"array","title":"Users"},"total":{"type":"integer","title":"Total"}},"type":"object","required":["users","total"],"title":"AdminUsersResponse"},"AffirmationHistoryItem":{"properties":{"date":{"type":"string","title":"Date"},"moon_phase":{"type":"string","title":"Moon Phase"},"affirmation":{"title":"Affirmation"}},"additionalProperties":true,"type":"object","required":["date","moon_phase","affirmation"],"title":"AffirmationHistoryItem"},"AffirmationHistoryResponse":{"properties":{"affirmations":{"items":{"$ref":"#/components/schemas/AffirmationHistoryItem"},"type":"array","title":"Affirmations"}},"type":"object","required":["affirmations"],"title":"AffirmationHistoryResponse"},"AffirmationResponse":{"properties":{"date":{"type":"string","title":"Date"},"moon_phase":{"type":"string","title":"Moon Phase"},"affirmation":{"title":"Affirmation"},"cached":{"type":"boolean","title":"Cached"}},"additionalProperties":true,"type":"object","required":["date","moon_phase","affirmation","cached"],"title":"AffirmationResponse"},"ApiKeyIssueRequest":{"properties":{"email":{"type":"string","format":"email","title":"Email"},"tier":{"type":"string","pattern":"^(free|pro|internal)$","title":"Tier","default":"free"},"daily_limit":{"anyOf":[{"type":"integer","maximum":10000000.0,"minimum":1.0},{"type":"null"}],"title":"Daily Limit"}},"type":"object","required":["email"],"title":"ApiKeyIssueRequest"},"AppleAuthRequest":{"properties":{"identity_token":{"type":"string","minLength":10,"title":"Identity Token"},"authorization_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization Code"},"email":{"anyOf":[{"type":"string","format":"email"},{"type":"null"}],"title":"Email"},"full_name":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}],"title":"Full Name"}},"type":"object","required":["identity_token"],"title":"AppleAuthRequest","description":"Apple Sign-In payload from `sign_in_with_apple` Flutter plugin.\n\n`identity_token` is required (the JWT we verify against Apple JWKS).\n`authorization_code` is optional but should always be sent — Flutter\nmust include it so we can exchange for a `refresh_token` and persist\nit for revoke-on-delete (Apple Guideline 5.1.1(v)). Without it, the\nuser signs in fine but their account stays \"un-revokable\" — the\nStellara entry in iPhone Settings → Apple ID → Apps using Apple ID\nwon't auto-clear on Delete Account (a soft compliance miss; Apple\nsometimes flags this on review).\n\n`email` and `full_name` are present ONLY at the FIRST authorization\nfor a given (App ID, Apple ID) pair. On subsequent sign-ins, Apple\nreturns identity_token only — `apple_sub` (the JWT's `sub` claim) is\nthe only stable identifier that persists across sessions.\n\n`email` is accepted for wire-compat with the Flutter client but is\nNEVER used for account lookup or linking — the backend takes the\nemail exclusively from the Apple-signed JWT (`claims[\"email\"]` +\n`email_verified`). Trusting a client-supplied email here would let\na caller with any valid identity_token attach their `apple_sub` to\nan arbitrary existing account. `full_name` is display-only."},"AstroAngle":{"properties":{"sign":{"type":"string","title":"Sign"},"degree":{"type":"number","title":"Degree"},"absolute_longitude":{"type":"number","title":"Absolute Longitude"}},"type":"object","required":["sign","degree","absolute_longitude"],"title":"AstroAngle"},"AstroAngles":{"properties":{"ascendant":{"$ref":"#/components/schemas/AstroAngle"},"midheaven":{"$ref":"#/components/schemas/AstroAngle"},"descendant":{"$ref":"#/components/schemas/AstroAngle"},"imum_coeli":{"$ref":"#/components/schemas/AstroAngle"}},"type":"object","required":["ascendant","midheaven","descendant","imum_coeli"],"title":"AstroAngles"},"AstroChartInputEcho":{"properties":{"birth_datetime_utc":{"type":"string","title":"Birth Datetime Utc"},"latitude":{"type":"number","title":"Latitude"},"longitude":{"type":"number","title":"Longitude"},"house_system":{"type":"string","title":"House System"}},"type":"object","required":["birth_datetime_utc","latitude","longitude","house_system"],"title":"AstroChartInputEcho"},"AstroHouse":{"properties":{"house":{"type":"integer","title":"House"},"sign":{"type":"string","title":"Sign"},"degree":{"type":"number","title":"Degree"},"absolute_longitude":{"type":"number","title":"Absolute Longitude"}},"type":"object","required":["house","sign","degree","absolute_longitude"],"title":"AstroHouse"},"AstroHouseOverlayItem":{"properties":{"planet":{"type":"string","title":"Planet"},"house":{"type":"integer","title":"House"}},"type":"object","required":["planet","house"],"title":"AstroHouseOverlayItem"},"AstroHouseOverlays":{"properties":{"a_in_b_houses":{"items":{"$ref":"#/components/schemas/AstroHouseOverlayItem"},"type":"array","title":"A In B Houses"},"b_in_a_houses":{"items":{"$ref":"#/components/schemas/AstroHouseOverlayItem"},"type":"array","title":"B In A Houses"}},"type":"object","required":["a_in_b_houses","b_in_a_houses"],"title":"AstroHouseOverlays"},"AstroMeta":{"properties":{"engine":{"type":"string","title":"Engine"},"zodiac":{"type":"string","title":"Zodiac"},"house_system":{"type":"string","title":"House System"},"orbs":{"additionalProperties":{"type":"integer"},"type":"object","title":"Orbs"},"time_resolution":{"type":"string","title":"Time Resolution"},"points":{"items":{"type":"string"},"type":"array","title":"Points"}},"additionalProperties":true,"type":"object","required":["engine","zodiac","house_system","orbs","time_resolution","points"],"title":"AstroMeta"},"AstroNatalAspect":{"properties":{"point_a":{"type":"string","title":"Point A"},"point_b":{"type":"string","title":"Point B"},"aspect":{"type":"string","title":"Aspect"},"exact_degrees":{"type":"integer","title":"Exact Degrees"},"orb":{"type":"number","title":"Orb"},"movement":{"type":"string","title":"Movement"}},"type":"object","required":["point_a","point_b","aspect","exact_degrees","orb","movement"],"title":"AstroNatalAspect"},"AstroPoint":{"properties":{"name":{"type":"string","title":"Name"},"sign":{"type":"string","title":"Sign"},"degree":{"type":"number","title":"Degree"},"absolute_longitude":{"type":"number","title":"Absolute Longitude"},"house":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"House"},"retrograde":{"type":"boolean","title":"Retrograde"},"speed":{"type":"number","title":"Speed"},"declination":{"type":"number","title":"Declination"}},"type":"object","required":["name","sign","degree","absolute_longitude","retrograde","speed","declination"],"title":"AstroPoint"},"AstroSynastryAspect":{"properties":{"point_a":{"type":"string","title":"Point A"},"point_b":{"type":"string","title":"Point B"},"aspect":{"type":"string","title":"Aspect"},"exact_degrees":{"type":"integer","title":"Exact Degrees"},"orb":{"type":"number","title":"Orb"}},"type":"object","required":["point_a","point_b","aspect","exact_degrees","orb"],"title":"AstroSynastryAspect"},"AstroTransitAspect":{"properties":{"transiting":{"type":"string","title":"Transiting"},"natal":{"type":"string","title":"Natal"},"aspect":{"type":"string","title":"Aspect"},"exact_degrees":{"type":"integer","title":"Exact Degrees"},"orb":{"type":"number","title":"Orb"},"movement":{"type":"string","title":"Movement"}},"type":"object","required":["transiting","natal","aspect","exact_degrees","orb","movement"],"title":"AstroTransitAspect"},"AstroTransitingPoint":{"properties":{"name":{"type":"string","title":"Name"},"sign":{"type":"string","title":"Sign"},"degree":{"type":"number","title":"Degree"},"absolute_longitude":{"type":"number","title":"Absolute Longitude"},"retrograde":{"type":"boolean","title":"Retrograde"},"speed":{"type":"number","title":"Speed"},"declination":{"type":"number","title":"Declination"},"natal_house":{"type":"integer","title":"Natal House"}},"type":"object","required":["name","sign","degree","absolute_longitude","retrograde","speed","declination","natal_house"],"title":"AstroTransitingPoint"},"AstrologyNatalResponse":{"properties":{"input":{"$ref":"#/components/schemas/AstroChartInputEcho"},"planets":{"items":{"$ref":"#/components/schemas/AstroPoint"},"type":"array","title":"Planets"},"houses":{"items":{"$ref":"#/components/schemas/AstroHouse"},"type":"array","title":"Houses"},"angles":{"$ref":"#/components/schemas/AstroAngles"},"aspects":{"items":{"$ref":"#/components/schemas/AstroNatalAspect"},"type":"array","title":"Aspects"},"meta":{"$ref":"#/components/schemas/AstroMeta"}},"type":"object","required":["input","planets","houses","angles","aspects","meta"],"title":"AstrologyNatalResponse"},"AstrologyResolvePlaceResponse":{"properties":{"query":{"type":"string","title":"Query"},"display_name":{"type":"string","title":"Display Name"},"latitude":{"type":"number","title":"Latitude"},"longitude":{"type":"number","title":"Longitude"},"timezone":{"type":"string","title":"Timezone"},"local_datetime":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Local Datetime"},"utc_offset":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Utc Offset"},"birth_datetime":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Birth Datetime"},"birth_datetime_utc":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Birth Datetime Utc"},"time_assumed_noon":{"type":"boolean","title":"Time Assumed Noon","default":false},"local_time_ambiguity":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Local Time Ambiguity"},"age_years":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Age Years"},"source":{"type":"string","title":"Source"}},"type":"object","required":["query","display_name","latitude","longitude","timezone","source"],"title":"AstrologyResolvePlaceResponse","description":"Place name (+ optional naive local time) -> coordinates, IANA zone and\na ready-to-use tz-aware ``birth_datetime`` for the natal endpoints."},"AstrologySynastryResponse":{"properties":{"person_a":{"$ref":"#/components/schemas/AstrologyNatalResponse"},"person_b":{"$ref":"#/components/schemas/AstrologyNatalResponse"},"inter_aspects":{"items":{"$ref":"#/components/schemas/AstroSynastryAspect"},"type":"array","title":"Inter Aspects"},"house_overlays":{"$ref":"#/components/schemas/AstroHouseOverlays"},"meta":{"$ref":"#/components/schemas/AstroMeta"}},"type":"object","required":["person_a","person_b","inter_aspects","house_overlays","meta"],"title":"AstrologySynastryResponse"},"AstrologyTransitsResponse":{"properties":{"natal":{"$ref":"#/components/schemas/AstrologyNatalResponse"},"target_datetime_utc":{"type":"string","title":"Target Datetime Utc"},"transiting_planets":{"items":{"$ref":"#/components/schemas/AstroTransitingPoint"},"type":"array","title":"Transiting Planets"},"aspects_to_natal":{"items":{"$ref":"#/components/schemas/AstroTransitAspect"},"type":"array","title":"Aspects To Natal"},"meta":{"$ref":"#/components/schemas/AstroMeta"}},"type":"object","required":["natal","target_datetime_utc","transiting_planets","aspects_to_natal","meta"],"title":"AstrologyTransitsResponse"},"AuditLogEntry":{"properties":{"id":{"type":"integer","title":"Id"},"admin_email":{"type":"string","title":"Admin Email"},"action":{"type":"string","title":"Action"},"target_type":{"type":"string","title":"Target Type"},"target_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Target Id"},"old_value":{"title":"Old Value"},"new_value":{"title":"New Value"},"ip_address":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ip Address"},"created_at":{"title":"Created At"}},"type":"object","required":["id","admin_email","action","target_type"],"title":"AuditLogEntry"},"BanRequest":{"properties":{"reason":{"type":"string","title":"Reason","default":""}},"type":"object","title":"BanRequest"},"BillingStatusResponse":{"properties":{"status":{"type":"string","title":"Status"}},"type":"object","required":["status"],"title":"BillingStatusResponse","description":"Always ``sent_if_valid`` — the free-key request never reveals whether\nan address is known or rate-gated."},"BillingUrlResponse":{"properties":{"url":{"type":"string","title":"Url"}},"type":"object","required":["url"],"title":"BillingUrlResponse","description":"Hosted Stripe page (Checkout or Customer Portal) to redirect to."},"Body_analyze_palm_api_palmistry_analyze_post":{"properties":{"image":{"type":"string","format":"binary","title":"Image","description":"Photo of the hand"},"hand_type":{"type":"string","title":"Hand Type","description":"Hand type: left or right","default":"right"},"dominant_hand":{"type":"string","title":"Dominant Hand","description":"User's dominant hand","default":"right"}},"type":"object","required":["image"],"title":"Body_analyze_palm_api_palmistry_analyze_post"},"Body_analyze_palm_dual_api_palmistry_analyze_dual_post":{"properties":{"left_image":{"type":"string","format":"binary","title":"Left Image","description":"Photo of the LEFT hand"},"right_image":{"type":"string","format":"binary","title":"Right Image","description":"Photo of the RIGHT hand"},"dominant_hand":{"type":"string","title":"Dominant Hand","description":"User's dominant hand","default":"right"}},"type":"object","required":["left_image","right_image"],"title":"Body_analyze_palm_dual_api_palmistry_analyze_dual_post"},"BulkGrantRequest":{"properties":{"user_ids":{"items":{"type":"integer"},"type":"array","maxItems":100,"title":"User Ids"},"amount":{"type":"integer","maximum":10000.0,"minimum":1.0,"title":"Amount"},"reason":{"type":"string","title":"Reason","default":""}},"type":"object","required":["user_ids","amount"],"title":"BulkGrantRequest"},"ChartInputRequest":{"properties":{"birth_datetime":{"type":"string","title":"Birth Datetime","description":"ISO-8601 datetime WITH timezone, e.g. '1990-05-17T14:30:00Z' or '1990-05-17T17:30:00+03:00'. Naive values are rejected."},"latitude":{"type":"number","maximum":90.0,"minimum":-90.0,"title":"Latitude","description":"Birth latitude, degrees"},"longitude":{"type":"number","maximum":180.0,"minimum":-180.0,"title":"Longitude","description":"Birth longitude, degrees"},"house_system":{"type":"string","title":"House System","description":"House system: one of ['alcabitius', 'campanus', 'equal', 'koch', 'morinus', 'placidus', 'porphyry', 'regiomontanus', 'topocentric', 'whole_sign']","default":"placidus"}},"type":"object","required":["birth_datetime","latitude","longitude"],"title":"ChartInputRequest","description":"One person's birth data."},"ChatHistoryResponse":{"properties":{"messages":{"items":{},"type":"array","title":"Messages","default":[]}},"type":"object","title":"ChatHistoryResponse"},"ChatMessageRequest":{"properties":{"message":{"type":"string","maxLength":8000,"minLength":1,"title":"Message"}},"type":"object","required":["message"],"title":"ChatMessageRequest"},"ChatMessageResponse":{"properties":{"reply":{"type":"string","title":"Reply"},"session_id":{"type":"integer","title":"Session Id"},"message_id":{"type":"integer","title":"Message Id"}},"type":"object","required":["reply","session_id","message_id"],"title":"ChatMessageResponse"},"ChatSessionResponse":{"properties":{"session_id":{"type":"integer","title":"Session Id"},"created_at":{"title":"Created At"}},"type":"object","required":["session_id"],"title":"ChatSessionResponse"},"CheckoutRequest":{"properties":{"email":{"type":"string","format":"email","title":"Email"},"plan":{"type":"string","pattern":"^api_pro_monthly$","title":"Plan","default":"api_pro_monthly"}},"type":"object","required":["email"],"title":"CheckoutRequest"},"CoinCostItem":{"properties":{"key":{"type":"string","title":"Key"},"operation":{"type":"string","title":"Operation"},"display_name":{"type":"string","title":"Display Name"},"value":{"type":"integer","title":"Value"},"updated_at":{"title":"Updated At"}},"type":"object","required":["key","operation","display_name","value"],"title":"CoinCostItem"},"CoinCostUpdateItem":{"properties":{"key":{"type":"string","title":"Key"},"value":{"type":"integer","maximum":1000.0,"minimum":0.0,"title":"Value"}},"type":"object","required":["key","value"],"title":"CoinCostUpdateItem"},"CoinGrantRequest":{"properties":{"user_id":{"type":"integer","title":"User Id"},"amount":{"type":"integer","maximum":10000.0,"minimum":1.0,"title":"Amount"},"reason":{"type":"string","title":"Reason","default":""}},"type":"object","required":["user_id","amount"],"title":"CoinGrantRequest"},"CoinTransactionItem":{"properties":{"id":{"type":"integer","title":"Id"},"amount":{"type":"integer","title":"Amount"},"operation":{"type":"string","title":"Operation"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"created_at":{"type":"string","format":"date-time","title":"Created At"}},"type":"object","required":["id","amount","operation","created_at"],"title":"CoinTransactionItem","description":"One row from coin_transactions, shaped for the user-facing history."},"CompatibilityCheckRequest":{"properties":{"partner_name":{"type":"string","maxLength":100,"minLength":1,"title":"Partner Name"},"partner_birth_date":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$","title":"Partner Birth Date","description":"YYYY-MM-DD"},"partner_birth_time":{"anyOf":[{"type":"string","pattern":"^([01]\\d|2[0-3]):[0-5]\\d(:[0-5]\\d)?$"},{"type":"null"}],"title":"Partner Birth Time","description":"HH:MM"},"partner_birth_place":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Partner Birth Place"},"category":{"type":"string","pattern":"^(love|work|business|friendship|family)$","title":"Category","description":"Compatibility dimension: love, work, business, friendship, family","default":"love"},"relationship_label":{"anyOf":[{"type":"string","maxLength":100},{"type":"null"}],"title":"Relationship Label"},"partner_gender":{"anyOf":[{"type":"string","pattern":"^(male|female|other|unspecified)$"},{"type":"null"}],"title":"Partner Gender","description":"Optional partner gender for synastry tone. One of male/female/other/unspecified."},"partner_birth_time_certainty":{"anyOf":[{"type":"string","pattern":"^(exact|approximate|unknown)$"},{"type":"null"}],"title":"Partner Birth Time Certainty","description":"How sure is the user about partner's birth time? exact|approximate|unknown."}},"type":"object","required":["partner_name","partner_birth_date"],"title":"CompatibilityCheckRequest"},"CompatibilityCheckResponse":{"properties":{"id":{"type":"integer","title":"Id"},"partner_name":{"type":"string","title":"Partner Name"},"partner_zodiac_sign":{"type":"string","title":"Partner Zodiac Sign"},"overall_score":{"type":"number","title":"Overall Score","default":0.0},"aspects":{"title":"Aspects","default":[]},"analysis":{"type":"string","title":"Analysis","default":""},"created_at":{"type":"string","title":"Created At"}},"additionalProperties":true,"type":"object","required":["id","partner_name","partner_zodiac_sign","created_at"],"title":"CompatibilityCheckResponse"},"CompatibilityHistoryResponse":{"properties":{"items":{"items":{},"type":"array","title":"Items"},"total":{"type":"integer","title":"Total"},"page":{"type":"integer","title":"Page"},"per_page":{"type":"integer","title":"Per Page"}},"type":"object","required":["items","total","page","per_page"],"title":"CompatibilityHistoryResponse"},"DreamCreateRequest":{"properties":{"request_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Request Id"},"description":{"type":"string","maxLength":5000,"minLength":10,"title":"Description"},"dream_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Dream Date"},"emotions":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}],"title":"Emotions"}},"type":"object","required":["description"],"title":"DreamCreateRequest"},"DreamDetailResponse":{"properties":{"session":{"title":"Session"},"messages":{"items":{},"type":"array","title":"Messages"},"interpretation":{"title":"Interpretation"}},"additionalProperties":true,"type":"object","required":["session","messages"],"title":"DreamDetailResponse"},"DreamHistoryResponse":{"properties":{"dreams":{"items":{},"type":"array","title":"Dreams"}},"type":"object","required":["dreams"],"title":"DreamHistoryResponse"},"DreamInterpretRequest":{"properties":{"request_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Request Id"}},"type":"object","title":"DreamInterpretRequest"},"DreamInterpretResponse":{"properties":{"session_id":{"type":"integer","title":"Session Id"},"status":{"type":"string","title":"Status"},"interpretation":{"title":"Interpretation"}},"type":"object","required":["session_id","status","interpretation"],"title":"DreamInterpretResponse"},"DreamMessageRequest":{"properties":{"request_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Request Id"},"session_id":{"type":"integer","title":"Session Id"},"message":{"type":"string","maxLength":2000,"minLength":1,"title":"Message"}},"type":"object","required":["session_id","message"],"title":"DreamMessageRequest"},"DreamOperationResponse":{"properties":{"request_id":{"type":"string","title":"Request Id"},"status":{"type":"string","title":"Status"},"session_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Session Id"},"response":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Response"},"error_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Code"},"error_status":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Error Status"},"refund_status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Refund Status"}},"type":"object","required":["request_id","status"],"title":"DreamOperationResponse"},"DreamReplyResponse":{"properties":{"session_id":{"type":"integer","title":"Session Id"},"status":{"type":"string","title":"Status"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"},"interpretation":{"anyOf":[{},{"type":"null"}],"title":"Interpretation"},"questions_remaining":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Questions Remaining"}},"additionalProperties":true,"type":"object","required":["session_id","status"],"title":"DreamReplyResponse"},"DreamStartResponse":{"properties":{"session_id":{"type":"integer","title":"Session Id"},"status":{"type":"string","title":"Status"},"message":{"type":"string","title":"Message"}},"type":"object","required":["session_id","status","message"],"title":"DreamStartResponse"},"EconomicsRow":{"properties":{"group_key":{"type":"string","title":"Group Key"},"total_requests":{"type":"integer","title":"Total Requests","default":0},"total_input_tokens":{"type":"integer","title":"Total Input Tokens","default":0},"total_output_tokens":{"type":"integer","title":"Total Output Tokens","default":0},"total_coins_charged":{"type":"integer","title":"Total Coins Charged","default":0},"estimated_cost_usd":{"type":"number","title":"Estimated Cost Usd","default":0.0},"coins_per_dollar":{"type":"number","title":"Coins Per Dollar","default":0.0},"avg_cost_per_request_usd":{"type":"number","title":"Avg Cost Per Request Usd","default":0.0}},"type":"object","required":["group_key"],"title":"EconomicsRow"},"ExportData":{"properties":{"app_name":{"type":"string","title":"App Name","default":"Stellara"},"user_name":{"type":"string","title":"User Name","default":""},"zodiac_sign":{"type":"string","title":"Zodiac Sign","default":""},"period_type":{"type":"string","title":"Period Type"},"category":{"type":"string","title":"Category"},"content":{"title":"Content"},"rating":{"type":"number","title":"Rating","default":0.0},"lucky_numbers":{"type":"string","title":"Lucky Numbers","default":""},"lucky_colors":{"type":"string","title":"Lucky Colors","default":""},"period_start":{"type":"string","title":"Period Start","default":""},"generated_at":{"type":"string","title":"Generated At","default":""}},"additionalProperties":true,"type":"object","required":["period_type","category","content"],"title":"ExportData"},"ExportHoroscopeResponse":{"properties":{"export_data":{"$ref":"#/components/schemas/ExportData"}},"type":"object","required":["export_data"],"title":"ExportHoroscopeResponse"},"FeatureFlagItem":{"properties":{"key":{"type":"string","title":"Key"},"feature":{"type":"string","title":"Feature"},"enabled":{"type":"boolean","title":"Enabled"},"updated_at":{"title":"Updated At"}},"type":"object","required":["key","feature","enabled"],"title":"FeatureFlagItem"},"FeatureFlagUpdate":{"properties":{"enabled":{"type":"boolean","title":"Enabled"}},"type":"object","required":["enabled"],"title":"FeatureFlagUpdate"},"FreeKeyRequest":{"properties":{"email":{"type":"string","format":"email","title":"Email"},"locale":{"anyOf":[{"type":"string","pattern":"^[A-Za-z]{2}([-_][A-Za-z]{2,4})?$"},{"type":"null"}],"title":"Locale"}},"type":"object","required":["email"],"title":"FreeKeyRequest"},"GiftCodeCreateRequest":{"properties":{"count":{"type":"integer","maximum":100.0,"minimum":1.0,"title":"Count","default":1},"duration_days":{"type":"integer","maximum":3660.0,"minimum":1.0,"title":"Duration Days","default":365},"campaign_name":{"anyOf":[{"type":"string","maxLength":120},{"type":"null"}],"title":"Campaign Name"},"order_id":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Order Id"}},"type":"object","title":"GiftCodeCreateRequest"},"GiftCodeRevokeRequest":{"properties":{"order_id":{"type":"string","maxLength":255,"minLength":1,"title":"Order Id"},"reason":{"anyOf":[{"type":"string","maxLength":120},{"type":"null"}],"title":"Reason"}},"type":"object","required":["order_id"],"title":"GiftCodeRevokeRequest"},"GiftCodeRevokeResponse":{"properties":{"order_id":{"type":"string","title":"Order Id"},"total_count":{"type":"integer","title":"Total Count"},"revoked_count":{"type":"integer","title":"Revoked Count"},"redeemed_count":{"type":"integer","title":"Redeemed Count"},"already_inactive_count":{"type":"integer","title":"Already Inactive Count"}},"type":"object","required":["order_id","total_count","revoked_count","redeemed_count","already_inactive_count"],"title":"GiftCodeRevokeResponse"},"GiftRedeemRequest":{"properties":{"code":{"type":"string","maxLength":80,"minLength":4,"title":"Code"}},"type":"object","required":["code"],"title":"GiftRedeemRequest"},"GiftRedeemResponse":{"properties":{"status":{"type":"string","title":"Status"},"subscription":{"$ref":"#/components/schemas/SubscriptionInfo"}},"type":"object","required":["status","subscription"],"title":"GiftRedeemResponse"},"GoogleAuthRequest":{"properties":{"google_token":{"type":"string","title":"Google Token"}},"type":"object","required":["google_token"],"title":"GoogleAuthRequest"},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"HoroscopeResponse":{"properties":{"period_type":{"type":"string","title":"Period Type"},"category":{"type":"string","title":"Category"},"period_start":{"type":"string","title":"Period Start"},"zodiac_sign":{"type":"string","title":"Zodiac Sign"},"natal_summary":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Natal Summary"},"transits_summary":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Transits Summary"},"moon_phase":{"title":"Moon Phase","default":{}},"horoscope":{"title":"Horoscope","default":{}}},"additionalProperties":true,"type":"object","required":["period_type","category","period_start","zodiac_sign"],"title":"HoroscopeResponse"},"LoginRequest":{"properties":{"email":{"type":"string","format":"email","title":"Email"},"password":{"type":"string","title":"Password"}},"type":"object","required":["email","password"],"title":"LoginRequest"},"LunarDayResponse":{"properties":{"date":{"type":"string","title":"Date"},"phase_name_en":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Phase Name En"},"phase_name_ru":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Phase Name Ru"},"phase_key":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Phase Key"},"illumination":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Illumination"},"emoji":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Emoji"}},"additionalProperties":true,"type":"object","required":["date"],"title":"LunarDayResponse"},"LunarMonthResponse":{"properties":{"year":{"type":"integer","title":"Year"},"month":{"type":"integer","title":"Month"},"days":{"items":{},"type":"array","title":"Days"}},"type":"object","required":["year","month","days"],"title":"LunarMonthResponse"},"MessageResponse":{"properties":{"message":{"type":"string","title":"Message"}},"type":"object","required":["message"],"title":"MessageResponse"},"ModelUsageItem":{"properties":{"model":{"type":"string","title":"Model"},"calls":{"type":"integer","title":"Calls"},"requests":{"type":"integer","title":"Requests"},"tokens":{"type":"integer","title":"Tokens"},"avg_latency_ms":{"type":"number","title":"Avg Latency Ms","default":0.0}},"type":"object","required":["model","calls","requests","tokens"],"title":"ModelUsageItem"},"NatalChartResponse":{"properties":{"source":{"type":"string","title":"Source"},"chart":{"title":"Chart"}},"additionalProperties":true,"type":"object","required":["source","chart"],"title":"NatalChartResponse"},"NatalRequest":{"properties":{"birth_datetime":{"type":"string","title":"Birth Datetime","description":"ISO-8601 datetime WITH timezone, e.g. '1990-05-17T14:30:00Z' or '1990-05-17T17:30:00+03:00'. Naive values are rejected."},"latitude":{"type":"number","maximum":90.0,"minimum":-90.0,"title":"Latitude","description":"Birth latitude, degrees"},"longitude":{"type":"number","maximum":180.0,"minimum":-180.0,"title":"Longitude","description":"Birth longitude, degrees"},"house_system":{"type":"string","title":"House System","description":"House system: one of ['alcabitius', 'campanus', 'equal', 'koch', 'morinus', 'placidus', 'porphyry', 'regiomontanus', 'topocentric', 'whole_sign']","default":"placidus"},"include_minor_aspects":{"type":"boolean","title":"Include Minor Aspects","description":"Also report semi-sextile / semi-square / quintile / sesquiquadrate / biquintile / quincunx with tight orbs","default":false}},"type":"object","required":["birth_datetime","latitude","longitude"],"title":"NatalRequest"},"NewUsersByDayItem":{"properties":{"date":{"type":"string","title":"Date"},"count":{"type":"integer","title":"Count"}},"type":"object","required":["date","count"],"title":"NewUsersByDayItem"},"NotificationProcessResponse":{"properties":{"status":{"type":"string","title":"Status"},"processed":{"type":"integer","title":"Processed"}},"type":"object","required":["status","processed"],"title":"NotificationProcessResponse"},"NotificationSettingsResponse":{"properties":{"notifications_enabled":{"type":"boolean","title":"Notifications Enabled"},"daily_horoscope_push":{"type":"boolean","title":"Daily Horoscope Push"},"push_time":{"type":"string","title":"Push Time"}},"type":"object","required":["notifications_enabled","daily_horoscope_push","push_time"],"title":"NotificationSettingsResponse"},"PalmistryCompareResponse":{"properties":{"reading_old":{"$ref":"#/components/schemas/PalmistryReadingResponse"},"reading_new":{"$ref":"#/components/schemas/PalmistryReadingResponse"},"diff":{"title":"Diff","default":{}},"days_between":{"type":"integer","title":"Days Between","default":0}},"additionalProperties":true,"type":"object","required":["reading_old","reading_new"],"title":"PalmistryCompareResponse"},"PalmistryDeleteResponse":{"properties":{"status":{"type":"string","title":"Status"}},"type":"object","required":["status"],"title":"PalmistryDeleteResponse"},"PalmistryHistoryItem":{"properties":{"id":{"type":"integer","title":"Id"},"hand_type":{"type":"string","title":"Hand Type"},"dominant_hand":{"type":"string","title":"Dominant Hand","default":"right"},"hand_shape":{"type":"string","title":"Hand Shape","default":""},"summary":{"type":"string","title":"Summary"},"created_at":{"type":"string","title":"Created At"}},"additionalProperties":true,"type":"object","required":["id","hand_type","summary","created_at"],"title":"PalmistryHistoryItem"},"PalmistryHistoryResponse":{"properties":{"readings":{"items":{"$ref":"#/components/schemas/PalmistryHistoryItem"},"type":"array","title":"Readings"}},"type":"object","required":["readings"],"title":"PalmistryHistoryResponse"},"PalmistryReadingResponse":{"properties":{"id":{"type":"integer","title":"Id"},"hand_type":{"type":"string","title":"Hand Type"},"dominant_hand":{"type":"string","title":"Dominant Hand","default":"right"},"hand_shape":{"type":"string","title":"Hand Shape","default":""},"hand_shape_meaning":{"type":"string","title":"Hand Shape Meaning","default":""},"lines":{"title":"Lines","default":{}},"mounts":{"title":"Mounts","default":{}},"special_marks":{"title":"Special Marks","default":[]},"natal_integration":{"title":"Natal Integration","default":{}},"summary":{"type":"string","title":"Summary","default":""},"recommendations":{"title":"Recommendations","default":[]},"reading_confidence":{"type":"string","title":"Reading Confidence","default":"medium"},"created_at":{"type":"string","title":"Created At"}},"additionalProperties":true,"type":"object","required":["id","hand_type","created_at"],"title":"PalmistryReadingResponse"},"ProfileResponse":{"properties":{"id":{"type":"integer","title":"Id"},"email":{"type":"string","title":"Email"},"name":{"type":"string","title":"Name"},"last_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Name"},"gender":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gender"},"birth_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Birth Date"},"birth_time":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Birth Time"},"birth_time_certainty":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Birth Time Certainty"},"birth_place":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Birth Place"},"current_city":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Current City"},"zodiac_sign":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Zodiac Sign"},"avatar_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Avatar Url"},"coin_balance":{"type":"integer","title":"Coin Balance","default":0},"birth_lat":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Birth Lat"},"birth_lng":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Birth Lng"},"birth_timezone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Birth Timezone"},"current_lat":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Current Lat"},"current_lng":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Current Lng"},"current_timezone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Current Timezone"},"partner_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Partner Name"},"partner_birth_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Partner Birth Date"},"partner_birth_time":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Partner Birth Time"},"partner_birth_place":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Partner Birth Place"},"partner_zodiac_sign":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Partner Zodiac Sign"},"astro_profile":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Astro Profile"}},"type":"object","required":["id","email","name"],"title":"ProfileResponse"},"ProfileUpdateRequest":{"properties":{"name":{"anyOf":[{"type":"string","maxLength":100,"minLength":1},{"type":"null"}],"title":"Name"},"last_name":{"anyOf":[{"type":"string","maxLength":100},{"type":"null"}],"title":"Last Name"},"gender":{"anyOf":[{"type":"string","pattern":"^(male|female|other)$"},{"type":"null"}],"title":"Gender"},"birth_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Birth Date"},"birth_time":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Birth Time"},"birth_time_certainty":{"anyOf":[{"type":"string","pattern":"^(exact|approximate|unknown)$"},{"type":"null"}],"title":"Birth Time Certainty"},"birth_place":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Birth Place"},"current_city":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Current City"},"avatar_url":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}],"title":"Avatar Url"},"partner_name":{"anyOf":[{"type":"string","maxLength":100},{"type":"null"}],"title":"Partner Name"},"partner_birth_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Partner Birth Date"},"partner_birth_time":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Partner Birth Time"},"partner_birth_place":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Partner Birth Place"}},"type":"object","title":"ProfileUpdateRequest"},"PromptUpdate":{"properties":{"prompt_template":{"anyOf":[{"type":"string","maxLength":32768},{"type":"null"}],"title":"Prompt Template"},"model":{"anyOf":[{"type":"string","pattern":"^gemini-[a-z0-9.-]+$"},{"type":"null"}],"title":"Model"},"max_output_tokens":{"anyOf":[{"type":"integer","maximum":16384.0,"minimum":256.0},{"type":"null"}],"title":"Max Output Tokens"},"temperature":{"anyOf":[{"type":"number","maximum":2.0,"minimum":0.0},{"type":"null"}],"title":"Temperature"},"is_active":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Active"}},"type":"object","title":"PromptUpdate"},"PurgeOldTransactionsResponse":{"properties":{"status":{"type":"string","title":"Status"},"deleted":{"type":"integer","title":"Deleted"},"retention_years":{"type":"integer","title":"Retention Years"}},"type":"object","required":["status","deleted","retention_years"],"title":"PurgeOldTransactionsResponse"},"RefreshCoinsResponse":{"properties":{"status":{"type":"string","title":"Status"},"scanned":{"type":"integer","title":"Scanned"},"granted":{"type":"integer","title":"Granted"},"skipped":{"type":"integer","title":"Skipped"}},"type":"object","required":["status","scanned","granted","skipped"],"title":"RefreshCoinsResponse"},"RefreshRequest":{"properties":{"refresh_token":{"type":"string","title":"Refresh Token"}},"type":"object","required":["refresh_token"],"title":"RefreshRequest"},"RegisterRequest":{"properties":{"email":{"type":"string","format":"email","title":"Email"},"password":{"type":"string","minLength":8,"title":"Password"},"name":{"type":"string","maxLength":100,"minLength":1,"title":"Name"}},"type":"object","required":["email","password","name"],"title":"RegisterRequest"},"RegisterTokenRequest":{"properties":{"token":{"type":"string","maxLength":512,"title":"Token"},"device_info":{"type":"string","maxLength":255,"title":"Device Info","default":""}},"type":"object","required":["token"],"title":"RegisterTokenRequest"},"RequestPasswordResetRequest":{"properties":{"email":{"type":"string","format":"email","title":"Email"}},"type":"object","required":["email"],"title":"RequestPasswordResetRequest"},"ResetPasswordRequest":{"properties":{"email":{"type":"string","format":"email","title":"Email"},"code":{"type":"string","maxLength":6,"minLength":6,"title":"Code"},"new_password":{"type":"string","minLength":8,"title":"New Password"}},"type":"object","required":["email","code","new_password"],"title":"ResetPasswordRequest"},"ResolvePlaceRequest":{"properties":{"place":{"type":"string","maxLength":200,"minLength":2,"title":"Place","description":"Free-text place of birth, e.g. 'Warsaw, Poland' or 'Варшава'"},"local_datetime":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Local Datetime","description":"Local wall-clock birth time WITHOUT an offset: 'YYYY-MM-DDTHH:MM[:SS]' or 'YYYY-MM-DD' (date only = 12:00 assumed). The offset is derived from the place's IANA zone, including historical DST."}},"type":"object","required":["place"],"title":"ResolvePlaceRequest"},"RestorePurchaseResponse":{"properties":{"status":{"type":"string","title":"Status"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"},"subscription":{"anyOf":[{"$ref":"#/components/schemas/SubscriptionInfo"},{"type":"null"}]}},"additionalProperties":true,"type":"object","required":["status"],"title":"RestorePurchaseResponse"},"RestoreRequest":{"properties":{"purchase_token":{"type":"string","minLength":1,"title":"Purchase Token"},"platform":{"type":"string","pattern":"^(android|ios)$","title":"Platform","default":"android"}},"type":"object","required":["purchase_token"],"title":"RestoreRequest"},"RetryAcknowledgeResponse":{"properties":{"status":{"type":"string","title":"Status"},"scanned":{"type":"integer","title":"Scanned"},"acknowledged":{"type":"integer","title":"Acknowledged"},"still_pending":{"type":"integer","title":"Still Pending"}},"type":"object","required":["status","scanned","acknowledged","still_pending"],"title":"RetryAcknowledgeResponse"},"SettingUpdate":{"properties":{"key":{"type":"string","maxLength":64,"minLength":1,"title":"Key"},"value":{"type":"string","maxLength":4096,"title":"Value"}},"type":"object","required":["key","value"],"title":"SettingUpdate"},"SubscriptionInfo":{"properties":{"tier":{"type":"string","title":"Tier"},"product_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Product Id"},"expires_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Expires At"},"subscription_status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Subscription Status"}},"type":"object","required":["tier"],"title":"SubscriptionInfo"},"SynastryRequest":{"properties":{"person_a":{"$ref":"#/components/schemas/ChartInputRequest"},"person_b":{"$ref":"#/components/schemas/ChartInputRequest"},"include_minor_aspects":{"type":"boolean","title":"Include Minor Aspects","default":false}},"type":"object","required":["person_a","person_b"],"title":"SynastryRequest"},"TTSRequest":{"properties":{"text":{"type":"string","maxLength":20000,"minLength":10,"title":"Text"},"language":{"type":"string","pattern":"^(ru|en|es|fr|de|it|pt|pl|tr|ar|zh|ja|ko|hi)$","title":"Language","default":"ru"},"auto_summarize":{"type":"boolean","title":"Auto Summarize","default":true},"source_type":{"anyOf":[{"type":"string","pattern":"^(horoscope|dream|tarot|palmistry|compatibility|natal)$"},{"type":"null"}],"title":"Source Type"}},"type":"object","required":["text"],"title":"TTSRequest","description":"Body schema for the TTS endpoint."},"TarotCardInfo":{"properties":{"id":{"type":"integer","title":"Id"},"name":{"type":"string","title":"Name"},"name_ru":{"type":"string","title":"Name Ru"},"arcana":{"type":"string","title":"Arcana"},"suit":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Suit"},"number":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Number"},"keywords":{"title":"Keywords"},"keywords_ru":{"title":"Keywords Ru"},"image_key":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Image Key"}},"additionalProperties":true,"type":"object","required":["id","name","name_ru","arcana"],"title":"TarotCardInfo"},"TarotCardListResponse":{"properties":{"cards":{"items":{},"type":"array","title":"Cards"}},"type":"object","required":["cards"],"title":"TarotCardListResponse"},"TarotDrawResponse":{"properties":{"id":{"type":"integer","title":"Id"},"card":{"$ref":"#/components/schemas/TarotCardInfo"},"card_name":{"type":"string","title":"Card Name"},"card_name_ru":{"type":"string","title":"Card Name Ru"},"card_name_en":{"type":"string","title":"Card Name En"},"is_reversed":{"type":"boolean","title":"Is Reversed"},"is_major_arcana":{"type":"boolean","title":"Is Major Arcana"},"suit_name":{"type":"string","title":"Suit Name","default":""},"keywords":{"title":"Keywords","default":[]},"interpretation":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Interpretation"},"horoscope_context":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Horoscope Context"},"draw_date":{"type":"string","title":"Draw Date"},"advice":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Advice"}},"additionalProperties":true,"type":"object","required":["id","card","card_name","card_name_ru","card_name_en","is_reversed","is_major_arcana","draw_date"],"title":"TarotDrawResponse"},"TarotHistoryItem":{"properties":{"id":{"type":"integer","title":"Id"},"card_name":{"type":"string","title":"Card Name"},"card_name_ru":{"type":"string","title":"Card Name Ru"},"arcana":{"type":"string","title":"Arcana"},"suit":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Suit"},"number":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Number"},"image_key":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Image Key"},"is_reversed":{"type":"boolean","title":"Is Reversed"},"interpretation":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Interpretation"},"horoscope_context":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Horoscope Context"},"draw_date":{"type":"string","title":"Draw Date"}},"additionalProperties":true,"type":"object","required":["id","card_name","card_name_ru","arcana","is_reversed","draw_date"],"title":"TarotHistoryItem"},"TarotHistoryResponse":{"properties":{"draws":{"items":{"$ref":"#/components/schemas/TarotHistoryItem"},"type":"array","title":"Draws"}},"type":"object","required":["draws"],"title":"TarotHistoryResponse"},"TestPushResponse":{"properties":{"sent":{"type":"boolean","title":"Sent"},"token_count":{"type":"integer","title":"Token Count"},"detail":{"type":"string","title":"Detail","default":""}},"type":"object","required":["sent","token_count"],"title":"TestPushResponse"},"TokenPriceCreate":{"properties":{"model_name":{"type":"string","maxLength":80,"minLength":1,"title":"Model Name"},"input_price_per_1m":{"type":"number","minimum":0.0,"title":"Input Price Per 1M"},"output_price_per_1m":{"type":"number","minimum":0.0,"title":"Output Price Per 1M"},"effective_date":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$","title":"Effective Date"}},"type":"object","required":["model_name","input_price_per_1m","output_price_per_1m","effective_date"],"title":"TokenPriceCreate"},"TokenPriceRow":{"properties":{"id":{"type":"integer","title":"Id"},"model_name":{"type":"string","title":"Model Name"},"input_price_per_1m":{"type":"number","title":"Input Price Per 1M"},"output_price_per_1m":{"type":"number","title":"Output Price Per 1M"},"effective_date":{"type":"string","title":"Effective Date"},"source":{"type":"string","title":"Source","default":"manual"},"created_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created By"},"created_at":{"title":"Created At"}},"type":"object","required":["id","model_name","input_price_per_1m","output_price_per_1m","effective_date"],"title":"TokenPriceRow"},"TokenResponse":{"properties":{"access_token":{"type":"string","title":"Access Token"},"refresh_token":{"type":"string","title":"Refresh Token"},"token_type":{"type":"string","title":"Token Type","default":"bearer"},"is_new_user":{"type":"boolean","title":"Is New User","default":false}},"type":"object","required":["access_token","refresh_token"],"title":"TokenResponse"},"TransitsRequest":{"properties":{"natal":{"$ref":"#/components/schemas/ChartInputRequest"},"target_datetime":{"type":"string","title":"Target Datetime","description":"ISO-8601 datetime WITH timezone, e.g. '1990-05-17T14:30:00Z' or '1990-05-17T17:30:00+03:00'. Naive values are rejected."},"include_minor_aspects":{"type":"boolean","title":"Include Minor Aspects","default":false}},"type":"object","required":["natal","target_datetime"],"title":"TransitsRequest"},"TransitsResponse":{"properties":{"date":{"type":"string","title":"Date"},"zodiac_sign":{"type":"string","title":"Zodiac Sign"},"transits":{"title":"Transits"}},"additionalProperties":true,"type":"object","required":["date","zodiac_sign","transits"],"title":"TransitsResponse"},"UpdateNotificationSettingsRequest":{"properties":{"notifications_enabled":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Notifications Enabled"},"daily_horoscope_push":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Daily Horoscope Push"},"push_time":{"anyOf":[{"type":"string","pattern":"^([01]\\d|2[0-3]):[0-5]\\d(:[0-5]\\d)?$"},{"type":"null"}],"title":"Push Time"},"timezone":{"anyOf":[{"type":"string","maxLength":64},{"type":"null"}],"title":"Timezone"}},"type":"object","title":"UpdateNotificationSettingsRequest"},"UserSettingsResponse":{"properties":{"user_id":{"type":"integer","title":"User Id"},"timezone":{"type":"string","title":"Timezone","default":"UTC"},"language":{"type":"string","title":"Language","default":"ru"},"text_language":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Text Language"},"notifications_enabled":{"type":"boolean","title":"Notifications Enabled","default":true},"daily_horoscope_push":{"type":"boolean","title":"Daily Horoscope Push","default":true},"theme":{"type":"string","title":"Theme","default":"dark"}},"type":"object","required":["user_id"],"title":"UserSettingsResponse"},"UserSettingsUpdateRequest":{"properties":{"timezone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Timezone"},"language":{"anyOf":[{"type":"string","pattern":"^(ru|en|zh|ja|es|de)$"},{"type":"null"}],"title":"Language"},"text_language":{"anyOf":[{"type":"string","pattern":"^(ru|en|es|fr|de|it|pt|pl|tr|ar|zh|ja|ko|hi)$"},{"type":"null"}],"title":"Text Language"},"notifications_enabled":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Notifications Enabled"},"daily_horoscope_push":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Daily Horoscope Push"},"theme":{"anyOf":[{"type":"string","pattern":"^(dark|light)$"},{"type":"null"}],"title":"Theme"}},"type":"object","title":"UserSettingsUpdateRequest"},"UserTransactionsResponse":{"properties":{"transactions":{"items":{"$ref":"#/components/schemas/CoinTransactionItem"},"type":"array","title":"Transactions","default":[]},"total":{"type":"integer","title":"Total","default":0},"limit":{"type":"integer","title":"Limit","default":50},"offset":{"type":"integer","title":"Offset","default":0},"total_earned":{"type":"integer","title":"Total Earned","default":0},"total_spent":{"type":"integer","title":"Total Spent","default":0}},"type":"object","title":"UserTransactionsResponse"},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"VerifyPurchaseRequest":{"properties":{"product_id":{"type":"string","maxLength":100,"minLength":1,"title":"Product Id"},"purchase_token":{"type":"string","minLength":1,"title":"Purchase Token"},"platform":{"type":"string","pattern":"^(android|ios)$","title":"Platform","default":"android"}},"type":"object","required":["product_id","purchase_token"],"title":"VerifyPurchaseRequest"},"VerifyPurchaseResponse":{"properties":{"status":{"type":"string","title":"Status"},"subscription":{"anyOf":[{"$ref":"#/components/schemas/SubscriptionInfo"},{"type":"null"}]},"coins_granted":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Coins Granted"},"coin_balance":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Coin Balance"}},"type":"object","required":["status"],"title":"VerifyPurchaseResponse"}},"securitySchemes":{"HTTPBearer":{"type":"http","scheme":"bearer"}}}}