{"openapi":"3.1.0","info":{"title":"TrendMatrix API","description":"Quantitative stock analysis API. 10-dimension scoring, BUY/SELL verdicts, price targets for 2,236 US stocks. Updated nightly.","version":"0.1.0"},"paths":{"/api/v1/events":{"post":{"tags":["events"],"summary":"Post Event","description":"Ingest a single analytics event.","operationId":"post_event_api_v1_events_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EventPayload"}}},"required":true},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/meta":{"get":{"summary":"Get Meta","description":"Latest run metadata + verdict summary + display thresholds.\n\n`config.thresholds` exposes the numeric bands the frontend needs to color\nmacro stats. They MUST come from verdict_config.py, not hard-coded in the\nclient — otherwise the header colors drift from the regime the model used\n(the \"three VIX band definitions\" bug). Never duplicate these values; the\ntest suite fails if MacroBar.tsx references magic numbers for these fields.","operationId":"get_meta_api_meta_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/meta/score_components":{"get":{"summary":"Get Score Components","description":"Glossary of score-component sub-signals for the Rating Breakdown UI.\n\nSingle source of truth for component labels, units, and scoring rationale.\nFrontend fetches once per session and renders per-stock breakdowns using\nthis metadata. Adding a new scorer sub-signal? Register it in\n`pipeline/score_components.py` and the UI picks it up automatically.","operationId":"get_score_components_api_meta_score_components_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/stocks":{"get":{"summary":"Get Stocks","description":"List stocks with optional filters.","operationId":"get_stocks_api_stocks_get","parameters":[{"name":"verdict","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by verdict (comma-separated)","title":"Verdict"},"description":"Filter by verdict (comma-separated)"},{"name":"sector","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sector"}},{"name":"symbols","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated symbol allowlist (e.g. 'AAPL,MSFT,NVDA'). Capped at 100. Use for fast watchlist / saved-set fetches.","title":"Symbols"},"description":"Comma-separated symbol allowlist (e.g. 'AAPL,MSFT,NVDA'). Capped at 100. Use for fast watchlist / saved-set fetches."},{"name":"sort","in":"query","required":false,"schema":{"type":"string","default":"overall_score","title":"Sort"}},{"name":"dir","in":"query","required":false,"schema":{"type":"string","default":"DESC","title":"Dir"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":3000,"minimum":1,"default":100,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}},{"name":"fields","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated fields to include (default: all)","title":"Fields"},"description":"Comma-separated fields to include (default: all)"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/stocks/{symbol}":{"get":{"summary":"Get Stock","description":"Get full data for a single stock.\n\nEnriches with `concentrations` + `edgar_8k_events` from the stock-intel\nskill's JSON output at request time (mtime-cached, sub-ms). This avoids\npushing those fields through publish.py / SQLite — see\n`plans/stock-intel-skill.md` section 12.\n\nCached for 60s in-process via `_stock_cached`. Symbol-detail page\nSSR was 0.87-1.82s before cache (every click slow); cache HIT cuts\nthe underlying API to <5ms.","operationId":"get_stock_api_stocks__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/v1/stocks/{symbol}/verdict-history":{"get":{"summary":"Get Stock Verdict History","description":"Reverse-chrono verdict history for a single stock.\n\nReads from the `verdict_history` table (populated on every nightly snapshot\nand on refresh when verdict/score/targets change). Public endpoint — powers\nthe \"Verdict History\" section on the stock detail page.","operationId":"get_stock_verdict_history_api_v1_stocks__symbol__verdict_history_get","parameters":[{"name":"symbol","in":"path","required":true,"schema":{"type":"string","title":"Symbol"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"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/v1/verdict-changes":{"get":{"summary":"Get Recent Verdict Changes","description":"Recent verdict-flip rows across the universe.\n\nPowers the Verdict Changes 24h zone on the home page (HOME-6).\nReturns rows where the verdict changed since the last write within\nthe requested window. Each row carries the symbol, name, detected\ntimestamp, current + previous verdict, and current + previous score.","operationId":"get_recent_verdict_changes_api_v1_verdict_changes_get","parameters":[{"name":"hours","in":"query","required":false,"schema":{"type":"integer","maximum":168,"minimum":1,"default":24,"title":"Hours"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"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/v1/methodology/verdict-distribution":{"get":{"summary":"Get Methodology Verdict Distribution","description":"Verdict distribution + sparkline + avg-tenure for the methodology panel.\n\nPure aggregation on existing tables; no new compute path. The `today`\nblock is the latest run's verdict counts; `history` is one entry per\nretained nightly snapshot ordered chronologically; `avg_tenure_days` is\nthe mean number of days a verdict label persists before flipping.\n\nHistory depth is bounded by `keep_nightly_days` retention (currently\n14 days) — `months` is honored as a ceiling but the response will not\nextend past whatever nightlies are still on disk.","operationId":"get_methodology_verdict_distribution_api_v1_methodology_verdict_distribution_get","parameters":[{"name":"months","in":"query","required":false,"schema":{"type":"integer","maximum":24,"minimum":1,"default":12,"title":"Months"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/stocks/{symbol}/peers":{"get":{"summary":"Get Peers","description":"Get peer stocks in the same industry + similar market cap tier (SQL-optimized).","operationId":"get_peers_api_stocks__symbol__peers_get","parameters":[{"name":"symbol","in":"path","required":true,"schema":{"type":"string","title":"Symbol"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":20,"minimum":1,"default":8,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/verdicts/{verdict}":{"get":{"summary":"Get By Verdict","description":"Get stocks by verdict type.","operationId":"get_by_verdict_api_verdicts__verdict__get","parameters":[{"name":"verdict","in":"path","required":true,"schema":{"type":"string","title":"Verdict"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":3000,"minimum":1,"default":100,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/sectors":{"get":{"summary":"Get Sectors","description":"Sector breakdown with average scores.\n\nFilters out the synthetic \"Unknown\" bucket — stocks land there when\nyfinance/finnhub provide no sector classification, but the bucket has\nno corresponding rollup page (slug \"unknown\" → 404 by design — there's\nno industry primer or coherent thesis for an unclassified group).\nShowing it in the dropdown promised navigation we couldn't honor.\nThe 5-7 affected stocks are still served by `/api/stocks` itself.","operationId":"get_sectors_api_sectors_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/industry_primers/manifest":{"get":{"summary":"Get Industry Primers Manifest","description":"Index of all sector + industry primers (slug, name, count, primer_short).\n\nEnriched with buy_count, sell_count, avg_score per entry when the DB\nhas data — surfaces signal density on the /sectors and /industries\nhub pages so users can scan \"where are the BUYs?\" at a glance\n(FINDING-011 from 2026-04-25 design audit).","operationId":"get_industry_primers_manifest_api_industry_primers_manifest_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/sectors/{slug}/rollup":{"get":{"summary":"Get Sector Rollup","operationId":"get_sector_rollup_api_sectors__slug__rollup_get","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/industries/{slug}/rollup":{"get":{"summary":"Get Industry Rollup","operationId":"get_industry_rollup_api_industries__slug__rollup_get","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/search":{"get":{"summary":"Search","description":"Search stocks by symbol or name.","operationId":"search_api_search_get","parameters":[{"name":"q","in":"query","required":true,"schema":{"type":"string","minLength":1,"title":"Q"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"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/news":{"get":{"summary":"Get News","description":"Pre-ranked news from the latest analysis run (paginated).","operationId":"get_news_api_news_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":2000,"minimum":1,"default":200,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/movers":{"get":{"summary":"Get Movers","description":"Today's top gainers and losers by daily price change.","operationId":"get_movers_api_movers_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}}},"components":{"schemas":{"EventPayload":{"properties":{"v":{"type":"integer","title":"V","description":"Schema version, must be 1"},"uid":{"type":"string","minLength":1,"title":"Uid","description":"Client visitor ID (tm_uid ULID)"},"ts":{"type":"string","minLength":1,"title":"Ts","description":"ISO 8601 timestamp from client"},"path":{"type":"string","minLength":1,"title":"Path","description":"Page path (e.g., /stocks/NVDA)"},"event":{"type":"string","minLength":1,"title":"Event","description":"Event name (e.g., page_view)"},"props":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Props","description":"Optional properties"}},"type":"object","required":["v","uid","ts","path","event"],"title":"EventPayload"},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"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"}}}}