{"openapi":"3.1.0","info":{"title":"XFINLAB API","version":"1.0.0"},"paths":{"/api/analyze/{symbol}":{"get":{"tags":["Market"],"summary":"Analyze Stock","operationId":"analyze_stock_api_analyze__symbol__get","parameters":[{"name":"symbol","in":"path","required":true,"schema":{"type":"string","title":"Symbol"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/market/{symbol}":{"get":{"tags":["Market"],"summary":"Market Stock","operationId":"market_stock_api_market__symbol__get","parameters":[{"name":"symbol","in":"path","required":true,"schema":{"type":"string","title":"Symbol"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/api/analyze/{symbol}":{"get":{"tags":["Analysis","Analysis"],"summary":"Analyze Stock","description":"Analyze a stock using market data and trading strategy.\n\nParameters:\n- symbol (str): Stock ticker symbol\n\nReturns:\n- dict: Analysis results containing:\n    - symbol: Normalized symbol\n    - market: Market data\n    - strategy: Dictionary with score and signal","operationId":"analyze_stock_api_api_analyze__symbol__get","parameters":[{"name":"symbol","in":"path","required":true,"schema":{"type":"string","title":"Symbol"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/event/{event_type}":{"get":{"tags":["Event"],"summary":"Get Event","operationId":"get_event_api_event__event_type__get","parameters":[{"name":"event_type","in":"path","required":true,"schema":{"type":"string","title":"Event Type"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/full-analysis/{ticker}":{"get":{"tags":["Full Analysis"],"summary":"Full Analysis","description":"Full investment analysis pipeline\n\nFlow:\n    MarketDataService → market_score\n    StrategyEngine    → strategy_score\n    NewsService       → news articles\n    NewsEngine        → news_score\n    RiskEngine        → risk_score\n    ScoringEngine     → final_score + rating\n\nArgs:\n    ticker (str): Stock symbol e.g. AAPL, NVDA, TSLA\n\nReturns:\n    FullAnalysisResponse: Complete investment decision","operationId":"full_analysis_api_full_analysis__ticker__get","parameters":[{"name":"ticker","in":"path","required":true,"schema":{"type":"string","title":"Ticker"}},{"name":"token","in":"query","required":false,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FullAnalysisResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/screener":{"get":{"tags":["Screener"],"summary":"Screener","description":"Dashboard Screener panel. Was previously always the same 3\nhardcoded stocks (AAPL/NVDA/TSLA) with hand-picked fake scores for\nevery user. Now scores the user's real watchlist (or a small\ndefault basket if logged out / no watchlist yet) from live market\n+ news data, then ranks/filters through the existing\nScreenerEngine formula.","operationId":"screener_api_screener_get","parameters":[{"name":"token","in":"query","required":false,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/portfolio":{"get":{"tags":["Portfolio"],"summary":"Portfolio","description":"Dashboard Portfolio Allocation panel. Was previously always the\nsame 3 hardcoded stocks (NVDA/AAPL/MSFT) with fixed fake scores\nfor every user, regardless of what they actually track. Now\nallocates across the user's real watchlist (or a small default\nbasket if logged out / no watchlist yet), weighted by each\nticker's real market_score computed from live data.","operationId":"portfolio_api_portfolio_get","parameters":[{"name":"token","in":"query","required":false,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/anomaly":{"get":{"tags":["Anomaly"],"summary":"Anomaly","description":"Dashboard Anomaly Radar panel. Was previously always the exact\nsame hardcoded reading (2.5M volume vs 900K average, +6.8% price\nchange) for every user, every time, regardless of the real\nmarket. Now scans the user's real watchlist (or a small default\nbasket) using live volume/price data per ticker.\n\nResponse shape changed from a single flat anomaly object to a list\nof per-ticker results, since we're now scanning multiple tickers\ninstead of one fixed one - dashboard.html's loadAnomaly() was\nupdated to match.","operationId":"anomaly_api_anomaly_get","parameters":[{"name":"token","in":"query","required":false,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/anomaly/search/{ticker}":{"get":{"tags":["Anomaly"],"summary":"Anomaly Search","description":"Single-ticker anomaly check -- lets a user check ANY global ticker,\nseparate from the batch watchlist scan above. Same AnomalyEngine\nmath, same real volume/price data, just scoped to one symbol instead\nof the whole watchlist.\n\nUnlike the batch endpoint above (which only lists tickers that\nactually have an anomaly, since it's a \"what needs my attention\"\nradar), this ALWAYS returns a result even when severity is NONE --\nsomeone explicitly searching one ticker wants an honest \"no anomaly\ndetected\" answer, not silence.\n\n2026-07-31 fix (\"顯示「攞唔到 AAPL 嘅市場數據...」\" regardless of UI\nlanguage): both error messages below used to be hardcoded Traditional\nChinese literals, so an English/Japanese/etc-mode user searching an\nunrecognized or data-unavailable ticker still saw raw Cantonese. Now\naccepts `lang` (same param anomaly.html already sends everywhere else\nvia I18N.currentLang) and looks the two messages up from\nservices/i18n.py's per-language dict, defaulting to the original\nChinese text for zh-HK/zh-TW/unset lang -- these are plain UI strings\n(not AI-generated prose), so a direct dict lookup is simpler and more\nreliable here than the ai_language_instruction() prompt-steering\npattern used for LLM endpoints elsewhere.","operationId":"anomaly_search_api_anomaly_search__ticker__get","parameters":[{"name":"ticker","in":"path","required":true,"schema":{"type":"string","title":"Ticker"}},{"name":"lang","in":"query","required":false,"schema":{"type":"string","title":"Lang"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/anomaly/history/{ticker}":{"get":{"tags":["Anomaly"],"summary":"Anomaly History","description":"Past 30 days of volume/price anomalies for a single ticker, with\nrelated news attached to each flagged day -- powers the \"過去30日\n成交量異常\" section on anomaly.html's single-ticker search result.\n\nSingle-ticker only, by explicit design choice: extending this to the\nbatch watchlist scan (GET /anomaly above) would multiply the yfinance\n+ NewsAPI workload by the watchlist size, and was intentionally left\nout of scope for this feature.\n\n2026-09-14 fix (site-wide \"hardcoded string ignores lang\" audit): this\nsibling of anomaly_search() above never accepted `lang` at all, even\nthough anomaly.html already sends I18N.currentLang to every OTHER\nendpoint on this page. Reuses anomaly_search()'s already-all-47-\nlanguage `anom_ticker_format_error` key for the exact same message\n(just phrased slightly differently in the original Chinese literal),\nand threads `lang` into scan_last_30_days() for its own error/label\nstrings below.","operationId":"anomaly_history_api_anomaly_history__ticker__get","parameters":[{"name":"ticker","in":"path","required":true,"schema":{"type":"string","title":"Ticker"}},{"name":"lang","in":"query","required":false,"schema":{"type":"string","title":"Lang"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/pairs-scan":{"post":{"tags":["Pairs Scan"],"summary":"Pairs Scan","operationId":"pairs_scan_api_pairs_scan_post","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Body"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/research/{ticker}":{"get":{"tags":["Research"],"summary":"Research","operationId":"research_api_research__ticker__get","parameters":[{"name":"ticker","in":"path","required":true,"schema":{"type":"string","title":"Ticker"}},{"name":"token","in":"query","required":false,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/report/generate":{"post":{"tags":["Report"],"summary":"Generate Live Report","operationId":"generate_live_report_api_report_generate_post","parameters":[{"name":"token","in":"query","required":false,"schema":{"type":"string","title":"Token"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LiveReportRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/report/{ticker}":{"get":{"tags":["Report"],"summary":"Generate Report","operationId":"generate_report_api_report__ticker__get","parameters":[{"name":"ticker","in":"path","required":true,"schema":{"type":"string","title":"Ticker"}},{"name":"token","in":"query","required":false,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/auth/register":{"post":{"tags":["Auth"],"summary":"Register","operationId":"register_api_auth_register_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserRegister"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/auth/login":{"post":{"tags":["Auth"],"summary":"Login","operationId":"login_api_auth_login_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserLogin"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/auth/me":{"get":{"tags":["Auth"],"summary":"Get Me","operationId":"get_me_api_auth_me_get","parameters":[{"name":"token","in":"query","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/auth/logout":{"post":{"tags":["Auth"],"summary":"Logout","operationId":"logout_api_auth_logout_post","parameters":[{"name":"token","in":"query","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/auth/profile":{"put":{"tags":["Auth"],"summary":"Update Profile","operationId":"update_profile_api_auth_profile_put","parameters":[{"name":"token","in":"query","required":true,"schema":{"type":"string","title":"Token"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProfileUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/quota/usage":{"get":{"tags":["Quota"],"summary":"Get Usage","operationId":"get_usage_api_quota_usage_get","parameters":[{"name":"token","in":"query","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/quota/check/{feature}":{"get":{"tags":["Quota"],"summary":"Check Quota","operationId":"check_quota_api_quota_check__feature__get","parameters":[{"name":"feature","in":"path","required":true,"schema":{"type":"string","title":"Feature"}},{"name":"token","in":"query","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/points/status":{"get":{"tags":["Points"],"summary":"Points Status","description":"Read-only status for the points badge (js/points-badge.js) and the\naccount/dashboard page: current points in the active 7-day cycle,\nthe 500-point target, and any active temporary Basic upgrade.\nOnly meaningful for free-plan users -- paying users simply won't\nhave accumulated points (services/quota_middleware.py only ever\ncalls points_service.record_and_check() for genuinely free users),\nso this returns zeros for them rather than an error.","operationId":"points_status_api_points_status_get","parameters":[{"name":"token","in":"query","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/achievements/status":{"get":{"tags":["Achievements"],"summary":"Achievements Status","description":"Read-only status for the achievements/badges UI (account/dashboard\npage): total active days, current/longest streak, unlocked badges,\nand the single nearest not-yet-unlocked badge. Works for every plan\n(free and paid) -- see services/quota_middleware.py's record_activity()\ncall sites for how usage gets counted regardless of tier.","operationId":"achievements_status_api_achievements_status_get","parameters":[{"name":"token","in":"query","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/referral/code":{"get":{"tags":["Referral"],"summary":"Get Referral Code","operationId":"get_referral_code_api_referral_code_get","parameters":[{"name":"token","in":"query","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/referral/use/{code}":{"post":{"tags":["Referral"],"summary":"Use Referral","operationId":"use_referral_api_referral_use__code__post","parameters":[{"name":"code","in":"path","required":true,"schema":{"type":"string","title":"Code"}},{"name":"token","in":"query","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/referral/stats":{"get":{"tags":["Referral"],"summary":"Get Stats","operationId":"get_stats_api_referral_stats_get","parameters":[{"name":"token","in":"query","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/analytics/track":{"post":{"tags":["Analytics"],"summary":"Track Event","operationId":"track_event_api_analytics_track_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/analytics/stats":{"get":{"tags":["Analytics"],"summary":"Get Stats","operationId":"get_stats_api_analytics_stats_get","parameters":[{"name":"token","in":"query","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/ai-analysis":{"post":{"tags":["AI Analysis"],"summary":"Ai Analysis","operationId":"ai_analysis_api_ai_analysis_post","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Body"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/evidence-scorecard":{"post":{"tags":["Evidence Scorecard"],"summary":"Evidence Scorecard","operationId":"evidence_scorecard_api_evidence_scorecard_post","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Body"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/news-denoise":{"post":{"tags":["News"],"summary":"News Denoise","operationId":"news_denoise_api_news_denoise_post","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Body"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/company-compare":{"post":{"tags":["Compare"],"summary":"Company Compare","operationId":"company_compare_api_company_compare_post","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Body"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/stress-lab":{"post":{"tags":["Stress Lab"],"summary":"Stress Lab","operationId":"stress_lab_api_stress_lab_post","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Body"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/chart-search/{symbol}":{"get":{"tags":["Chart Analysis"],"summary":"Chart Search","description":"Global-ticker-search chart analysis -- the \"type a ticker, no\nscreenshot\" flow. Returns real OHLC bars (for client-side candlestick\nrendering) plus the same real-data indicators the image-upload flow\nabove already computes (support/resistance/RSI/MACD/Fibonacci/\nconfluence). AI vision is never invoked here -- there's no image to\nlook at, and the numeric levels already come straight from real\nhistorical data, same as always.","operationId":"chart_search_api_chart_search__symbol__get","parameters":[{"name":"symbol","in":"path","required":true,"schema":{"type":"string","title":"Symbol"}},{"name":"period","in":"query","required":false,"schema":{"type":"string","default":"6mo","title":"Period"}},{"name":"interval","in":"query","required":false,"schema":{"type":"string","default":"1d","title":"Interval"}},{"name":"lang","in":"query","required":false,"schema":{"type":"string","title":"Lang"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/chart-search/{symbol}/commentary":{"get":{"tags":["Chart Analysis"],"summary":"Chart Search Commentary","description":"Optional, user-triggered plain-text AI summary of the real numeric\ndata above. Deliberately a separate, lazy endpoint rather than being\nbundled into /chart-search -- the default search-and-chart flow costs\nzero LLM calls; this only runs when the user explicitly clicks\n\"Generate AI commentary\". Text-only completion (get_ai_response), not\nvision -- there's no screenshot in this flow, so there's nothing for a\nvision model to look at; it's purely writing up numbers that were\nalready computed from real market data.\n\n2026-07-31 fix: this used to hard-force \"用繁體中文\" (Traditional\nChinese) into the prompt regardless of the site's selected UI\nlanguage -- same backend-hardcoded-language bug already fixed once\nfor the screener prompt (task #317/#327) and once for\nhistorical_analog_service.py (task #530). Now accepts the same\n`lang` param the sibling /chart-search and /multi-timeframe endpoints\non this router already take, and swaps in the shared\nai_language_instruction(lang) helper (services/i18n.py) instead of a\nliteral Chinese sentence -- same helper api/chat.py and\napi/ai_analysis.py's screener prompt already use, so this doesn't\ninvent a second translation mechanism for the same concept.","operationId":"chart_search_commentary_api_chart_search__symbol__commentary_get","parameters":[{"name":"symbol","in":"path","required":true,"schema":{"type":"string","title":"Symbol"}},{"name":"period","in":"query","required":false,"schema":{"type":"string","default":"6mo","title":"Period"}},{"name":"interval","in":"query","required":false,"schema":{"type":"string","default":"1d","title":"Interval"}},{"name":"token","in":"query","required":false,"schema":{"type":"string","title":"Token"}},{"name":"lang","in":"query","required":false,"schema":{"type":"string","title":"Lang"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/chart-search/{symbol}/multi-timeframe":{"get":{"tags":["Chart Analysis"],"summary":"Chart Search Multi Timeframe","description":"Optional, user-triggered multi-timeframe alignment check -- compares\nWeekly/Daily/1-Hour trend + confluence direction for the same symbol.\nNot called automatically on every search since it costs 3x the\nhistorical-data fetches of a normal /chart-search call.","operationId":"chart_search_multi_timeframe_api_chart_search__symbol__multi_timeframe_get","parameters":[{"name":"symbol","in":"path","required":true,"schema":{"type":"string","title":"Symbol"}},{"name":"lang","in":"query","required":false,"schema":{"type":"string","title":"Lang"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/chat":{"post":{"tags":["Chat"],"summary":"Chat","description":"XFINLAB AI 投資助手 chat endpoint.\n\nPreviously proxied to Dify (services/dify_service.py). Now uses the same\nai/ai_router.py provider (Groq by default) already trusted elsewhere in\nthe codebase (e.g. chart_analysis.py) — no external Dify account needed.\nRequest/response shape is unchanged, so the frontend (chat.html) needs\nno changes.","operationId":"chat_api_chat_post","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Body"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/auth/forgot-password":{"post":{"tags":["Password Reset"],"summary":"Forgot Password","operationId":"forgot_password_api_auth_forgot_password_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ForgotPasswordRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/auth/reset-password":{"post":{"tags":["Password Reset"],"summary":"Reset Password","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":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/watchlist/add/{ticker}":{"post":{"tags":["Watchlist"],"summary":"Add To Watchlist","operationId":"add_to_watchlist_api_watchlist_add__ticker__post","parameters":[{"name":"ticker","in":"path","required":true,"schema":{"type":"string","title":"Ticker"}},{"name":"token","in":"query","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/watchlist/remove/{ticker}":{"delete":{"tags":["Watchlist"],"summary":"Remove From Watchlist","operationId":"remove_from_watchlist_api_watchlist_remove__ticker__delete","parameters":[{"name":"ticker","in":"path","required":true,"schema":{"type":"string","title":"Ticker"}},{"name":"token","in":"query","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/watchlist":{"get":{"tags":["Watchlist"],"summary":"Get Watchlist","operationId":"get_watchlist_api_watchlist_get","parameters":[{"name":"token","in":"query","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/stats":{"get":{"tags":["Admin"],"summary":"Get Stats","operationId":"get_stats_api_admin_stats_get","parameters":[{"name":"token","in":"query","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/health":{"get":{"tags":["Admin"],"summary":"Get Health","operationId":"get_health_api_admin_health_get","parameters":[{"name":"token","in":"query","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/error-alerts":{"get":{"tags":["Admin"],"summary":"Get Error Alerts","operationId":"get_error_alerts_api_admin_error_alerts_get","parameters":[{"name":"token","in":"query","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/users":{"get":{"tags":["Admin"],"summary":"Get Users","operationId":"get_users_api_admin_users_get","parameters":[{"name":"token","in":"query","required":true,"schema":{"type":"string","title":"Token"}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","default":1,"title":"Page"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":20,"title":"Limit"}}],"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}/upgrade":{"post":{"tags":["Admin"],"summary":"Upgrade User","operationId":"upgrade_user_api_admin_users__user_id__upgrade_post","parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"integer","title":"User Id"}},{"name":"token","in":"query","required":true,"schema":{"type":"string","title":"Token"}}],"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}/downgrade":{"post":{"tags":["Admin"],"summary":"Downgrade User","operationId":"downgrade_user_api_admin_users__user_id__downgrade_post","parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"integer","title":"User Id"}},{"name":"token","in":"query","required":true,"schema":{"type":"string","title":"Token"}}],"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}/mark-annual-pro":{"post":{"tags":["Admin"],"summary":"Mark Annual Pro","description":"2026-07-27: manual stand-in for a real payment webhook -- confirms\n`user_id` paid for an ANNUAL Pro subscription (there is no live Stripe/\nPayPal integration yet). Sets their real plan to Pro with a genuine\n1-year expiry, and -- if they were referred -- grants the referrer 1\nyear of Pro (or Pro+, once they've referred REFERRAL_PROPLUS_THRESHOLD\npaying annual-Pro conversions) via services/referral_service.py. Once\na real payment gateway exists, its webhook should call\nReferralService.mark_annual_pro_payment() directly instead of this\nendpoint; the reward logic itself doesn't change.","operationId":"mark_annual_pro_api_admin_users__user_id__mark_annual_pro_post","parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"integer","title":"User Id"}},{"name":"token","in":"query","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/content-variants/today":{"get":{"tags":["Admin"],"summary":"Get Content Variants","description":"2026-07-27 'Level 1 content leverage' growth batch: returns\ntoday's ready-to-copy-paste post text (X/Threads/LinkedIn/Facebook/\nemail/push), generated daily by api/market_pulse.py's\n_notify_free_signals_ready() from the same real signals data behind\nfree-signals.html and the Telegram push. Read-only -- does not post\nanywhere; AJ copies each field into that platform's own composer.","operationId":"get_content_variants_api_admin_content_variants_today_get","parameters":[{"name":"token","in":"query","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/content-variants/regenerate":{"post":{"tags":["Admin"],"summary":"Regenerate Content Variants","description":"Manual on-demand regeneration -- bypasses the daily job's\nonce-per-day idempotency guard (that guard exists to stop the\nautomated cron from re-firing, not to stop an admin from refreshing\non purpose, e.g. to test this feature or pull a fresh copy mid-day\nafter signals have moved).","operationId":"regenerate_content_variants_api_admin_content_variants_regenerate_post","parameters":[{"name":"token","in":"query","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/seo/pages":{"get":{"tags":["Admin"],"summary":"Seo List Pages","description":"Growth OS Phase 1 -- read-only: how many ticker/comparison SEO\nlanding pages exist right now (glob of the repo root, see\nservices/seo_page_generator.py), plus how many of those were created\nvia this engine specifically (vs. the earlier hand-built batch).","operationId":"seo_list_pages_api_admin_seo_pages_get","parameters":[{"name":"token","in":"query","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/seo/suggestions":{"get":{"tags":["Admin"],"summary":"Seo Suggestions","description":"Growth OS Phase 1 -- read-only: assets from the site's own\nautocomplete.js ticker universe that don't have a landing page yet,\nranked by their existing popularity score. Answers \"what should I\ngenerate next\" instead of guessing.","operationId":"seo_suggestions_api_admin_seo_suggestions_get","parameters":[{"name":"token","in":"query","required":true,"schema":{"type":"string","title":"Token"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":30,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/seo/generate":{"post":{"tags":["Admin"],"summary":"Seo Generate","description":"Growth OS Phase 1 -- creates one new ticker landing page + appends\nit to sitemap.xml. Gated by the seo_auto_engine feature flag so it can\nbe paused instantly from the Feature Flags panel. Never overwrites an\nexisting page (services/seo_page_generator.py's create_ticker_page\nraises FileExistsError instead).","operationId":"seo_generate_api_admin_seo_generate_post","parameters":[{"name":"token","in":"query","required":true,"schema":{"type":"string","title":"Token"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"default":{},"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/email/stats":{"get":{"tags":["Admin"],"summary":"Email Digest Stats","description":"Growth OS Phase 3 -- subscriber counts for the admin panel.","operationId":"email_digest_stats_api_admin_email_stats_get","parameters":[{"name":"token","in":"query","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/email/send-now":{"post":{"tags":["Admin"],"summary":"Email Digest Send Now","description":"Manual on-demand send -- bypasses the daily job's once-per-day\nidempotency guard, same rationale as /admin/content-variants/\nregenerate above (e.g. testing this feature, or resending after\nfixing a bad subject line).","operationId":"email_digest_send_now_api_admin_email_send_now_post","parameters":[{"name":"token","in":"query","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/widgets/stats":{"get":{"tags":["Admin"],"summary":"Widget Stats","description":"Growth OS Phase 4 -- today's + all-time embed-view counts per\nwidget type, from api/widgets.py's widget_embed_log table (created\nlazily on first embed load, so this tolerates the table not existing\nyet on a fresh deploy).","operationId":"widget_stats_api_admin_widgets_stats_get","parameters":[{"name":"token","in":"query","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/intelligence/key-limit":{"post":{"tags":["Admin"],"summary":"Set Intelligence Key Limit","description":"Set a lower daily cap for one key, matched by its key_preview\n(e.g. 'xfl_AbCd...wxyz'). daily_limit=-2 (default) -> 50; 0 removes\nthe override.","operationId":"set_intelligence_key_limit_api_admin_intelligence_key_limit_post","parameters":[{"name":"token","in":"query","required":true,"schema":{"type":"string","title":"Token"}},{"name":"key_preview","in":"query","required":true,"schema":{"type":"string","title":"Key Preview"}},{"name":"daily_limit","in":"query","required":false,"schema":{"type":"integer","default":-2,"title":"Daily Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/intelligence/usage":{"get":{"tags":["Admin"],"summary":"Intelligence Usage","description":"Growth OS Phase 5 (2026-08-04) -- devrel view over the Intelligence\nAPI's real usage, joining three tables that were each built for a\nnarrower purpose and never had a combined admin view before:\napi_keys (admin-issued), self_serve_api_keys (automated free-tier\nsignup, services/api_key_service.py), and intelligence_api_usage\n(per-key per-day weighted-call counter, services/\nintelligence_quota_service.py). No new table -- purely a read-side\njoin for visibility, same \"packaging, not new infrastructure\" posture\nas api/intelligence.py itself.\n\nPer-key `last_used_at` and issuance metadata come from whichever of\nthe two key tables issued it; usage counts come from\nintelligence_api_usage keyed by the raw key string, matching how\napi/intelligence.py's _check_and_spend_quota() already writes to it.\n\n2026-09-07 (security review: hash keys at rest -- see services/\napi_key_service.py's verify_key() docstring for the full story):\napi_keys.key/self_serve_api_keys.key now hold a HASH for every\nmigrated/freshly-issued row, not the raw key -- so this view's join\nagainst intelligence_api_usage (still raw-key-keyed; that table is\nan ephemeral per-day counter, not a persistent credential store, and\nwas deliberately left out of the hashing migration) can no longer\nmatch on the key value directly. Fixed by hashing each usage row's\nraw api_key here instead, so the join happens hash-to-hash --\nintelligence_api_usage itself is untouched, this endpoint just reads\nit differently.","operationId":"intelligence_usage_api_admin_intelligence_usage_get","parameters":[{"name":"token","in":"query","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/referral/stats":{"get":{"tags":["Admin"],"summary":"Referral Stats","description":"Growth OS Phase 6 (2026-08-04) -- referral stats dashboard: total\ncodes generated, total successful referrals, total paid (annual-Pro)\nconversions, and a top-referrers leaderboard. Also returns the\ncurrent reward-amount config so the admin panel can render the\noverview and the editable reward form from one call.","operationId":"referral_stats_api_admin_referral_stats_get","parameters":[{"name":"token","in":"query","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/referral/config/{key}":{"post":{"tags":["Admin"],"summary":"Set Referral Config","description":"Growth OS Phase 6 -- toggle one referral reward amount (points\nbonuses, Pro-grant days, or the Pro+ threshold) live, no redeploy.\n`value` is a plain int query/body param (FastAPI accepts it as a\nquery param here, matching this file's existing convention for\nsimple scalar admin actions -- see issue_key()/mark_annual_pro()\nabove). Validated against the known key whitelist in\nservices/referral_service.py's set_config().","operationId":"set_referral_config_api_admin_referral_config__key__post","parameters":[{"name":"key","in":"path","required":true,"schema":{"type":"string","title":"Key"}},{"name":"value","in":"query","required":true,"schema":{"type":"integer","title":"Value"}},{"name":"token","in":"query","required":false,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/referral/floating-basic/run":{"post":{"tags":["Admin"],"summary":"Run Floating Basic Grants Now","description":"2026-08-24 -- manual \"Run Now\" trigger for the floating-Basic\nreferral cron (backend/main.py's _run_referral_floating_basic_job,\nnormally 22:15 daily), same purpose as /admin/security-scan/run\nabove: lets AJ verify the mechanism works right after wiring a new\nreferrer/referred-payment scenario instead of waiting for the next\nscheduled run.","operationId":"run_floating_basic_grants_now_api_admin_referral_floating_basic_run_post","parameters":[{"name":"token","in":"query","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/video/status":{"get":{"tags":["Admin"],"summary":"Video Engine Status","description":"Growth OS Phase 7 -- admin-panel status check: is GOOGLE_TTS_API_KEY\nset, is ffmpeg present on this deploy, and metadata (status/duration/\nslide count) for the most recent generation attempt (success or\nfailure), from services/video_engine_service.py's video_generation_log\ntable. Always returns a usable dict even on a fresh deploy with no\nvideo ever generated.","operationId":"video_engine_status_api_admin_video_status_get","parameters":[{"name":"token","in":"query","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/video/generate":{"post":{"tags":["Admin"],"summary":"Video Engine Generate","description":"Growth OS Phase 7 -- on-demand \"Generate Now\" trigger for the admin\npanel, so this can be tested/verified before wiring any automatic\ndaily schedule. Gated by the video_engine flag (default OFF) on top\nof the admin-token check -- this is the heaviest single operation in\nGrowth OS (TTS calls + ffmpeg render), so it must never run from a\nstale/cached admin.html session if the feature's been intentionally\nturned off. Runs synchronously and returns the result dict as-is\n(including a graceful {\"available\": False, \"message\": ...} if TTS or\nffmpeg isn't actually configured on this deploy).\n\n2026-08-04 v2: lang/aspect_ratio/theme are now real choices (see\nservices/video_engine_service.py's _SCRIPT/_ASPECT_RATIOS/_THEMES).\npost_to_telegram defaults to False and is an explicit per-click admin\nopt-in, not automatic -- repeatedly clicking Generate Now while\ntesting shouldn't quietly spam a live public channel every time;\nthe admin has to tick the box each time they actually want that.\n\n2026-10-01 (AJ: \"加可選GEN不同資產，佢日日都一樣果3隻\" -- the daily video\nalways used whatever _compute_free_signals() auto-ranked top today,\nwhich on a quiet market can repeat the same handful of tickers for\ndays): `tickers` is an optional comma-separated list (e.g.\n\"NVDA,BTC-USD,GC=F\"). Blank (the default, and what the unattended\ndaily schedule still passes) keeps the original auto-picked\ntoday's-top-signals behavior unchanged -- see\nservices/video_engine_service.py's generate_daily_video()/\n_build_custom_signals() docstrings.","operationId":"video_engine_generate_api_admin_video_generate_post","parameters":[{"name":"token","in":"query","required":true,"schema":{"type":"string","title":"Token"}},{"name":"lang","in":"query","required":false,"schema":{"type":"string","default":"zh-HK","title":"Lang"}},{"name":"aspect_ratio","in":"query","required":false,"schema":{"type":"string","default":"9:16","title":"Aspect Ratio"}},{"name":"theme","in":"query","required":false,"schema":{"type":"string","default":"dark","title":"Theme"}},{"name":"post_to_telegram","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Post To Telegram"}},{"name":"upload_to_youtube","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Upload To Youtube"}},{"name":"tickers","in":"query","required":false,"schema":{"type":"string","default":"","title":"Tickers"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/content-drafts/generate":{"post":{"tags":["Admin"],"summary":"Content Drafts Generate","description":"On-demand \"Generate Now\" for content drafts, same convention as\nvideo_engine_generate above. Safe to call more than once a day --\nunwanted drafts just get rejected in the review queue.","operationId":"content_drafts_generate_api_admin_content_drafts_generate_post","parameters":[{"name":"token","in":"query","required":true,"schema":{"type":"string","title":"Token"}},{"name":"count","in":"query","required":false,"schema":{"type":"integer","default":5,"title":"Count"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/content-drafts":{"get":{"tags":["Admin"],"summary":"Content Drafts List","operationId":"content_drafts_list_api_admin_content_drafts_get","parameters":[{"name":"token","in":"query","required":true,"schema":{"type":"string","title":"Token"}},{"name":"status","in":"query","required":false,"schema":{"type":"string","title":"Status"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":50,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/content-drafts/{draft_id}/approve":{"post":{"tags":["Admin"],"summary":"Content Drafts Approve","operationId":"content_drafts_approve_api_admin_content_drafts__draft_id__approve_post","parameters":[{"name":"draft_id","in":"path","required":true,"schema":{"type":"integer","title":"Draft Id"}},{"name":"token","in":"query","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/content-drafts/{draft_id}/reject":{"post":{"tags":["Admin"],"summary":"Content Drafts Reject","operationId":"content_drafts_reject_api_admin_content_drafts__draft_id__reject_post","parameters":[{"name":"draft_id","in":"path","required":true,"schema":{"type":"integer","title":"Draft Id"}},{"name":"token","in":"query","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/video/generate-custom":{"post":{"tags":["Admin"],"summary":"Video Engine Generate Custom","description":"2026-08-09 (admin chat-to-video feature, requested as \"Video Engine\n可以加個CHAT更彈性做任何影片嗎\" -- confirmed admin-only, not public):\nsibling to /admin/video/generate above, but instead of the fixed\ntoday's-signals format, takes a free-text `prompt` (e.g. \"make a video\nabout NVDA earnings, in Spanish, square format\") and generates\nnarration/slides for that arbitrary topic via\nservices/video_engine_service.generate_custom_video(). Same\nadmin-token + video_engine feature-flag gating as the daily-video\nendpoint -- this is still the heaviest single operation in Growth OS\n(TTS + ffmpeg), a chat text box doesn't change that cost.\n\nUses a JSON body (not query params) since `prompt` is free text that\ncan run long, unlike the daily endpoint's short enum-like query params.","operationId":"video_engine_generate_custom_api_admin_video_generate_custom_post","parameters":[{"name":"token","in":"query","required":true,"schema":{"type":"string","title":"Token"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"default":{},"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/series/start":{"post":{"tags":["Admin"],"summary":"Series Start","description":"Admin-only: N one-minute narration scripts + MP3 voiceovers for one\ntopic (for editing in CapCut). Background job; poll /admin/series/status.","operationId":"series_start_api_admin_series_start_post","parameters":[{"name":"token","in":"query","required":true,"schema":{"type":"string","title":"Token"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"default":{},"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/series/status/{job_id}":{"get":{"tags":["Admin"],"summary":"Series Status","operationId":"series_status_api_admin_series_status__job_id__get","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}},{"name":"token","in":"query","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/series/download/{job_id}":{"get":{"tags":["Admin"],"summary":"Series Download","operationId":"series_download_api_admin_series_download__job_id__get","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}},{"name":"token","in":"query","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/video/upload-media":{"post":{"tags":["Admin"],"summary":"Video Engine Upload Media","description":"Multipart upload -- `file` is the raw photo/video from the\nadmin's own computer, `frame_second` (optional, video only) is which\nsecond to extract a still frame from (defaults to the video's\nmidpoint if omitted). Max sizes/duration and allowed extensions are\nenforced inside save_media_upload() itself; a rejection there\nbecomes a 400 with the real reason, not a silent failure.","operationId":"video_engine_upload_media_api_admin_video_upload_media_post","parameters":[{"name":"token","in":"query","required":true,"schema":{"type":"string","title":"Token"}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_video_engine_upload_media_api_admin_video_upload_media_post"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/video/media-library":{"get":{"tags":["Admin"],"summary":"Video Engine Media Library","operationId":"video_engine_media_library_api_admin_video_media_library_get","parameters":[{"name":"token","in":"query","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/video/media/{asset_id}/thumbnail":{"get":{"tags":["Admin"],"summary":"Video Engine Media Thumbnail","description":"A plain GET with the admin token as a query param (same pattern\napi/video.py's public /video/latest uses for FileResponse) so this\nURL can go straight into an <img src=\"...\"> tag in admin.html\nwithout extra JS fetch/blob plumbing.","operationId":"video_engine_media_thumbnail_api_admin_video_media__asset_id__thumbnail_get","parameters":[{"name":"asset_id","in":"path","required":true,"schema":{"type":"integer","title":"Asset Id"}},{"name":"token","in":"query","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/video/media/{asset_id}":{"delete":{"tags":["Admin"],"summary":"Video Engine Delete Media","operationId":"video_engine_delete_media_api_admin_video_media__asset_id__delete","parameters":[{"name":"asset_id","in":"path","required":true,"schema":{"type":"integer","title":"Asset Id"}},{"name":"token","in":"query","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/widgets/branding":{"post":{"tags":["Admin"],"summary":"Set Widget Branding","description":"2026-08-09 (white-label Tier A, XFINLAB_Final_Strategy.md P2/P3):\nadmin-only setter for a Pro/Enterprise client's embed-widget branding\n(services/widget_branding_service.py). No self-serve UI yet -- same\n\"admin sets it up per paying client\" convention as\n/intelligence/admin/issue-key, since Pro/Enterprise billing itself is\nstill manual. body: {api_key, brand_name, accent_color, logo_url,\nbadge_mode}. badge_mode='hidden' (fully remove the XFINLAB badge) is\nrejected unless the key's tier is 'enterprise' -- the service layer\nitself enforces this, this endpoint is just the auth gate.","operationId":"set_widget_branding_api_admin_widgets_branding_post","parameters":[{"name":"token","in":"query","required":true,"schema":{"type":"string","title":"Token"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"default":{},"title":"Body"}}}},"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}":{"delete":{"tags":["Admin"],"summary":"Delete User","operationId":"delete_user_api_admin_users__user_id__delete","parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"integer","title":"User Id"}},{"name":"token","in":"query","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/push/telegram":{"post":{"tags":["Admin"],"summary":"Push Telegram","operationId":"push_telegram_api_admin_push_telegram_post","parameters":[{"name":"token","in":"query","required":true,"schema":{"type":"string","title":"Token"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"default":{},"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/audit-logs":{"get":{"tags":["Admin"],"summary":"Get Audit Logs","description":"Security & Operations Layer, Phase 2 -- surfaces the audit_logs table\n(login/register/admin actions, written by services/audit_log_service.py)\nin the admin dashboard. Capped at 200 to keep the response light.","operationId":"get_audit_logs_api_admin_audit_logs_get","parameters":[{"name":"token","in":"query","required":true,"schema":{"type":"string","title":"Token"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":100,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/feature-flags":{"get":{"tags":["Admin"],"summary":"Get Feature Flags","operationId":"get_feature_flags_api_admin_feature_flags_get","parameters":[{"name":"token","in":"query","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/feature-flags/{key}":{"post":{"tags":["Admin"],"summary":"Set Feature Flag","operationId":"set_feature_flag_api_admin_feature_flags__key__post","parameters":[{"name":"key","in":"path","required":true,"schema":{"type":"string","title":"Key"}},{"name":"token","in":"query","required":true,"schema":{"type":"string","title":"Token"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"default":{},"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/auth/login-methods":{"get":{"tags":["Admin"],"summary":"Get Login Methods","description":"Public (no admin token) -- login.html calls this on load to decide\nwhich social/OTP login buttons to render (task #333). Only ever\nexposes the 3 boolean flags plus Google's public client_id (which is\nNOT a secret -- Google's own docs have every \"Sign in with Google\"\nweb integration embed it directly in page HTML/JS). Never exposes\nGOOGLE_CLIENT_SECRET, LINE_CHANNEL_SECRET, or WHATSAPP_ACCESS_TOKEN.","operationId":"get_login_methods_api_auth_login_methods_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/admin/prediction-ledger":{"get":{"tags":["Admin"],"summary":"Get Prediction Ledger","description":"P1 of the Quant Research Factory roadmap (2026-08-10): surfaces\nservices/prediction_ledger_service.py's accuracy scoreboard --\nhit_rate_pct / avg_brier_score over every GRADED prediction, plus\nthe most recent 50 rows (graded and pending) for spot-checking.\nRead-only, does not trigger a fresh grading pass (that runs on its\nown daily schedule via backend/main.py's APScheduler job) -- same\nfast/read-only pattern as get_security_scan() above.\n2026-08-24: added `source` (e.g. \"capital_flow_forecast\" vs the\ndefault \"direction_probability_service\") now that more than one\nengine logs into this same table, so each can be scored on its own\n-- e.g. /admin/prediction-ledger?source=capital_flow_forecast to\npull the real hit-rate/Brier numbers for a Capital Flow landing page.","operationId":"get_prediction_ledger_api_admin_prediction_ledger_get","parameters":[{"name":"token","in":"query","required":true,"schema":{"type":"string","title":"Token"}},{"name":"symbol","in":"query","required":false,"schema":{"type":"string","title":"Symbol"}},{"name":"source","in":"query","required":false,"schema":{"type":"string","title":"Source"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/stripe-account-status":{"get":{"tags":["Admin"],"summary":"Get Stripe Account Status","description":"2026-08-24 (AJ: \"點知面家個STRIPE係咪完全可收款提款無問題\"): live check\nagainst Stripe's own Account.retrieve() -- see\napi/webhooks_stripe.py's get_account_status() for the full honesty\nnote on why this is a separate call from the public /stripe/status\n(that one only checks env vars, this one asks Stripe directly\nwhether the account can actually accept charges and pay out).","operationId":"get_stripe_account_status_api_admin_stripe_account_status_get","parameters":[{"name":"token","in":"query","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/fred-diagnostic":{"get":{"tags":["Admin"],"summary":"Get Fred Diagnostic","description":"2026-08-26 (debugging the persistent RRPONTSYD/M2SL/WALCL HTTP 400\nsurfaced by the new Data Factory error tracking): AJ manually\nreplicated the exact same request (same series_id, same api_key,\nsame params) via his own browser and it succeeded -- meaning the\nrequest shape and the key value HE tested are both fine. Re-pasting\nthe key into Railway's env var didn't clear the error either. This\nendpoint runs the IDENTICAL request server-side, from inside the\nactual Railway process, and returns FRED's real response instead of\nanother layer of guessing. Never echoes the key itself -- only its\nlength and whether it has leading/trailing whitespace (a very common\nreal cause of this exact symptom: an invisible copy-paste artifact\nthat works fine when a browser trims it but not when sent raw over\nHTTP), plus FRED's actual status code and response body for the\nRRPONTSYD call so we can see FRED's own error message text.","operationId":"get_fred_diagnostic_api_admin_fred_diagnostic_get","parameters":[{"name":"token","in":"query","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/sec-13d13g-search":{"get":{"tags":["Admin"],"summary":"Get Sec 13D13G Search","description":"2026-08-26 (Data Factory Step 6): admin-facing trigger + view for\nservices/sec_13d_13g_service.py's on-demand per-ticker lookup.","operationId":"get_sec_13d13g_search_api_admin_sec_13d13g_search_get","parameters":[{"name":"token","in":"query","required":true,"schema":{"type":"string","title":"Token"}},{"name":"ticker","in":"query","required":true,"schema":{"type":"string","title":"Ticker"}},{"name":"force_refresh","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Force Refresh"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/sec-13d13g-debug":{"get":{"tags":["Admin"],"summary":"Get Sec 13D13G Debug","description":"Learned from the 13F info-table filename bug (multiple back-and-forth\ndebugging rounds with AJ to pin down): this time, build the raw-\nresponse diagnostic upfront instead of reactively. Runs the exact\nsame EFTS request services.sec_13d_13g_service.search_recent_filings\nmakes, but returns SEC's raw JSON response instead of the parsed/\nfiltered result, so a field-name mismatch in _extract_hit_fields can\nbe spotted in one round-trip.","operationId":"get_sec_13d13g_debug_api_admin_sec_13d13g_debug_get","parameters":[{"name":"token","in":"query","required":true,"schema":{"type":"string","title":"Token"}},{"name":"ticker","in":"query","required":true,"schema":{"type":"string","title":"Ticker"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/api-trending-tickers":{"get":{"tags":["Admin"],"summary":"Get Api Trending Tickers","description":"2026-08-25 (AJ: \"咁FREE KEY比人用，我接到數據訓ENGINE或儲存之類嗎\"): the\nhonest answer at the time was \"no signal at all\" -- intelligence_api_\nusage only ever counted raw call volume. services/intelligence_quota_\nservice.py's log_query()/get_trending_tickers() close that gap with a\nlightweight, aggregate-only query log (endpoint+ticker+timestamp, never\nthe response payload) -- this is the admin-facing read of it. Answers\n\"what are developers actually asking the API about\" (a product signal\non which tickers/features to prioritize), not \"was the model right\"\n(that requires the Prediction Ledger's own internal grading loop, a\ndifferent question this endpoint deliberately doesn't claim to answer).","operationId":"get_api_trending_tickers_api_admin_api_trending_tickers_get","parameters":[{"name":"token","in":"query","required":true,"schema":{"type":"string","title":"Token"}},{"name":"days","in":"query","required":false,"schema":{"type":"integer","default":7,"title":"Days"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":20,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/data-sources":{"get":{"tags":["Admin"],"summary":"Get Data Sources","description":"2026-08-26 (AJ's \"Data Factory\" batch, \"起啦\"): foundation layer for\nan auto-extensible set of external data collectors (FRED, SEC EDGAR\nownership, CFTC COT, crypto exchanges, etc.). Unlike /admin/feature-\nflags (which needs every key hardcoded into _DEFAULT_FLAGS before it\ncan be toggled), each collector self-registers via\nservices.data_source_registry.register_source() the moment its module\nis imported -- this endpoint just reads whatever has registered so\nfar, so a brand-new collector shows up here automatically with no\nadmin.py edit required.","operationId":"get_data_sources_api_admin_data_sources_get","parameters":[{"name":"token","in":"query","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/data-sources/{source_key}/toggle":{"post":{"tags":["Admin"],"summary":"Toggle Data Source","operationId":"toggle_data_source_api_admin_data_sources__source_key__toggle_post","parameters":[{"name":"source_key","in":"path","required":true,"schema":{"type":"string","title":"Source Key"}},{"name":"token","in":"query","required":true,"schema":{"type":"string","title":"Token"}},{"name":"enabled","in":"query","required":false,"schema":{"type":"boolean","default":true,"title":"Enabled"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/cftc-cot-snapshot":{"get":{"tags":["Admin"],"summary":"Get Cftc Cot Snapshot","description":"2026-08-26 (Data Factory Step 3): debug/visibility endpoint for the\nnew services/cftc_cot_service.py collector -- lets AJ actually see\nthe positioning numbers being collected (net long/short per\ncontract), not just the run/error counts the Data Factory panel\nshows. Same pattern as /admin/api-trending-tickers: a thin read-only\nwrapper, no new business logic here.","operationId":"get_cftc_cot_snapshot_api_admin_cftc_cot_snapshot_get","parameters":[{"name":"token","in":"query","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/sec-13f-holdings":{"get":{"tags":["Admin"],"summary":"Get Sec 13F Holdings","description":"2026-08-26 (Data Factory Step 4): visibility endpoint for\nservices/sec_ownership_service.py. Without a cik param, lists the\nwatched-filer roster; with one, returns that filer's latest parsed\n13F holdings.","operationId":"get_sec_13f_holdings_api_admin_sec_13f_holdings_get","parameters":[{"name":"token","in":"query","required":true,"schema":{"type":"string","title":"Token"}},{"name":"cik","in":"query","required":false,"schema":{"type":"integer","title":"Cik"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/sec-13f-debug":{"get":{"tags":["Admin"],"summary":"Get Sec 13F Debug","description":"2026-08-26 (debugging Berkshire's 0-holdings result -- confirmed\nreproducible: 3 manual refreshes, Berkshire fails all 3 times while\nPershing Square/Scion succeed every time, so this is specific to\nBerkshire's filing, not a flaky network issue). Runs the exact same\n3-step pipeline services.sec_ownership_service.refresh_filer() uses\n(submissions.json -> filing index.json -> info table XML), but\nreturns each intermediate result instead of just persisting/failing\nsilently, so we can see exactly which step breaks for this CIK.","operationId":"get_sec_13f_debug_api_admin_sec_13f_debug_get","parameters":[{"name":"token","in":"query","required":true,"schema":{"type":"string","title":"Token"}},{"name":"cik","in":"query","required":true,"schema":{"type":"integer","title":"Cik"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/sec-13f-refresh":{"post":{"tags":["Admin"],"summary":"Refresh Sec 13F","description":"Manual trigger -- 13F filings only update quarterly so there's no\nscheduled job for this yet (see sec_ownership_service.refresh_all's\ndocstring); this lets AJ kick off a real run and immediately check\nthe Data Factory panel's run/error/last_success fields for\n'sec_13f_ownership' to confirm the live SEC round trip actually\nworks from Railway (the one thing not verifiable from the sandboxed\ndev environment this was built in).","operationId":"refresh_sec_13f_api_admin_sec_13f_refresh_post","parameters":[{"name":"token","in":"query","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/binance-exchange-snapshot":{"get":{"tags":["Admin"],"summary":"Get Binance Exchange Snapshot","description":"2026-08-26 (Data Factory Step 7): debug/visibility endpoint for\nservices/crypto_exchange_service.py, same pattern as\n/admin/cftc-cot-snapshot.","operationId":"get_binance_exchange_snapshot_api_admin_binance_exchange_snapshot_get","parameters":[{"name":"token","in":"query","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/eia-energy-snapshot":{"get":{"tags":["Admin"],"summary":"Get Eia Energy Snapshot","description":"2026-08-27 (Data Factory Step 8a, AJ: \"一次過全加可以嗎\"): debug/\nvisibility endpoint for services/eia_energy_service.py, same pattern\nas /admin/cftc-cot-snapshot.","operationId":"get_eia_energy_snapshot_api_admin_eia_energy_snapshot_get","parameters":[{"name":"token","in":"query","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/eia-energy-debug":{"get":{"tags":["Admin"],"summary":"Get Eia Energy Debug","description":"Upfront diagnostic (same lesson learned from the SEC 13F/13D-13G\ndebugging sagas -- build this BEFORE a live bug is reported, not\nafter) since the sandbox can't reach api.eia.gov directly. Runs one\nlive request per configured series and returns the raw HTTP status +\nparsed row count for each, so if EIA has reorganized a route/series\nID since this was written, the exact failing route/series shows up\nhere immediately instead of a generic 'zero observations' error.","operationId":"get_eia_energy_debug_api_admin_eia_energy_debug_get","parameters":[{"name":"token","in":"query","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/treasury-fiscal-snapshot":{"get":{"tags":["Admin"],"summary":"Get Treasury Fiscal Snapshot","description":"2026-08-27 (Data Factory Step 8b): debug/visibility endpoint for\nservices/treasury_fiscal_service.py, same pattern as\n/admin/cftc-cot-snapshot. No API key gate -- Treasury's Fiscal Data\nAPI is open with no key required.","operationId":"get_treasury_fiscal_snapshot_api_admin_treasury_fiscal_snapshot_get","parameters":[{"name":"token","in":"query","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/treasury-fiscal-debug":{"get":{"tags":["Admin"],"summary":"Get Treasury Fiscal Debug","description":"Same upfront-diagnostic rationale as /admin/eia-energy-debug --\none live request per configured Treasury dataset, raw status +\nrow count, so a field-name or route drift surfaces immediately.","operationId":"get_treasury_fiscal_debug_api_admin_treasury_fiscal_debug_get","parameters":[{"name":"token","in":"query","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/sec-xbrl-debug":{"get":{"tags":["Admin"],"summary":"Get Sec Xbrl Debug","description":"2026-08-28 (Data Factory batch, AJ: \"咁你一次過起\"): debug/visibility\nendpoint for services/sec_xbrl_service.py -- returns the full parsed\nfacts for one ticker (default AAPL) so a tag-alias miss or CIK lookup\nfailure is visible immediately, same upfront-diagnostic rationale as\n/admin/eia-energy-debug.","operationId":"get_sec_xbrl_debug_api_admin_sec_xbrl_debug_get","parameters":[{"name":"token","in":"query","required":true,"schema":{"type":"string","title":"Token"}},{"name":"ticker","in":"query","required":false,"schema":{"type":"string","default":"AAPL","title":"Ticker"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/cboe-vix-snapshot":{"get":{"tags":["Admin"],"summary":"Get Cboe Vix Snapshot","description":"2026-08-28 (Data Factory batch): debug/visibility endpoint for\nservices/cboe_vix_service.py. No API key required.","operationId":"get_cboe_vix_snapshot_api_admin_cboe_vix_snapshot_get","parameters":[{"name":"token","in":"query","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/cboe-vix-debug":{"get":{"tags":["Admin"],"summary":"Get Cboe Vix Debug","description":"Same upfront-diagnostic rationale as /admin/eia-energy-debug --\none live CSV fetch per index, raw status + parsed row shape, so a\nCBOE column-header drift surfaces immediately.","operationId":"get_cboe_vix_debug_api_admin_cboe_vix_debug_get","parameters":[{"name":"token","in":"query","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/fdic-bank-health-snapshot":{"get":{"tags":["Admin"],"summary":"Get Fdic Bank Health Snapshot","description":"2026-08-28 (Data Factory batch): debug/visibility endpoint for\nservices/fdic_banking_service.py. No API key required.","operationId":"get_fdic_bank_health_snapshot_api_admin_fdic_bank_health_snapshot_get","parameters":[{"name":"token","in":"query","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/usda-agriculture-snapshot":{"get":{"tags":["Admin"],"summary":"Get Usda Agriculture Snapshot","description":"2026-08-28 (Data Factory batch): debug/visibility endpoint for\nservices/usda_agriculture_service.py. Requires USDA_NASS_API_KEY.","operationId":"get_usda_agriculture_snapshot_api_admin_usda_agriculture_snapshot_get","parameters":[{"name":"token","in":"query","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/usda-agriculture-debug":{"get":{"tags":["Admin"],"summary":"Get Usda Agriculture Debug","description":"Same upfront-diagnostic rationale as /admin/eia-energy-debug --\none live request per configured USDA short_desc, raw status + row\ncount, so a short_desc drift surfaces immediately.","operationId":"get_usda_agriculture_debug_api_admin_usda_agriculture_debug_get","parameters":[{"name":"token","in":"query","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/coinbase-exchange-snapshot":{"get":{"tags":["Admin"],"summary":"Get Coinbase Exchange Snapshot","description":"2026-08-27 (Data Factory Step 8c): debug/visibility endpoint for\nservices/coinbase_exchange_service.py, same pattern as\n/admin/binance-exchange-snapshot.","operationId":"get_coinbase_exchange_snapshot_api_admin_coinbase_exchange_snapshot_get","parameters":[{"name":"token","in":"query","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/sec-form4-snapshot":{"get":{"tags":["Admin"],"summary":"Get Sec Form4 Snapshot","description":"2026-08-27 (Data Factory Step 9a): debug/visibility endpoint for\nservices/sec_form4_service.py, same pattern as /admin/cftc-cot-\nsnapshot -- takes a ticker since (unlike CFTC/EIA) insider trading\nis inherently per-company, not a fixed small set of contracts.","operationId":"get_sec_form4_snapshot_api_admin_sec_form4_snapshot_get","parameters":[{"name":"token","in":"query","required":true,"schema":{"type":"string","title":"Token"}},{"name":"ticker","in":"query","required":true,"schema":{"type":"string","title":"Ticker"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/sec-form4-debug":{"get":{"tags":["Admin"],"summary":"Get Sec Form4 Debug","description":"Upfront diagnostic (same lesson learned from the SEC 13F/13D-13G/EIA\ndebugging sagas -- build this BEFORE a live bug is reported): runs\neach step of the Form 4 pipeline separately and returns intermediate\nresults, so if EDGAR's browse-edgar Atom feed shape (the one part of\nthis pipeline that couldn't be verified from the sandbox) doesn't\nmatch what the parser expects, the raw feed text is visible here\ninstead of just a silent 'no filings' result.","operationId":"get_sec_form4_debug_api_admin_sec_form4_debug_get","parameters":[{"name":"token","in":"query","required":true,"schema":{"type":"string","title":"Token"}},{"name":"ticker","in":"query","required":true,"schema":{"type":"string","title":"Ticker"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/finra-short-interest-snapshot":{"get":{"tags":["Admin"],"summary":"Get Finra Short Interest Snapshot","description":"2026-08-27 (Data Factory Step 9b): debug/visibility endpoint for\nservices/finra_short_interest_service.py, same per-ticker-lookup\npattern as /admin/sec-form4-snapshot.","operationId":"get_finra_short_interest_snapshot_api_admin_finra_short_interest_snapshot_get","parameters":[{"name":"token","in":"query","required":true,"schema":{"type":"string","title":"Token"}},{"name":"ticker","in":"query","required":true,"schema":{"type":"string","title":"Ticker"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/finra-short-interest-debug":{"get":{"tags":["Admin"],"summary":"Get Finra Short Interest Debug","description":"Upfront diagnostic (same lesson learned this whole session): the one\npart of this collector that couldn't be verified from the sandbox is\nwhich candidate settlement-date file actually exists on FINRA's CDN\nright now, and whether its column headers match either of the two\nschema generations this parser tries. Returns the raw HTTP status\nfor EVERY candidate date tried (not just the first success), plus\nthe raw header row and first data row of whichever one worked.","operationId":"get_finra_short_interest_debug_api_admin_finra_short_interest_debug_get","parameters":[{"name":"token","in":"query","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/email-debug":{"get":{"tags":["Admin"],"summary":"Get Email Debug","description":"2026-08-27: diagnostic for the \"Key was issued but the confirmation\nemail failed to send\" error AJ hit on the self-serve Intelligence API\nsignup flow. services/email_service.py's EmailService.send() only ever\nlogs `print(f\"Email error: {e}\")` to server stdout on failure -- not\nvisible from the outside, and this whole SMTP path can't be exercised\nfrom the sandbox (no route to Railway's outbound network / real SMTP\ncreds). This endpoint surfaces exactly which step fails and why,\nwithout needing Railway CLI log access.\n\nStep 1 (always runs, no side effect): reports whether each required\nenv var is SET (never the actual secret value) plus the non-secret\nSMTP_HOST/SMTP_PORT values, then attempts connect -> starttls -> login\nagainst the real configured SMTP server, reporting the exact exception\nand which of those three steps it happened at. This alone diagnoses\nthe overwhelming majority of \"email failed\" cases (missing env var,\nwrong host/port, revoked app password, provider blocking the IP) with\nzero risk of actually sending anything.\n\nStep 2 (opt-in via send_test=true&test_to=you@example.com): only after\nstep 1's login succeeds, sends one real test email so AJ can confirm\ndelivery end-to-end (not just that auth works) -- e.g. Namecheap/Gmail\ncan accept a login but still silently drop mail past that point.\n\n2026-08-27: now branches on RESEND_API_KEY first (see services/\nemail_service.py's module docstring -- Railway blocks outbound SMTP\nbelow its Pro plan, confirmed via this exact endpoint's `connect:\n\"FAILED: timed out\"` result against mail.privateemail.com:587). When\nRESEND_API_KEY is set, this checks Resend's HTTP API instead (GET\n/domains, which validates the key without sending anything) and skips\nthe SMTP connect/starttls/login walk entirely, since EmailService.send\nitself would do the same -- this endpoint should diagnose whichever\npath send() actually takes, not a path it no longer uses.","operationId":"get_email_debug_api_admin_email_debug_get","parameters":[{"name":"token","in":"query","required":true,"schema":{"type":"string","title":"Token"}},{"name":"send_test","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Send Test"}},{"name":"test_to","in":"query","required":false,"schema":{"type":"string","title":"Test To"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/security-scan":{"get":{"tags":["Admin"],"summary":"Get Security Scan","description":"Task #326: surfaces the security watch (scripts/security_scan.py /\nservices/security_scan_service.py) in the admin panel, instead of it\nonly ever existing as terminal output from the external 6-hourly\nscheduled task. Returns the most recent scan result already\npersisted into xfinlab.db by the in-process APScheduler job\n(backend/main.py) or a prior manual run -- this is intentionally\nfast/read-only, it does NOT run a fresh scan on every page load.","operationId":"get_security_scan_api_admin_security_scan_get","parameters":[{"name":"token","in":"query","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/security-scan/run":{"post":{"tags":["Admin"],"summary":"Run Security Scan Now","description":"Manual \"Run Scan Now\" trigger for the admin panel. Runs synchronously\n(skips the slow pip-audit dependency scan so this stays fast enough\nfor a single HTTP request -- the in-process 6-hourly job still runs\nthe full scan including dependency CVEs) and persists the result\nlike every other run, so it immediately shows up in get_security_scan\nand in the history list too.","operationId":"run_security_scan_now_api_admin_security_scan_run_post","parameters":[{"name":"token","in":"query","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/pipeline/{ticker}":{"get":{"tags":["Pipeline"],"summary":"Run Pipeline","operationId":"run_pipeline_api_pipeline__ticker__get","parameters":[{"name":"ticker","in":"path","required":true,"schema":{"type":"string","title":"Ticker"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/feedback":{"post":{"tags":["Feedback"],"summary":"Submit Feedback","operationId":"submit_feedback_api_feedback_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FeedbackRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/feedback/list":{"get":{"tags":["Feedback"],"summary":"Get Feedback","description":"2026-07-30 addition: optional `type` filter, backward compatible --\nomitting it returns every row exactly as before. Added so the\nIntelligence API \"Request Early Access\" form (which inserts rows here\nwith type=\"intelligence_early_access\", see api/intelligence.py) can be\nlisted separately from general bug-report/feature-request feedback in\nadmin.html, without needing its own dedicated table.\n\n2026-08-18 fix (AJ saw inconsistent errors on admin.html -- Feature\nFlags panel said \"401: Invalid token\" while this panel said \"403:\nAdmin only\" for what turned out to be the exact same expired\nadminToken): this endpoint used to do its own ad-hoc\nverify_token()+sub-email check, collapsing \"token doesn't decode at\nall\" and \"token decodes but isn't the admin\" into a single 403 --\nunlike every other admin endpoint in api/admin.py, which uses the\nshared verify_admin() helper and reports those as 401 vs 403\nrespectively. Switched to verify_admin() so the error code/message is\nconsistent with the rest of the admin panel, and so this endpoint\nalso gets the IP-allowlist check and audit-log entry every other\nadmin action already gets (it was silently skipping both before).\nThe actual root cause of AJ's specific 401/403s is unrelated to this\ninconsistency -- see backend/auth/jwt_handler.py: JWT_SECRET isn't\nset in the deployment env, so a fresh random signing secret is\ngenerated on every backend restart, invalidating every existing\ntoken (including the admin session) until it's set permanently.","operationId":"get_feedback_api_feedback_list_get","parameters":[{"name":"token","in":"query","required":true,"schema":{"type":"string","title":"Token"}},{"name":"type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Type"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/recall-alerts/subscribe":{"post":{"tags":["Recall Alerts"],"summary":"Subscribe Recall Alert","operationId":"subscribe_recall_alert_api_recall_alerts_subscribe_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecallAlertSubscribeRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/recall-alerts/unsubscribe":{"get":{"tags":["Recall Alerts"],"summary":"Unsubscribe Recall Alert","operationId":"unsubscribe_recall_alert_api_recall_alerts_unsubscribe_get","parameters":[{"name":"token","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/onboarding/status":{"get":{"tags":["Onboarding"],"summary":"Get Onboarding Status","operationId":"get_onboarding_status_api_onboarding_status_get","parameters":[{"name":"token","in":"query","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/onboarding/complete-step/{step}":{"post":{"tags":["Onboarding"],"summary":"Complete Step","operationId":"complete_step_api_onboarding_complete_step__step__post","parameters":[{"name":"step","in":"path","required":true,"schema":{"type":"integer","title":"Step"}},{"name":"token","in":"query","required":true,"schema":{"type":"string","title":"Token"}},{"name":"lang","in":"query","required":false,"schema":{"type":"string","title":"Lang"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/i18n/detect":{"get":{"tags":["i18n"],"summary":"Detect Language","description":"Detect user language from IP","operationId":"detect_language_api_i18n_detect_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/i18n/{lang}":{"get":{"tags":["i18n"],"summary":"Get Language","description":"Get translations for specific language","operationId":"get_language_api_i18n__lang__get","parameters":[{"name":"lang","in":"path","required":true,"schema":{"type":"string","title":"Lang"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/auth/verify-email":{"get":{"tags":["Email Verification"],"summary":"Verify Email","operationId":"verify_email_api_auth_verify_email_get","parameters":[{"name":"token","in":"query","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/auth/resend-verification":{"post":{"tags":["Email Verification"],"summary":"Resend Verification","operationId":"resend_verification_api_auth_resend_verification_post","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Body"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/public-stats":{"get":{"tags":["Public Stats"],"summary":"Get Public Stats","operationId":"get_public_stats_api_public_stats_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/demo/analyze/{ticker}":{"get":{"tags":["Public Demo"],"summary":"Demo Analyze","operationId":"demo_analyze_api_demo_analyze__ticker__get","parameters":[{"name":"ticker","in":"path","required":true,"schema":{"type":"string","title":"Ticker"}},{"name":"lang","in":"query","required":false,"schema":{"type":"string","title":"Lang"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/market-pulse":{"get":{"tags":["Market Pulse"],"summary":"Market Pulse","operationId":"market_pulse_api_market_pulse_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/top-opportunities":{"get":{"tags":["Market Pulse"],"summary":"Top Opportunities","operationId":"top_opportunities_api_top_opportunities_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/free-signals":{"get":{"tags":["Market Pulse"],"summary":"Free Signals","operationId":"free_signals_api_free_signals_get","parameters":[{"name":"token","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/opportunity-categories":{"get":{"tags":["Market Pulse"],"summary":"Opportunity Categories","operationId":"opportunity_categories_api_opportunity_categories_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/hero-showcase":{"get":{"tags":["Hero Showcase"],"summary":"Hero Showcase","operationId":"hero_showcase_api_hero_showcase_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/push/vapid-public-key":{"get":{"tags":["Push"],"summary":"Get Vapid Public Key","operationId":"get_vapid_public_key_api_push_vapid_public_key_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/push/subscribe":{"post":{"tags":["Push"],"summary":"Subscribe","operationId":"subscribe_api_push_subscribe_post","parameters":[{"name":"token","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Token"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubscriptionPayload"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/push/unsubscribe":{"post":{"tags":["Push"],"summary":"Unsubscribe","operationId":"unsubscribe_api_push_unsubscribe_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnsubscribePayload"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/email/subscribe":{"post":{"tags":["Email Digest"],"summary":"Email Subscribe","operationId":"email_subscribe_api_email_subscribe_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubscribePayload"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/email/confirm":{"get":{"tags":["Email Digest"],"summary":"Email Confirm","operationId":"email_confirm_api_email_confirm_get","parameters":[{"name":"token","in":"query","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/email/unsubscribe":{"get":{"tags":["Email Digest"],"summary":"Email Unsubscribe","operationId":"email_unsubscribe_api_email_unsubscribe_get","parameters":[{"name":"token","in":"query","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/widgets/sentiment-index":{"get":{"tags":["Widgets"],"summary":"Widget Sentiment Index","operationId":"widget_sentiment_index_api_widgets_sentiment_index_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/widgets/heatmap":{"get":{"tags":["Widgets"],"summary":"Widget Heatmap","operationId":"widget_heatmap_api_widgets_heatmap_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":12,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/widgets/branding":{"get":{"tags":["Widgets"],"summary":"Widget Branding","description":"2026-08-09 (white-label Tier A): public lookup embed.js calls when\na data-xfl-key attribute is present on the script tag. Always 200s\nwith {\"available\": False} for a missing/free/invalid key -- never an\nerror, since a failed branding lookup must degrade to today's\ndefault XFINLAB-branded rendering, not a broken widget. See\nservices/widget_branding_service.py for the Pro/Enterprise tiering\nand re-verification-at-read-time contract.","operationId":"widget_branding_api_widgets_branding_get","parameters":[{"name":"key","in":"query","required":false,"schema":{"type":"string","title":"Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/widgets/embed.js":{"get":{"tags":["Widgets"],"summary":"Widget Embed Js","operationId":"widget_embed_js_api_widgets_embed_js_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/trending-stocks":{"get":{"tags":["Trending"],"summary":"Trending Stocks","description":"Country-prioritized 'trending / most actively traded' stocks, used\nby js/autocomplete.js's local-first autocomplete trending section\n(which only ever passes `country`) and by admin.html's \"Most Active\nStocks\" lookup (which also uses `sector`/`days`/`asset_class`).\n\n`country` should be an ISO country code (e.g. HK, TW, US) -- the\nfrontend passes whatever api/i18n.py's /i18n/detect resolved from the\nvisitor's IP. Ignored when `asset_class` is given and isn't \"Stocks\".\n\n`sector` (optional, 2026-09-28): one of SECTORS -- see\n/trending-stocks/sectors. Case-insensitive. Only the US basket has\nfull 5-tickers-per-sector coverage; other countries just tag their\nexisting small basket, so a sector filter there may return very few\n(or zero) results, which is an honest reflection of how small those\ncurated baskets are. Ignored when `asset_class` isn't \"Stocks\".\n\n`days` (optional, 2026-09-28): trailing trading-session window for\nthe volume ranking -- one of ALLOWED_DAYS. Any other value is\nsilently snapped to DEFAULT_DAYS. See\nservices/trending_stocks_service.py for the per-country data\nstrategy and why this needed real fixing (not just a new parameter)\nto be accurate.\n\n`asset_class` (optional, 2026-09-28): one of ASSET_CLASSES -- see\n/trending-stocks/sectors. Defaults to \"Stocks\" (the country/sector\nbasket above). Futures/Commodities/Forex/Crypto/Indices each rank a\nfixed small basket of real, liquid US-listed instruments -- see\nASSET_CLASS_BASKETS in trending_stocks_service.py for exactly which\ntickers and why each class uses the ticker format (ETF proxy vs. raw\nfutures/forex/crypto symbol) it does.","operationId":"trending_stocks_api_trending_stocks_get","parameters":[{"name":"country","in":"query","required":false,"schema":{"type":"string","default":"US","title":"Country"}},{"name":"sector","in":"query","required":false,"schema":{"type":"string","title":"Sector"}},{"name":"days","in":"query","required":false,"schema":{"type":"integer","default":7,"title":"Days"}},{"name":"asset_class","in":"query","required":false,"schema":{"type":"string","title":"Asset Class"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/trending-stocks/sectors":{"get":{"tags":["Trending"],"summary":"Trending Stocks Sectors","description":"The fixed sector list `sector` accepts, the asset classes\n`asset_class` accepts, and the day windows `days` accepts -- lets a\nfrontend build its filter dropdowns without hardcoding any of the\nthree twice.","operationId":"trending_stocks_sectors_api_trending_stocks_sectors_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/ticker-search":{"get":{"tags":["Ticker Search"],"summary":"Ticker Search","description":"Live global asset search, used by js/autocomplete.js to\nsupplement its local curated ~230-ticker list with real matches\nfrom Yahoo Finance's search index -- so any valid ticker (not just\nthe ones hand-picked into ASSETS) shows up as a suggestion.","operationId":"ticker_search_api_ticker_search_get","parameters":[{"name":"q","in":"query","required":false,"schema":{"type":"string","default":"","title":"Q"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/sparkline/{ticker}":{"get":{"tags":["Sparkline"],"summary":"Sparkline","description":"Last ~20 daily closes for one ticker, ready for\nXflSparkline.render() on the frontend. Always returns 200 with an\nempty list on any failure (bad ticker, data source down) -- callers\ntreat an empty/missing sparkline as \"just don't draw one for this\ncard\", never as an error to surface to the user.","operationId":"sparkline_api_sparkline__ticker__get","parameters":[{"name":"ticker","in":"path","required":true,"schema":{"type":"string","title":"Ticker"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/smart-route":{"get":{"tags":["Smart Route"],"summary":"Smart Route","description":"Powers the homepage's single \"tell me what you're thinking\" input\nbox (js/smart-router.js): given free text, figures out which\nEngine page best answers it (and any ticker mentioned), so a\nvisitor doesn't need to already know XFINLAB has 11 separate tools.\n\njs/smart-router.js tries its own fast regex/keyword pass first and\nonly calls this endpoint when that's inconclusive -- this endpoint\nre-runs the same fast pass server-side (cheap, no harm) and falls\nback to an AI classification call only if that also comes up\nempty. See services/intent_router_service.py for the full design\nrationale.","operationId":"smart_route_api_smart_route_get","parameters":[{"name":"q","in":"query","required":false,"schema":{"type":"string","default":"","title":"Q"}},{"name":"lang","in":"query","required":false,"schema":{"type":"string","title":"Lang"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/track-record":{"get":{"tags":["Backtest"],"summary":"Track Record","description":"Homepage 'Track Record' section data -- see\nservices/track_record_service.py's module docstring for the full\nmethodology (fixed 8-ticker basket, confluence_trend strategy, 24h\ncache). Always returns status ok with whatever was last successfully\ncomputed; symbols_tested==0 only if every backtest failed (e.g. data\nprovider outage), which the frontend should treat as \"not available\nyet\" rather than an error.","operationId":"track_record_api_track_record_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/backtest/{ticker}":{"get":{"tags":["Backtest"],"summary":"Backtest Ticker","operationId":"backtest_ticker_api_backtest__ticker__get","parameters":[{"name":"ticker","in":"path","required":true,"schema":{"type":"string","title":"Ticker"}},{"name":"strategy","in":"query","required":false,"schema":{"type":"string","default":"confluence_trend","title":"Strategy"}},{"name":"period","in":"query","required":false,"schema":{"type":"string","default":"2y","title":"Period"}},{"name":"lang","in":"query","required":false,"schema":{"type":"string","title":"Lang"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/backtest/{ticker}/compare":{"get":{"tags":["Backtest"],"summary":"Backtest Compare","operationId":"backtest_compare_api_backtest__ticker__compare_get","parameters":[{"name":"ticker","in":"path","required":true,"schema":{"type":"string","title":"Ticker"}},{"name":"period","in":"query","required":false,"schema":{"type":"string","default":"2y","title":"Period"}},{"name":"lang","in":"query","required":false,"schema":{"type":"string","title":"Lang"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/backtest/{ticker}/walk-forward":{"get":{"tags":["Backtest"],"summary":"Backtest Walk Forward","description":"2026-08-10 (P0 of the Quant Research Factory roadmap) -- out-of-\nsample validation, see services/backtest_service.py's\nrun_walk_forward() docstring for the full methodology (N chronological\nfolds + a 70/30 in-sample/out-of-sample split + a heuristic\noverfitting-risk flag).","operationId":"backtest_walk_forward_api_backtest__ticker__walk_forward_get","parameters":[{"name":"ticker","in":"path","required":true,"schema":{"type":"string","title":"Ticker"}},{"name":"strategy","in":"query","required":false,"schema":{"type":"string","default":"confluence_trend","title":"Strategy"}},{"name":"period","in":"query","required":false,"schema":{"type":"string","default":"2y","title":"Period"}},{"name":"n_folds","in":"query","required":false,"schema":{"type":"integer","default":4,"title":"N Folds"}},{"name":"lang","in":"query","required":false,"schema":{"type":"string","title":"Lang"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/formula-composer/{ticker}/scan":{"get":{"tags":["Backtest"],"summary":"Formula Composer Scan","description":"2026-08-10 (P2 of the Quant Research Factory roadmap) -- runs\nservices/formula_composer_service.py's small-scale combinatorial\nstrategy scan (35 candidate combinations of 6 existing causal\nindicator primitives, each walk-forward-validated) against `ticker`\nand returns the top out-of-sample survivors. See that module's\ndocstring for the full methodology and why this stays deliberately\nsmall rather than an exhaustive/genetic search.","operationId":"formula_composer_scan_api_formula_composer__ticker__scan_get","parameters":[{"name":"ticker","in":"path","required":true,"schema":{"type":"string","title":"Ticker"}},{"name":"period","in":"query","required":false,"schema":{"type":"string","default":"2y","title":"Period"}},{"name":"n_folds","in":"query","required":false,"schema":{"type":"integer","default":4,"title":"N Folds"}},{"name":"min_oos_trades","in":"query","required":false,"schema":{"type":"integer","default":5,"title":"Min Oos Trades"}},{"name":"top_n","in":"query","required":false,"schema":{"type":"integer","default":5,"title":"Top N"}},{"name":"lang","in":"query","required":false,"schema":{"type":"string","title":"Lang"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/formula-composer/leaderboard":{"get":{"tags":["Backtest"],"summary":"Formula Composer Leaderboard","description":"Reads the persisted formula_composer_candidates leaderboard --\neither one symbol's full last-scan table (best first) or, without a\nsymbol, the most recently scanned rows across all symbols. Read-only,\nnever triggers a fresh scan (use the /scan endpoint above for that).","operationId":"formula_composer_leaderboard_api_formula_composer_leaderboard_get","parameters":[{"name":"symbol","in":"query","required":false,"schema":{"type":"string","title":"Symbol"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":20,"title":"Limit"}},{"name":"lang","in":"query","required":false,"schema":{"type":"string","title":"Lang"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/regime-router/{ticker}/current-regime":{"get":{"tags":["Backtest"],"summary":"Regime Router Current","description":"2026-08-10 (P3 of the Quant Research Factory roadmap) -- current\ncausal regime classification for `ticker`. See services/regime_\nrouter_service.py's module docstring for why this uses its own\ncausal-only classifier rather than the live Confluence/Regime Belief\nengines (which depend on look-ahead-tainted swing-point inputs).\n\n2026-08-31: lang query param added and threaded through (AJ flagged\nmixed-language rendering -- see get_current_regime()'s docstring).","operationId":"regime_router_current_api_regime_router__ticker__current_regime_get","parameters":[{"name":"ticker","in":"path","required":true,"schema":{"type":"string","title":"Ticker"}},{"name":"lang","in":"query","required":false,"schema":{"type":"string","title":"Lang"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/regime-router/{ticker}/scan":{"get":{"tags":["Backtest"],"summary":"Regime Router Scan","description":"Runs services/regime_router_service.py's regime-conditional scan:\nsimulates all 35 formula_composer_service candidates over `ticker`'s\nfull history, buckets each trade by the causal regime active at its\nentry bar, and persists per-(candidate, regime) stats. Call this once\nper symbol before using /best-for-regime below.","operationId":"regime_router_scan_api_regime_router__ticker__scan_get","parameters":[{"name":"ticker","in":"path","required":true,"schema":{"type":"string","title":"Ticker"}},{"name":"period","in":"query","required":false,"schema":{"type":"string","default":"2y","title":"Period"}},{"name":"lang","in":"query","required":false,"schema":{"type":"string","title":"Lang"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/regime-router/{ticker}/best-for-regime":{"get":{"tags":["Backtest"],"summary":"Regime Router Best","description":"Reads the persisted regime-conditional leaderboard. If `regime` is\nomitted, first computes the ticker's CURRENT regime (same as\n/current-regime above) and looks that up -- the actual \"what should I\nuse right now\" answer this roadmap phase promised. Requires\n/scan to have been run for this symbol first.\n\n2026-08-31: lang query param added and threaded through to both\nget_current_regime() and get_best_for_regime() -- previously this\nendpoint had no lang param at all, so its \"reason\"/caveat text stayed\nhardcoded Cantonese no matter what language the calling page had\nselected (ai-analysis.html/chart-analysis.html's regime-router labels\nDO follow the page's selected language via data-i18n, which produced a\nvisibly mixed-language result whenever a non-Chinese language was\nselected -- AJ flagged this directly).","operationId":"regime_router_best_api_regime_router__ticker__best_for_regime_get","parameters":[{"name":"ticker","in":"path","required":true,"schema":{"type":"string","title":"Ticker"}},{"name":"regime","in":"query","required":false,"schema":{"type":"string","title":"Regime"}},{"name":"min_trades","in":"query","required":false,"schema":{"type":"integer","default":5,"title":"Min Trades"}},{"name":"lang","in":"query","required":false,"schema":{"type":"string","title":"Lang"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/rss-news":{"get":{"tags":["RSS News"],"summary":"Rss News","description":"Merged, newest-first financial-news + company-announcement\nheadlines across every compliant RSS source configured in\nservices/rss_news_service.py. See that module's docstring for exactly\nwhich sources are used and which were checked and ruled out.","operationId":"rss_news_api_rss_news_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":40,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/rss-news/search":{"get":{"tags":["RSS News"],"summary":"Rss News Search","description":"Keyword filter (company name / ticker) over the merged RSS feed --\nthe practical substitute for a per-company IR feed, since none of the\nwires this service uses offer true per-ticker subscriptions.","operationId":"rss_news_search_api_rss_news_search_get","parameters":[{"name":"q","in":"query","required":true,"schema":{"type":"string","title":"Q"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":20,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/global-macro/regions":{"get":{"tags":["Global Macro"],"summary":"Global Macro Regions","description":"List the 10 regions covered by the global macro+news commentary\nlayer (see services/macro_data_service.REGIONS for the full\ndefinitions, including which World Bank proxy country backs each\nmulti-country grouping).","operationId":"global_macro_regions_api_global_macro_regions_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/global-macro/{region}":{"get":{"tags":["Global Macro"],"summary":"Global Macro Region","description":"Combined macro indicators + filtered news + FinBERT sentiment + AI\ncommentary for one region. See\nservices/global_macro_commentary_service.get_region_commentary() for\nthe honesty/degradation contract -- pieces that are unavailable (e.g.\nTaiwan has no World Bank macro data) are marked as such rather than\ncausing the whole response to fail.","operationId":"global_macro_region_api_global_macro__region__get","parameters":[{"name":"region","in":"path","required":true,"schema":{"type":"string","title":"Region"}},{"name":"lang","in":"query","required":false,"schema":{"type":"string","default":"zh-HK","title":"Lang"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/decision-journal/add/{symbol}":{"post":{"tags":["Decision Journal"],"summary":"Add Journal Entry","operationId":"add_journal_entry_api_decision_journal_add__symbol__post","parameters":[{"name":"symbol","in":"path","required":true,"schema":{"type":"string","title":"Symbol"}},{"name":"token","in":"query","required":true,"schema":{"type":"string","title":"Token"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/decision-journal":{"get":{"tags":["Decision Journal"],"summary":"Get Journal","operationId":"get_journal_api_decision_journal_get","parameters":[{"name":"token","in":"query","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/decision-journal/{entry_id}":{"delete":{"tags":["Decision Journal"],"summary":"Remove Journal Entry","operationId":"remove_journal_entry_api_decision_journal__entry_id__delete","parameters":[{"name":"entry_id","in":"path","required":true,"schema":{"type":"integer","title":"Entry Id"}},{"name":"token","in":"query","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/agent-debate/status":{"get":{"tags":["Agent Debate"],"summary":"Agent Debate Status","description":"Frontend calls this once to decide whether to show the AI辯論\nentry point at all -- see services/agent_debate_service.py's\ndocstring on why this is gated behind DEEPINFRA_API_KEY.","operationId":"agent_debate_status_api_agent_debate_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/agent-debate/{symbol}":{"get":{"tags":["Agent Debate"],"summary":"Agent Debate","description":"2026-07-20 fix: this endpoint used to take no token param at all and\nnever touched services/quota_middleware.py, so AI辯論 (4 sequential\nLLM calls per run -- the most expensive single feature on the site)\nrode completely outside the token-quota system anyone could hit it\nfor free, repeatedly, logged in or not. Wired into the same\ncheck_token_budget()/record_ai_token_usage() pattern api/chat.py\nalready uses: paid tiers get billed the real (summed, not\nfabricated) token cost of all 4 calls -- see\nservices/agent_debate_service.py's run_debate() docstring -- against\ntheir existing monthly budget; free users still just earn a point\n(same as every other AI feature), never hard-blocked.","operationId":"agent_debate_api_agent_debate__symbol__get","parameters":[{"name":"symbol","in":"path","required":true,"schema":{"type":"string","title":"Symbol"}},{"name":"token","in":"query","required":false,"schema":{"type":"string","title":"Token"}},{"name":"lang","in":"query","required":false,"schema":{"type":"string","title":"Lang"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/agent-debate/research/status":{"get":{"tags":["Agent Debate"],"summary":"Research Agency Status","description":"Same purpose as /agent-debate/status -- frontend gate for whether\nto show a \"Multi-Angle Research\" entry point at all.","operationId":"research_agency_status_api_agent_debate_research_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/agent-debate/{symbol}/research":{"get":{"tags":["Agent Debate"],"summary":"Agent Debate Research","operationId":"agent_debate_research_api_agent_debate__symbol__research_get","parameters":[{"name":"symbol","in":"path","required":true,"schema":{"type":"string","title":"Symbol"}},{"name":"token","in":"query","required":false,"schema":{"type":"string","title":"Token"}},{"name":"lang","in":"query","required":false,"schema":{"type":"string","title":"Lang"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/intelligence/status":{"get":{"tags":["Intelligence API"],"summary":"Intelligence Status","description":"Public, unauthenticated -- lets a prospective developer check what's\nlive before they even have a key, same pattern as\nGET /api/agent-debate/status.\n\n2026-08-17 (task #4 follow-up, \"重有咩可以升級\" -- upgrade #4, live status\nwidget): extended from the original 3 keys (events/sentiment/debate) to\ncover all 7 public endpoints. Only sentiment and debate have a genuine\nbinary gate here -- their route handlers explicitly raise 503 if\nfinbert_available()/debate_available() is false (see intelligence_\nsentiment/intelligence_debate above). The other 5 (events, intel,\ntechnical, stress_test, regime_signal) never 503 by design -- on an\nupstream hiccup they soft-fail to 200 with an `error` field or null\nsub-fields instead (see the schema docs on intelligence-api.html). So\n`True` here means \"not gated behind an external reachability check\",\nnot a claimed uptime guarantee -- deliberately not fabricating a\nmonitoring signal that doesn't exist for endpoints that don't have one.","operationId":"intelligence_status_api_intelligence_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/intelligence/changelog":{"get":{"tags":["Intelligence API"],"summary":"Intelligence Changelog","description":"Public, unauthenticated. See INTELLIGENCE_CHANGELOG's docstring\ncomment above for why this is hand-maintained rather than generated.","operationId":"intelligence_changelog_api_intelligence_changelog_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/intelligence/roadmap":{"get":{"tags":["Intelligence API"],"summary":"Intelligence Roadmap","description":"Public, unauthenticated. See INTELLIGENCE_ROADMAP's docstring\ncomment above -- this is what backs the Pro plan's \"public roadmap\"\npromise on intelligence-api.html.","operationId":"intelligence_roadmap_api_intelligence_roadmap_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/intelligence/plan-visibility":{"get":{"tags":["Intelligence API"],"summary":"Intelligence Plan Visibility","description":"Public, unauthenticated -- intelligence-api.html calls this on load\nto decide which of the 3 pricing tier cards to render. Backed by the\nsame feature_flags table/admin.html toggle UI every other flag in this\napp already uses (api/admin.py's _DEFAULT_FLAGS: intel_plan_free_visible/\nintel_plan_pro_visible/intel_plan_enterprise_visible) -- the admin can\nhide any tier from the landing page without a redeploy, e.g. hiding\nEnterprise until there's a real reason to show it.","operationId":"intelligence_plan_visibility_api_intelligence_plan_visibility_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/intelligence/early-access":{"post":{"tags":["Intelligence API"],"summary":"Intelligence Early Access","description":"Public -- the landing page's \"Request Early Access\" form posts here.\nReuses api/feedback.py's existing `feedback` table (type=\n\"intelligence_early_access\") rather than a new bespoke table -- same\ninsert-then-notify-admin shape that module already has, just a\ndifferent `type` value so admin.html can list these separately (see\nGET /feedback/list's optional `type` filter).","operationId":"intelligence_early_access_api_intelligence_early_access_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EarlyAccessRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/intelligence/v1/signup":{"post":{"tags":["Intelligence API"],"summary":"Intelligence Self Serve Signup","description":"Public, unauthenticated. Issues a Free-tier key immediately and\nemails it -- never returned in the JSON response body, so it can't sit\nin browser devtools/network logs. Guarded by a disposable-email check\n(reusing services/disposable_email_domains.py, the same blocklist\nalready used for site registration) and a per-IP daily rate limit\n(services/api_key_service.check_self_serve_signup_rate) so this can't\nbe scripted into unlimited free-key generation.","operationId":"intelligence_self_serve_signup_api_intelligence_v1_signup_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FreeSignupRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/intelligence/v1/events":{"get":{"tags":["Intelligence API"],"summary":"Intelligence Events","description":"`ticker` accepts a single symbol, no ticker (all headlines), or a\ncomma-separated list up to _MAX_BATCH_TICKERS (e.g. \"AAPL,MSFT,TSLA\")\nfor watchlist-style queries -- roadmap item #3, 2026-08-17. No-ticker\nand single-ticker requests return the exact same shape as before this\nchange; a multi-ticker request merges/dedupes results across all\nrequested symbols and adds a `tickers` field to each event.","operationId":"intelligence_events_api_intelligence_v1_events_get","parameters":[{"name":"ticker","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ticker"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":20,"title":"Limit"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/intelligence/v1/sentiment":{"get":{"tags":["Intelligence API"],"summary":"Intelligence Sentiment","description":"`ticker` accepts a single symbol or a comma-separated list up to\n_MAX_BATCH_TICKERS (e.g. \"AAPL,MSFT,TSLA\") -- roadmap item #3,\n2026-08-17. A single ticker returns the exact same shape as before\nthis change. A comma-separated list returns `results_by_ticker` keyed\nby symbol instead of one flat `results` array -- averaging sentiment\nscores across unrelated tickers into a single number would be a\nmisleading aggregate, not a real one.","operationId":"intelligence_sentiment_api_intelligence_v1_sentiment_get","parameters":[{"name":"ticker","in":"query","required":true,"schema":{"type":"string","title":"Ticker"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":10,"title":"Limit"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/intelligence/v1/debate":{"get":{"tags":["Intelligence API"],"summary":"Intelligence Debate","description":"Wraps the existing 4-call Bull/Bear/Risk-Manager debate. This is the\nsingle most expensive endpoint in this router (see\nservices/agent_debate_service.py) -- weighted 5x in the quota counter,\nsame reasoning api/agent_debate.py already applies to logged-in users.","operationId":"intelligence_debate_api_intelligence_v1_debate_get","parameters":[{"name":"ticker","in":"query","required":true,"schema":{"type":"string","title":"Ticker"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/intelligence/v1/intel/latest":{"get":{"tags":["Intelligence API"],"summary":"Intelligence Latest","operationId":"intelligence_latest_api_intelligence_v1_intel_latest_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":5,"title":"Limit"}},{"name":"lang","in":"query","required":false,"schema":{"type":"string","default":"zh-HK","title":"Lang"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/intelligence/v1/intel/{ticker}":{"get":{"tags":["Intelligence API"],"summary":"Intelligence Ticker","operationId":"intelligence_ticker_api_intelligence_v1_intel__ticker__get","parameters":[{"name":"ticker","in":"path","required":true,"schema":{"type":"string","title":"Ticker"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":5,"title":"Limit"}},{"name":"lang","in":"query","required":false,"schema":{"type":"string","default":"zh-HK","title":"Lang"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/intelligence/v1/technical/{ticker}":{"get":{"tags":["Intelligence API"],"summary":"Intelligence Technical","description":"Confluence direction/confidence, trend, MACD, volume, chart patterns,\nand market-structure (BOS/CHOCH/liquidity-sweep/order-flow/volume-\nprofile/institutional-footprint) for one ticker -- everything\nai-analysis.html's dashboard shows, minus AI prose. `lang` reuses the\nsame site-wide per-language translation this endpoint's underlying\nfunction already does for the website (task #592's fix), so API\nconsumers get real localized labels too, not just en/zh.","operationId":"intelligence_technical_api_intelligence_v1_technical__ticker__get","parameters":[{"name":"ticker","in":"path","required":true,"schema":{"type":"string","title":"Ticker"}},{"name":"period","in":"query","required":false,"schema":{"type":"string","default":"6mo","title":"Period"}},{"name":"interval","in":"query","required":false,"schema":{"type":"string","default":"1d","title":"Interval"}},{"name":"lang","in":"query","required":false,"schema":{"type":"string","default":"en","title":"Lang"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/intelligence/v1/stress-test":{"post":{"tags":["Intelligence API"],"summary":"Intelligence Stress Test","description":"Real historical-bootstrap Monte Carlo (see services/monte_carlo_\nservice.py's module docstring for the honesty notes on method/\nlimitations -- returned verbatim in the `method`/`note` fields, never\nstripped out for API consumers). Same MAX_HORIZON_DAYS/MAX_N_SIMULATIONS\ncaps stress-lab.html's own callers get; POST (not GET) since this is\nthe heaviest-compute endpoint in this router after `debate`/`intel`.","operationId":"intelligence_stress_test_api_intelligence_v1_stress_test_post","parameters":[{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StressTestRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/intelligence/v1/forecast/{ticker}":{"get":{"tags":["Intelligence API"],"summary":"Intelligence Forecast","description":"Bear/Base/Bull fan-chart price path (10th/50th/90th percentile of a\nreal historical-return bootstrap, see services/probabilistic_forecast_\nservice.py's module docstring for the full honesty contract), plus an\nindependently-validated ML up-probability cross-check when one exists,\nplus a capital-flow/liquidity regime reading. Never fabricates a fixed\nbull/base/bear PROBABILITY split -- the returned band_note explains\nwhat the percentiles actually mean.","operationId":"intelligence_forecast_api_intelligence_v1_forecast__ticker__get","parameters":[{"name":"ticker","in":"path","required":true,"schema":{"type":"string","title":"Ticker"}},{"name":"horizon_days","in":"query","required":false,"schema":{"type":"integer","default":5,"title":"Horizon Days"}},{"name":"n_simulations","in":"query","required":false,"schema":{"type":"integer","title":"N Simulations"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/intelligence/v1/regime-signal/{ticker}":{"get":{"tags":["Intelligence API"],"summary":"Intelligence Regime Signal","description":"Current causal market regime for `ticker` (services/regime_router_\nservice.py's own causal-only classifier, see that module's docstring\nfor why it doesn't reuse the live Confluence/Regime Belief engines)\nplus whichever composed signal combo (services/formula_composer_\nservice.py) has historically performed best in that regime. If\n`regime` is omitted, the current regime is computed first and used\nfor the lookup -- the actual \"what should I use right now\" answer.","operationId":"intelligence_regime_signal_api_intelligence_v1_regime_signal__ticker__get","parameters":[{"name":"ticker","in":"path","required":true,"schema":{"type":"string","title":"Ticker"}},{"name":"regime","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Regime"}},{"name":"min_trades","in":"query","required":false,"schema":{"type":"integer","default":5,"title":"Min Trades"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/intelligence/v1/insider/{ticker}":{"get":{"tags":["Intelligence API"],"summary":"Intelligence Insider","description":"SEC Form 4 insider-trading transactions for `ticker` (services/\nsec_form4_service.py -- non-derivative open-market transactions from\nthe most recent filings cross-indexed under the issuer's own CIK via\nEDGAR's browse-edgar feed, not just what the issuer itself filed).\n24h server-side cached, so repeat calls for the same ticker within a\nday don't re-hit EDGAR.","operationId":"intelligence_insider_api_intelligence_v1_insider__ticker__get","parameters":[{"name":"ticker","in":"path","required":true,"schema":{"type":"string","title":"Ticker"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/intelligence/v1/company-network/{ticker}":{"get":{"tags":["Intelligence API"],"summary":"Intelligence Company Network","description":"Company relationship intelligence for `ticker` (services/\ncompany_network_service.py -- Phase 1 of the \"Company Intelligence\"\ndirection, 2026-08-29). Combines four already-collected Data Factory\nsignals into one view: SEC 13F institutional ownership + conviction\nscore, SEC 13D/13G activist filings, SEC Form 4 insider trading, and\nCFTC COT futures positioning if the ticker has a directly linked\ncontract. Zero new data sources -- pure re-packaging of endpoints\nalready live elsewhere in this API (/v1/insider is a subset of this\nresponse's insider_trading field).\n\nAlso returns `what_changed`: a day-over-day diff of this ticker's\nnetwork_summary numbers against the prior day's snapshot. Pure\nmechanical diff, no AI -- `available: False` the first time a ticker\nis queried on a given day (no prior snapshot to diff against yet)\nrather than fabricating a baseline.","operationId":"intelligence_company_network_api_intelligence_v1_company_network__ticker__get","parameters":[{"name":"ticker","in":"path","required":true,"schema":{"type":"string","title":"Ticker"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/intelligence/v1/short-interest/{ticker}":{"get":{"tags":["Intelligence API"],"summary":"Intelligence Short Interest","description":"FINRA bi-weekly equity short-interest for `ticker` (services/\nfinra_short_interest_service.py -- the genuinely free public\nsettlement-date flat file, distinct from FINRA's member-firm-gated\nQuery API). `available: false` with no reported short position is a\nreal, honest \"not currently shorted at reportable levels\" result, not\nan error.","operationId":"intelligence_short_interest_api_intelligence_v1_short_interest__ticker__get","parameters":[{"name":"ticker","in":"path","required":true,"schema":{"type":"string","title":"Ticker"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/intelligence/v1/energy/{ticker}":{"get":{"tags":["Intelligence API"],"summary":"Intelligence Energy","description":"EIA energy-fundamentals context for `ticker` (services/eia_energy_\nservice.py -- WTI crude spot, Henry Hub nat-gas spot, and Lower-48\nworking nat-gas storage). Only populated for tickers with a real\ncrude/nat-gas linkage (currently USO/UNG, see that module's\n_TICKER_TO_SERIES) -- any other ticker returns `data: null`, never a\nfabricated reading for an unrelated symbol.","operationId":"intelligence_energy_api_intelligence_v1_energy__ticker__get","parameters":[{"name":"ticker","in":"path","required":true,"schema":{"type":"string","title":"Ticker"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/intelligence/v1/real-estate/{ticker}":{"get":{"tags":["Intelligence API"],"summary":"Intelligence Real Estate","description":"FRED US housing-market context for `ticker` (services/real_estate_\nservice.py -- 30-year mortgage rate, Case-Shiller home price index,\nhousing starts, existing home sales). Only populated for tickers with\na real housing-market linkage (homebuilders, REITs, a mortgage\noriginator, housing-sector ETFs -- see that module's _TICKER_TO_NAME)\n-- any other ticker returns `data: null`, never a fabricated reading\nfor an unrelated symbol.","operationId":"intelligence_real_estate_api_intelligence_v1_real_estate__ticker__get","parameters":[{"name":"ticker","in":"path","required":true,"schema":{"type":"string","title":"Ticker"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/intelligence/v1/supply-chain/{ticker}":{"get":{"tags":["Intelligence API"],"summary":"Intelligence Supply Chain","description":"FRED US manufacturing/supply-chain context for `ticker` (services/\nsupply_chain_service.py -- inventory/sales ratio, manufacturing new\norders, durable goods orders, industrial production, manufacturing\nemployment). Only populated for tickers with a real freight/logistics\nlinkage (carriers, railroads, transportation ETFs -- see that\nmodule's _TICKER_TO_NAME) -- any other ticker returns `data: null`,\nnever a fabricated reading for an unrelated symbol.","operationId":"intelligence_supply_chain_api_intelligence_v1_supply_chain__ticker__get","parameters":[{"name":"ticker","in":"path","required":true,"schema":{"type":"string","title":"Ticker"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/intelligence/v1/consumer-demand/{ticker}":{"get":{"tags":["Intelligence API"],"summary":"Intelligence Consumer Demand","description":"FRED US consumer-spending context for `ticker` (services/\nconsumer_demand_service.py -- retail sales, personal consumption\nexpenditures, durable goods consumption). NOT Google Trends search-\ninterest data -- see that module's docstring for why (no officially\nlicensed, commercial-use-safe search-trends API exists). Only\npopulated for tickers with a real consumer-spending linkage (large\nretailers, e-commerce, consumer-discretionary ETFs -- see that\nmodule's _TICKER_TO_NAME) -- any other ticker returns `data: null`,\nnever a fabricated reading for an unrelated symbol.","operationId":"intelligence_consumer_demand_api_intelligence_v1_consumer_demand__ticker__get","parameters":[{"name":"ticker","in":"path","required":true,"schema":{"type":"string","title":"Ticker"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/intelligence/v1/opportunity-radar":{"get":{"tags":["Intelligence API"],"summary":"Intelligence Opportunity Radar","description":"Structural macro-mismatch read across US real estate, supply\nchain/manufacturing, and consumer demand (services/\nopportunity_radar_service.py), plus a US macro backdrop. Not\nticker-specific -- one market-wide snapshot per call, same shape as\n/v1/vix-term-structure.\n\nDeliberately NOT a fabricated single \"Opportunity Score\" -- each\nindicator reports its own real % change over its trailing\nobservation window (exact dates included), and each industry\nreports a plain count of how many of its indicators are improving/\nworsening/flat. See that module's docstring for the full honesty\ncontract.","operationId":"intelligence_opportunity_radar_api_intelligence_v1_opportunity_radar_get","parameters":[{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/intelligence/v1/exchange/{ticker}":{"get":{"tags":["Intelligence API"],"summary":"Intelligence Exchange","description":"Same crypto ticker's live 24h stats from two real spot exchanges --\nBinance (services/crypto_exchange_service.py) and Coinbase (services/\ncoinbase_exchange_service.py) -- side by side. Only populated for the\ntracked crypto tickers both services cover; any other ticker returns\n`data: null`, never a fabricated cross-exchange reading.","operationId":"intelligence_exchange_api_intelligence_v1_exchange__ticker__get","parameters":[{"name":"ticker","in":"path","required":true,"schema":{"type":"string","title":"Ticker"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/intelligence/v1/fundamentals/{ticker}":{"get":{"tags":["Intelligence API"],"summary":"Intelligence Fundamentals","description":"Latest annual (10-K) financial-statement facts for `ticker`\n(services/sec_xbrl_service.py -- revenue, net income, diluted EPS,\ntotal assets/liabilities, operating cash flow, straight from SEC\nXBRL Company Facts). The first real fundamentals data in the\nIntelligence API -- every other endpoint here is positioning,\nevent-driven activity, or macro/commodity context, not the\ncompany's own reported financial statements. 24h server-side\ncached.","operationId":"intelligence_fundamentals_api_intelligence_v1_fundamentals__ticker__get","parameters":[{"name":"ticker","in":"path","required":true,"schema":{"type":"string","title":"Ticker"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/intelligence/v1/fundamentals/{ticker}/as-of":{"get":{"tags":["Intelligence API"],"summary":"Intelligence Fundamentals As Of","description":"Point-in-time-correct fundamentals: what was ACTUALLY KNOWN for\n`ticker` as of `as_of_date` (YYYY-MM-DD), not the current/latest\nvalue (services/point_in_time_store.py, piloted on\nsec_xbrl_service.py -- 2026-09-17, direct response to backtesting\nlook-ahead-bias feedback on r/quant). Each returned concept reflects\nonly vintages that were already filed and past a small availability\nbuffer by that date; a restated figure that only became known later\nis never leaked back into an earlier as_of_date.\n\nRequires the ticker to have been ingested at least once via the\nregular /fundamentals/{ticker} endpoint or the daily SEC XBRL\nrefresh job first -- this endpoint only reads accumulated\npoint-in-time history, it never makes a live SEC fetch (a fetch\ntoday cannot tell you what was knowable on a past date).","operationId":"intelligence_fundamentals_as_of_api_intelligence_v1_fundamentals__ticker__as_of_get","parameters":[{"name":"ticker","in":"path","required":true,"schema":{"type":"string","title":"Ticker"}},{"name":"as_of_date","in":"query","required":true,"schema":{"type":"string","title":"As Of Date"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/intelligence/v1/evidence-scorecard/{ticker}":{"get":{"tags":["Intelligence API"],"summary":"Intelligence Evidence Scorecard","description":"2026-09-29: multi-dimensional support/oppose/neutral evidence\nscorecard (services/evidence_scorecard_service.py) -- up to 11\nindependent real data sources (technical confluence, fundamentals,\n13F/13D-G/Form-4, VIX term structure, CFTC COT, capital flow, the\nshipping proxy, news sentiment, and the backtested direction-\nprobability model) checked and tallied, with disagreement between\nthem explicitly flagged rather than averaged away. `asset_class` is\none of Stocks/Futures/Crypto/Forex/Commodities/Indices (see\nservices/trending_stocks_service.py's ASSET_CLASSES) -- the four\nSEC-filing-based dimensions only apply to Stocks (that data doesn't\nexist for other asset classes; see the service module's docstring\nfor why a smaller dimension count there is honest, not a gap).","operationId":"intelligence_evidence_scorecard_api_intelligence_v1_evidence_scorecard__ticker__get","parameters":[{"name":"ticker","in":"path","required":true,"schema":{"type":"string","title":"Ticker"}},{"name":"asset_class","in":"query","required":false,"schema":{"type":"string","default":"Stocks","title":"Asset Class"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/intelligence/v1/impact/{ticker}":{"get":{"tags":["Intelligence API"],"summary":"Intelligence Impact","description":"2026-09-30 (AJ: monetization gap-analysis session -- asked \"重有咩\n有差距的可收費\", identified relationship/impact data as the one\ngenuinely missing, genuinely buildable, genuinely differentiated\nlayer vs. generic financial data APIs that only sell price +\nfundamentals): given a ticker, who is upstream (suppliers),\ndownstream (customers), same-sector (peers), and directly competing\n(competitors) -- plus market-wide capital flow and this ticker's\nCFTC futures-positioning context if applicable, composed from\nservices/relationship_graph_service.py, services/capital_flow_\nengine.py, and services/cftc_cot_service.py (all pre-existing, real\ndata sources -- nothing new fetched here, just a new composition).\n\nSupplier/customer/competitor relationships are a hand-curated\nSTARTING set (see relationship_graph_service.py's module docstring\nfor sourcing and scope) -- a ticker outside that set returns an\nhonest empty list for those three fields (never a guess), while\nsector_peers still populates from the existing sector-tagged US\nbasket for any US large-cap ticker.","operationId":"intelligence_impact_api_intelligence_v1_impact__ticker__get","parameters":[{"name":"ticker","in":"path","required":true,"schema":{"type":"string","title":"Ticker"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/intelligence/v1/hypotheses":{"post":{"tags":["Intelligence API"],"summary":"Create Hypothesis Endpoint","operationId":"create_hypothesis_endpoint_api_intelligence_v1_hypotheses_post","parameters":[{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateHypothesisRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["Intelligence API"],"summary":"List Hypotheses Endpoint","operationId":"list_hypotheses_endpoint_api_intelligence_v1_hypotheses_get","parameters":[{"name":"ticker","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ticker"}},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":50,"title":"Limit"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/intelligence/v1/hypotheses/{hypothesis_id}":{"get":{"tags":["Intelligence API"],"summary":"Get Hypothesis Endpoint","operationId":"get_hypothesis_endpoint_api_intelligence_v1_hypotheses__hypothesis_id__get","parameters":[{"name":"hypothesis_id","in":"path","required":true,"schema":{"type":"integer","title":"Hypothesis Id"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"patch":{"tags":["Intelligence API"],"summary":"Update Hypothesis Endpoint","operationId":"update_hypothesis_endpoint_api_intelligence_v1_hypotheses__hypothesis_id__patch","parameters":[{"name":"hypothesis_id","in":"path","required":true,"schema":{"type":"integer","title":"Hypothesis Id"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateHypothesisRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/intelligence/v1/hypotheses/{hypothesis_id}/evidence":{"post":{"tags":["Intelligence API"],"summary":"Add Hypothesis Evidence Endpoint","operationId":"add_hypothesis_evidence_endpoint_api_intelligence_v1_hypotheses__hypothesis_id__evidence_post","parameters":[{"name":"hypothesis_id","in":"path","required":true,"schema":{"type":"integer","title":"Hypothesis Id"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddEvidenceRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/intelligence/v1/hypotheses/{hypothesis_id}/replay":{"get":{"tags":["Intelligence API"],"summary":"Replay Hypothesis Endpoint","description":"2026-10-02 (AJ: \"起啦\" -- follow-up to a \"what's the single\nhighest-leverage next step\" discussion): the reproducibility/\nverification extension to Research Memory. Diffs the real-world\ncomposite snapshot (price, confluence direction, headline count,\ninsider activity, short interest) frozen when this hypothesis was\ncreated against a fresh one computed right now -- \"what's actually\nchanged since I made this call\", not another AI opinion about\nwhether the hypothesis still holds. Costs more quota than a plain\nhypothesis CRUD call (see intelligence_quota_service.ENDPOINT_WEIGHT's\n\"hypothesis_replay\" entry) since it does a real live fetch, same as\n/v1/technical or /v1/regime-signal.","operationId":"replay_hypothesis_endpoint_api_intelligence_v1_hypotheses__hypothesis_id__replay_get","parameters":[{"name":"hypothesis_id","in":"path","required":true,"schema":{"type":"integer","title":"Hypothesis Id"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/intelligence/v1/vix-term-structure":{"get":{"tags":["Intelligence API"],"summary":"Intelligence Vix Term Structure","description":"CBOE VIX9D/VIX/VIX3M/VIX6M term structure (services/\ncboe_vix_service.py) -- the options market's own forward-looking\nvolatility curve, plus a contango/backwardation regime read. Not\nticker-specific -- one market-wide snapshot per call.","operationId":"intelligence_vix_term_structure_api_intelligence_v1_vix_term_structure_get","parameters":[{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/intelligence/v1/bank-health/{ticker}":{"get":{"tags":["Intelligence API"],"summary":"Intelligence Bank Health","description":"FDIC Call Report health (ROA/ROE/assets/equity) for `ticker`'s\nlead bank subsidiary (services/fdic_banking_service.py). Only\npopulated for the handful of major publicly-traded bank holding\ncompanies this module explicitly maps to a real FDIC certificate\nnumber (see _TICKER_TO_CERT) -- any other ticker returns `data:\nnull`, never a guessed match. Reflects the regulated bank\nsubsidiary's own Call Report, not consolidated holding-company\nGAAP financials.","operationId":"intelligence_bank_health_api_intelligence_v1_bank_health__ticker__get","parameters":[{"name":"ticker","in":"path","required":true,"schema":{"type":"string","title":"Ticker"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/intelligence/v1/agriculture/{ticker}":{"get":{"tags":["Intelligence API"],"summary":"Intelligence Agriculture","description":"USDA agricultural-commodity price-received context for `ticker`\n(services/usda_agriculture_service.py -- corn/wheat/soybean, pairs\nwith CORN/WEAT/SOYB the same way /v1/energy pairs with USO/UNG).\nOnly populated for tickers with a real commodity linkage; any other\nticker returns `data: null`.","operationId":"intelligence_agriculture_api_intelligence_v1_agriculture__ticker__get","parameters":[{"name":"ticker","in":"path","required":true,"schema":{"type":"string","title":"Ticker"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/intelligence/v1/consumer-safety/{ticker}":{"get":{"tags":["Intelligence API"],"summary":"Intelligence Consumer Safety","description":"FDA-regulated recall and adverse-event context for `ticker`\n(services/openfda_service.py) -- food/drug/device enforcement\n(recalls) plus food-specific consumer adverse-event reports (CAERS),\ntrailing 12 months. No API key required on openFDA's side (a first\nfor this codebase's Data Factory collectors -- see that module's\ndocstring). Only populated for tickers with a real keyword mapping;\nany other ticker returns `data: null`.","operationId":"intelligence_consumer_safety_api_intelligence_v1_consumer_safety__ticker__get","parameters":[{"name":"ticker","in":"path","required":true,"schema":{"type":"string","title":"Ticker"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/intelligence/v1/product-recalls/{ticker}":{"get":{"tags":["Intelligence API"],"summary":"Intelligence Product Recalls","description":"CPSC consumer product recall context for `ticker`\n(services/cpsc_service.py) -- general consumer products (toys,\nappliances, furniture, electronics, tools), trailing 12 months,\nbroader scope than /v1/consumer-safety's FDA-regulated categories.\nNo API key required on CPSC's side. Only populated for tickers with\na real manufacturer-keyword mapping; any other ticker returns\n`data: null`. Note: CPSC's own live backend has shown intermittent\nreliability issues as of this endpoint's ship date (see that\nmodule's docstring) -- a `fetch_error: true` in the response body\n(not a 5xx) is how that's surfaced honestly rather than a fabricated\n\"zero recalls\".","operationId":"intelligence_product_recalls_api_intelligence_v1_product_recalls__ticker__get","parameters":[{"name":"ticker","in":"path","required":true,"schema":{"type":"string","title":"Ticker"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/intelligence/v1/recall-search":{"get":{"tags":["Intelligence API"],"summary":"Intelligence Recall Search","description":"2026-09-14 (AJ: \"開條賺錢新路\" -- Recall Alert API for e-commerce\nsellers): free-text CPSC consumer-product recall search by brand or\nproduct name (services/cpsc_service.py's search_recalls_by_keyword())\n-- the sibling /v1/product-recalls/{ticker} above only answers \"does\nthis PUBLIC COMPANY have recalls\", keyed off a small pre-mapped list\nof ~24 large tickers; an Amazon/Shopify seller's own brand is almost\nnever one of those, so this takes any free-text keyword instead. Same\nlive CPSC source, same honesty posture (fetch_error:true in-body on a\ngenuine upstream failure, never a fabricated zero-recalls reading).","operationId":"intelligence_recall_search_api_intelligence_v1_recall_search_get","parameters":[{"name":"keyword","in":"query","required":true,"schema":{"type":"string","title":"Keyword"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/intelligence/v1/webhooks/subscribe":{"post":{"tags":["Intelligence API"],"summary":"Intelligence Webhooks Subscribe","description":"Pro-tier feature (2026-08-28, AJ: \"重有咩賺錢位\" -> Webhook Pro專屬):\npush notifications instead of polling, for real Data Factory\nevents -- see services/webhook_service.py's VALID_EVENT_TYPES for\nthe exact list. Most are backed by a daily scheduled job that already\nran for another reason (honest same-cadence delivery, never a\nfabricated \"real-time\" claim); recall_match (2026-09-14) is the first\nexception -- see backend/main.py's recall_alert_scan job, added\nspecifically to back this event type. To watch a brand/product\nkeyword for new recalls: event_type=\"recall_match\", ticker=\"<your\nkeyword>\" (the `ticker` field doubles as the free-text keyword here --\nsee that event type's comment in webhook_service.py). Does NOT spend\nquota -- this is a management action, not a data read.","operationId":"intelligence_webhooks_subscribe_api_intelligence_v1_webhooks_subscribe_post","parameters":[{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookSubscribeRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/intelligence/v1/webhooks":{"get":{"tags":["Intelligence API"],"summary":"Intelligence Webhooks List","description":"Lists every webhook subscription owned by the caller's own API\nkey -- never another key's. Read-only, no quota spend.","operationId":"intelligence_webhooks_list_api_intelligence_v1_webhooks_get","parameters":[{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/intelligence/v1/webhooks/{webhook_id}":{"delete":{"tags":["Intelligence API"],"summary":"Intelligence Webhooks Unsubscribe","description":"Deletes a webhook subscription -- only if it belongs to the\ncaller's own API key (services/webhook_service.py's unsubscribe()\nchecks both id AND api_key in the same DELETE, so guessing another\nkey's id can never delete their subscription).","operationId":"intelligence_webhooks_unsubscribe_api_intelligence_v1_webhooks__webhook_id__delete","parameters":[{"name":"webhook_id","in":"path","required":true,"schema":{"type":"integer","title":"Webhook Id"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/intelligence/v1/world/market-map":{"get":{"tags":["Intelligence API"],"summary":"World Market Map","description":"Global market map: for each region, macro indicators (with source\nattribution -- world_bank/fred/ecb) + filtered headlines + FinBERT\nsentiment, plus a top-level global headlines feed. `regions` is an\noptional comma-separated subset (e.g. \"us,hk,china\"); omit for all 10.\nSee services/world_engine_service.list_regions() / GET\n/intelligence/v1/world/regions for valid keys.","operationId":"world_market_map_api_intelligence_v1_world_market_map_get","parameters":[{"name":"regions","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Regions"}},{"name":"news_limit","in":"query","required":false,"schema":{"type":"integer","default":6,"title":"News Limit"}},{"name":"include_sentiment","in":"query","required":false,"schema":{"type":"boolean","default":true,"title":"Include Sentiment"}},{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/intelligence/v1/world/regions":{"get":{"tags":["Intelligence API"],"summary":"World Regions","description":"No-cost lookup of valid region keys for GET /world/market-map --\nnot quota-weighted since it's static metadata, not a data fetch.","operationId":"world_regions_api_intelligence_v1_world_regions_get","parameters":[{"name":"X-API-Key","in":"header","required":false,"schema":{"type":"string","title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/intelligence/v1/my-key":{"get":{"tags":["Intelligence API"],"summary":"My Key Status","operationId":"my_key_status_api_intelligence_v1_my_key_get","parameters":[{"name":"token","in":"query","required":false,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/intelligence/v1/my-key/regenerate":{"post":{"tags":["Intelligence API"],"summary":"My Key Regenerate","operationId":"my_key_regenerate_api_intelligence_v1_my_key_regenerate_post","parameters":[{"name":"token","in":"query","required":false,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/intelligence/admin/issue-key":{"post":{"tags":["Intelligence API"],"summary":"Issue Key","operationId":"issue_key_api_intelligence_admin_issue_key_post","parameters":[{"name":"email","in":"query","required":true,"schema":{"type":"string","title":"Email"}},{"name":"tier","in":"query","required":false,"schema":{"type":"string","default":"free","title":"Tier"}},{"name":"token","in":"query","required":false,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/intelligence/admin/keys":{"get":{"tags":["Intelligence API"],"summary":"List Keys","operationId":"list_keys_api_intelligence_admin_keys_get","parameters":[{"name":"email","in":"query","required":true,"schema":{"type":"string","title":"Email"}},{"name":"token","in":"query","required":false,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/intelligence/admin/revoke-key":{"post":{"tags":["Intelligence API"],"summary":"Revoke Key","operationId":"revoke_key_api_intelligence_admin_revoke_key_post","parameters":[{"name":"key_id","in":"query","required":true,"schema":{"type":"integer","title":"Key Id"}},{"name":"token","in":"query","required":false,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/mcp":{"get":{"tags":["MCP Server"],"summary":"Mcp Info","description":"Human-facing info page for anyone who opens the MCP URL directly in a\nbrowser instead of connecting an MCP client to it.\n\n2026-09-15 (AJ: MCP Marketplace listing follow-up): added a `pricing`\nfield. This server was already a \"remote hosted\" MCP server per MCP\nMarketplace's own two-model taxonomy (auth/quota handled server-side,\nsame X-API-Key + tier system as the REST API -- no separate MCP-only\ngating needed), so listing it there needs no new access-control code,\njust a clear pricing summary for anyone landing on this URL directly\nfrom the marketplace listing. Numbers must stay in sync with\nservices/intelligence_quota_service.py's TIER_LIMITS and intelligence-\napi.html's plan cards -- same \"recommendation, not hardcoded truth\nelsewhere\" caveat as that module's own docstring.\n\n2026-09-15 (follow-up, same day): a valid mcp-marketplace.io license\nkey (see _has_valid_marketplace_license() above) is now real -- it\nupgrades a free XFINLAB key's quota to Pro for every tool call it's\npresented on. This was NOT true when the listing was first submitted\n(it was deliberately left on the \"Free\" pricing tier for exactly this\nreason -- see git history). Once a \"Monthly\" price is set on the\nlisting to match, this text should be updated to say so explicitly.","operationId":"mcp_info_api_mcp_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"post":{"tags":["MCP Server"],"summary":"Mcp Endpoint","operationId":"mcp_endpoint_api_mcp_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/free-tools-demo/vix-term-structure":{"get":{"tags":["Free Tools Demo"],"summary":"Demo Vix Term Structure","description":"Global, no ticker needed -- backs free-market-regime.html. Same\nservices.cboe_vix_service.get_snapshot() the paid\n/intelligence/v1/vix-term-structure endpoint calls; nothing trimmed\nhere since this one's already a single global snapshot, not\nper-ticker depth.","operationId":"demo_vix_term_structure_api_free_tools_demo_vix_term_structure_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/free-tools-demo/sentiment":{"get":{"tags":["Free Tools Demo"],"summary":"Demo Sentiment","description":"Backs free-stock-sentiment-api.html. Same FinBERT pipeline as the\npaid /intelligence/v1/sentiment endpoint (rss_news_service +\nfinbert_sentiment_service), teaser-capped to 5 headlines instead of the\npaid endpoint's up-to-25.","operationId":"demo_sentiment_api_free_tools_demo_sentiment_get","parameters":[{"name":"ticker","in":"query","required":false,"schema":{"type":"string","default":"","title":"Ticker"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/free-tools-demo/technical":{"get":{"tags":["Free Tools Demo"],"summary":"Demo Technical","description":"Backs free-technical-analysis-api.html. Same\nservices.technical_analysis_service.get_technical_analysis() the paid\n/intelligence/v1/technical/{ticker} endpoint calls, trimmed to a small\nteaser subset (trend/RSI/confluence/support-resistance) -- deliberately\ndropping market_structure, chart patterns, decision_levels and the raw\nOHLC series, which stay Intelligence API selling points, not free-demo\ncontent.","operationId":"demo_technical_api_free_tools_demo_technical_get","parameters":[{"name":"ticker","in":"query","required":false,"schema":{"type":"string","default":"","title":"Ticker"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/free-tools-demo/real-estate":{"get":{"tags":["Free Tools Demo"],"summary":"Demo Real Estate","description":"Backs free-real-estate-api.html. Same services.real_estate_service\n.get_real_estate_context_for_ticker() the paid /intelligence/v1/\nreal-estate/{ticker} endpoint calls -- no trimming, this is already a\nsmall scoped response. Only whitelisted housing-linked tickers\nreturn real data; see that module's _TICKER_TO_NAME.","operationId":"demo_real_estate_api_free_tools_demo_real_estate_get","parameters":[{"name":"ticker","in":"query","required":false,"schema":{"type":"string","default":"","title":"Ticker"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/free-tools-demo/supply-chain":{"get":{"tags":["Free Tools Demo"],"summary":"Demo Supply Chain","description":"Backs free-supply-chain-api.html. Same services.supply_chain_service\n.get_supply_chain_context_for_ticker() the paid /intelligence/v1/\nsupply-chain/{ticker} endpoint calls. Only whitelisted freight/\nlogistics tickers return real data; see that module's\n_TICKER_TO_NAME.","operationId":"demo_supply_chain_api_free_tools_demo_supply_chain_get","parameters":[{"name":"ticker","in":"query","required":false,"schema":{"type":"string","default":"","title":"Ticker"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/free-tools-demo/consumer-demand":{"get":{"tags":["Free Tools Demo"],"summary":"Demo Consumer Demand","description":"Backs free-consumer-demand-api.html. Same\nservices.consumer_demand_service.get_consumer_demand_context_for_\nticker() the paid /intelligence/v1/consumer-demand/{ticker} endpoint\ncalls. Only whitelisted retail/consumer tickers return real data;\nsee that module's _TICKER_TO_NAME.","operationId":"demo_consumer_demand_api_free_tools_demo_consumer_demand_get","parameters":[{"name":"ticker","in":"query","required":false,"schema":{"type":"string","default":"","title":"Ticker"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/free-tools-demo/opportunity-radar":{"get":{"tags":["Free Tools Demo"],"summary":"Demo Opportunity Radar","description":"Backs opportunity-radar.html. Same services.opportunity_radar_\nservice.get_opportunity_radar() the paid /intelligence/v1/\nopportunity-radar endpoint calls -- global, no ticker, no trimming\n(this is already the full honest computation, not a large payload\nwith selling-point fields to hold back).","operationId":"demo_opportunity_radar_api_free_tools_demo_opportunity_radar_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/free-tools-demo/consumer-safety":{"get":{"tags":["Free Tools Demo"],"summary":"Demo Consumer Safety","description":"Backs a future free-consumer-safety-api.html. Same\nservices.openfda_service.get_consumer_safety_context_for_ticker()\nthe paid /intelligence/v1/consumer-safety/{ticker} endpoint calls.\nOnly whitelisted food/drug/device-linked tickers return real data;\nsee that module's _TICKER_TO_KEYWORDS.","operationId":"demo_consumer_safety_api_free_tools_demo_consumer_safety_get","parameters":[{"name":"ticker","in":"query","required":false,"schema":{"type":"string","default":"","title":"Ticker"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/free-tools-demo/product-recalls":{"get":{"tags":["Free Tools Demo"],"summary":"Demo Product Recalls","description":"Backs a future free-product-recalls-api.html. Same\nservices.cpsc_service.get_recall_context_for_ticker() the paid\n/intelligence/v1/product-recalls/{ticker} endpoint calls. Only\nwhitelisted manufacturer-linked tickers return real data; see that\nmodule's _TICKER_TO_KEYWORDS.","operationId":"demo_product_recalls_api_free_tools_demo_product_recalls_get","parameters":[{"name":"ticker","in":"query","required":false,"schema":{"type":"string","default":"","title":"Ticker"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/free-tools-demo/trust-status":{"get":{"tags":["Free Tools Demo"],"summary":"Demo Trust Status","description":"Backs trust.html -- 2026-08-31 (AJ: \"零捏造信任賣點\", the pick out of\nthe market-pain-point research batch: 2026's AI-hallucination-in-\nfinance trust crisis -- $2.3B in Q1 2026 trading losses tied to\nAI-misstated earnings, FINRA/FCA now flagging hallucinations as a\ncompliance risk -- makes \"every number is either real or an honest\n'unavailable', never guessed\" a genuine market differentiator worth\nproving in public, not just claiming).\n\nExposes services.data_source_registry.list_sources() -- the same\ntable admin.html's Data Factory panel reads -- but PUBLICLY and\nread-only, whitelisting only non-sensitive columns. Deliberately\nstrips last_error/last_error_at/last_error_notified_at: raw exception\ntext can leak internal details (stack fragments, upstream account/\nquota specifics) that have no business being public, even though the\nwhole point of this page is transparency about which sources are\nlive vs down. \"This source failed recently\" is the honest, safe\nsignal; the raw error string is not.","operationId":"demo_trust_status_api_free_tools_demo_trust_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/research-card/{ticker}":{"get":{"tags":["Research Card"],"summary":"Research Card","operationId":"research_card_api_research_card__ticker__get","parameters":[{"name":"ticker","in":"path","required":true,"schema":{"type":"string","title":"Ticker"}},{"name":"lang","in":"query","required":false,"schema":{"type":"string","default":"en","title":"Lang"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/broker-affiliates":{"get":{"tags":["Broker Affiliates"],"summary":"Broker Affiliates","description":"Public, unauthenticated -- returns [] until at least one broker in\nservices/broker_affiliate_config.py has a real affiliate_url set.\nIntentionally NOT gated behind login/plan tier: this is a neutral\ninformational panel, not a premium feature.","operationId":"broker_affiliates_api_broker_affiliates_get","parameters":[{"name":"region","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Region"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/historical-analog/{symbol}":{"get":{"tags":["Historical Analog"],"summary":"Historical Analog","description":"Honest 'what actually happened last time this ticker was in a\nsimilar trend+volatility regime' read -- see services/\nhistorical_analog_service.py's docstring for why this replaces a\nfabricated macro-shock-slider design.\n\n2026-07-30 fix (\"做咩又有不同語言 全網跟進\"): this endpoint used to have\nno lang param at all, so every string it returned (error messages,\nregime_label, methodology sentence) was hardcoded Cantonese regardless\nof the site's selected UI language. ai-analysis.html now passes\n?lang=<I18N.currentLang>; find_analogs() uses it to translate via\nservices/i18n.py (falls back to the old Cantonese behavior when no\nlang is supplied, so any other caller is unaffected).","operationId":"historical_analog_api_historical_analog__symbol__get","parameters":[{"name":"symbol","in":"path","required":true,"schema":{"type":"string","title":"Symbol"}},{"name":"forward_days","in":"query","required":false,"schema":{"type":"integer","default":10,"title":"Forward Days"}},{"name":"period","in":"query","required":false,"schema":{"type":"string","default":"2y","title":"Period"}},{"name":"lang","in":"query","required":false,"schema":{"type":"string","title":"Lang"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/captcha/challenge":{"get":{"tags":["Captcha"],"summary":"Captcha Challenge","operationId":"captcha_challenge_api_captcha_challenge_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/captcha/verify":{"post":{"tags":["Captcha"],"summary":"Captcha Verify","operationId":"captcha_verify_api_captcha_verify_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CaptchaVerifyRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/auth/google":{"post":{"tags":["Social Login"],"summary":"Google Login","operationId":"google_login_api_auth_google_post","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Body"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/auth/line/start":{"get":{"tags":["Social Login"],"summary":"Line Login Start","operationId":"line_login_start_api_auth_line_start_get","parameters":[{"name":"state","in":"query","required":false,"schema":{"type":"string","default":"","title":"State"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/auth/line/callback":{"get":{"tags":["Social Login"],"summary":"Line Login Callback","operationId":"line_login_callback_api_auth_line_callback_get","parameters":[{"name":"code","in":"query","required":false,"schema":{"type":"string","default":"","title":"Code"}},{"name":"state","in":"query","required":false,"schema":{"type":"string","default":"","title":"State"}},{"name":"error","in":"query","required":false,"schema":{"type":"string","default":"","title":"Error"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/auth/whatsapp/send-otp":{"post":{"tags":["WhatsApp OTP"],"summary":"Send Whatsapp Otp","operationId":"send_whatsapp_otp_api_auth_whatsapp_send_otp_post","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Body"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/auth/whatsapp/verify-otp":{"post":{"tags":["WhatsApp OTP"],"summary":"Verify Whatsapp Otp","operationId":"verify_whatsapp_otp_api_auth_whatsapp_verify_otp_post","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Body"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/telegram/webhook":{"post":{"tags":["Telegram Webhook"],"summary":"Telegram Webhook","operationId":"telegram_webhook_api_telegram_webhook_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/paddle/status":{"get":{"tags":["Paddle Webhook"],"summary":"Paddle Status","description":"Public, no secrets -- mirrors /stripe/status so pricing.html can\nshow a real checkout button only for tiers AJ has actually configured\nin both PADDLE_API_KEY and that tier's price ID env var.","operationId":"paddle_status_api_paddle_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/paddle/create-checkout-session":{"post":{"tags":["Paddle Webhook"],"summary":"Create Checkout Session","description":"Creates a real Paddle transaction via the REST API and returns its\nhosted checkout.url -- same \"create server-side, redirect the browser\"\nshape as api/webhooks_stripe.py's endpoint of the same name, so\npricing.html's frontend code can treat both providers identically.","operationId":"create_checkout_session_api_paddle_create_checkout_session_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/webhooks/paddle":{"post":{"tags":["Paddle Webhook"],"summary":"Paddle Webhook","operationId":"paddle_webhook_api_webhooks_paddle_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/stripe/status":{"get":{"tags":["Stripe Payments"],"summary":"Stripe Status","description":"Public, no secrets -- lets pricing.html know which plan/cycle\ncombos are actually live yet, so it can show a real checkout button\nonly for tiers AJ has finished configuring, and fall back to the old\n'coming soon' message for the rest instead of a confusing 503.","operationId":"stripe_status_api_stripe_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/stripe/create-checkout-session":{"post":{"tags":["Stripe Payments"],"summary":"Create Checkout Session","operationId":"create_checkout_session_api_stripe_create_checkout_session_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/stripe/create-addon-checkout-session":{"post":{"tags":["Stripe Payments"],"summary":"Create Addon Checkout Session","description":"2026-08-24: sibling to create_checkout_session() above, but for a\nsingle à la carte feature unlock (see services/feature_addon_service.py)\ninstead of a full plan. Deliberately its own endpoint rather than a\nbranch inside the plan one -- the two sell fundamentally different\nthings (a whole plan vs one feature) and mixing their request/response\nshapes would make both harder to read.","operationId":"create_addon_checkout_session_api_stripe_create_addon_checkout_session_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/stripe/create-api-checkout-session":{"post":{"tags":["Stripe Payments"],"summary":"Create Api Checkout Session","description":"2026-08-24: Intelligence API self-serve Pro checkout -- see the\nVALID_API_TIERS block above for why this is public/unauthenticated\n(email-keyed, not JWT-keyed) unlike the two checkout endpoints above\nit. Body: {\"email\": \"...\", \"tier\": \"pro\"}. On success the webhook\nhandler below issues a real API key and emails it -- this endpoint\nitself never returns a key, same \"never in a JSON response\" posture as\nevery other key-issuance path in this codebase.","operationId":"create_api_checkout_session_api_stripe_create_api_checkout_session_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/webhooks/stripe":{"post":{"tags":["Stripe Payments"],"summary":"Stripe Webhook","operationId":"stripe_webhook_api_webhooks_stripe_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/video/status":{"get":{"tags":["Video Engine"],"summary":"Video Status","operationId":"video_status_api_video_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/video/latest":{"get":{"tags":["Video Engine"],"summary":"Video Latest","operationId":"video_latest_api_video_latest_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/formulas":{"get":{"tags":["Formula Engine"],"summary":"List Formulas","description":"Full catalog, grouped by category -- what formula-engine.html renders.","operationId":"list_formulas_api_formulas_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/formulas/{formula_id}":{"get":{"tags":["Formula Engine"],"summary":"Get Formula","description":"Metadata (params, description, category) for one formula by ID.","operationId":"get_formula_api_formulas__formula_id__get","parameters":[{"name":"formula_id","in":"path","required":true,"schema":{"type":"string","title":"Formula Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/formulas/compute/{formula_id}":{"post":{"tags":["Formula Engine"],"summary":"Compute Formula","description":"Runs one formula with the given kwargs, returns its result.\n400 on a bad/missing required param (surfaced as the underlying\nTypeError message -- e.g. \"missing a required argument: 'strike'\" --\nwhich is specific enough for API callers to fix their request without\nneeding a hand-written validator per formula).","operationId":"compute_formula_api_formulas_compute__formula_id__post","parameters":[{"name":"formula_id","in":"path","required":true,"schema":{"type":"string","title":"Formula Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ComputeRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/":{"get":{"summary":"Root","operationId":"root__get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/health":{"get":{"summary":"Health","description":"Lightweight, unauthenticated health check for Railway / uptime\nmonitors. Deliberately separate from /api/admin/health, which needs an\nadmin token and makes slow external network calls (market/news/crypto\nAPIs) — not something a monitor should be hitting every 30 seconds.\nThis one just confirms the process is up and the database is reachable.\nExempt from rate limiting so frequent automated pings never 429.","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}}},"components":{"schemas":{"AddEvidenceRequest":{"properties":{"kind":{"type":"string","title":"Kind"},"text":{"type":"string","title":"Text"},"source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source"}},"type":"object","required":["kind","text"],"title":"AddEvidenceRequest"},"Body_video_engine_upload_media_api_admin_video_upload_media_post":{"properties":{"file":{"type":"string","contentMediaType":"application/octet-stream","title":"File"},"frame_second":{"type":"number","title":"Frame Second"}},"type":"object","required":["file"],"title":"Body_video_engine_upload_media_api_admin_video_upload_media_post"},"CaptchaVerifyRequest":{"properties":{"challenge_token":{"type":"string","title":"Challenge Token"},"x":{"type":"number","title":"X"},"elapsed_ms":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Elapsed Ms"}},"type":"object","required":["challenge_token","x"],"title":"CaptchaVerifyRequest"},"ComputeRequest":{"properties":{"params":{"additionalProperties":true,"type":"object","title":"Params","default":{}}},"type":"object","title":"ComputeRequest"},"CreateHypothesisRequest":{"properties":{"ticker":{"type":"string","title":"Ticker"},"statement":{"type":"string","title":"Statement"},"confidence":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Confidence"}},"type":"object","required":["ticker","statement"],"title":"CreateHypothesisRequest"},"EarlyAccessRequest":{"properties":{"email":{"type":"string","title":"Email"},"company":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Company"},"tier_interest":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tier Interest"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"}},"type":"object","required":["email"],"title":"EarlyAccessRequest"},"FeedbackRequest":{"properties":{"type":{"type":"string","title":"Type"},"message":{"type":"string","title":"Message"},"email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email"},"token":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Token"},"lang":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Lang"}},"type":"object","required":["type","message"],"title":"FeedbackRequest"},"ForgotPasswordRequest":{"properties":{"email":{"type":"string","title":"Email"}},"type":"object","required":["email"],"title":"ForgotPasswordRequest"},"FreeSignupRequest":{"properties":{"email":{"type":"string","title":"Email"}},"type":"object","required":["email"],"title":"FreeSignupRequest"},"FullAnalysisResponse":{"properties":{"ticker":{"type":"string","title":"Ticker"},"price":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Price"},"market_score":{"type":"number","title":"Market Score"},"strategy_score":{"type":"number","title":"Strategy Score"},"news_score":{"type":"number","title":"News Score"},"risk_score":{"type":"number","title":"Risk Score"},"final_score":{"type":"number","title":"Final Score"},"rating":{"type":"string","title":"Rating"},"news":{"additionalProperties":true,"type":"object","title":"News"},"risk":{"additionalProperties":true,"type":"object","title":"Risk"}},"type":"object","required":["ticker","price","market_score","strategy_score","news_score","risk_score","final_score","rating","news","risk"],"title":"FullAnalysisResponse"},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"LiveReportRequest":{"properties":{"ticker":{"type":"string","title":"Ticker"},"decisionScore":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Decisionscore"},"confidencePct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Confidencepct"},"riskLabel":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Risklabel"},"keyReasons":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Keyreasons"},"suggestedAction":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Suggestedaction"},"invalidation":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Invalidation"},"stopLoss":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Stoploss"},"takeProfits":{"anyOf":[{"items":{"type":"number"},"type":"array"},{"type":"null"}],"title":"Takeprofits"},"riskPct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Riskpct"}},"type":"object","required":["ticker"],"title":"LiveReportRequest","description":"2026-07-31 (task #600): mirrors exactly the fields js/decision-\nfooter.js's renderDecisionFooter() already accepts and renders on\nai-analysis.html/chart-analysis.html -- the frontend just forwards the\nSAME object it already built for the on-screen Decision Report, so\nthe PDF can never disagree with what's on screen. See ReportGenerator\n.generate_from_live_data()'s docstring for why this is a separate,\nsimpler path from the older /report/{ticker} LLM-narrative endpoint\nbelow."},"ProfileUpdate":{"properties":{"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"avatar_gender":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Avatar Gender"}},"type":"object","title":"ProfileUpdate"},"RecallAlertSubscribeRequest":{"properties":{"email":{"type":"string","title":"Email"},"keyword":{"type":"string","title":"Keyword"}},"type":"object","required":["email","keyword"],"title":"RecallAlertSubscribeRequest"},"ResetPasswordRequest":{"properties":{"token":{"type":"string","title":"Token"},"new_password":{"type":"string","title":"New Password"}},"type":"object","required":["token","new_password"],"title":"ResetPasswordRequest"},"StressTestRequest":{"properties":{"symbol":{"type":"string","title":"Symbol"},"amount":{"type":"number","title":"Amount"},"horizon_days":{"type":"integer","title":"Horizon Days","default":252},"n_simulations":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"N Simulations"},"lang":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Lang"}},"type":"object","required":["symbol","amount"],"title":"StressTestRequest"},"SubscribePayload":{"properties":{"email":{"type":"string","title":"Email"}},"type":"object","required":["email"],"title":"SubscribePayload"},"SubscriptionKeys":{"properties":{"p256dh":{"type":"string","title":"P256Dh"},"auth":{"type":"string","title":"Auth"}},"type":"object","required":["p256dh","auth"],"title":"SubscriptionKeys"},"SubscriptionPayload":{"properties":{"endpoint":{"type":"string","title":"Endpoint"},"keys":{"$ref":"#/components/schemas/SubscriptionKeys"}},"type":"object","required":["endpoint","keys"],"title":"SubscriptionPayload"},"UnsubscribePayload":{"properties":{"endpoint":{"type":"string","title":"Endpoint"}},"type":"object","required":["endpoint"],"title":"UnsubscribePayload"},"UpdateHypothesisRequest":{"properties":{"statement":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Statement"},"confidence":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Confidence"},"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"}},"type":"object","title":"UpdateHypothesisRequest"},"UserLogin":{"properties":{"email":{"type":"string","title":"Email"},"password":{"type":"string","title":"Password"}},"type":"object","required":["email","password"],"title":"UserLogin"},"UserRegister":{"properties":{"email":{"type":"string","title":"Email"},"password":{"type":"string","title":"Password"},"name":{"type":"string","title":"Name"},"captcha_token":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Captcha Token"},"device_fingerprint":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Device Fingerprint"},"ref_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ref Code"}},"type":"object","required":["email","password","name"],"title":"UserRegister"},"UserResponse":{"properties":{"id":{"type":"integer","title":"Id"},"email":{"type":"string","title":"Email"},"name":{"type":"string","title":"Name"},"plan":{"type":"string","title":"Plan"},"token":{"type":"string","title":"Token"},"requires_verification":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Requires Verification","default":false},"avatar_gender":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Avatar Gender"},"oauth_provider":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Oauth Provider"}},"type":"object","required":["id","email","name","plan","token"],"title":"UserResponse"},"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"},"WebhookSubscribeRequest":{"properties":{"event_type":{"type":"string","title":"Event Type"},"url":{"type":"string","title":"Url"},"ticker":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ticker"}},"type":"object","required":["event_type","url"],"title":"WebhookSubscribeRequest"}}}}