{"openapi":"3.1.0","info":{"title":"AllVista API","version":"2026.09.07","summary":"Financial intelligence platform API behind news.all-vista.org","description":"Trump Truth Social, X/Twitter financial news, BWE crypto news, macro, earnings, US equities, options & institutional flow, China A-shares / Stock Connect and the Daily Brief.\n\nAuthentication: send the key in `X-API-Key` or `Authorization: Bearer`. Public keys (avk_…) are limited to 1500 requests per IP per 5 minutes, and 60 per IP (600 global) on LLM-backed operations (`x-llm: true`). Server keys (avs_…) are unlimited.","contact":{"url":"https://news.all-vista.org/api/docs"}},"servers":[{"url":"https://news.all-vista.org"}],"externalDocs":{"description":"Human-readable reference","url":"https://news.all-vista.org/api/docs"},"security":[{"ApiKeyHeader":[]},{"BearerKey":[]}],"tags":[{"name":"Trump Truth Social","description":"Truth Social posts with AI market-impact analysis, aggregate impact stats and intraday price reactions."},{"name":"X Financial News","description":"Curated X/Twitter financial posts, per-post analysis and rolling digests."},{"name":"BWE Crypto News","description":"BWEnews crypto feed with analysis and digests."},{"name":"Digests","description":"Read-only access to the hourly X and BWE digests."},{"name":"Macro","description":"Daily macro brief, live indicators and the economic events calendar."},{"name":"Earnings","description":"Earnings calendar, realised results and implied-vs-realised moves."},{"name":"Stocks","description":"Ticker search, prices, news, fundamentals, factor exposure and options history."},{"name":"Options & Institutional Flow","description":"Market tide, gamma exposure, dark pool prints, congressional trades, mood and the daily flow recap."},{"name":"China","description":"A-share market overview, A/H premium and Stock Connect flows."},{"name":"Daily Brief","description":"The cross-source morning brief."},{"name":"Search & Trending","description":"Site-wide search and the hourly trending board."},{"name":"System","description":"Health and freshness of every data file behind the site."}],"paths":{"/api/posts":{"get":{"operationId":"posts","tags":["Trump Truth Social"],"summary":"Paginated feed of Trump's Truth Social posts with search & AI analysis","description":"Newest first. Each post carries `_analysis` (the cached `AnalysisResult` from `POST /api/analyze`, or `null`). Media-only posts, posts that are only a URL, and posts the analysis flagged as empty are hidden from the feed.\n\n`page` is clamped to at least 1 and `limit` to 1–100 (the response echoes the effective values); the page window is `(page - 1) * limit` to `page * limit` over the filtered list.\n\n```bash\ncurl \"https://news.all-vista.org/api/posts?q=tariff&from=2025-03-01&limit=5\"\n\ncurl \"https://news.all-vista.org/api/posts?page=2&limit=50\"\n```","responses":{"200":{"description":"PostPage\n\n```json\n{\n  \"posts\": [\n    {\n      \"id\": \"114090816417968547\",\n      \"created_at\": \"2025-02-28T14:23:00.000Z\",\n      \"content\": \"<p>post HTML content</p>\",\n      \"language\": \"en\",\n      \"url\": \"https://truthsocial.com/@realDonaldTrump/...\",\n      \"account\": { \"username\": \"realDonaldTrump\", \"followers_count\": 9200000, \"verified\": true, ... },\n      \"media_attachments\": [ { \"id\": \"...\", \"type\": \"image\", \"url\": \"/media/....png\", \"preview_url\": \"...\", ... } ],\n      \"card\": { \"url\": \"...\", \"title\": \"...\", \"description\": \"...\", \"image\": \"...\", \"provider_name\": \"...\" },  // or null\n      \"replies_count\": 1200,\n      \"reblogs_count\": 2100,\n      \"favourites_count\": 8500,\n      \"_analysis\": { ... }  // AnalysisResult if available, else null\n    }\n  ],\n  \"total\": 842,\n  \"page\": 1,\n  \"limit\": 20,\n  \"hasMore\": true\n}\n```","content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"posts":[{"id":"114090816417968547","created_at":"2025-02-28T14:23:00.000Z","content":"<p>post HTML content</p>","language":"en","url":"https://truthsocial.com/@realDonaldTrump/...","account":{"username":"realDonaldTrump","followers_count":9200000,"verified":true},"media_attachments":[{"id":"...","type":"image","url":"/media/....png","preview_url":"..."}],"card":{"url":"...","title":"...","description":"...","image":"...","provider_name":"..."},"replies_count":1200,"reblogs_count":2100,"favourites_count":8500,"_analysis":{}}],"total":842,"page":1,"limit":20,"hasMore":true}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"parameters":[{"name":"page","in":"query","required":false,"description":"Page number (1-based); values below 1 are clamped to 1, and 0 or a non-numeric value falls back to 1.","schema":{"type":"integer","default":1,"minimum":1}},{"name":"limit","in":"query","required":false,"description":"Posts per page, clamped to 1–100; 0 or a non-numeric value falls back to 20.","schema":{"type":"integer","default":20,"minimum":1,"maximum":100}},{"name":"q","in":"query","required":false,"description":"Case-insensitive substring search over the post text and the linked card's title and description.","schema":{"type":"string","example":"tariff"}},{"name":"from","in":"query","required":false,"description":"Date range start, YYYY-MM-DD (inclusive, from 00:00 UTC).","schema":{"type":"string","example":"2025-03-01"}},{"name":"to","in":"query","required":false,"description":"Date range end, YYYY-MM-DD (inclusive, through 23:59:59.999 UTC).","schema":{"type":"string","example":"2025-03-31"}}]}},"/api/analyze":{"post":{"operationId":"analyze","tags":["Trump Truth Social"],"summary":"AI analysis of a Trump post's market impact","description":"Returns the cached `AnalysisResult` when one exists (with `X-AI-Provider: cache`); otherwise runs the LLM on the post text, its linked article excerpt and attached images, stores the result, and returns it with `X-AI-Provider` / `X-AI-Model` headers.\n\n⚠ This endpoint triggers an LLM call (typically ~60 s; the route allows up to 300 s). Returns `202` if analysis is already in progress for this post.\n\nCached results are served with `X-AI-Provider: cache` and `X-AI-Model: cached`; fresh results carry the provider and model that produced them.\n\nLLM-backed: counts against the `llm` rate limit and returns `X-AI-Provider` / `X-AI-Model` headers.\n\n```bash\ncurl -X POST https://news.all-vista.org/api/analyze -H \"Content-Type: application/json\" -d '{\"post_id\": \"114090816417968547\"}'\n```","responses":{"200":{"description":"AnalysisResult\n\n```json\n{\n  \"topic\": \"tariff\",             // macro|energy|tariff|social_media|legal|election|other\n  \"impact_direction\": \"bearish\", // bullish|bearish|neutral|uncertain\n  \"impact_magnitude\": 2,         // 0-3\n  \"confidence\": 0.85,            // 0.0-1.0\n  \"suggested_symbols\": [\"SPY\", \"QQQ\", \"EEM\"],\n  \"headline\": \"...\",             // Chinese headline\n  \"headline_en\": \"...\",          // English headline\n  \"translation\": \"...\",          // Chinese translation of post\n  \"rationale\": \"...\",            // Chinese summary\n  \"rationale_en\": \"...\",         // English summary\n  \"key_points\": [\"...\"],         // Chinese key points\n  \"key_points_en\": [\"...\"],      // English key points\n  \"market_impact\": \"...\",        // Chinese detailed analysis\n  \"market_impact_en\": \"...\",     // English detailed analysis\n  \"risk_factors\": [\"...\"],       // Chinese risk factors\n  \"risk_factors_en\": [\"...\"],    // English risk factors\n  \"time_horizon\": \"short_term\"   // short_term|medium_term|long_term|unclear\n}\n```","content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"topic":"tariff","impact_direction":"bearish","impact_magnitude":2,"confidence":0.85,"suggested_symbols":["SPY","QQQ","EEM"],"headline":"...","headline_en":"...","translation":"...","rationale":"...","rationale_en":"...","key_points":["..."],"key_points_en":["..."],"market_impact":"...","market_impact_en":"...","risk_factors":["..."],"risk_factors_en":["..."],"time_horizon":"short_term"}}}},"202":{"description":"Analysis already in progress for this post — retry later"},"400":{"description":"Missing `post_id`"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"Post not found"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"description":"Analysis failed (all LLM providers errored)"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"post_id":{"type":"string","example":"114090816417968547","description":"Truth Social post ID."}},"required":["post_id"]}}}},"x-llm":true}},"/api/trump-impact":{"get":{"operationId":"trumpImpact","tags":["Trump Truth Social"],"summary":"Market reaction to Trump posts — per-post session moves or a rolling summary","description":"Exactly one of `ids` or `summary` is required; `ids` takes precedence when both are present. Each post maps to the first US session it could move (posts at or after 16:00 ET, or on a weekend / holiday, roll to the next session) and the reaction is that session's close-to-close % move for SPY, QQQ, TLT, USO, GLD and IBIT.\n\nClose-to-close moves of the session a post landed in. This is correlation, not attribution — several posts usually share one session.\n\n`pending` means the session has not closed or its bars have not arrived yet (files land about 00:10 ET the next day); such rows are re-evaluated after 10 minutes. Unknown ids come back as `no_data`. The summary is memoised for 10 minutes per `days` value and lists at most 5 `topPosts` (one representative post per session).\n\n```bash\ncurl \"https://news.all-vista.org/api/trump-impact?summary=30\"\n\ncurl \"https://news.all-vista.org/api/trump-impact?ids=114090816417968547,114090900000000000\"\n```","responses":{"200":{"description":"PostReaction / ImpactSummary\n\n```json\n// ?ids=114090816417968547\n{\n  \"reactions\": {\n    \"114090816417968547\": {\n      \"session\": \"2025-02-28\",   // US session the post maps to; null while pending / no_data\n      \"status\": \"done\",          // done | pending | no_data\n      \"moves\": { \"SPY\": -1.59, \"QQQ\": -2.11, \"TLT\": 0.42, \"USO\": -0.8, \"GLD\": 0.31, \"IBIT\": -3.2 }\n    }\n  }\n}\n\n// ?summary=30\n{\n  \"days\": 30,\n  \"posts\": 212, \"postsDone\": 204, \"postsPending\": 8,\n  \"postsWithSpyMoveGe1pct\": 61,\n  \"avgAbsSpyOnPostingDays\": 0.84,   // mean |SPY %| over sessions with >= 1 post (null when none)\n  \"avgAbsSpyAllDays\": 0.71,         // mean |SPY %| over every completed session in the window\n  \"sessions\": 21, \"postingSessions\": 19,\n  \"latestSession\": \"2026-08-14\",\n  \"topPosts\": [\n    { \"id\": \"...\", \"created_at\": \"...\", \"headline\": \"...\", \"headline_en\": \"...\", \"url\": \"...\",\n      \"session\": \"2026-08-14\", \"spyPct\": -2.31, \"sessionPosts\": 4 }\n  ],\n  \"generatedAt\": \"2026-08-15T12:00:00.000Z\"\n}\n```","content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"x-example-text":"// ?ids=114090816417968547\n{\n  \"reactions\": {\n    \"114090816417968547\": {\n      \"session\": \"2025-02-28\",   // US session the post maps to; null while pending / no_data\n      \"status\": \"done\",          // done | pending | no_data\n      \"moves\": { \"SPY\": -1.59, \"QQQ\": -2.11, \"TLT\": 0.42, \"USO\": -0.8, \"GLD\": 0.31, \"IBIT\": -3.2 }\n    }\n  }\n}\n\n// ?summary=30\n{\n  \"days\": 30,\n  \"posts\": 212, \"postsDone\": 204, \"postsPending\": 8,\n  \"postsWithSpyMoveGe1pct\": 61,\n  \"avgAbsSpyOnPostingDays\": 0.84,   // mean |SPY %| over sessions with >= 1 post (null when none)\n  \"avgAbsSpyAllDays\": 0.71,         // mean |SPY %| over every completed session in the window\n  \"sessions\": 21, \"postingSessions\": 19,\n  \"latestSession\": \"2026-08-14\",\n  \"topPosts\": [\n    { \"id\": \"...\", \"created_at\": \"...\", \"headline\": \"...\", \"headline_en\": \"...\", \"url\": \"...\",\n      \"session\": \"2026-08-14\", \"spyPct\": -2.31, \"sessionPosts\": 4 }\n  ],\n  \"generatedAt\": \"2026-08-15T12:00:00.000Z\"\n}"}}},"400":{"description":"Neither `ids` nor `summary` given; `ids` not 1–60 numeric ids; or `summary` not an integer 1–365"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"description":"Internal error"}},"parameters":[{"name":"ids","in":"query","required":false,"description":"1–60 comma-separated numeric Truth Social post ids (duplicates removed) → `{ reactions: { [id]: PostReaction } }`.","schema":{"type":"string","description":"Comma-separated string values","example":"114090816417968547,114090900000000000"}},{"name":"summary","in":"query","required":false,"description":"Look-back window in days → `ImpactSummary`. An empty value (`?summary=`) means 30.","schema":{"type":"integer","default":30,"example":30,"minimum":1,"maximum":365}}]}},"/api/trump-intraday":{"get":{"operationId":"trumpIntraday","tags":["Trump Truth Social"],"summary":"15- and 60-minute market moves after each Trump post (SPY, QQQ, mentioned tickers)","description":"Exactly one of `ids` or `summary` is required; `ids` takes precedence. Posts inside 09:30–15:45 ET on a session are measured from the last 1-minute bar before the post (`mode: rth`); anything else is measured from the next session's 09:30 open (`mode: session_open`).\n\nPre-computed on the AWS market database from 1-minute bars (`trump_intraday.json`, twice daily). Posts outside regular hours are measured from the next session open and flagged `session_open`. `available: false` until the job has delivered a file.\n\nIds the job has not seen yet are `pending` when the post is newer than the snapshot it consumed, `no_data` otherwise. `moves` holds SPY, QQQ and up to three of the post's own symbols.\n\n```bash\ncurl \"https://news.all-vista.org/api/trump-intraday?summary=1\"\n\ncurl \"https://news.all-vista.org/api/trump-intraday?ids=115123456789012345\"\n```","responses":{"200":{"description":"IntradayReaction / IntradaySummaryResponse\n\n```json\n// ?ids=115123456789012345\n{\n  \"reactions\": {\n    \"115123456789012345\": {\n      \"status\": \"done\",                        // done | pending | no_data\n      \"mode\": \"rth\",                           // rth | session_open (null while pending / no_data)\n      \"session\": \"2026-09-01\",\n      \"anchor_ts\": \"2026-09-01T14:42:00Z\",     // last 1-minute bar before the post (UTC)\n      \"truncated\": false,                      // session closed before the +60 mark; its last bar was used\n      \"moves\": { \"SPY\": { \"m15\": 0.21, \"m60\": -0.05 }, \"QQQ\": { ... }, \"TSLA\": { ... } },\n      \"spy15_pct_rank\": 62                     // |SPY 15m| percentile vs every 15-minute SPY window of the period\n    }\n  }\n}\n\n// ?summary=1\n{\n  \"available\": true,                           // false (other fields null / 0) until the AWS job has delivered a file\n  \"generated_at\": \"2026-09-02T13:57:00+00:00\",\n  \"days\": 90,\n  \"window\": { \"start\": \"2026-06-04\", \"end\": \"2026-09-01\" },\n  \"last_session\": \"2026-09-01\",\n  \"baseline\": { \"symbol\": \"SPY\", \"sessions\": 62, \"n15\": 24180, \"n60\": 22940,\n                \"abs15\": { \"mean\": 0.080, \"p50\": 0.05, \"p75\": 0.09, \"p90\": 0.16, \"p95\": 0.22, \"p99\": 0.41 },\n                \"abs60\": { ... } },\n  \"summary\": {\n    \"posts\": 812, \"done\": 790, \"pending\": 12, \"no_data\": 10,\n    \"rth\": 310, \"session_open\": 480, \"truncated\": 6,\n    \"big_move_pct\": 0.5, \"spy15_big\": 14, \"spy15_big_share\": 0.018,\n    \"rth_mean_abs_spy15\": 0.074, \"rth_mean_abs_spy60\": 0.15, \"session_open_mean_abs_spy15\": 0.12,\n    \"baseline_mean_abs15\": 0.080, \"baseline_mean_abs60\": 0.17,\n    \"top\": [ { \"id\": \"...\", \"created_at\": \"...\", \"mode\": \"rth\", \"session\": \"...\", \"anchor_ts\": \"...\",\n               \"spy15\": -0.62, \"spy60\": -0.9, \"spy15_pct_rank\": 99, \"truncated\": false, \"anchor_posts\": 1,\n               \"moves\": { ... }, \"headline\": \"...\", \"headline_en\": \"...\", \"url\": \"...\" } ]\n  }\n}\n```","content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"x-example-text":"// ?ids=115123456789012345\n{\n  \"reactions\": {\n    \"115123456789012345\": {\n      \"status\": \"done\",                        // done | pending | no_data\n      \"mode\": \"rth\",                           // rth | session_open (null while pending / no_data)\n      \"session\": \"2026-09-01\",\n      \"anchor_ts\": \"2026-09-01T14:42:00Z\",     // last 1-minute bar before the post (UTC)\n      \"truncated\": false,                      // session closed before the +60 mark; its last bar was used\n      \"moves\": { \"SPY\": { \"m15\": 0.21, \"m60\": -0.05 }, \"QQQ\": { ... }, \"TSLA\": { ... } },\n      \"spy15_pct_rank\": 62                     // |SPY 15m| percentile vs every 15-minute SPY window of the period\n    }\n  }\n}\n\n// ?summary=1\n{\n  \"available\": true,                           // false (other fields null / 0) until the AWS job has delivered a file\n  \"generated_at\": \"2026-09-02T13:57:00+00:00\",\n  \"days\": 90,\n  \"window\": { \"start\": \"2026-06-04\", \"end\": \"2026-09-01\" },\n  \"last_session\": \"2026-09-01\",\n  \"baseline\": { \"symbol\": \"SPY\", \"sessions\": 62, \"n15\": 24180, \"n60\": 22940,\n                \"abs15\": { \"mean\": 0.080, \"p50\": 0.05, \"p75\": 0.09, \"p90\": 0.16, \"p95\": 0.22, \"p99\": 0.41 },\n                \"abs60\": { ... } },\n  \"summary\": {\n    \"posts\": 812, \"done\": 790, \"pending\": 12, \"no_data\": 10,\n    \"rth\": 310, \"session_open\": 480, \"truncated\": 6,\n    \"big_move_pct\": 0.5, \"spy15_big\": 14, \"spy15_big_share\": 0.018,\n    \"rth_mean_abs_spy15\": 0.074, \"rth_mean_abs_spy60\": 0.15, \"session_open_mean_abs_spy15\": 0.12,\n    \"baseline_mean_abs15\": 0.080, \"baseline_mean_abs60\": 0.17,\n    \"top\": [ { \"id\": \"...\", \"created_at\": \"...\", \"mode\": \"rth\", \"session\": \"...\", \"anchor_ts\": \"...\",\n               \"spy15\": -0.62, \"spy60\": -0.9, \"spy15_pct_rank\": 99, \"truncated\": false, \"anchor_posts\": 1,\n               \"moves\": { ... }, \"headline\": \"...\", \"headline_en\": \"...\", \"url\": \"...\" } ]\n  }\n}"}}},"400":{"description":"Neither `ids` nor `summary` given, or `ids` not 1–60 numeric ids"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"description":"Internal error"}},"parameters":[{"name":"ids","in":"query","required":false,"description":"1–60 comma-separated numeric post ids (duplicates removed) → `{ reactions: { [id]: IntradayReaction } }`.","schema":{"type":"string","description":"Comma-separated string values","example":"115123456789012345"}},{"name":"summary","in":"query","required":false,"description":"Any value (conventionally `1`) returns the period summary instead of per-post rows.","schema":{"type":"string","example":"1"}}]}},"/api/x-posts":{"get":{"operationId":"xPosts","tags":["X Financial News"],"summary":"Paginated feed of curated financial news from X/Twitter","description":"Newest first. Each post carries `_analysis` (the cached `XAnalysisResult` from `POST /api/x-analyze` or `POST /api/x-analyze-batch`, or `null`).\n\n```bash\ncurl \"https://news.all-vista.org/api/x-posts?limit=10\"\n\ncurl \"https://news.all-vista.org/api/x-posts?author=Reuters&page=1&limit=20\"\n```","responses":{"200":{"description":"XPostPage\n\n```json\n{\n  \"posts\": [\n    {\n      \"id\": \"x_2032098083790561289\",\n      \"timestamp\": \"2026-03-17T08:32:00.000Z\",\n      \"raw_text\": \"BREAKING: Fed holds rates steady...\",\n      \"author\": \"Reuters\",\n      \"_analysis\": { \"direction\": \"neutral\", \"translation\": \"...\", \"one_liner\": \"...\", \"one_liner_en\": \"...\" }  // or null\n    }\n  ],\n  \"total\": 915,\n  \"page\": 1,\n  \"limit\": 50,\n  \"hasMore\": true\n}\n```","content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"posts":[{"id":"x_2032098083790561289","timestamp":"2026-03-17T08:32:00.000Z","raw_text":"BREAKING: Fed holds rates steady...","author":"Reuters","_analysis":{"direction":"neutral","translation":"...","one_liner":"...","one_liner_en":"..."}}],"total":915,"page":1,"limit":50,"hasMore":true}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"parameters":[{"name":"page","in":"query","required":false,"description":"Page number (1-based).","schema":{"type":"integer","default":1}},{"name":"limit","in":"query","required":false,"description":"Posts per page.","schema":{"type":"integer","default":50}},{"name":"before","in":"query","required":false,"description":"Cursor: only posts strictly older than this ISO 8601 timestamp (page then counts from there). Invalid → 400. Added 2026-10-20; the feed pages use it instead of offsets.","schema":{"type":"string","example":"2026-10-18T12:00:00Z"}},{"name":"author","in":"query","required":false,"description":"Filter by author handle, exact match (e.g. \"WSJ\", \"zerohedge\").","schema":{"type":"string","example":"Reuters"}}]}},"/api/x-analyze":{"post":{"operationId":"xAnalyze","tags":["X Financial News"],"summary":"AI sentiment analysis of an X financial news post","description":"Returns the cached result when one exists (with `X-AI-Provider: cache`); otherwise classifies the headline with the LLM, stores it, and returns it with `X-AI-Provider` / `X-AI-Model` headers.\n\n⚠ Triggers an LLM call (~60 s). Returns `202` if in progress.\n\nLLM-backed: counts against the `llm` rate limit and returns `X-AI-Provider` / `X-AI-Model` headers.\n\n```bash\ncurl -X POST https://news.all-vista.org/api/x-analyze -H \"Content-Type: application/json\" -d '{\"message_id\": \"x_2032098083790561289\"}'\n```","responses":{"200":{"description":"XAnalysisResult\n\n```json\n{\n  \"direction\": \"bearish\",     // bullish|bearish|neutral|uncertain\n  \"translation\": \"...\",       // Chinese translation\n  \"one_liner\": \"...\",         // Chinese market commentary\n  \"one_liner_en\": \"...\"       // English market commentary\n}\n```","content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"direction":"bearish","translation":"...","one_liner":"...","one_liner_en":"..."}}}},"202":{"description":"Analysis already in progress for this post — retry later"},"400":{"description":"Missing `message_id`"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"Message not found"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"description":"Analysis failed (all LLM providers errored)"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"message_id":{"type":"string","example":"x_2032098083790561289","description":"X post ID as returned by `/api/x-posts`."}},"required":["message_id"]}}}},"x-llm":true}},"/api/x-analyze-batch":{"post":{"operationId":"xAnalyzeBatch","tags":["X Financial News"],"summary":"Classify up to 25 X headlines in one LLM call","description":"Fills the same cache as `POST /api/x-analyze` for many posts at once. Ids that are already cached or unknown are dropped before the call; only the remaining headlines are sent, in a single prompt. The body reports counts only — read the results back through `/api/x-posts` (`_analysis`).\n\n⚠ One LLM call per request, with a 240 s timeout (route `maxDuration` is 300 s). Use a client timeout of about 290 s.\n\nWhen every id is cached or unknown the route answers `200` with `analyzed: 0` and `provider: null` without calling the LLM. Otherwise `X-AI-Provider` / `X-AI-Model` headers name the provider that ran. Headlines are truncated to 600 characters in the prompt.\n\nDriven hourly by `scripts/analyze_trending.py`, which sends the last 24 h of posts for the trending tickers in chunks of 20 (route limit is 25) and counts a `500` as a spent LLM call.\n\nLLM-backed: counts against the `llm` rate limit and returns `X-AI-Provider` / `X-AI-Model` headers.\n\n```bash\ncurl -X POST https://news.all-vista.org/api/x-analyze-batch -H \"Content-Type: application/json\" -d '{\"ids\": [\"x_2032098083790561289\", \"x_2032098083790561290\"]}'\n```","responses":{"200":{"description":"BatchAnalysisReport\n\n```json\n{\n  \"requested\": 20,          // distinct ids in the request\n  \"analyzed\": 17,           // items the model returned well-formed; written to the cache\n  \"skipped_cached\": 2,      // ids that already had an analysis (not sent)\n  \"skipped_unknown\": 0,     // ids not present in the X archive (not sent)\n  \"failed\": 1,              // sent ids the model skipped or returned malformed\n  \"provider\": \"anthropic\",  // null when nothing was sent (no LLM call)\n  \"model\": \"claude-sonnet-4-6\"\n}\n```","content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"requested":20,"analyzed":17,"skipped_cached":2,"skipped_unknown":0,"failed":1,"provider":"anthropic","model":"claude-sonnet-4-6"}}}},"202":{"description":"The same set of uncached ids is already being analysed — retry later"},"400":{"description":"Body is not JSON, or `ids` is not an array of 1–25 non-empty strings (each ≤ 64 chars)"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"description":"Batch analysis failed (the LLM call errored; nothing was cached)"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"ids":{"type":"array","items":{"type":"string"},"example":"[\"x_2032098083790561289\", \"x_2032098083790561290\"]","description":"1–25 X post ids (non-empty strings of at most 64 characters; duplicates removed)."}},"required":["ids"]}}}},"x-llm":true}},"/api/x-digest":{"post":{"operationId":"xDigest","tags":["X Financial News"],"summary":"AI-generated summary of X news over a time window","description":"Cached per `{window}_{start}`: a repeat call with the same `window` and the identical `start` string returns the stored digest (with `X-AI-Provider: cache`). Posts with `start <= timestamp < end` are summarised.\n\n⚠ Triggers an LLM call (~120 s; the route allows up to 300 s). Returns `202` if in progress. Max 300 headlines per digest (the newest 300 in the window).\n\n`window` must be one of the four listed values and `start` / `end` must parse as timestamps (Date.parse); all three are checked before the cache lookup. The cache key still uses the raw `start` string, so send the canonical YYYY-MM-DDTHH:MM:SS.000Z form to hit the same digest as `GET /api/digests`.\n\nLLM-backed: counts against the `llm` rate limit and returns `X-AI-Provider` / `X-AI-Model` headers.\n\n```bash\ncurl -X POST https://news.all-vista.org/api/x-digest -H \"Content-Type: application/json\" -d '{\"window\": \"1d\", \"start\": \"2026-04-01T00:00:00.000Z\", \"end\": \"2026-04-02T00:00:00.000Z\"}'\n```","responses":{"200":{"description":"DigestResult\n\n```json\n{\n  \"sentiment\": \"bearish\",     // bullish|bearish|neutral|mixed\n  \"title\": \"...\",             // Chinese one-line title\n  \"title_en\": \"...\",          // English one-line title\n  \"summary\": \"...\",           // Chinese 2-3 paragraph analysis\n  \"summary_en\": \"...\",        // English 2-3 paragraph analysis\n  \"key_themes\": [\"tariffs\", \"fed policy\", \"tech earnings\"],\n  \"notable_tickers\": [\"NVDA\", \"TSLA\", \"SPY\"]\n}\n```","content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"sentiment":"bearish","title":"...","title_en":"...","summary":"...","summary_en":"...","key_themes":["tariffs","fed policy","tech earnings"],"notable_tickers":["NVDA","TSLA","SPY"]}}}},"202":{"description":"Digest for this window/start is already being generated — retry later"},"400":{"description":"Missing `window`, `start` or `end`; `window` not one of 4h, 12h, 1d, 1w (`window must be one of 4h, 12h, 1d, 1w`); or `start` / `end` not parseable as ISO-8601 timestamps"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"No posts in range"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"description":"Digest failed (all LLM providers errored)"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"window":{"type":"string","enum":["4h","12h","1d","1w"],"example":"1d","description":"Time window label; becomes the prefix of the cache key."},"start":{"type":"string","example":"2026-04-01T00:00:00.000Z","description":"Window start, ISO 8601 (inclusive). Use the exact form YYYY-MM-DDTHH:MM:SS.000Z so the key matches `GET /api/digests`."},"end":{"type":"string","example":"2026-04-02T00:00:00.000Z","description":"Window end, ISO 8601 (exclusive)."}},"required":["window","start","end"]}}}},"x-llm":true}},"/api/bwe-posts":{"get":{"operationId":"bwePosts","tags":["BWE Crypto News"],"summary":"Paginated feed of BWE crypto/blockchain news with AI analysis","description":"Newest first. `source` and `headline` are parsed from `raw_text` (format `**SOURCE: headline**`). Each post carries `_analysis` (the cached `BWEAnalysisResult` from `POST /api/bwe-analyze`, or `null`).\n\n```bash\ncurl \"https://news.all-vista.org/api/bwe-posts?limit=10\"\n\ncurl \"https://news.all-vista.org/api/bwe-posts?source=Binance%20EN&limit=20\"\n```","responses":{"200":{"description":"BWEPostPage\n\n```json\n{\n  \"posts\": [\n    {\n      \"id\": \"bwe_20260401_001\",\n      \"timestamp\": \"2026-04-01T08:00:00.000Z\",\n      \"raw_text\": \"**Binance EN: BTC breaks $90K...**\\n...\",\n      \"source\": \"Binance EN\",\n      \"headline\": \"BTC breaks $90K resistance\",\n      \"_analysis\": {\n        \"direction\": \"bullish\",\n        \"translation\": \"...\",\n        \"one_liner\": \"...\",\n        \"one_liner_en\": \"...\"\n      }\n    }\n  ],\n  \"total\": 61,\n  \"page\": 1,\n  \"limit\": 50,\n  \"hasMore\": true\n}\n```","content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"posts":[{"id":"bwe_20260401_001","timestamp":"2026-04-01T08:00:00.000Z","raw_text":"**Binance EN: BTC breaks $90K...**\n...","source":"Binance EN","headline":"BTC breaks $90K resistance","_analysis":{"direction":"bullish","translation":"...","one_liner":"...","one_liner_en":"..."}}],"total":61,"page":1,"limit":50,"hasMore":true}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"parameters":[{"name":"page","in":"query","required":false,"description":"Page number (1-based).","schema":{"type":"integer","default":1}},{"name":"limit","in":"query","required":false,"description":"Posts per page.","schema":{"type":"integer","default":50}},{"name":"before","in":"query","required":false,"description":"Cursor: only posts strictly older than this ISO 8601 timestamp (page then counts from there). Invalid → 400. Added 2026-10-20; the feed pages use it instead of offsets.","schema":{"type":"string","example":"2026-10-18T12:00:00Z"}},{"name":"source","in":"query","required":false,"description":"Filter by source, exact match (e.g. \"DB\", \"AggrNews\", \"Binance EN\").","schema":{"type":"string","example":"Binance EN"}}]}},"/api/bwe-analyze":{"post":{"operationId":"bweAnalyze","tags":["BWE Crypto News"],"summary":"AI sentiment analysis of a BWE crypto/blockchain headline","description":"Returns the cached result when one exists (with `X-AI-Provider: cache`); otherwise classifies the parsed `headline` with the LLM, stores it, and returns it with `X-AI-Provider` / `X-AI-Model` headers.\n\n⚠ Triggers an LLM call (~60 s). Returns `202` if in progress.\n\nLLM-backed: counts against the `llm` rate limit and returns `X-AI-Provider` / `X-AI-Model` headers.\n\n```bash\ncurl -X POST https://news.all-vista.org/api/bwe-analyze -H \"Content-Type: application/json\" -d '{\"message_id\": \"bwe_20260312_001\"}'\n```","responses":{"200":{"description":"BWEAnalysisResult\n\n```json\n{\n  \"direction\": \"bullish\",     // bullish|bearish|neutral|uncertain\n  \"translation\": \"...\",       // Chinese translation\n  \"one_liner\": \"...\",         // Chinese market commentary\n  \"one_liner_en\": \"...\"       // English market commentary\n}\n```","content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"direction":"bullish","translation":"...","one_liner":"...","one_liner_en":"..."}}}},"202":{"description":"Analysis already in progress for this post — retry later"},"400":{"description":"Missing `message_id`"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"Message not found"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"description":"Analysis failed (all LLM providers errored)"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"message_id":{"type":"string","example":"bwe_20260312_001","description":"BWE post ID as returned by `/api/bwe-posts`."}},"required":["message_id"]}}}},"x-llm":true}},"/api/bwe-digest":{"post":{"operationId":"bweDigest","tags":["BWE Crypto News"],"summary":"AI-generated summary of BWE crypto news over a time window","description":"Cached per `{window}_{start}`: a repeat call with the same `window` and the identical `start` string returns the stored digest (with `X-AI-Provider: cache`). Every post with `start <= timestamp < end` is sent to the model (no headline cap).\n\n⚠ Triggers an LLM call (~120 s; the route allows up to 300 s). Returns `202` if in progress.\n\n`window` must be one of the four listed values and `start` / `end` must parse as timestamps (Date.parse); all three are checked before the cache lookup. The cache key still uses the raw `start` string, so send the canonical YYYY-MM-DDTHH:MM:SS.000Z form to hit the same digest as `GET /api/digests`.\n\nLLM-backed: counts against the `llm` rate limit and returns `X-AI-Provider` / `X-AI-Model` headers.\n\n```bash\ncurl -X POST https://news.all-vista.org/api/bwe-digest -H \"Content-Type: application/json\" -d '{\"window\": \"1d\", \"start\": \"2026-04-01T00:00:00.000Z\", \"end\": \"2026-04-02T00:00:00.000Z\"}'\n```","responses":{"200":{"description":"DigestResult\n\n```json\n{\n  \"sentiment\": \"mixed\",       // bullish|bearish|neutral|mixed\n  \"title\": \"...\",             // Chinese one-line title\n  \"title_en\": \"...\",          // English one-line title\n  \"summary\": \"...\",           // Chinese 2-3 paragraph analysis\n  \"summary_en\": \"...\",        // English 2-3 paragraph analysis\n  \"key_themes\": [\"BTC ETF\", \"DeFi regulation\"],\n  \"notable_tickers\": [\"BTC\", \"ETH\", \"SOL\"]\n}\n```","content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"sentiment":"mixed","title":"...","title_en":"...","summary":"...","summary_en":"...","key_themes":["BTC ETF","DeFi regulation"],"notable_tickers":["BTC","ETH","SOL"]}}}},"202":{"description":"Digest for this window/start is already being generated — retry later"},"400":{"description":"Missing `window`, `start` or `end`; `window` not one of 4h, 12h, 1d, 1w (`window must be one of 4h, 12h, 1d, 1w`); or `start` / `end` not parseable as ISO-8601 timestamps"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"No posts in range"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"description":"Digest failed (all LLM providers errored)"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"window":{"type":"string","enum":["4h","12h","1d","1w"],"example":"1d","description":"Time window label; becomes the prefix of the cache key."},"start":{"type":"string","example":"2026-04-01T00:00:00.000Z","description":"Window start, ISO 8601 (inclusive). Use the exact form YYYY-MM-DDTHH:MM:SS.000Z so the key matches `GET /api/digests`."},"end":{"type":"string","example":"2026-04-02T00:00:00.000Z","description":"Window end, ISO 8601 (exclusive)."}},"required":["window","start","end"]}}}},"x-llm":true}},"/api/digests":{"get":{"operationId":"digests","tags":["Digests"],"summary":"Read cached AI digests — no LLM call, returns pre-computed summaries","description":"Lists every digest stored for a source, optionally narrowed to one window. Entries are sorted by cache key descending, i.e. newest first within each window.\n\nThis endpoint only reads from cache — it never triggers an LLM call. Use `POST /api/x-digest` or `POST /api/bwe-digest` to generate new digests.\n\n⚠ There is no `limit` or `since` parameter: the response is the whole store for the selected window (over a thousand 4h digests, several MB). Always pass `window` and slice `digests[]` client-side for the newest N; they are sorted newest first.\n\n`window`, when given, is validated against the same four values as the digest POST routes and then matched as the key prefix `{window}_`.\n\n```bash\ncurl \"https://news.all-vista.org/api/digests?source=x&window=1d\"\n\ncurl \"https://news.all-vista.org/api/digests?source=bwe\"\n```","responses":{"200":{"description":"DigestList\n\n```json\n{\n  \"source\": \"x\",\n  \"window\": \"1d\",              // \"all\" when the query had no window\n  \"total\": 42,\n  \"digests\": [\n    {\n      \"key\": \"1d_2026-04-01T00:00:00.000Z\",   // \"{window}_{start}\"\n      \"window\": \"1d\",\n      \"start\": \"2026-04-01T00:00:00.000Z\",\n      \"sentiment\": \"bearish\",\n      \"title\": \"...\",\n      \"title_en\": \"Tariff fears dominate as markets sell off\",\n      \"summary\": \"...\",\n      \"summary_en\": \"...\",\n      \"key_themes\": [\"tariffs\", \"fed policy\"],\n      \"notable_tickers\": [\"SPY\", \"QQQ\", \"NVDA\"]\n    }\n  ]\n}\n```","content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"source":"x","window":"1d","total":42,"digests":[{"key":"1d_2026-04-01T00:00:00.000Z","window":"1d","start":"2026-04-01T00:00:00.000Z","sentiment":"bearish","title":"...","title_en":"Tariff fears dominate as markets sell off","summary":"...","summary_en":"...","key_themes":["tariffs","fed policy"],"notable_tickers":["SPY","QQQ","NVDA"]}]}}}},"400":{"description":"Missing or invalid `source` (must be `x` or `bwe`), or `window` given but not one of 4h, 12h, 1d, 1w (`window must be one of 4h, 12h, 1d, 1w`)"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"parameters":[{"name":"source","in":"query","required":true,"description":"Which digest store to read.","schema":{"type":"string","enum":["x","bwe"],"example":"x"}},{"name":"window","in":"query","required":false,"description":"Only digests whose key starts with this window; must be one of the four values when given, omit for all windows (`window: \"all\"` in the response).","schema":{"type":"string","enum":["4h","12h","1d","1w"],"example":"1d"}},{"name":"from","in":"query","required":false,"description":"ISO 8601 lower bound (inclusive) on the digest window start. The feed pages use from/to to fetch digests for older posts as they page in (since 2026-10-20).","schema":{"type":"string","example":"2026-10-01T00:00:00Z"}},{"name":"to","in":"query","required":false,"description":"ISO 8601 upper bound (exclusive) on the digest window start. Invalid from/to → 400.","schema":{"type":"string","example":"2026-10-08T00:00:00Z"}}]}},"/api/macro-analysis":{"get":{"operationId":"macroAnalysis","tags":["Macro"],"summary":"AI-generated daily macro market brief with live data","description":"Formats the latest macro snapshot (live Polygon overlay for equities/ETFs) into a prompt and asks the LLM for a ~350-word institutional-style macro brief. The result is cached in memory per snapshot id and locale, so repeat calls are instant until a new snapshot lands.\n\nResponse headers `X-AI-Provider` and `X-AI-Model` indicate which LLM generated the analysis. Cached replays report `X-AI-Provider: cache` / `X-AI-Model: cached`.\n\nReturns `text/plain` (~350 words). The prompt includes live S&P 500, Nasdaq, 10Y yield, oil, VIX, BTC, gold, and 20+ macro indicators; indicators without a reading are passed to the model as n/a.\n\nGeneration order: remote Claude (when enabled) first, then the API provider fallback chain (`ANTHROPIC_MODEL_MACRO`, Codex, Qwen). Up to 20 snapshot/locale pairs are kept in the in-memory cache.\n\nLLM-backed: counts against the `llm` rate limit and returns `X-AI-Provider` / `X-AI-Model` headers.\n\n```bash\ncurl \"https://news.all-vista.org/api/macro-analysis?locale=zh\"\n```","responses":{"200":{"description":"text/plain\n\n```json\nContent-Type: text/plain; charset=utf-8\nX-AI-Provider: anthropic        // or \"cache\" when served from the in-memory cache\nX-AI-Model: claude-opus-4-6     // or \"cached\"\n\nGrowth is cooling but not collapsing: payrolls printed +142K against a 4.2% unemployment\nrate while real GDP held at 2.1% QoQ ... (~350 words, no title)\n```","content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"x-example-text":"Content-Type: text/plain; charset=utf-8\nX-AI-Provider: anthropic        // or \"cache\" when served from the in-memory cache\nX-AI-Model: claude-opus-4-6     // or \"cached\"\n\nGrowth is cooling but not collapsing: payrolls printed +142K against a 4.2% unemployment\nrate while real GDP held at 2.1% QoQ ... (~350 words, no title)"}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"No snapshot data available (plain-text body) — macro.json has no valid snapshot yet"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"parameters":[{"name":"locale","in":"query","required":false,"description":"Language of the brief; any value other than zh falls back to en.","schema":{"type":"string","enum":["en","zh"],"default":"en"}}],"x-llm":true,"x-cache":"no-cache"}},"/api/macro-indicators":{"get":{"operationId":"macroIndicators","tags":["Macro"],"summary":"Raw macro indicator values across all eight factor groups — no LLM call","description":"Returns the latest macro snapshot as JSON. Each indicator is either a price item { close, change_pct } or an economic indicator { value, date }. Categories: market, growth, inflation, risk_appetite, liquidity, fiat_credit, ai_tech, global_markets.\n\nPure data endpoint — no LLM, no AI provider headers. Use `/api/macro-analysis` when you want a written brief.\n\n`live` and `history` are checked before the snapshot is read, so a bad flag value is a 400 even when no snapshot exists; the response echoes the effective `live` as a JSON boolean.\n\n`close` and `value` are `null` when a series has no reading yet (e.g. `mag7_avg` before the live overlay); render a dash rather than zero.\n\n```bash\ncurl \"https://news.all-vista.org/api/macro-indicators?category=inflation\"\n\ncurl \"https://news.all-vista.org/api/macro-indicators?keys=cpi,unemployment_rate,vix,btc\"\n\ncurl \"https://news.all-vista.org/api/macro-indicators?live=false&history=true\"\n```","responses":{"200":{"description":"MacroIndicators\n\n```json\n{\n  \"snapshot_id\": \"macro_20260511_080005\",\n  \"timestamp\": \"2026-05-11T08:00:05Z\",\n  \"live\": true,\n  \"indicators\": {\n    \"market\": {\n      \"sp500\":        { \"close\": 7399.2, \"change_pct\": 0.81 },\n      \"nasdaq\":       { \"close\": 26247.1, \"change_pct\": 1.72 },\n      \"treasury_10y\": { \"close\": 4.36,   \"change_pct\": -0.45 },\n      \"oil\":          { \"close\": 99.1,   \"change_pct\": 0.55 }\n    },\n    \"inflation\": {\n      \"cpi\":       { \"value\": 3.3, \"date\": \"2026-04-01\" },\n      \"core_pce\":  { \"value\": 3.2, \"date\": \"2026-03-01\" }\n    }\n    // ... growth, risk_appetite, liquidity, fiat_credit, ai_tech, global_markets\n  },\n  \"history\": {                                   // only with history=true\n    \"total_pc_ratio\": [ { \"date\": \"2026-01-02\", \"value\": 0.91 }, ... ],\n    \"equity_pc_ratio\": [ ... ], \"us_debt_gdp\": [ ... ], \"fiscal_deficit_gdp\": [ ... ]\n  }\n}\n```","content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"snapshot_id":"macro_20260511_080005","timestamp":"2026-05-11T08:00:05Z","live":true,"indicators":{"market":{"sp500":{"close":7399.2,"change_pct":0.81},"nasdaq":{"close":26247.1,"change_pct":1.72},"treasury_10y":{"close":4.36,"change_pct":-0.45},"oil":{"close":99.1,"change_pct":0.55}},"inflation":{"cpi":{"value":3.3,"date":"2026-04-01"},"core_pce":{"value":3.2,"date":"2026-03-01"}}},"history":{"total_pc_ratio":[{"date":"2026-01-02","value":0.91}],"equity_pc_ratio":[],"us_debt_gdp":[],"fiscal_deficit_gdp":[]}}}}},"400":{"description":"`live` or `history` not one of true, false, 1, 0 (`live and history accept true|false|1|0`); invalid category (body lists valid_categories); or an empty keys parameter"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"No snapshot data available"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"parameters":[{"name":"category","in":"query","required":false,"description":"Restrict to one factor group.","schema":{"type":"string","enum":["market","growth","inflation","risk_appetite","liquidity","fiat_credit","ai_tech","global_markets"]}},{"name":"keys","in":"query","required":false,"description":"Comma-separated indicator keys, e.g. cpi,unemployment_rate,vix; matches are returned grouped by their category (categories with no match are omitted).","schema":{"type":"string","description":"Comma-separated string values","example":"cpi,unemployment_rate,vix"}},{"name":"live","in":"query","required":false,"description":"Overlay equity/ETF prices with live Polygon.io quotes (indices, crypto and economic indicators stay from the snapshot). Accepts exactly true, false, 1 or 0; any other value is a 400.","schema":{"type":"string","enum":["true","false","1","0"],"default":"true"}},{"name":"history","in":"query","required":false,"description":"Append a history map (~120 daily points each) for indicators without a public ticker: total_pc_ratio, equity_pc_ratio, us_debt_gdp, fiscal_deficit_gdp. Accepts exactly true, false, 1 or 0; any other value is a 400.","schema":{"type":"string","enum":["true","false","1","0"],"default":"false"}}],"x-cache":"no-cache"}},"/api/events":{"get":{"operationId":"events","tags":["Macro"],"summary":"Merged economic-events calendar: US/CN/HK macro releases, central-bank decisions, mega-cap US and HK earnings","description":"Builds a day-bucketed calendar from today (UTC) through today plus days (inclusive). Macro and central-bank releases come from events.json (Unusual Whales for the US, TuShare for CN/HK/GLOBAL), HK results dates from hk_earnings.json, and US earnings from the Finnhub calendar file restricted to revenue estimates of at least $10B. US earnings are tagged with the options-implied move.\n\n`importance` is `high` for every earnings and central-bank row; macro releases are `high` only when the title matches a market-moving keyword list (CPI, PPI, PCE, GDP, PMI, ISM, payrolls, FOMC, retail sales, jobless claims, LPR/MLF, ...) and `normal` otherwise.\n\n`meta.impliedMovePct` (percent, one decimal) is added to at most the first 40 US earnings rows from the Unusual Whales IV term structure (first expiry on/after the report date, cached 12 h); it is absent when no usable term structure exists. `meta.hkCode` is present only for US tickers that are also listed in Hong Kong.\n\nThe response is memoised in memory for 10 minutes per `days` value. Missing source files are not an error — the affected rows are simply absent and `fetched_at` / `hk_checked_at` are `null`.\n\n```bash\ncurl \"https://news.all-vista.org/api/events\"\n\ncurl \"https://news.all-vista.org/api/events?days=7\"\n```","responses":{"200":{"description":"CalendarPayload\n\n```json\n{\n  \"fetched_at\": \"2026-09-07T05:05:12+00:00\",      // events.json fetch time; null when the file is missing\n  \"hk_checked_at\": \"2026-09-06T02:10:00+00:00\",   // hk_earnings.json check time; null when missing\n  \"days\": [\n    {\n      \"date\": \"2026-09-10\",\n      \"events\": [                                  // sorted by time_utc, untimed events last\n        {\n          \"id\": \"3f9c1a2b7d4e\",                    // sha1(source|date|title)[:12] for macro rows\n          \"date\": \"2026-09-10\",\n          \"time_utc\": \"12:30\", \"time_et\": \"08:30\", \"time_hkt\": \"20:30\",   // null when the release has no time\n          \"region\": \"US\",                          // US | CN | HK | GLOBAL\n          \"type\": \"macro\",                         // macro | central_bank | earnings\n          \"importance\": \"high\",                    // high | normal\n          \"title_zh\": \"美国CPI同比\", \"title_en\": \"CPI YoY\",\n          \"prev\": \"2.9%\", \"forecast\": \"3.0%\",      // as published (strings); null when unknown\n          \"source\": \"uw\",                          // uw | tushare | hk_list | finnhub\n          \"meta\": {}\n        },\n        {\n          \"id\": \"us-BABA-2026-09-10\", \"date\": \"2026-09-10\",\n          \"time_utc\": null, \"time_et\": null, \"time_hkt\": null,\n          \"region\": \"US\", \"type\": \"earnings\", \"importance\": \"high\",\n          \"title_zh\": \"BABA 财报\", \"title_en\": \"BABA earnings\",\n          \"prev\": null, \"forecast\": null, \"source\": \"finnhub\",\n          \"meta\": { \"symbol\": \"BABA\", \"hour\": \"bmo\", \"hkCode\": \"09988.HK\", \"impliedMovePct\": 6.8 }\n        },\n        {\n          \"id\": \"hk-00700-2026-09-10\", \"date\": \"2026-09-10\",\n          \"time_utc\": null, \"time_et\": null, \"time_hkt\": null,\n          \"region\": \"HK\", \"type\": \"earnings\", \"importance\": \"high\",\n          \"title_zh\": \"腾讯控股 业绩\", \"title_en\": \"Tencent results\",\n          \"prev\": null, \"forecast\": null, \"source\": \"hk_list\",\n          \"meta\": { \"symbol\": \"00700\", \"confirmed\": true }\n        }\n      ]\n    }\n  ]\n}\n```","content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"fetched_at":"2026-09-07T05:05:12+00:00","hk_checked_at":"2026-09-06T02:10:00+00:00","days":[{"date":"2026-09-10","events":[{"id":"3f9c1a2b7d4e","date":"2026-09-10","time_utc":"12:30","time_et":"08:30","time_hkt":"20:30","region":"US","type":"macro","importance":"high","title_zh":"美国CPI同比","title_en":"CPI YoY","prev":"2.9%","forecast":"3.0%","source":"uw","meta":{}},{"id":"us-BABA-2026-09-10","date":"2026-09-10","time_utc":null,"time_et":null,"time_hkt":null,"region":"US","type":"earnings","importance":"high","title_zh":"BABA 财报","title_en":"BABA earnings","prev":null,"forecast":null,"source":"finnhub","meta":{"symbol":"BABA","hour":"bmo","hkCode":"09988.HK","impliedMovePct":6.8}},{"id":"hk-00700-2026-09-10","date":"2026-09-10","time_utc":null,"time_et":null,"time_hkt":null,"region":"HK","type":"earnings","importance":"high","title_zh":"腾讯控股 业绩","title_en":"Tencent results","prev":null,"forecast":null,"source":"hk_list","meta":{"symbol":"00700","confirmed":true}}]}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"parameters":[{"name":"days","in":"query","required":false,"description":"Number of days ahead to include (window is today through today+days, inclusive); non-numeric values fall back to 14.","schema":{"type":"integer","default":14,"minimum":1,"maximum":30}}]}},"/api/earnings":{"get":{"operationId":"earnings","tags":["Earnings"],"summary":"US-equity earnings calendar (stored window: ~7 days back to ~30 days ahead, refreshed daily 05:15 UTC)","description":"Filters the Finnhub earnings calendar file. Without an explicit date or from the response is forward-looking from today (ET); past results with surprises and price reactions live at /api/earnings/results.\n\n`hour` values: `bmo` = before market open, `amc` = after market close, `dmh` = during market hours, empty string = unannounced. `*Actual` fields are `null` until the company reports.\n\n`filters.from` echoes the effective lower bound, i.e. today (ET) when it was defaulted; `filters.tier` echoes the lower-cased tier. Items keep the file order (by date); within a day they are not re-sorted.\n\n`tier` is lower-cased before the check, so MEGA works, but anything outside all / big / mega is a 400 (`valid_tiers` in the body) rather than being treated as all.\n\n```bash\ncurl \"https://news.all-vista.org/api/earnings?symbol=AAPL,MSFT,GOOGL,META,AMZN,NVDA,TSLA&from=2026-04-27&to=2026-05-11\"\n\ncurl \"https://news.all-vista.org/api/earnings?tier=mega&group_by=date\"\n\ncurl \"https://news.all-vista.org/api/earnings?date=2026-04-29&hour=amc\"\n```","responses":{"200":{"description":"EarningsCalendar\n\n```json\n{\n  \"source\": \"finnhub\",\n  \"fetched_at\": \"2026-04-27T05:15:08Z\",\n  \"from_date\": \"2026-04-20\",                  // stored window (file), not the filtered range\n  \"to_date\":   \"2026-05-27\",\n  \"total\": 7,                                 // matches before limit\n  \"returned\": 7,\n  \"filters\": { \"tier\": \"mega\", \"date\": null, \"from\": \"2026-04-27\", \"to\": null, \"symbol\": null, \"hour\": null },\n  \"items\": [\n    {\n      \"date\": \"2026-04-29\",\n      \"symbol\": \"AMZN\",\n      \"hour\": \"amc\",\n      \"quarter\": 1,\n      \"year\": 2026,\n      \"epsActual\": null,\n      \"epsEstimate\": 1.667,\n      \"revenueActual\": null,\n      \"revenueEstimate\": 180846456490\n    }\n  ]\n  // with group_by=date: \"days\": [ { \"date\": \"2026-04-29\", \"count\": 3, \"entries\": [ ... ] } ] replaces \"items\"\n}\n```","content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"source":"finnhub","fetched_at":"2026-04-27T05:15:08Z","from_date":"2026-04-20","to_date":"2026-05-27","total":7,"returned":7,"filters":{"tier":"mega","date":null,"from":"2026-04-27","to":null,"symbol":null,"hour":null},"items":[{"date":"2026-04-29","symbol":"AMZN","hour":"amc","quarter":1,"year":2026,"epsActual":null,"epsEstimate":1.667,"revenueActual":null,"revenueEstimate":180846456490}]}}}},"400":{"description":"Unknown `tier` — body is { \"error\": \"invalid tier\", \"valid_tiers\": [\"all\", \"big\", \"mega\"] }"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"parameters":[{"name":"tier","in":"query","required":false,"description":"Revenue-estimate filter: big = at least $100M, mega = at least $10B; case-insensitive, any other value is rejected with 400.","schema":{"type":"string","enum":["all","big","mega"],"default":"all"}},{"name":"date","in":"query","required":false,"description":"Exact-day filter (YYYY-MM-DD); when set, from no longer defaults to today.","schema":{"type":"string","example":"2026-04-29"}},{"name":"from","in":"query","required":false,"description":"Inclusive lower bound (YYYY-MM-DD). When neither date nor from is given the response starts at today (ET); pass from explicitly to reach the past week.","schema":{"type":"string","default":"today (ET)"}},{"name":"to","in":"query","required":false,"description":"Inclusive upper bound (YYYY-MM-DD).","schema":{"type":"string","example":"2026-05-11"}},{"name":"symbol","in":"query","required":false,"description":"One or more comma-separated tickers (case-insensitive), e.g. AAPL,MSFT.","schema":{"type":"string","description":"Comma-separated string values","example":"AAPL,MSFT"}},{"name":"hour","in":"query","required":false,"description":"Reporting time, exact match on the stored value: bmo = before market open, amc = after market close, dmh = during market hours.","schema":{"type":"string","enum":["bmo","amc","dmh"]}},{"name":"limit","in":"query","required":false,"description":"Cap on returned items (total still reports the unfiltered count).","schema":{"type":"integer","default":5000,"maximum":10000}},{"name":"group_by","in":"query","required":false,"description":"Set to date to receive days[] (each { date, count, entries[] }, ascending by date) instead of a flat items[].","schema":{"type":"string","enum":["date"]}}]}},"/api/earnings/results":{"get":{"operationId":"earningsResults","tags":["Earnings"],"summary":"Recent reports (revenue est ≥ $1B): EPS / revenue surprise and price reaction","description":"Companies with a revenue estimate of at least $1B that reported in the last days ET calendar days (inclusive of today), with surprise percentages, a beat/miss/mixed verdict and the close-to-close move of the session that priced the report.\n\nSorted by date desc, then revenue estimate desc, then symbol. `reactionPct` is `null` while the pricing session is still open (sessions count as closed at 16:15 ET) or when a bar is missing.\n\n`verdict` is `beat` when every available surprise is ≥ 0, `miss` when every one is < 0, `mixed` when EPS and revenue disagree in sign. Results are memoised for 5 minutes on the source file's mtime.\n\n```bash\ncurl \"https://news.all-vista.org/api/earnings/results?days=14\"\n```","responses":{"200":{"description":"ResultsPayload\n\n```json\n{\n  \"generated_at\": \"2026-09-02T12:00:03Z\",\n  \"from\": \"2026-08-26\", \"to\": \"2026-09-02\", \"days\": 7,\n  \"source_fetched_at\": \"2026-09-02T05:15:08Z\",\n  \"summary\": { \"reported\": 38, \"beats\": 27, \"mixed\": 2, \"misses\": 9, \"withReaction\": 35, \"avgReactionPct\": 1.12 },\n  \"items\": [\n    {\n      \"date\": \"2026-08-27\", \"symbol\": \"NVDA\", \"hour\": \"amc\", \"quarter\": 2, \"year\": 2027,\n      \"epsActual\": 1.05, \"epsEstimate\": 1.01, \"revenueActual\": 46743000000, \"revenueEstimate\": 46020000000,\n      \"epsSurprisePct\": 3.96, \"revenueSurprisePct\": 1.57,\n      \"verdict\": \"beat\",             // beat | miss | mixed | null (no surprise computable)\n      \"beat\": true,                  // compatibility flag: true = beat, false = miss or mixed, null = no verdict\n      \"reactionPct\": -0.82,          // close-to-close move of the session that priced the report\n      \"reactionBasis\": \"amc\",        // amc → next session (also used for dmh / unknown); bmo → same session\n      \"reactionSession\": \"2026-08-28\"\n    }\n  ]\n}\n```","content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"generated_at":"2026-09-02T12:00:03Z","from":"2026-08-26","to":"2026-09-02","days":7,"source_fetched_at":"2026-09-02T05:15:08Z","summary":{"reported":38,"beats":27,"mixed":2,"misses":9,"withReaction":35,"avgReactionPct":1.12},"items":[{"date":"2026-08-27","symbol":"NVDA","hour":"amc","quarter":2,"year":2027,"epsActual":1.05,"epsEstimate":1.01,"revenueActual":46743000000,"revenueEstimate":46020000000,"epsSurprisePct":3.96,"revenueSurprisePct":1.57,"verdict":"beat","beat":true,"reactionPct":-0.82,"reactionBasis":"amc","reactionSession":"2026-08-28"}]}}}},"400":{"description":"days must be an integer between 1 and 30"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"description":"failed to build earnings results"}},"parameters":[{"name":"days","in":"query","required":false,"description":"Look-back window in ET calendar days; must be an integer within range (out-of-range or non-integer values are rejected, not clamped).","schema":{"type":"integer","default":7,"minimum":1,"maximum":30}}]}},"/api/earnings-vol":{"get":{"operationId":"earningsVol","tags":["Earnings"],"summary":"Realised vs options-implied move for a symbol's last eight earnings reports","description":"Per-symbol record of how the stock actually moved across each of its recent reports versus what the ATM straddle implied the day before. Either ticker or top=1 is required.\n\nBuilt on the AWS market database from daily stock and option bars (`earnings_vol.json`, ~1.1k symbols: market cap ≥ $10B plus anything reporting within ±30 days). `503` while the file is missing; `record: null` for an uncovered symbol. Powers the card on `/stocks` and the calendar detail pane.\n\n`hour` can also be `dmh` (during market hours) or empty; both use the AMC rule for the realised move. `events` are oldest first, at most 8.\n\n```bash\ncurl \"https://news.all-vista.org/api/earnings-vol?ticker=NVDA\"\n\ncurl \"https://news.all-vista.org/api/earnings-vol?top=1&limit=10\"\n```","responses":{"200":{"description":"EarningsVolResponse (ticker=NVDA)\n\n```json\n{\n  \"symbol\": \"NVDA\",\n  \"generated_at\": \"2026-09-02T18:15:00+00:00\",\n  \"as_of\": \"2026-09-01\",\n  \"record\": {\n    \"n\": 8, \"n_implied\": 8,\n    \"avg_abs_realised\": 6.1,            // % close-to-close across the report (BMO: day vs prior; AMC: next vs day)\n    \"avg_implied\": 7.4,                 // % ATM straddle / spot at T-1, nearest expiry after the report\n    \"ratio\": 0.82,                      // realised ÷ implied\n    \"hit_rate\": 0.25,                   // share of reports where |realised| > implied\n    \"next\": { \"date\": \"2026-11-19\", \"hour\": \"amc\" } | null,\n    \"events\": [ { \"date\": \"2026-08-27\", \"hour\": \"amc\", \"period\": \"Q2 FY27\", \"session\": \"2026-08-28\", \"spot\": 181.2, \"realised_pct\": -3.4, \"implied_pct\": 6.8, \"expiry\": \"2026-08-29\" }, ... ]\n  } | null\n}\n\n// top=1\n{\n  \"generated_at\": \"...\", \"as_of\": \"2026-09-01\",\n  \"coverage\": { \"symbols\": 1120, \"symbols_with_implied\": 980, \"events\": 8400, \"events_with_realised\": 8100, \"events_with_implied\": 6900 },\n  \"items\": [ { \"symbol\": \"SMCI\", \"avg_abs_realised\": 14.2, \"avg_implied\": 9.1, \"gap\": 5.1, \"ratio\": 1.56, \"hit_rate\": 0.75, \"n_implied\": 8, \"next\": { ... } | null }, ... ]   // sorted by |gap| desc\n}\n```","content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"x-example-text":"{\n  \"symbol\": \"NVDA\",\n  \"generated_at\": \"2026-09-02T18:15:00+00:00\",\n  \"as_of\": \"2026-09-01\",\n  \"record\": {\n    \"n\": 8, \"n_implied\": 8,\n    \"avg_abs_realised\": 6.1,            // % close-to-close across the report (BMO: day vs prior; AMC: next vs day)\n    \"avg_implied\": 7.4,                 // % ATM straddle / spot at T-1, nearest expiry after the report\n    \"ratio\": 0.82,                      // realised ÷ implied\n    \"hit_rate\": 0.25,                   // share of reports where |realised| > implied\n    \"next\": { \"date\": \"2026-11-19\", \"hour\": \"amc\" } | null,\n    \"events\": [ { \"date\": \"2026-08-27\", \"hour\": \"amc\", \"period\": \"Q2 FY27\", \"session\": \"2026-08-28\", \"spot\": 181.2, \"realised_pct\": -3.4, \"implied_pct\": 6.8, \"expiry\": \"2026-08-29\" }, ... ]\n  } | null\n}\n\n// top=1\n{\n  \"generated_at\": \"...\", \"as_of\": \"2026-09-01\",\n  \"coverage\": { \"symbols\": 1120, \"symbols_with_implied\": 980, \"events\": 8400, \"events_with_realised\": 8100, \"events_with_implied\": 6900 },\n  \"items\": [ { \"symbol\": \"SMCI\", \"avg_abs_realised\": 14.2, \"avg_implied\": 9.1, \"gap\": 5.1, \"ratio\": 1.56, \"hit_rate\": 0.75, \"n_implied\": 8, \"next\": { ... } | null }, ... ]   // sorted by |gap| desc\n}"}}},"400":{"description":"ticker missing or not matching ^[A-Z][A-Z0-9.-]{0,9}$ (only when top is not 1)"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"description":"earnings vol unavailable (unexpected read error)"},"503":{"description":"earnings vol data unavailable — earnings_vol.json has not been pulled yet"}},"parameters":[{"name":"ticker","in":"query","required":false,"description":"Symbol (case-insensitive, a leading $ is stripped) that must match ^[A-Z][A-Z0-9.-]{0,9}$ after normalisation → { symbol, generated_at, as_of, record | null }.","schema":{"type":"string","example":"NVDA"}},{"name":"top","in":"query","required":false,"description":"Set to 1 to instead list the symbols with the largest |avg realised − avg implied| gap (needs at least 4 paired events) → { generated_at, as_of, coverage, items[] }; takes precedence over ticker.","schema":{"type":"string","enum":["1"]}},{"name":"limit","in":"query","required":false,"description":"Row cap for the top=1 board; non-numeric values fall back to the default.","schema":{"type":"integer","default":20,"minimum":1,"maximum":100}}]}},"/api/brief":{"get":{"operationId":"brief","tags":["Daily Brief"],"summary":"The aggregated Daily Brief behind /brief — markets, calendar, AI digests, smart money, Trump","description":"Read-only aggregation of caches the site already maintains (macro snapshot, today's calendar, latest X and BWE digests, flow recap, Trump impact). No query parameters; the brief is memoised server-side for 60 s and shared across concurrent callers.\n\nRead-only aggregation of existing caches — no LLM call. Each section is `null` when its source is unavailable, so consumers should treat every block as optional (`trump` is always present, possibly with empty `items`). `scripts/push_brief.py` posts this payload to a webhook at 07:30 ET.\n\n```bash\ncurl \"https://news.all-vista.org/api/brief\"\n```","responses":{"200":{"description":"Brief\n\n```json\n{\n  \"generated_at\": \"2026-09-02T11:30:00.000Z\",\n  \"date_et\": \"2026-09-02\",\n  \"glance\": { \"zh\": \"...\", \"en\": \"...\" } | null,        // one-line header\n  \"markets\": { \"source_updated_at\": \"...\", \"headline\": [ { \"key\": \"sp500\", \"label\": \"S&P 500\", \"close\": 6512.3, \"change_pct\": -0.42 } ], \"movers\": [ ... ] } | null,\n  \"calendar\": { \"date\": \"2026-09-02\", \"items\": [ ... ] } | null,   // high-importance events + mega-cap earnings today\n  \"x_digest\":  { \"window\": \"1d\", \"sentiment\": \"bearish\", \"title_en\": \"...\", \"summary_en\": \"...\", \"key_themes\": [...], \"notable_tickers\": [...] } | null,\n  \"bwe_digest\": { ... } | null,\n  \"flow_recap\": { \"date\": \"2026-09-01\", \"title_en\": \"...\", \"summary_en\": \"...\", \"key_tickers\": [\"NVDA\", \"TSLA\"] } | null,\n  \"trump\": { \"since\": \"2026-09-01T11:30:00.000Z\", \"total_posts\": 14, \"analyzed_posts\": 11, \"items\": [ { \"id\": \"...\", \"headline_en\": \"...\", \"impact_direction\": \"bearish\", \"impact_magnitude\": 2, \"suggested_symbols\": [\"SPY\"] } ] }\n}\n```","content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"x-example-text":"{\n  \"generated_at\": \"2026-09-02T11:30:00.000Z\",\n  \"date_et\": \"2026-09-02\",\n  \"glance\": { \"zh\": \"...\", \"en\": \"...\" } | null,        // one-line header\n  \"markets\": { \"source_updated_at\": \"...\", \"headline\": [ { \"key\": \"sp500\", \"label\": \"S&P 500\", \"close\": 6512.3, \"change_pct\": -0.42 } ], \"movers\": [ ... ] } | null,\n  \"calendar\": { \"date\": \"2026-09-02\", \"items\": [ ... ] } | null,   // high-importance events + mega-cap earnings today\n  \"x_digest\":  { \"window\": \"1d\", \"sentiment\": \"bearish\", \"title_en\": \"...\", \"summary_en\": \"...\", \"key_themes\": [...], \"notable_tickers\": [...] } | null,\n  \"bwe_digest\": { ... } | null,\n  \"flow_recap\": { \"date\": \"2026-09-01\", \"title_en\": \"...\", \"summary_en\": \"...\", \"key_tickers\": [\"NVDA\", \"TSLA\"] } | null,\n  \"trump\": { \"since\": \"2026-09-01T11:30:00.000Z\", \"total_posts\": 14, \"analyzed_posts\": 11, \"items\": [ { \"id\": \"...\", \"headline_en\": \"...\", \"impact_direction\": \"bearish\", \"impact_magnitude\": 2, \"suggested_symbols\": [\"SPY\"] } ] }\n}"}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"description":"Brief unavailable"}},"x-cache":"no-store"}},"/api/china/market":{"get":{"operationId":"chinaMarket","tags":["China"],"summary":"A-share indices, industry performance, market breadth, top movers and Stock Connect turnover","description":"Both payloads are pre-computed files (china_market.json from the AWS job, hsgt.json from the Finland TuShare fetch); nothing is queried per request. No query parameters.\n\nPrices in CNY. Northbound figures are gross turnover — HKEX stopped publishing daily net buying in Aug 2024. `503` only while neither file exists yet; when just one is missing its block is `null` and the response is still `200`.\n\n```bash\ncurl \"https://news.all-vista.org/api/china/market\"\n```","responses":{"200":{"description":"ChinaMarketBundle\n\n```json\n{\n  \"market\": {\n    \"session_date\": \"2026-09-01\",\n    \"indices\":    [ { \"code\": \"000001.SH\", \"name_en\": \"SSE Composite\", \"close\": 3851.2, \"pct\": 0.42, \"amount_bn\": 944.1, \"spark\": [ ... 60 ] } ],\n    \"industries\": [ { \"industry\": \"半导体\", \"name_en\": \"Semiconductors\", \"count\": 162, \"med_pct\": 1.8, \"adv\": 120, \"dec\": 40, \"amount_bn\": 210.3, \"top\": { ... } } ],\n    \"breadth\":    { \"total\": 5312, \"advancers\": 3105, \"decliners\": 1980, \"limit_up\": 61, \"limit_down\": 7, \"st_count\": 168, \"turnover_bn\": 2054.0, \"buckets\": [ ... ], \"by_board\": [ ... ] } | null,\n    \"movers\":     { \"by_amount\": [ ... ], \"gainers\": [ ... ], \"losers\": [ ... ] },\n    \"generated_at\": \"2026-09-02T13:49:00+00:00\"\n  } | null,\n  \"hsgt\": {\n    \"session_date\": \"2026-09-01\",\n    \"flows\": [ { \"date\": \"2026-09-01\", \"north\": 1350.2, \"hgt\": 640.1, \"sgt\": 710.1, \"south\": 480.3 } ],   // CNY 100m, 60 sessions\n    \"top10\": { \"sh\": [ { \"symbol\": \"600519.SH\", \"name\": \"贵州茅台\", \"close\": 1480.0, \"pct\": 0.9, \"amount_m\": 2130.5 } ], \"sz\": [ ... ] },\n    \"generated_at\": \"...\"\n  } | null,\n  \"ages\": { \"market_hours\": 4.5, \"hsgt_hours\": 4.6 }   // hours since each file's generated_at; null when that file is missing\n}\n```","content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"x-example-text":"{\n  \"market\": {\n    \"session_date\": \"2026-09-01\",\n    \"indices\":    [ { \"code\": \"000001.SH\", \"name_en\": \"SSE Composite\", \"close\": 3851.2, \"pct\": 0.42, \"amount_bn\": 944.1, \"spark\": [ ... 60 ] } ],\n    \"industries\": [ { \"industry\": \"半导体\", \"name_en\": \"Semiconductors\", \"count\": 162, \"med_pct\": 1.8, \"adv\": 120, \"dec\": 40, \"amount_bn\": 210.3, \"top\": { ... } } ],\n    \"breadth\":    { \"total\": 5312, \"advancers\": 3105, \"decliners\": 1980, \"limit_up\": 61, \"limit_down\": 7, \"st_count\": 168, \"turnover_bn\": 2054.0, \"buckets\": [ ... ], \"by_board\": [ ... ] } | null,\n    \"movers\":     { \"by_amount\": [ ... ], \"gainers\": [ ... ], \"losers\": [ ... ] },\n    \"generated_at\": \"2026-09-02T13:49:00+00:00\"\n  } | null,\n  \"hsgt\": {\n    \"session_date\": \"2026-09-01\",\n    \"flows\": [ { \"date\": \"2026-09-01\", \"north\": 1350.2, \"hgt\": 640.1, \"sgt\": 710.1, \"south\": 480.3 } ],   // CNY 100m, 60 sessions\n    \"top10\": { \"sh\": [ { \"symbol\": \"600519.SH\", \"name\": \"贵州茅台\", \"close\": 1480.0, \"pct\": 0.9, \"amount_m\": 2130.5 } ], \"sz\": [ ... ] },\n    \"generated_at\": \"...\"\n  } | null,\n  \"ages\": { \"market_hours\": 4.5, \"hsgt_hours\": 4.6 }   // hours since each file's generated_at; null when that file is missing\n}"}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"description":"Failed to read China market data"},"503":{"description":"China market data not available yet — neither china_market.json nor hsgt.json exists (body also carries the empty bundle)"}},"x-cache":"public, max-age=300, stale-while-revalidate=600"}},"/api/china/ah":{"get":{"operationId":"chinaAh","tags":["China"],"summary":"A/H premium for ~40 dual listings and ADR/H premium for ~15 US-listed Chinese names","description":"Joins the two AWS job files (A + H closes, ADR closes + FRED DEXCHUS) into one snapshot with an equal-weight premium index, sector medians and per-pair rows. Memoised until one of the files changes on disk. No query parameters.\n\nPremium = A price in HKD ÷ H price − 1 (CNY→HKD via FRED DEXCHUS and the 7.80 peg); ADR premium uses the ADS ratio. Joined from the CN/HK and US job files (`ah_pairs_cnhk.json`, `ah_pairs_us.json`); `available: false` with a `reason` (`no_cnhk`, `no_us`, `no_fx`, `no_sessions`) until both exist.\n\nMissing data never yields a non-200 status — check `available` and `stale` instead. `fx` and `index` are `null` in the unavailable case; `sectors`, `pairs` and `adr` are empty arrays.\n\n```bash\ncurl \"https://news.all-vista.org/api/china/ah\"\n```","responses":{"200":{"description":"AhSnapshot\n\n```json\n{\n  \"available\": true,                       // false (with \"reason\") until both job files and an FX rate exist\n  \"generated_at\": \"2026-09-02T13:57:00+00:00\",\n  \"stale\": false,                          // true when either source file is older than 72 h\n  \"as_of\": \"2026-09-01\",\n  \"fx\": { \"cny_per_usd\": 7.12, \"cny_per_hkd\": 0.913, \"hkd_per_usd\": 7.80, \"date\": \"2026-08-29\" },\n  \"index\": { \"latest\": 41.9, \"date\": \"2026-09-01\", \"chg1d\": -0.3, \"chg20d\": 1.8, \"min\": 35.1, \"max\": 47.8, \"median\": 40.2, \"pairs_used\": 38,\n             \"series\": [ { \"d\": \"2026-03-10\", \"v\": 38.2 }, ... 120 ] },   // equal-weight A/H premium, %\n  \"sectors\": [ { \"sector\": \"Banks\", \"count\": 9, \"median\": 27.4 } ],\n  \"pairs\": [ { \"a\": \"601398.SH\", \"h\": \"01398.HK\", \"name_en\": \"ICBC\", \"sector\": \"Banks\", \"date\": \"2026-09-01\", \"a_cny\": 7.21, \"h_hkd\": 5.90, \"a_hkd\": 7.89, \"premium\": 33.8, \"chg1d\": ..., \"chg20d\": ..., \"spark\": [ ... ] } ],\n  \"adr\":   [ { \"adr\": \"BABA\", \"h\": \"09988.HK\", \"ratio\": 8, \"adr_usd\": 132.1, \"h_hkd\": 128.9, \"adr_hkd\": 128.8, \"premium\": -0.34, ... } ],\n  \"adr_skipped\": [ { \"adr\": \"...\", \"h\": \"...\", \"reason\": \"ratio_unverified\" } ],   // ratio_unverified | no_adr_bars | no_h_bars | no_overlap\n  \"dropped\": []\n}\n```","content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"x-example-text":"{\n  \"available\": true,                       // false (with \"reason\") until both job files and an FX rate exist\n  \"generated_at\": \"2026-09-02T13:57:00+00:00\",\n  \"stale\": false,                          // true when either source file is older than 72 h\n  \"as_of\": \"2026-09-01\",\n  \"fx\": { \"cny_per_usd\": 7.12, \"cny_per_hkd\": 0.913, \"hkd_per_usd\": 7.80, \"date\": \"2026-08-29\" },\n  \"index\": { \"latest\": 41.9, \"date\": \"2026-09-01\", \"chg1d\": -0.3, \"chg20d\": 1.8, \"min\": 35.1, \"max\": 47.8, \"median\": 40.2, \"pairs_used\": 38,\n             \"series\": [ { \"d\": \"2026-03-10\", \"v\": 38.2 }, ... 120 ] },   // equal-weight A/H premium, %\n  \"sectors\": [ { \"sector\": \"Banks\", \"count\": 9, \"median\": 27.4 } ],\n  \"pairs\": [ { \"a\": \"601398.SH\", \"h\": \"01398.HK\", \"name_en\": \"ICBC\", \"sector\": \"Banks\", \"date\": \"2026-09-01\", \"a_cny\": 7.21, \"h_hkd\": 5.90, \"a_hkd\": 7.89, \"premium\": 33.8, \"chg1d\": ..., \"chg20d\": ..., \"spark\": [ ... ] } ],\n  \"adr\":   [ { \"adr\": \"BABA\", \"h\": \"09988.HK\", \"ratio\": 8, \"adr_usd\": 132.1, \"h_hkd\": 128.9, \"adr_hkd\": 128.8, \"premium\": -0.34, ... } ],\n  \"adr_skipped\": [ { \"adr\": \"...\", \"h\": \"...\", \"reason\": \"ratio_unverified\" } ],   // ratio_unverified | no_adr_bars | no_h_bars | no_overlap\n  \"dropped\": []\n}"}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"description":"A/H premium unavailable (unexpected read error)"}}}},"/api/china/pro":{"get":{"operationId":"chinaPro","tags":["China"],"summary":"Public-fund heavy holdings (latest two report periods) and convertible bonds","description":"Serves cn_pro.json produced by the AWS CN/HK job. Both sections are returned by default; section restricts the payload to one of them.\n\nMoney in 亿元 (CNY 100m), shares in 万股; premiums and returns are fractions. `503` while `cn_pro.json` has not been produced; `400` for an unknown `section`.\n\n```bash\ncurl \"https://news.all-vista.org/api/china/pro\"\n\ncurl \"https://news.all-vista.org/api/china/pro?section=cbs\"\n```","responses":{"200":{"description":"CnProResponse\n\n```json\n{\n  \"generated_at\": \"2026-09-02T14:07:00+00:00\",\n  \"age_hours\": 4.2,\n  \"dates\": { \"trade_date\": \"2026-09-01\", \"fund_period\": \"2026-06-30\", \"fund_prev_period\": \"2026-03-31\" },\n  \"funds\": {                                       // omitted when section=cbs\n    \"period\": \"2026-06-30\", \"prev_period\": \"2026-03-31\",\n    \"universes\": { \"all\": { \"top\": [ { \"code\": \"600519.SH\", \"name\": \"贵州茅台\", \"industry\": \"白酒\", \"funds\": 1412, \"mkv\": 1580.2, \"d_funds\": -38, \"d_mkv\": -92.1 } ], \"increases\": [ ... ], \"decreases\": [ ... ], \"n_funds\": 8123, \"total_mkv\": 27110.5 }, \"active\": { ... } },\n    \"top_funds\": [ { \"code\": \"005827.OF\", \"name\": \"...\", \"top10_mkv\": 210.4, \"ret_1m\": 3.1, \"ret_3m\": 9.8 } ]\n  },\n  \"cbs\": {                                         // omitted when section=funds\n    \"session_date\": \"2026-09-01\",\n    \"rows\": [ { \"code\": \"113050.SH\", \"name\": \"南银转债\", \"stk_code\": \"601009.SH\", \"close\": 121.3, \"pct_chg\": 0.4, \"conv_price\": 8.02, \"stk_close\": 9.10, \"premium\": 6.9, \"ytm\": -1.2, \"call\": { \"type\": \"公告提示强赎\", \"call_date\": \"2026-09-20\" } | null, ... } ],\n    ...\n  }\n}\n```","content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"x-example-text":"{\n  \"generated_at\": \"2026-09-02T14:07:00+00:00\",\n  \"age_hours\": 4.2,\n  \"dates\": { \"trade_date\": \"2026-09-01\", \"fund_period\": \"2026-06-30\", \"fund_prev_period\": \"2026-03-31\" },\n  \"funds\": {                                       // omitted when section=cbs\n    \"period\": \"2026-06-30\", \"prev_period\": \"2026-03-31\",\n    \"universes\": { \"all\": { \"top\": [ { \"code\": \"600519.SH\", \"name\": \"贵州茅台\", \"industry\": \"白酒\", \"funds\": 1412, \"mkv\": 1580.2, \"d_funds\": -38, \"d_mkv\": -92.1 } ], \"increases\": [ ... ], \"decreases\": [ ... ], \"n_funds\": 8123, \"total_mkv\": 27110.5 }, \"active\": { ... } },\n    \"top_funds\": [ { \"code\": \"005827.OF\", \"name\": \"...\", \"top10_mkv\": 210.4, \"ret_1m\": 3.1, \"ret_3m\": 9.8 } ]\n  },\n  \"cbs\": {                                         // omitted when section=funds\n    \"session_date\": \"2026-09-01\",\n    \"rows\": [ { \"code\": \"113050.SH\", \"name\": \"南银转债\", \"stk_code\": \"601009.SH\", \"close\": 121.3, \"pct_chg\": 0.4, \"conv_price\": 8.02, \"stk_close\": 9.10, \"premium\": 6.9, \"ytm\": -1.2, \"call\": { \"type\": \"公告提示强赎\", \"call_date\": \"2026-09-20\" } | null, ... } ],\n    ...\n  }\n}"}}},"400":{"description":"section must be 'funds' or 'cbs'"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"description":"cn_pro unavailable (unexpected read error)"},"503":{"description":"cn_pro data not available yet — the AWS job has not produced cn_pro.json"}},"parameters":[{"name":"section","in":"query","required":false,"description":"Return one section only; an empty value is treated as no filter.","schema":{"type":"string","enum":["funds","cbs"]}}]}},"/api/stock-search":{"get":{"operationId":"stockSearch","tags":["Stocks"],"summary":"Search US stocks & ETFs by ticker or name","description":"Matches the query against the ticker universe: exact ticker first, then ticker prefixes, then company names containing the query. Within each tier larger market caps come first.\n\nA missing or empty `q` returns `200` with `results: []` rather than an error.\n\n```bash\ncurl \"https://news.all-vista.org/api/stock-search?q=nvidia&limit=5\"\n```","responses":{"200":{"description":"SearchResults\n\n```json\n{\n  \"results\": [\n    {\n      \"ticker\": \"NVDA\",\n      \"name\": \"NVIDIA Corp\",\n      \"market_cap\": 2800000000000,     // null when unknown\n      \"primary_exchange\": \"XNAS\",\n      \"type\": \"CS\",                    // CS, ETF, ...\n      \"sic_description\": \"Semiconductors\",\n      \"market_cap_tier\": \"mega\"\n    }\n  ]\n}\n```","content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"results":[{"ticker":"NVDA","name":"NVIDIA Corp","market_cap":2800000000000,"primary_exchange":"XNAS","type":"CS","sic_description":"Semiconductors","market_cap_tier":"mega"}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"parameters":[{"name":"q","in":"query","required":true,"description":"Search query (min 1 character); case-insensitive.","schema":{"type":"string","example":"nvidia"}},{"name":"limit","in":"query","required":false,"description":"Max results, clamped to 1–20; 0 or a non-numeric value falls back to 10.","schema":{"type":"integer","default":10,"minimum":1,"maximum":20}}]}},"/api/stock-ohlc":{"get":{"operationId":"stockOhlc","tags":["Stocks"],"summary":"Daily OHLCV candlestick bars for a ticker","description":"Reads the local daily aggregates for every weekday in the range (both ends inclusive); weekends and days without a bar are skipped.\n\nAn unknown ticker or a range with no data returns `200` with an empty `bars` array (no `404`).\n\n```bash\ncurl \"https://news.all-vista.org/api/stock-ohlc?ticker=AAPL&start=2026-01-01\"\n```","responses":{"200":{"description":"OHLCResponse\n\n```json\n{\n  \"ticker\": \"NVDA\",\n  \"bars\": [\n    {\n      \"date\": \"2026-03-17\",\n      \"open\": 142.50,\n      \"high\": 145.20,\n      \"low\": 141.80,\n      \"close\": 144.90,\n      \"volume\": 38500000\n    }\n  ]\n}\n```","content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"ticker":"NVDA","bars":[{"date":"2026-03-17","open":142.5,"high":145.2,"low":141.8,"close":144.9,"volume":38500000}]}}}},"400":{"description":"ticker missing"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"parameters":[{"name":"ticker","in":"query","required":true,"description":"Stock/ETF ticker symbol (case-insensitive; echoed back upper-cased).","schema":{"type":"string","example":"AAPL"}},{"name":"start","in":"query","required":false,"description":"Start date, YYYY-MM-DD (inclusive).","schema":{"type":"string","default":"180 days ago"}},{"name":"end","in":"query","required":false,"description":"End date, YYYY-MM-DD (inclusive).","schema":{"type":"string","default":"today (UTC)"}}]}},"/api/stock-news":{"get":{"operationId":"stockNews","tags":["Stocks"],"summary":"X/Twitter posts mentioning a stock (matched by ticker, $tag, or company name)","description":"Looks the ticker up in the server-side news index built from every X post (cashtag, then bare symbol, then company name, with false-positive guards). Posts are newest first with exact-duplicate texts removed.\n\nThe full match list is returned (no paging). An unknown ticker returns `200` with an empty `news` array.\n\n```bash\ncurl \"https://news.all-vista.org/api/stock-news?ticker=TSLA\"\n```","responses":{"200":{"description":"StockNews\n\n```json\n{\n  \"ticker\": \"TSLA\",\n  \"news\": [\n    {\n      \"id\": \"x_20320980...\",\n      \"timestamp\": \"2026-03-15T12:00:00.000Z\",\n      \"raw_text\": \"$TSLA deliveries beat expectations...\",\n      \"author\": \"CNBC\",\n      \"matchScore\": 1.0,          // 1.0 = cashtag, 0.9 = bare symbol, 0.8 = company name\n      \"matchReason\": \"$TSLA\",\n      \"matchTier\": \"cashtag\"      // \"cashtag\" | \"symbol\" | \"name\"\n    }\n  ]\n}\n```","content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"ticker":"TSLA","news":[{"id":"x_20320980...","timestamp":"2026-03-15T12:00:00.000Z","raw_text":"$TSLA deliveries beat expectations...","author":"CNBC","matchScore":1,"matchReason":"$TSLA","matchTier":"cashtag"}]}}}},"400":{"description":"ticker missing"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"parameters":[{"name":"ticker","in":"query","required":true,"description":"Stock ticker symbol; dual share classes share one list (GOOGL resolves to GOOG).","schema":{"type":"string","example":"TSLA"}}]}},"/api/stock-returns":{"get":{"operationId":"stockReturns","tags":["Stocks"],"summary":"Forward returns (T+1, T+3, T+5 trading days) from an event date","description":"Closes on the next 1, 3 and 5 trading days after the event date, expressed as percentage change from the event-day close.\n\nWhen `date` is not a trading day (or the ticker has no bar that day) the route still answers `200` with `eventClose: null` and `t1`/`t3`/`t5` all `null`.\n\n`returnPct` is a percentage (0.90 = +0.90%). A horizon is `null` while fewer than that many trading days have elapsed since the event.\n\n```bash\ncurl \"https://news.all-vista.org/api/stock-returns?ticker=NVDA&date=2026-03-17\"\n```","responses":{"200":{"description":"ForwardReturns\n\n```json\n{\n  \"eventDate\": \"2026-03-17\",\n  \"eventClose\": 144.90,\n  \"t1\": { \"date\": \"2026-03-18\", \"close\": 146.20, \"returnPct\": 0.90 },\n  \"t3\": { \"date\": \"2026-03-20\", \"close\": 143.50, \"returnPct\": -0.97 },\n  \"t5\": { \"date\": \"2026-03-24\", \"close\": 148.00, \"returnPct\": 2.14 }   // null until 5 trading days have elapsed\n}\n```","content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"eventDate":"2026-03-17","eventClose":144.9,"t1":{"date":"2026-03-18","close":146.2,"returnPct":0.9},"t3":{"date":"2026-03-20","close":143.5,"returnPct":-0.97},"t5":{"date":"2026-03-24","close":148,"returnPct":2.14}}}}},"400":{"description":"ticker or date missing"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"parameters":[{"name":"ticker","in":"query","required":true,"description":"Stock ticker symbol (case-insensitive).","schema":{"type":"string","example":"NVDA"}},{"name":"date","in":"query","required":true,"description":"Event date, YYYY-MM-DD (must be a trading day with a bar for the ticker).","schema":{"type":"string","example":"2026-03-17"}}]}},"/api/stock-analysis":{"post":{"operationId":"stockAnalysis","tags":["Stocks"],"summary":"AI-generated equity analysis with technicals + news synthesis","description":"Builds a prompt from the ticker's OHLC summary, the X posts matched inside the window and (when on file) the SEC fundamentals brief, then returns the model's ~400-word write-up as plain text.\n\n⚠ Triggers an LLM call (~120s; the route allows up to 300s).\n\nResults are memoised in process memory per `ticker`/`startDate`/`endDate`/`locale` (last 50 keys); a hit returns immediately with `X-AI-Provider: cache`.\n\nLLM-backed: counts against the `llm` rate limit and returns `X-AI-Provider` / `X-AI-Model` headers.\n\n```bash\ncurl -X POST https://news.all-vista.org/api/stock-analysis -H \"Content-Type: application/json\" -d '{\"ticker\":\"NVDA\",\"locale\":\"zh\"}'\n```","responses":{"200":{"description":"text/plain\n\n```json\n// Content-Type: text/plain; charset=utf-8\n// X-AI-Provider: <provider>   X-AI-Model: <model>   (\"cache\" / \"cached\" on a memoised hit)\n\nNVDA has spent the last six months in a steady uptrend ...\n(~400 words covering technical setup, news impact, 1-2 week outlook and key risk factors)\n```","content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"x-example-text":"// Content-Type: text/plain; charset=utf-8\n// X-AI-Provider: <provider>   X-AI-Model: <model>   (\"cache\" / \"cached\" on a memoised hit)\n\nNVDA has spent the last six months in a steady uptrend ...\n(~400 words covering technical setup, news impact, 1-2 week outlook and key risk factors)"}}},"400":{"description":"ticker missing (plain-text body)"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"ticker":{"type":"string","example":"NVDA","description":"Stock ticker symbol."},"startDate":{"type":"string","default":"180 days ago","description":"Analysis start date, YYYY-MM-DD."},"endDate":{"type":"string","default":"today (UTC)","description":"Analysis end date, YYYY-MM-DD."},"locale":{"type":"string","enum":["en","zh"],"default":"en","description":"Output language; any value other than zh falls back to en."}},"required":["ticker"]}}}},"x-llm":true,"x-cache":"no-cache"}},"/api/ticker-hub":{"get":{"operationId":"tickerHub","tags":["Stocks"],"summary":"One symbol across every module — earnings, flow, dark pool, congress, Trump, X mentions","description":"Server-side aggregate of everything the site knows about a symbol. Each section is built independently and is `null` when the module has nothing for the symbol OR its upstream failed; only `flow`, `darkpool` and `congress` carry `fetchedAt`/`stale` to tell the two apart. `earnings` is `null` whenever the next report is outside the stored earnings window (about 7 days back to 30 days ahead), which is the common case for a name that reported recently.\n\nEach block is `null` when the module has nothing for the symbol or its source failed; `mentions` has no staleness flag of its own, so check `/api/health` (`x` file age) before trusting a low `count24h`. Powers the strip at the top of `/stocks?ticker=`.\n\nThe assembled hub is memoised for 2 minutes per symbol; a hub with a failed upstream section (`flow`, `darkpool` or `congress` null) is rebuilt after 20s. Each Unusual Whales call times out after 8s, so a slow upstream nulls its own block instead of failing the request.\n\n```bash\ncurl \"https://news.all-vista.org/api/ticker-hub?ticker=NVDA\"\n```","responses":{"200":{"description":"TickerHub\n\n```json\n{\n  \"symbol\": \"NVDA\",\n  \"generatedAt\": \"2026-09-02T12:00:03Z\",\n  \"todayEt\": \"2026-09-02\",\n  \"info\":       { \"ticker\": \"NVDA\", \"name\": \"NVIDIA Corp\", \"exchange\": \"XNAS\", \"type\": \"CS\", \"marketCap\": 4.4e12, \"marketCapTier\": \"Mega Cap (>$100B)\", \"industry\": \"SEMICONDUCTORS & RELATED DEVICES\" } | null,   // tier labels are display strings, not enums\n  \"earnings\":   { \"date\": \"2026-09-24\", \"hour\": \"amc\", \"daysUntil\": 15, \"quarter\": 3, \"year\": 2026, \"epsEstimate\": 1.21, \"revenueEstimate\": 54.1e9, \"impliedMovePct\": 6.4 } | null,   // null unless the next report is within ~30 days\n  \"flow\":       { \"date\": \"2026-09-02\", \"count\": 42, \"highSignalCount\": 5, \"capped\": false, \"callPremium\": 18400000, \"putPremium\": 6100000,\n                  \"top\": [ { \"id\": \"...\", \"type\": \"call\", \"strike\": 190, \"expiry\": \"2026-09-19\", \"premium\": 2100000, \"isSweep\": true, \"isFloor\": false, \"highSignal\": true, \"createdAt\": \"...\" } ],\n                  \"fetchedAt\": \"...\", \"stale\": false } | null,\n  \"darkpool\":   { \"date\": \"2026-09-02\", \"count\": 17, \"totalPremium\": 210000000, \"minPremium\": 1000000,\n                  \"top\": [ { \"price\": 181.2, \"size\": 172000, \"premium\": 31200000, \"executedAt\": \"...\" } ], \"fetchedAt\": \"...\", \"stale\": false } | null,\n  \"congress\":   { \"count\": 4, \"latest\": [ { \"name\": \"...\", \"memberType\": \"house\", \"txnType\": \"Purchase\", \"amounts\": \"$15K–$50K\", \"transactionDate\": \"...\", \"filedAtDate\": \"...\" } ],\n                  \"fetchedAt\": \"...\", \"stale\": false } | null,\n  \"trumpPosts\": { \"count90d\": 2, \"latest\": [ { \"id\": \"...\", \"created_at\": \"...\", \"headline\": \"...\", \"headline_en\": \"...\", \"impact_direction\": \"bullish\", \"url\": \"...\" } ] } | null,\n  \"mentions\":   { \"daily\": [ { \"date\": \"2026-08-20\", \"count\": 12 }, ... ],   // 14 ET days, oldest first\n                  \"count24h\": 9, \"count14d\": 131,\n                  \"latest\": [ { \"id\": \"...\", \"author\": \"...\", \"text\": \"...\", \"timestamp\": \"...\" } ] } | null,\n  \"optionsHistory\": { \"ticker\": \"NVDA\", \"date\": \"2026-09-01\", \"pc_ratio\": 0.63, \"pct_pc\": 41, \"total_vol\": 2100000, \"pct_vol\": 55, \"median_pc\": 0.71 } | null\n}\n```","content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"x-example-text":"{\n  \"symbol\": \"NVDA\",\n  \"generatedAt\": \"2026-09-02T12:00:03Z\",\n  \"todayEt\": \"2026-09-02\",\n  \"info\":       { \"ticker\": \"NVDA\", \"name\": \"NVIDIA Corp\", \"exchange\": \"XNAS\", \"type\": \"CS\", \"marketCap\": 4.4e12, \"marketCapTier\": \"Mega Cap (>$100B)\", \"industry\": \"SEMICONDUCTORS & RELATED DEVICES\" } | null,   // tier labels are display strings, not enums\n  \"earnings\":   { \"date\": \"2026-09-24\", \"hour\": \"amc\", \"daysUntil\": 15, \"quarter\": 3, \"year\": 2026, \"epsEstimate\": 1.21, \"revenueEstimate\": 54.1e9, \"impliedMovePct\": 6.4 } | null,   // null unless the next report is within ~30 days\n  \"flow\":       { \"date\": \"2026-09-02\", \"count\": 42, \"highSignalCount\": 5, \"capped\": false, \"callPremium\": 18400000, \"putPremium\": 6100000,\n                  \"top\": [ { \"id\": \"...\", \"type\": \"call\", \"strike\": 190, \"expiry\": \"2026-09-19\", \"premium\": 2100000, \"isSweep\": true, \"isFloor\": false, \"highSignal\": true, \"createdAt\": \"...\" } ],\n                  \"fetchedAt\": \"...\", \"stale\": false } | null,\n  \"darkpool\":   { \"date\": \"2026-09-02\", \"count\": 17, \"totalPremium\": 210000000, \"minPremium\": 1000000,\n                  \"top\": [ { \"price\": 181.2, \"size\": 172000, \"premium\": 31200000, \"executedAt\": \"...\" } ], \"fetchedAt\": \"...\", \"stale\": false } | null,\n  \"congress\":   { \"count\": 4, \"latest\": [ { \"name\": \"...\", \"memberType\": \"house\", \"txnType\": \"Purchase\", \"amounts\": \"$15K–$50K\", \"transactionDate\": \"...\", \"filedAtDate\": \"...\" } ],\n                  \"fetchedAt\": \"...\", \"stale\": false } | null,\n  \"trumpPosts\": { \"count90d\": 2, \"latest\": [ { \"id\": \"...\", \"created_at\": \"...\", \"headline\": \"...\", \"headline_en\": \"...\", \"impact_direction\": \"bullish\", \"url\": \"...\" } ] } | null,\n  \"mentions\":   { \"daily\": [ { \"date\": \"2026-08-20\", \"count\": 12 }, ... ],   // 14 ET days, oldest first\n                  \"count24h\": 9, \"count14d\": 131,\n                  \"latest\": [ { \"id\": \"...\", \"author\": \"...\", \"text\": \"...\", \"timestamp\": \"...\" } ] } | null,\n  \"optionsHistory\": { \"ticker\": \"NVDA\", \"date\": \"2026-09-01\", \"pc_ratio\": 0.63, \"pct_pc\": 41, \"total_vol\": 2100000, \"pct_vol\": 55, \"median_pc\": 0.71 } | null\n}"}}},"400":{"description":"ticker missing or not matching ^[A-Z][A-Z0-9.-]{0,9}$"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"description":"ticker hub unavailable"}},"parameters":[{"name":"ticker","in":"query","required":true,"description":"Symbol matching ^[A-Z][A-Z0-9.-]{0,9}$ (leading $ and lower case are normalised).","schema":{"type":"string","example":"NVDA"}}]}},"/api/fundamentals":{"get":{"operationId":"fundamentals","tags":["Stocks"],"summary":"SEC company facts per symbol — 12 quarters + 5 fiscal years, TTM, margins, valuation","description":"Serves the per-symbol SEC fundamentals JSON written by the AWS job (a plain file read memoised on mtime; nothing queries the market database per request). Money is in whole currency units, ratios are decimal fractions.\n\nBuilt twice daily on the AWS market database from the SEC companyfacts table (`fundamentals/<TICKER>.json`, ~5,100 symbols). Powers the fundamentals panel on `/stocks` and the \"SEC fundamentals\" section of `POST /api/stock-analysis`.\n\n`pe_ttm` is `null` when TTM EPS is zero or negative or when statements are not in USD; `net_cash` = cash + short-term investments - total debt.\n\n```bash\ncurl \"https://news.all-vista.org/api/fundamentals?ticker=NVDA\"\n```","responses":{"200":{"description":"Fundamentals\n\n```json\n{\n  \"symbol\": \"NVDA\",\n  \"name\": \"NVIDIA Corp\",\n  \"entity_name\": \"NVIDIA CORP\",\n  \"cik\": \"0001045810\",\n  \"sic\": \"3674\",\n  \"currency\": \"USD\",                 // statement currency; valuation multiples only when USD\n  \"mixed_currency\": false, \"has_ifrs\": false,\n  \"security_type\": \"CS\", \"adr\": false,   // ADR: market cap + multiples omitted (ADS ratio unknown)\n  \"snapshot_date\": \"2026-09-01\",\n  \"as_of\": \"2026-09-01\",\n  \"latest_filed\": \"2026-08-27\",\n  \"price\": { \"close\": 181.20, \"session\": \"2026-09-01\" },\n  \"shares\": { \"outstanding\": 24.3e9, \"as_of\": \"2026-08-22\", \"source\": \"dei\" },     // source: \"dei\" | \"diluted_wavg\" | null\n  \"ttm\": { \"revenue\": 187e9, \"gross_margin\": 0.73, \"operating_margin\": 0.61, \"net_margin\": 0.54, \"fcf\": 78e9, \"quarters\": [ ...4 period ends ], ... } | null,\n  \"valuation\": { \"market_cap\": 4.4e12, \"pe_ttm\": 44.1, \"ps_ttm\": 23.5, \"pb\": 41.2, \"fcf_yield\": 0.018, \"eps_ttm\": 4.11, \"revenue_ttm\": 187e9 },\n  \"quarters\": [ { \"label\": \"Q2 FY27\", \"end\": \"2026-07-26\", \"fy\": 2027, \"fq\": 2, \"filed\": \"2026-08-27\", \"derived\": false, \"revenue\": ..., \"net_income\": ..., \"eps_diluted\": ..., \"revenue_yoy\": 0.56, ... } ],   // <= 12, oldest first\n  \"annual\":   [ { \"label\": \"FY26\", \"fq\": null, ... } ],                                                                                                              // <= 5, oldest first\n  \"balance\":  { \"end\": \"2026-07-26\", \"cash\": ..., \"debt_total\": ..., \"net_cash\": ..., \"total_assets\": ..., \"total_liabilities\": ..., \"equity\": ... } | null,\n  \"generated_at\": \"2026-09-02T18:05:58+00:00\"\n}\n```","content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"x-example-text":"{\n  \"symbol\": \"NVDA\",\n  \"name\": \"NVIDIA Corp\",\n  \"entity_name\": \"NVIDIA CORP\",\n  \"cik\": \"0001045810\",\n  \"sic\": \"3674\",\n  \"currency\": \"USD\",                 // statement currency; valuation multiples only when USD\n  \"mixed_currency\": false, \"has_ifrs\": false,\n  \"security_type\": \"CS\", \"adr\": false,   // ADR: market cap + multiples omitted (ADS ratio unknown)\n  \"snapshot_date\": \"2026-09-01\",\n  \"as_of\": \"2026-09-01\",\n  \"latest_filed\": \"2026-08-27\",\n  \"price\": { \"close\": 181.20, \"session\": \"2026-09-01\" },\n  \"shares\": { \"outstanding\": 24.3e9, \"as_of\": \"2026-08-22\", \"source\": \"dei\" },     // source: \"dei\" | \"diluted_wavg\" | null\n  \"ttm\": { \"revenue\": 187e9, \"gross_margin\": 0.73, \"operating_margin\": 0.61, \"net_margin\": 0.54, \"fcf\": 78e9, \"quarters\": [ ...4 period ends ], ... } | null,\n  \"valuation\": { \"market_cap\": 4.4e12, \"pe_ttm\": 44.1, \"ps_ttm\": 23.5, \"pb\": 41.2, \"fcf_yield\": 0.018, \"eps_ttm\": 4.11, \"revenue_ttm\": 187e9 },\n  \"quarters\": [ { \"label\": \"Q2 FY27\", \"end\": \"2026-07-26\", \"fy\": 2027, \"fq\": 2, \"filed\": \"2026-08-27\", \"derived\": false, \"revenue\": ..., \"net_income\": ..., \"eps_diluted\": ..., \"revenue_yoy\": 0.56, ... } ],   // <= 12, oldest first\n  \"annual\":   [ { \"label\": \"FY26\", \"fq\": null, ... } ],                                                                                                              // <= 5, oldest first\n  \"balance\":  { \"end\": \"2026-07-26\", \"cash\": ..., \"debt_total\": ..., \"net_cash\": ..., \"total_assets\": ..., \"total_liabilities\": ..., \"equity\": ... } | null,\n  \"generated_at\": \"2026-09-02T18:05:58+00:00\"\n}"}}},"400":{"description":"ticker missing or not matching ^[A-Z][A-Z0-9.-]{0,9}$"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"no company facts for the symbol (ETFs, funds, most foreign filers)"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"description":"fundamentals unavailable"}},"parameters":[{"name":"ticker","in":"query","required":true,"description":"Symbol matching ^[A-Z][A-Z0-9.-]{0,9}$ (leading $ and lower case are normalised).","schema":{"type":"string","example":"NVDA"}}],"x-cache":"private, max-age=300"}},"/api/factors":{"get":{"operationId":"factors","tags":["Stocks"],"summary":"Fama-French 5 + momentum factor returns, and per-stock factor exposures","description":"Reads `factors.json` from the AWS job: Ken French daily factors (ff5 + momentum) and a one-year daily OLS of excess total returns for the ~500 largest names plus a few benchmark ETFs. Returns are decimal fractions (0.01 = 1%); sparkline values are cumulative percent. Every window is anchored on `latest_factor_date`, not today.\n\n`503` while `factors.json` has not been produced. Powers the factor table on `/macro` and the exposure chip in the ticker hub strip on `/stocks`.\n\n```bash\ncurl \"https://news.all-vista.org/api/factors\"\n\ncurl \"https://news.all-vista.org/api/factors?ticker=NVDA\"\n```","responses":{"200":{"description":"FactorSummary | FactorExposureResponse\n\n```json\n// GET /api/factors  (table)\n{\n  \"as_of\": \"2026-09-02\",\n  \"latest_factor_date\": \"2026-06-30\",     // Ken French data lags the market by weeks\n  \"factor_lag_days\": 64,\n  \"models\": { \"ff5\": [ ... ], ... },\n  \"factor_order\": [\"MKT_RF\", \"SMB\", \"HML\", \"RMW\", \"CMA\", \"MOM\", \"RF\"],\n  \"factors\": { \"MKT_RF\": { \"model\": \"ff5\", \"last\": 0.004, \"mtd\": 0.012, \"qtd\": 0.031, \"ytd\": 0.084, \"y1\": 0.152, \"vol_ann\": 0.14,\n                           \"spark\": [ ...60 ], \"spark_start\": \"2026-04-02\", \"latest_date\": \"2026-06-30\", \"n_1y\": 250 }, ... },\n  \"exposure_window\": { \"start\": \"2025-06-30\", \"end\": \"2026-06-30\", \"n_dates\": 250, \"regressors\": [ ... ], \"min_n\": 120 },\n  \"universe\": { \"count\": 512, \"benchmarks\": [\"SPY\", \"QQQ\", ...], \"min_market_cap\": 2.1e10, \"skipped\": 3, ... },\n  \"generated_at\": \"2026-09-02T14:02:00+00:00\"\n}\n\n// GET /api/factors?ticker=NVDA\n{\n  \"ticker\": \"NVDA\",\n  \"exposure\": { \"beta\": { \"MKT_RF\": 1.47, \"SMB\": -0.19, \"HML\": -0.12, \"RMW\": 0.40, \"CMA\": -1.45, \"MOM\": 0.30 },\n                \"alpha_ann\": 0.21, \"r2\": 0.62, \"idio_vol_ann\": 0.31, \"n\": 250 } | null,\n  \"latest_factor_date\": \"2026-06-30\",\n  \"exposure_window\": { \"start\": \"2025-06-30\", \"end\": \"2026-06-30\", \"n_dates\": 250, \"regressors\": [ ... ], \"min_n\": 120 },\n  \"generated_at\": \"2026-09-02T14:02:00+00:00\"\n}\n```","content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"x-example-text":"// GET /api/factors  (table)\n{\n  \"as_of\": \"2026-09-02\",\n  \"latest_factor_date\": \"2026-06-30\",     // Ken French data lags the market by weeks\n  \"factor_lag_days\": 64,\n  \"models\": { \"ff5\": [ ... ], ... },\n  \"factor_order\": [\"MKT_RF\", \"SMB\", \"HML\", \"RMW\", \"CMA\", \"MOM\", \"RF\"],\n  \"factors\": { \"MKT_RF\": { \"model\": \"ff5\", \"last\": 0.004, \"mtd\": 0.012, \"qtd\": 0.031, \"ytd\": 0.084, \"y1\": 0.152, \"vol_ann\": 0.14,\n                           \"spark\": [ ...60 ], \"spark_start\": \"2026-04-02\", \"latest_date\": \"2026-06-30\", \"n_1y\": 250 }, ... },\n  \"exposure_window\": { \"start\": \"2025-06-30\", \"end\": \"2026-06-30\", \"n_dates\": 250, \"regressors\": [ ... ], \"min_n\": 120 },\n  \"universe\": { \"count\": 512, \"benchmarks\": [\"SPY\", \"QQQ\", ...], \"min_market_cap\": 2.1e10, \"skipped\": 3, ... },\n  \"generated_at\": \"2026-09-02T14:02:00+00:00\"\n}\n\n// GET /api/factors?ticker=NVDA\n{\n  \"ticker\": \"NVDA\",\n  \"exposure\": { \"beta\": { \"MKT_RF\": 1.47, \"SMB\": -0.19, \"HML\": -0.12, \"RMW\": 0.40, \"CMA\": -1.45, \"MOM\": 0.30 },\n                \"alpha_ann\": 0.21, \"r2\": 0.62, \"idio_vol_ann\": 0.31, \"n\": 250 } | null,\n  \"latest_factor_date\": \"2026-06-30\",\n  \"exposure_window\": { \"start\": \"2025-06-30\", \"end\": \"2026-06-30\", \"n_dates\": 250, \"regressors\": [ ... ], \"min_n\": 120 },\n  \"generated_at\": \"2026-09-02T14:02:00+00:00\"\n}"}}},"400":{"description":"ticker given but not matching ^[A-Z][A-Z0-9.-]{0,9}$"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"description":"factor data unavailable"},"503":{"description":"factors.json has not been produced yet"}},"parameters":[{"name":"ticker","in":"query","required":false,"description":"Without it: the factor performance table. With it: that symbol's 1-year regression betas (exposure: null outside the ~500 largest names + benchmark ETFs). Leading $ and lower case are normalised; BRK-B is accepted for BRK.B.","schema":{"type":"string","example":"NVDA"}}],"x-cache":"public, max-age=300, stale-while-revalidate=600"}},"/api/options-history":{"get":{"operationId":"optionsHistory","tags":["Stocks"],"summary":"One year of daily option volume, put/call ratio and the recent OI / GEX / max-pain snapshots","description":"Reads `options_history.json` from the AWS job: for ~36 symbols a 1-year daily option volume series (call/put/P-C/total with percentiles of the latest values) and, for the snapshot window, daily OI totals, a GEX proxy and front-week max pain.\n\nPre-computed twice daily on the AWS market database (`options_history.json`). GEX is a proxy (Σ gamma × OI × 100 × spot² × 0.01, calls − puts); OI comes from Polygon snapshots (a few % under OCC). `data: null` outside the universe, `503` while the file is missing. Powers the History tab on `/flow` and the P/C context line in the ticker hub strip.\n\n```bash\ncurl \"https://news.all-vista.org/api/options-history?ticker=QQQ\"\n```","responses":{"200":{"description":"OptionsHistoryResponse\n\n```json\n{\n  \"generated_at\": \"2026-09-02T18:16:00+00:00\",\n  \"as_of\": \"2026-09-01\",\n  \"window\": { \"start\": \"2025-09-02\", \"end\": \"2026-09-01\" },\n  \"snapshot_start\": \"2026-08-11\",          // first day with greeks / OI snapshots\n  \"max_pain_horizon_days\": 7,\n  \"symbols\": [\"SPY\", \"QQQ\", ..., \"AAPL\", ...],   // ETFs first, then stocks\n  \"universe\": { \"etfs\": [ ... ], \"stocks\": [ ... ] },\n  \"ticker\": \"SPY\",\n  \"data\": {\n    \"kind\": \"etf\",\n    \"dates\": [ ...250 ], \"call_vol\": [ ... ], \"put_vol\": [ ... ], \"pc_ratio\": [ ... ], \"total_vol\": [ ... ],   // oldest first, aligned by index\n    \"oi\": { \"dates\": [ ... ], \"call_oi\": [ ... ], \"put_oi\": [ ... ], \"pc_oi\": [ ... ], \"gex\": [ ... ], \"max_pain\": [ ... ], \"mp_exp\": [ ... ], \"spot\": [ ... ] },\n    \"pct\":    { \"pc_ratio\": 62, \"total_vol\": 48 },   // percentile of the latest value within the year\n    \"median\": { \"pc_ratio\": 0.91, \"total_vol\": 2950000 },\n    \"latest\": { \"date\": \"2026-09-01\", \"call_vol\": 1640000, \"put_vol\": 1360000, \"total_vol\": 3000000, \"pc_ratio\": 0.83 }\n  } | null\n}\n```","content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"x-example-text":"{\n  \"generated_at\": \"2026-09-02T18:16:00+00:00\",\n  \"as_of\": \"2026-09-01\",\n  \"window\": { \"start\": \"2025-09-02\", \"end\": \"2026-09-01\" },\n  \"snapshot_start\": \"2026-08-11\",          // first day with greeks / OI snapshots\n  \"max_pain_horizon_days\": 7,\n  \"symbols\": [\"SPY\", \"QQQ\", ..., \"AAPL\", ...],   // ETFs first, then stocks\n  \"universe\": { \"etfs\": [ ... ], \"stocks\": [ ... ] },\n  \"ticker\": \"SPY\",\n  \"data\": {\n    \"kind\": \"etf\",\n    \"dates\": [ ...250 ], \"call_vol\": [ ... ], \"put_vol\": [ ... ], \"pc_ratio\": [ ... ], \"total_vol\": [ ... ],   // oldest first, aligned by index\n    \"oi\": { \"dates\": [ ... ], \"call_oi\": [ ... ], \"put_oi\": [ ... ], \"pc_oi\": [ ... ], \"gex\": [ ... ], \"max_pain\": [ ... ], \"mp_exp\": [ ... ], \"spot\": [ ... ] },\n    \"pct\":    { \"pc_ratio\": 62, \"total_vol\": 48 },   // percentile of the latest value within the year\n    \"median\": { \"pc_ratio\": 0.91, \"total_vol\": 2950000 },\n    \"latest\": { \"date\": \"2026-09-01\", \"call_vol\": 1640000, \"put_vol\": 1360000, \"total_vol\": 3000000, \"pc_ratio\": 0.83 }\n  } | null\n}"}}},"400":{"description":"ticker not matching ^[A-Z][A-Z0-9.-]{0,9}$ (1-10 characters)"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"description":"options history unavailable"},"503":{"description":"options_history.json missing"}},"parameters":[{"name":"ticker","in":"query","required":false,"description":"SPY, QQQ, IWM, DIA, TLT, GLD or one of the ~30 largest stocks; symbols in the response lists the universe. Trimmed, a leading $ stripped and upper-cased like the other ticker routes, then must match ^[A-Z][A-Z0-9.-]{0,9}$.","schema":{"type":"string","default":"SPY","example":"QQQ"}}],"x-cache":"public, max-age=300, stale-while-revalidate=600"}},"/api/flow/tide":{"get":{"operationId":"flowTide","tags":["Options & Institutional Flow"],"summary":"Intraday market tide — cumulative net call/put premium for the whole US market","description":"Unusual Whales market tide (/api/market/market-tide), one point per minute-ish bucket for the current session, mapped to short keys. Served from the in-process UW cache: 2 min TTL while the market is open, 30 min otherwise; the last good payload is returned with `degraded: true` if UW fails, and the route answers 503 when UW fails with nothing cached.\n\nA 200 with `degraded: true` is a stale cache hit served because the live UW call failed. A 503 (`error: \"Unusual Whales unavailable\"`, empty `items`, `fetched_at: null`) means UW was unreachable and nothing was cached yet.\n\n`freshness` (every flow route since 2026-10-20): `state` is `live` (fetched ≤ 5 min ago in session, or since the latest session open when the market is closed), `delayed` (≤ 60 min in session, or the previous session), `stale` (older) or `offline` (nothing cached — the 503 bodies). `asOf` is the fetch time, `ageMinutes` its age. The cache is persisted to disk, so a restart keeps the last good payloads.\n\n```bash\ncurl \"https://news.all-vista.org/api/flow/tide\"\n```","responses":{"200":{"description":"TideResponse\n\n```json\n{\n  \"items\": [\n    {\n      \"t\": \"2026-09-04T09:30:00-04:00\", // ET-offset ISO timestamp of the bucket\n      \"call\": 12345678.9,               // net call premium, USD (cumulative for the session)\n      \"put\": -4567890.1,                // net put premium, USD\n      \"vol\": 154321                     // net option volume, contracts\n    },\n    ...\n  ],\n  \"fetched_at\": \"2026-09-04T14:31:02.117Z\", // when the UW payload was fetched (null in the 503 body)\n  \"market_open\": true,                      // Mon–Fri 09:30–16:00 ET\n  \"degraded\": false, // true = stale-on-error payload (200) or the empty 503 body\n  \"freshness\": { \"kind\": \"intraday\", \"state\": \"live\", \"asOf\": \"2026-09-04T14:31:02.117Z\", \"ageMinutes\": 0.4 } // live | delayed | stale | offline (session-aware)\n}\n\n// 503 — Unusual Whales failed and nothing was cached yet\n{ \"items\": [], \"fetched_at\": null, \"market_open\": true, \"degraded\": true, \"error\": \"Unusual Whales unavailable\" }\n```","content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"x-example-text":"{\n  \"items\": [\n    {\n      \"t\": \"2026-09-04T09:30:00-04:00\", // ET-offset ISO timestamp of the bucket\n      \"call\": 12345678.9,               // net call premium, USD (cumulative for the session)\n      \"put\": -4567890.1,                // net put premium, USD\n      \"vol\": 154321                     // net option volume, contracts\n    },\n    ...\n  ],\n  \"fetched_at\": \"2026-09-04T14:31:02.117Z\", // when the UW payload was fetched (null in the 503 body)\n  \"market_open\": true,                      // Mon–Fri 09:30–16:00 ET\n  \"degraded\": false, // true = stale-on-error payload (200) or the empty 503 body\n  \"freshness\": { \"kind\": \"intraday\", \"state\": \"live\", \"asOf\": \"2026-09-04T14:31:02.117Z\", \"ageMinutes\": 0.4 } // live | delayed | stale | offline (session-aware)\n}\n\n// 503 — Unusual Whales failed and nothing was cached yet\n{ \"items\": [], \"fetched_at\": null, \"market_open\": true, \"degraded\": true, \"error\": \"Unusual Whales unavailable\" }"}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/TooManyRequests"},"503":{"description":"Unusual Whales unavailable and nothing cached — same body shape with `items: []`, `fetched_at: null`, `degraded: true` and `error`"}}}},"/api/flow/gex":{"get":{"operationId":"flowGex","tags":["Options & Institutional Flow"],"summary":"Dealer gamma exposure by strike near spot, with call/put walls","description":"Unusual Whales spot exposures per strike (/api/stock/{ticker}/spot-exposures/strike, full chain of up to 500 strikes). The route keeps strikes within ±12% of spot, picks the 34 closest to spot and orders them top-down; `callWall` is the strike with the largest call gamma and `putWall` the strike with the most negative put gamma inside that window. Same UW cache as the other flow routes (2 min open / 30 min closed): stale-on-error with `degraded: true`, 503 when UW fails with nothing cached.\n\nOn upstream failure the last good payload is served with `degraded: true` (200). With nothing cached the route answers 503 with `spot: 0`, empty `strikes`, both walls `0`, `fetched_at: null` and `error: \"Unusual Whales unavailable\"`.\n\n```bash\ncurl \"https://news.all-vista.org/api/flow/gex?ticker=SPY\"\n\ncurl \"https://news.all-vista.org/api/flow/gex?ticker=nvda\"\n```","responses":{"200":{"description":"GexResponse\n\n```json\n{\n  \"ticker\": \"SPY\",\n  \"spot\": 648.12,                 // underlying price reported by UW (0 when no data)\n  \"strikes\": [                    // ≤ 34 strikes within ±12% of spot, highest strike first\n    {\n      \"strike\": 660,\n      \"callG\": 812345678.5,       // dealer call gamma exposure (open-interest based), USD per 1% move\n      \"putG\": -234567890.2,       // dealer put gamma exposure, always ≤ 0 (sign normalised)\n      \"net\": 577777788.3          // callG + putG\n    },\n    ...\n  ],\n  \"callWall\": 650,                // strike with the largest callG in the window\n  \"putWall\": 640,                 // strike with the most negative putG in the window\n  \"fetched_at\": \"2026-09-04T14:31:02.117Z\",\n  \"market_open\": true,\n  \"degraded\": false,\n  \"freshness\": { \"kind\": \"intraday\", \"state\": \"live\", \"asOf\": \"2026-09-04T14:31:02.117Z\", \"ageMinutes\": 0.4 } // live | delayed | stale | offline (session-aware)\n}\n```","content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"ticker":"SPY","spot":648.12,"strikes":[{"strike":660,"callG":812345678.5,"putG":-234567890.2,"net":577777788.3}],"callWall":650,"putWall":640,"fetched_at":"2026-09-04T14:31:02.117Z","market_open":true,"degraded":false,"freshness":{"kind":"intraday","state":"live","asOf":"2026-09-04T14:31:02.117Z","ageMinutes":0.4}}}}},"400":{"description":"Unsupported ticker — `ticker` is not one of the ten allowed symbols"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/TooManyRequests"},"503":{"description":"Unusual Whales unavailable and nothing cached — same body shape with `spot: 0`, empty `strikes`, both walls `0`, `fetched_at: null`, `degraded: true` and `error`"}},"parameters":[{"name":"ticker","in":"query","required":false,"description":"Underlying symbol (case-insensitive); only the ten listed tickers are accepted.","schema":{"type":"string","enum":["SPY","QQQ","IWM","NVDA","AAPL","TSLA","MSFT","META","AMZN","GOOGL"],"default":"SPY","example":"QQQ"}}]}},"/api/flow/darkpool":{"get":{"operationId":"flowDarkpool","tags":["Options & Institutional Flow"],"summary":"Ten largest recent dark-pool prints by notional","description":"Unusual Whales recent dark-pool prints (/api/darkpool/recent, UW caps the page at 200) sorted by premium and cut to the top 10. Served from the in-process UW cache (2 min while the market is open, 30 min otherwise) with stale-on-error fallback; 503 when UW fails with nothing cached.\n\nA 200 with `degraded: true` is a stale cache hit; a 503 carries `error: \"Unusual Whales unavailable\"` and empty `items`.\n\n```bash\ncurl \"https://news.all-vista.org/api/flow/darkpool\"\n```","responses":{"200":{"description":"DarkpoolResponse\n\n```json\n{\n  \"items\": [                                   // top 10 by premium, largest first\n    {\n      \"ticker\": \"NVDA\",\n      \"price\": 176.42,                         // execution price\n      \"size\": 1250000,                         // shares\n      \"premium\": 220525000,                    // notional, USD (price × size)\n      \"executedAt\": \"2026-09-04T14:05:11.000Z\" // ISO UTC\n    },\n    ...\n  ],\n  \"fetched_at\": \"2026-09-04T14:31:02.117Z\",\n  \"market_open\": true,\n  \"degraded\": false,\n  \"freshness\": { \"kind\": \"intraday\", \"state\": \"live\", \"asOf\": \"2026-09-04T14:31:02.117Z\", \"ageMinutes\": 0.4 } // live | delayed | stale | offline (session-aware)\n}\n```","content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"items":[{"ticker":"NVDA","price":176.42,"size":1250000,"premium":220525000,"executedAt":"2026-09-04T14:05:11.000Z"}],"fetched_at":"2026-09-04T14:31:02.117Z","market_open":true,"degraded":false,"freshness":{"kind":"intraday","state":"live","asOf":"2026-09-04T14:31:02.117Z","ageMinutes":0.4}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/TooManyRequests"},"503":{"description":"Unusual Whales unavailable and nothing cached — same body shape with `items: []`, `fetched_at: null`, `degraded: true` and `error`"}}}},"/api/flow/congress":{"get":{"operationId":"flowCongress","tags":["Options & Institutional Flow"],"summary":"Congressional stock trades filed in the last 14 days","description":"Unusual Whales recent congress trades (/api/congress/recent-trades, latest 100 filings) filtered to disclosures whose `filedAtDate` falls within the last 14 calendar days. Served from the in-process UW cache (2 min while the market is open, 30 min otherwise) with stale-on-error fallback; 503 when UW fails with nothing cached.\n\nFilings are disclosed weeks after the trade; `transactionDate` can be well outside the 14-day window that applies to `filedAtDate`.\n\nA 200 with `degraded: true` is a stale cache hit; a 503 carries `error: \"Unusual Whales unavailable\"` and empty `items`.\n\n```bash\ncurl \"https://news.all-vista.org/api/flow/congress\"\n```","responses":{"200":{"description":"CongressResponse\n\n```json\n{\n  \"items\": [\n    {\n      \"name\": \"Nancy Pelosi\",          // reporter name (falls back to the filer name)\n      \"memberType\": \"house\",           // house | senate\n      \"ticker\": \"NVDA\",\n      \"txnType\": \"Buy\",                // Buy | Sell | ... as reported by UW\n      \"amounts\": \"$1,000,001 - $5,000,000\", // disclosure bracket, not an exact figure\n      \"transactionDate\": \"2026-08-21\", // YYYY-MM-DD\n      \"filedAtDate\": \"2026-09-02\"      // YYYY-MM-DD; only filings ≤ 14 days old are kept\n    },\n    ...\n  ],\n  \"fetched_at\": \"2026-09-04T14:31:02.117Z\",\n  \"market_open\": true,\n  \"degraded\": false,\n  \"freshness\": { \"kind\": \"intraday\", \"state\": \"live\", \"asOf\": \"2026-09-04T14:31:02.117Z\", \"ageMinutes\": 0.4 } // live | delayed | stale | offline (session-aware)\n}\n```","content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"items":[{"name":"Nancy Pelosi","memberType":"house","ticker":"NVDA","txnType":"Buy","amounts":"$1,000,001 - $5,000,000","transactionDate":"2026-08-21","filedAtDate":"2026-09-02"}],"fetched_at":"2026-09-04T14:31:02.117Z","market_open":true,"degraded":false,"freshness":{"kind":"intraday","state":"live","asOf":"2026-09-04T14:31:02.117Z","ageMinutes":0.4}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/TooManyRequests"},"503":{"description":"Unusual Whales unavailable and nothing cached — same body shape with `items: []`, `fetched_at: null`, `degraded: true` and `error`"}}}},"/api/flow/mood":{"get":{"operationId":"flowMood","tags":["Options & Institutional Flow"],"summary":"Market mood: index-ETF fund flows, insider buy/sell tallies and sector-ETF option flow","description":"Aggregates three Unusual Whales feeds in parallel: daily in/outflow for SPY, QQQ, IWM and DIA (/api/etfs/{T}/in-outflow, latest 10 days), market-wide insider purchases vs sales per filing day (/api/market/insider-buy-sells, 14 days) and the sector ETF table (/api/market/sector-etfs). Each feed uses the shared UW cache (2 min open / 30 min closed); a failed feed comes back empty and flips `degraded` while the rest is still returned. When every feed fails with nothing cached the route answers 503.\n\n`fetched_at` is the oldest cache timestamp among the six UW requests that answered (four ETF flows, insiders, sectors), so every section is at least that fresh; individual sections may be newer. It is `null` only in the 503 body.\n\nPartial failures stay 200: a feed that failed with nothing cached is simply empty and `degraded` is true. Only when all feeds fail is the status 503 with `error: \"Unusual Whales unavailable\"`.\n\n```bash\ncurl \"https://news.all-vista.org/api/flow/mood\"\n```","responses":{"200":{"description":"MoodResponse\n\n```json\n{\n  \"etf\": {\n    \"SPY\": [                              // newest first, ≤ 10 days\n      { \"date\": \"2026-09-03\", \"prem\": -812345678, \"close\": 646.9 }, // prem = net fund flow, USD\n      ...\n    ],\n    \"QQQ\": [ ... ], \"IWM\": [ ... ], \"DIA\": [ ... ]   // an empty array means that feed failed\n  },\n  \"insider\": [                            // per filing day, ≤ 14 rows\n    {\n      \"date\": \"2026-09-03\",\n      \"buys\": 118,                        // number of purchase filings\n      \"buyN\": 45120000,                   // purchase notional, USD (absolute)\n      \"sells\": 402,\n      \"sellN\": 1210500000\n    },\n    ...\n  ],\n  \"sectors\": [\n    {\n      \"ticker\": \"XLK\",\n      \"name\": \"Technology Select Sector SPDR Fund\",\n      \"last\": 245.31,\n      \"changePct\": 0.84,                  // (last − open) / open × 100, 0 when open is missing\n      \"callPrem\": 91234567,               // today's call premium, USD\n      \"putPrem\": 45678901,                // today's put premium, USD\n      \"flow\": -12345678                   // latest daily in/out flow, USD\n    },\n    ...\n  ],\n  \"fetched_at\": \"2026-09-04T14:29:40.002Z\", // OLDEST UW cache timestamp among the feeds that answered (null in the 503 body)\n  \"market_open\": true,\n  \"degraded\": false, // true if any of the three feeds was stale or failed\n  \"freshness\": { \"kind\": \"intraday\", \"state\": \"live\", \"asOf\": \"2026-09-04T14:31:02.117Z\", \"ageMinutes\": 0.4 } // live | delayed | stale | offline (session-aware)\n}\n```","content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"etf":{"SPY":[{"date":"2026-09-03","prem":-812345678,"close":646.9}],"QQQ":[],"IWM":[],"DIA":[]},"insider":[{"date":"2026-09-03","buys":118,"buyN":45120000,"sells":402,"sellN":1210500000}],"sectors":[{"ticker":"XLK","name":"Technology Select Sector SPDR Fund","last":245.31,"changePct":0.84,"callPrem":91234567,"putPrem":45678901,"flow":-12345678}],"fetched_at":"2026-09-04T14:29:40.002Z","market_open":true,"degraded":false,"freshness":{"kind":"intraday","state":"live","asOf":"2026-09-04T14:31:02.117Z","ageMinutes":0.4}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/TooManyRequests"},"503":{"description":"Unusual Whales unavailable — every feed failed with nothing cached; same body shape with empty `etf` arrays, `insider: []`, `sectors: []`, `fetched_at: null`, `degraded: true` and `error`"}}}},"/api/flow/alerts":{"get":{"operationId":"flowAlerts","tags":["Options & Institutional Flow"],"summary":"Large options flow alerts (≥ $250K, 1–60 DTE) with a high-signal flag","description":"Unusual Whales flow alerts (/api/option-trades/flow-alerts, 200 most recent with min_premium=250000) normalised to numbers and filtered to premium ≥ $250K and 1–60 days to expiry, newest first, at most 120 rows. `highSignal` marks the strict subset used by the daily recap: premium ≥ $500K, 7–42 DTE, sweep or floor print, and ask-side premium above bid-side. Served from the shared UW cache (2 min open / 30 min closed) with stale-on-error fallback; 503 when UW fails with nothing cached.\n\nA 200 with `degraded: true` is a stale cache hit; a 503 carries `error: \"Unusual Whales unavailable\"` and empty `items`.\n\n```bash\ncurl \"https://news.all-vista.org/api/flow/alerts\"\n```","responses":{"200":{"description":"FlowAlertsResponse\n\n```json\n{\n  \"items\": [                                   // newest first, ≤ 120\n    {\n      \"id\": \"1a2b3c4d-...\",                    // UW alert id\n      \"ticker\": \"NVDA\",\n      \"type\": \"call\",                          // call | put\n      \"strike\": 180,\n      \"expiry\": \"2026-09-19\",                  // YYYY-MM-DD\n      \"premium\": 1250000,                      // total premium, USD\n      \"askSidePremium\": 1100000,\n      \"bidSidePremium\": 150000,\n      \"isSweep\": true,\n      \"isFloor\": false,\n      \"rule\": \"RepeatedHits\",                  // UW alert rule name\n      \"createdAt\": \"2026-09-04T14:02:31.000Z\", // ISO\n      \"underlyingPrice\": 176.4,\n      \"volume\": 8420,\n      \"openInterest\": 15310,\n      \"highSignal\": true                       // ≥ $500K, 7–42 DTE, sweep/floor, ask > bid\n    },\n    ...\n  ],\n  \"fetched_at\": \"2026-09-04T14:31:02.117Z\",\n  \"market_open\": true,\n  \"degraded\": false,\n  \"freshness\": { \"kind\": \"intraday\", \"state\": \"live\", \"asOf\": \"2026-09-04T14:31:02.117Z\", \"ageMinutes\": 0.4 } // live | delayed | stale | offline (session-aware)\n}\n```","content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"items":[{"id":"1a2b3c4d-...","ticker":"NVDA","type":"call","strike":180,"expiry":"2026-09-19","premium":1250000,"askSidePremium":1100000,"bidSidePremium":150000,"isSweep":true,"isFloor":false,"rule":"RepeatedHits","createdAt":"2026-09-04T14:02:31.000Z","underlyingPrice":176.4,"volume":8420,"openInterest":15310,"highSignal":true}],"fetched_at":"2026-09-04T14:31:02.117Z","market_open":true,"degraded":false,"freshness":{"kind":"intraday","state":"live","asOf":"2026-09-04T14:31:02.117Z","ageMinutes":0.4}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/TooManyRequests"},"503":{"description":"Unusual Whales unavailable and nothing cached — same body shape with `items: []`, `fetched_at: null`, `degraded: true` and `error`"}}}},"/api/flow/recap":{"get":{"operationId":"flowRecap","tags":["Options & Institutional Flow"],"summary":"Latest AI smart-money recap (bilingual), as generated by the daily POST","description":"Returns the recap file written by `POST /api/flow/recap` (stored at /root/data/flow/recap.json, overridable via `FLOW_RECAP_PATH`). It is regenerated once per US trading day after the close, so `date` is the ET trading date the recap covers; the previous recap is kept if a generation fails. A store file that is not valid JSON yields 503 until the next successful POST replaces it.\n\n```bash\ncurl \"https://news.all-vista.org/api/flow/recap\"\n```","responses":{"200":{"description":"FlowRecapFile\n\n```json\n{\n  \"date\": \"2026-09-04\",                        // ET trading date the recap covers\n  \"generated_at\": \"2026-09-04T21:16:40.512Z\",  // ISO UTC\n  \"provider\": \"remote_claude\",                 // LLM provider that produced it\n  \"model\": \"claude-sonnet-4-6\",\n  \"data\": {\n    \"sentiment\": \"mixed\",                      // bullish | bearish | neutral | mixed\n    \"title\": \"半导体看涨大单主导，指数对冲仍在\",\n    \"title_en\": \"Semis lead call buying while index hedges persist\",\n    \"summary\": \"2–3 段中文复盘 …\",\n    \"summary_en\": \"2–3 paragraph English recap ...\",\n    \"key_tickers\": [\"NVDA\", \"SPY\", \"AMD\"]      // uppercase, ≤ 8\n  }\n}\n```","content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"date":"2026-09-04","generated_at":"2026-09-04T21:16:40.512Z","provider":"remote_claude","model":"claude-sonnet-4-6","data":{"sentiment":"mixed","title":"半导体看涨大单主导，指数对冲仍在","title_en":"Semis lead call buying while index hedges persist","summary":"2–3 段中文复盘 …","summary_en":"2–3 paragraph English recap ...","key_tickers":["NVDA","SPY","AMD"]}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"No recap yet — the file has never been generated"},"429":{"$ref":"#/components/responses/TooManyRequests"},"503":{"description":"Recap store file is corrupt (regenerate with POST) — body is `{ error, hint }`"}}},"post":{"operationId":"flowRecapGenerate","tags":["Options & Institutional Flow"],"summary":"Generate today's AI smart-money recap from live Unusual Whales data and persist it (idempotent per ET trading day)","description":"Idempotent per ET trading day. On a weekend or NYSE holiday nothing is generated (200, `skipped: true`). When the store already holds a recap whose `date` is today and `force` is not true, that recap is returned with `cached: true` and `X-AI-Provider: cache` — no LLM or Unusual Whales call. Otherwise the route pulls four Unusual Whales feeds (flow alerts ≥ $250K, market tide, dark-pool prints, latest 50 congress trades), keeps only high-signal alerts (≥ $500K, 7–42 DTE, sweep/block, ask-side), aggregates the top 8 tickers by premium, top 5 dark-pool prints and 8 congress trades, then asks the LLM for a strict-JSON bilingual recap. The result is written to the recap store and returned; the cron calls this on weekdays after the US close.\n\n⚠ A fresh generation costs one LLM request (up to two on retry, `maxTokens` 4096, route `maxDuration` 300 s) and four Unusual Whales requests, and is counted against the stricter LLM rate limit (60 per IP / 600 global per 5 min) for the public key. Same-day repeats without `force: true` are free: they return the stored recap with `cached: true` and `X-AI-Provider: cache` / `X-AI-Model: cached`.\n\nThe scheduled 21:15 ET run sends `{ \"force\": true }`, so an earlier same-day call (for example a pre-market regeneration) never blocks the end-of-day recap. The JSON body is optional and only `force` is read; a missing or non-JSON body counts as `force: false`. Provider chain and models come from `ANTHROPIC_MODEL_FLOW_RECAP`, `CODEX_MODEL_FLOW_RECAP` and `QWEN_MODEL_FLOW_RECAP` (falling back to the site-wide defaults); the picked provider/model are echoed in `X-AI-Provider` / `X-AI-Model`.\n\nThe closure check uses the computed NYSE calendar (src/lib/market-calendar.ts): weekends plus New Year's Day, Martin Luther King Jr. Day, Presidents' Day, Good Friday, Memorial Day, Juneteenth, Independence Day, Labor Day, Thanksgiving and Christmas (observed dates), plus the ad-hoc closures listed in that module. Early-close days are trading days. The skip response carries `reason` (`weekend`, `holiday` or, since 2026-10-20, `stale_upstream` when Unusual Whales has not refreshed since the latest session open — `as_of` is the newest fetch), the holiday name in `holiday`, and the ET `date`; `force` does not override it.\n\nIf the store file is not valid JSON it is moved aside as recap.json.corrupt-<timestamp> before the LLM call, so the new recap can always be written; until then `GET /api/flow/recap` answers 503.\n\nLLM-backed: counts against the `llm` rate limit and returns `X-AI-Provider` / `X-AI-Model` headers.\n\n```bash\ncurl -X POST \"https://news.all-vista.org/api/flow/recap\"\n\ncurl -X POST \"https://news.all-vista.org/api/flow/recap\" -H \"Content-Type: application/json\" -d '{\"force\": true}'\n```","responses":{"200":{"description":"FlowRecapFile\n\n```json\n// Fresh generation — same shape as GET /api/flow/recap, plus X-AI-Provider / X-AI-Model response headers\n{\n  \"date\": \"2026-09-04\",\n  \"generated_at\": \"2026-09-04T21:16:40.512Z\",\n  \"provider\": \"remote_claude\",\n  \"model\": \"claude-sonnet-4-6\",\n  \"data\": {\n    \"sentiment\": \"mixed\",            // bullish | bearish | neutral | mixed\n    \"title\": \"...\", \"title_en\": \"...\",\n    \"summary\": \"...\", \"summary_en\": \"...\",\n    \"key_tickers\": [\"NVDA\", \"SPY\"]\n  }\n}\n\n// Same day, recap already stored and force is not true — X-AI-Provider: cache, X-AI-Model: cached\n{ \"date\": \"2026-09-04\", \"generated_at\": \"...\", \"provider\": \"...\", \"model\": \"...\", \"data\": { ... }, \"cached\": true }\n\n// NYSE closed (America/New_York date) — nothing is generated, nothing is stored\n{ \"skipped\": true, \"reason\": \"weekend\", \"date\": \"2026-09-06\" }\n{ \"skipped\": true, \"reason\": \"holiday\", \"holiday\": \"Labor Day\", \"date\": \"2026-09-07\" }\n{ \"skipped\": true, \"reason\": \"stale_upstream\", \"as_of\": \"2026-10-17T01:15:02.000Z\", \"date\": \"2026-10-20\" }\n\n// 202 — another generation is already running in this process\n{ \"status\": \"in_progress\", \"date\": \"2026-09-04\" }\n```","content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"x-example-text":"// Fresh generation — same shape as GET /api/flow/recap, plus X-AI-Provider / X-AI-Model response headers\n{\n  \"date\": \"2026-09-04\",\n  \"generated_at\": \"2026-09-04T21:16:40.512Z\",\n  \"provider\": \"remote_claude\",\n  \"model\": \"claude-sonnet-4-6\",\n  \"data\": {\n    \"sentiment\": \"mixed\",            // bullish | bearish | neutral | mixed\n    \"title\": \"...\", \"title_en\": \"...\",\n    \"summary\": \"...\", \"summary_en\": \"...\",\n    \"key_tickers\": [\"NVDA\", \"SPY\"]\n  }\n}\n\n// Same day, recap already stored and force is not true — X-AI-Provider: cache, X-AI-Model: cached\n{ \"date\": \"2026-09-04\", \"generated_at\": \"...\", \"provider\": \"...\", \"model\": \"...\", \"data\": { ... }, \"cached\": true }\n\n// NYSE closed (America/New_York date) — nothing is generated, nothing is stored\n{ \"skipped\": true, \"reason\": \"weekend\", \"date\": \"2026-09-06\" }\n{ \"skipped\": true, \"reason\": \"holiday\", \"holiday\": \"Labor Day\", \"date\": \"2026-09-07\" }\n{ \"skipped\": true, \"reason\": \"stale_upstream\", \"as_of\": \"2026-10-17T01:15:02.000Z\", \"date\": \"2026-10-20\" }\n\n// 202 — another generation is already running in this process\n{ \"status\": \"in_progress\", \"date\": \"2026-09-04\" }"}}},"202":{"description":"Another generation is already running — body `{ status: \"in_progress\", date }`; retry in a minute (the finished recap is then served as `cached: true`)"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"description":"Recap generation failed — both LLM attempts failed (or the store refused the write); the previous recap file is left untouched"},"502":{"description":"Upstream data unavailable — one of the four Unusual Whales requests failed with nothing cached"}},"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"force":{"type":"boolean","default":false,"example":true,"description":"Regenerate even if today's recap already exists; must be the JSON boolean true (any other value, or no body, means false)."}}}}}},"x-llm":true}},"/api/search":{"get":{"operationId":"search","tags":["Search & Trending"],"summary":"Site-wide text search across Trump posts, X headlines, BWE wire, AI digests and tickers","description":"Case-insensitive AND-substring search: every whitespace-separated token (max 8, a leading `$` is stripped) must appear in the item. Corpora, in result order: a synthetic `ticker` hit when the query is `$SYM` or a lone UPPERCASE token that resolves in the ticker table; Trump Truth Social posts (text plus cached AI headline and card title); X headlines (text + author); BWE wire posts (headline, text, source); and X/BWE AI digests (titles, key themes, notable tickers). Reads the on-disk data files directly with memoised indexes, so results reflect the latest fetched data.\n\nLowercase single words (\"oil\", \"gold\") are never treated as tickers, so they only search the text corpora; use `$oil`-style cashtags or uppercase to force the ticker hit.\n\nThe first query after a data file changes rebuilds that corpus index (~100 ms for X); subsequent queries take ~10–40 ms.\n\n```bash\ncurl \"https://news.all-vista.org/api/search?q=tariff%20china&limit=20\"\n\ncurl \"https://news.all-vista.org/api/search?q=%24NVDA&lang=zh\"\n```","responses":{"200":{"description":"SearchResponse\n\n```json\n{\n  \"q\": \"$NVDA export\",\n  \"lang\": \"en\",\n  \"took_ms\": 12.4,\n  \"counts\": {                       // total matches per type BEFORE the per-type cap of 10\n    \"ticker\": 1, \"trump\": 3, \"x\": 87, \"bwe\": 5, \"digest\": 2\n  },\n  \"results\": [                      // grouped: ticker → trump → x → bwe → digest, newest first within a group\n    {\n      \"type\": \"ticker\",             // ticker | trump | x | bwe | digest\n      \"id\": \"NVDA\",\n      \"title\": \"NVDA · NVIDIA Corp\",\n      \"snippet\": \"XNAS · CS · mega\",   // exchange · type · market-cap tier\n      \"timestamp\": \"\",              // \"\" for the ticker hit, ISO otherwise\n      \"href\": \"/stocks?ticker=NVDA\", // locale-less internal link\n      \"meta\": \"SEMICONDUCTORS & RELATED DEVICES\"\n    },\n    {\n      \"type\": \"trump\",\n      \"id\": \"114090816417968547\",\n      \"title\": \"Trump threatens new chip export curbs\",   // AI headline (lang-aware) or truncated text\n      \"snippet\": \"…a ~150-char window around the first token hit…\",\n      \"timestamp\": \"2026-09-03T18:22:10.000Z\",\n      \"href\": \"/trump?post=114090816417968547\",\n      \"externalUrl\": \"https://truthsocial.com/@realDonaldTrump/114090816417968547\",\n      \"meta\": \"@realDonaldTrump\"\n    },\n    { \"type\": \"x\", \"id\": \"1963...\", \"title\": \"NVIDIA says export ...\", \"snippet\": \"\", \"timestamp\": \"...\", \"href\": \"/x\", \"meta\": \"DeItaone\" },\n    { \"type\": \"bwe\", \"id\": \"...\", \"title\": \"...\", \"snippet\": \"中文译文…\", \"timestamp\": \"...\", \"href\": \"/bwe\", \"meta\": \"Reuters\" },\n    { \"type\": \"digest\", \"id\": \"x:24h_2026-09-03T00:00:00.000Z\", \"title\": \"...\", \"snippet\": \"theme · theme\", \"timestamp\": \"2026-09-03T00:00:00.000Z\", \"href\": \"/x\", \"meta\": \"X · 24h\" }\n  ]\n}\n```","content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"q":"$NVDA export","lang":"en","took_ms":12.4,"counts":{"ticker":1,"trump":3,"x":87,"bwe":5,"digest":2},"results":[{"type":"ticker","id":"NVDA","title":"NVDA · NVIDIA Corp","snippet":"XNAS · CS · mega","timestamp":"","href":"/stocks?ticker=NVDA","meta":"SEMICONDUCTORS & RELATED DEVICES"},{"type":"trump","id":"114090816417968547","title":"Trump threatens new chip export curbs","snippet":"…a ~150-char window around the first token hit…","timestamp":"2026-09-03T18:22:10.000Z","href":"/trump?post=114090816417968547","externalUrl":"https://truthsocial.com/@realDonaldTrump/114090816417968547","meta":"@realDonaldTrump"},{"type":"x","id":"1963...","title":"NVIDIA says export ...","snippet":"","timestamp":"...","href":"/x","meta":"DeItaone"},{"type":"bwe","id":"...","title":"...","snippet":"中文译文…","timestamp":"...","href":"/bwe","meta":"Reuters"},{"type":"digest","id":"x:24h_2026-09-03T00:00:00.000Z","title":"...","snippet":"theme · theme","timestamp":"2026-09-03T00:00:00.000Z","href":"/x","meta":"X · 24h"}]}}}},"400":{"description":"`q` shorter than 2 or longer than 100 characters, or `limit` not a number in 1–60"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"description":"search failed — a data file could not be read"}},"parameters":[{"name":"q","in":"query","required":true,"description":"Search text; trimmed, must be 2–100 characters.","schema":{"type":"string","example":"$NVDA export"}},{"name":"limit","in":"query","required":false,"description":"Total results to return; each content type contributes at most 10, and trimming to the limit keeps every matching type represented.","schema":{"type":"integer","default":30,"minimum":1,"maximum":60}},{"name":"lang","in":"query","required":false,"description":"Which language of AI headlines/digest titles to prefer in `title`; any other value falls back to en.","schema":{"type":"string","enum":["en","zh"],"default":"en"}}]}},"/api/trending":{"get":{"operationId":"trending","tags":["Search & Trending"],"summary":"Tickers the X headline feed is talking about most in the last 24 hours","description":"Walks the per-ticker X headline index (cashtag, bare symbol and company-name matching from the /stocks matcher) and ranks common stocks and ADRs with ≥ 3 mentions in the last 24 h by 24 h count, then by the ratio to their 7-day daily average. The ranking is recomputed in-process at most every 5 minutes; the `sentiment` column comes from the per-post AI analysis cache, which scripts/analyze_trending.py refreshes hourly for the current trending tickers.\n\nFunds and ETFs (SPY, TLT, ...) are excluded even when matched; only equities in the ticker table rank.\n\n`limit` is validated like `/api/search` (parsed as a number, then range-checked) but must additionally be a whole number: fractional values such as 7.5 are rejected rather than floored.\n\n```bash\ncurl \"https://news.all-vista.org/api/trending?limit=10\"\n```","responses":{"200":{"description":"TrendingSnapshot\n\n```json\n{\n  \"generated_at\": \"2026-09-04T14:30:00.000Z\", // when the ranking was last computed (≤ 5 min old)\n  \"items\": [                                  // sorted by count24h desc, then ratio desc, then ticker\n    {\n      \"ticker\": \"NVDA\",\n      \"name\": \"Nvidia\",                       // short display name (corporate suffixes stripped)\n      \"count24h\": 42,                         // headlines mentioning it in the last 24 h (≥ 3 to appear)\n      \"avgDaily7d\": 11.57,                    // headlines per day over the previous 7 full days\n      \"ratio\": 3.63,                          // count24h / max(avgDaily7d, 0.5)\n      \"sentiment\": {                          // AI direction mix of the analysed 24 h posts, or null if none analysed\n        \"bullish\": 12, \"bearish\": 5, \"neutral\": 9, \"uncertain\": 2,\n        \"total\": 28                           // posts with an analysis; shares are x / total\n      },\n      \"latest\": {                             // newest headline mentioning the ticker, or null\n        \"id\": \"1963512345678901234\",\n        \"author\": \"DeItaone\",\n        \"raw_text\": \"NVIDIA SAYS ...\",\n        \"timestamp\": \"2026-09-04T14:12:05.000Z\"\n      }\n    },\n    ...\n  ]\n}\n```","content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"generated_at":"2026-09-04T14:30:00.000Z","items":[{"ticker":"NVDA","name":"Nvidia","count24h":42,"avgDaily7d":11.57,"ratio":3.63,"sentiment":{"bullish":12,"bearish":5,"neutral":9,"uncertain":2,"total":28},"latest":{"id":"1963512345678901234","author":"DeItaone","raw_text":"NVIDIA SAYS ...","timestamp":"2026-09-04T14:12:05.000Z"}}]}}}},"400":{"description":"`limit` is empty, not an integer, or outside 1–50 (`limit must be an integer between 1 and 50`)"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"description":"trending unavailable — the X posts, ticker table or analysis cache could not be read"}},"parameters":[{"name":"limit","in":"query","required":false,"description":"Number of tickers to return; must be an integer in 1–50 (an empty value or anything that is not an integer is rejected with 400).","schema":{"type":"integer","default":15,"minimum":1,"maximum":50}}]}},"/api/health":{"get":{"operationId":"health","tags":["System"],"summary":"Data-freshness check for every upstream feed the site depends on","description":"Reports the age of each data file the site reads and whether it is older than that file's threshold. Pushed feeds (`x`, `macro`, `bwe`, `events`, `earnings` under /root/data/*) are judged by file mtime against 48 h. AWS market-database files under `aws_data_dir` are judged by their payload `generated_at` (mtime as fallback): `china_market` and `hsgt` 80 h (A-share files only move on CN trading days), `ah_pairs_cnhk` and `ah_pairs_us` 48 h, `trump_intraday`, `factors`, `cn_pro`, `earnings_vol` and `options_history` 36 h. `fundamentals` is judged by fundamentals_index.json: index ≤ 36 h old, `count` ≥ 4000 symbols and zero `failed_batches`. A missing file reports `ageHours: null` and `stale: true`; `anyStale` is the overall status and also drives the HTTP status: 200 when every file is fresh, 503 when any file is stale (same JSON body either way).\n\nThe status code mirrors `anyStale`: 200 only when every file is fresh, 503 otherwise. The body does not change, so consumers that parse it (the site layout banner and scripts/healthcheck_alert.py, every 30 min) still read `anyStale` / per-file `stale`.\n\n`ageHours` is rounded to one decimal. For AWS files the age is measured from the payload's `generated_at`, so a file re-synced without new content still ages.\n\n```bash\ncurl \"https://news.all-vista.org/api/health\"\n```","responses":{"200":{"description":"HealthResponse\n\n```json\n// This example is served with HTTP 503 because anyStale is true; the body is identical on a 200\n{\n  \"checked_at\": \"2026-09-04T14:31:02.117Z\",\n  \"stale_threshold_hours\": 48,             // default threshold for the pushed feeds\n  \"aws_data_dir\": \"/root/data/aws\",\n  \"files\": [                               // 15 entries: 5 pushed, 9 AWS, fundamentals\n    { \"key\": \"x\",        \"ageHours\": 0.4,  \"stale\": false, \"thresholdHours\": 48 },\n    { \"key\": \"macro\",    \"ageHours\": 6.1,  \"stale\": false, \"thresholdHours\": 48 },\n    { \"key\": \"bwe\",      \"ageHours\": 1.2,  \"stale\": false, \"thresholdHours\": 48 },\n    { \"key\": \"events\",   \"ageHours\": 9.5,  \"stale\": false, \"thresholdHours\": 48 },\n    { \"key\": \"earnings\", \"ageHours\": 52.3, \"stale\": true,  \"thresholdHours\": 48 },\n    { \"key\": \"china_market\",   \"ageHours\": 20.7, \"stale\": false, \"thresholdHours\": 80 },\n    { \"key\": \"hsgt\",           \"ageHours\": 20.9, \"stale\": false, \"thresholdHours\": 80 },\n    { \"key\": \"ah_pairs_cnhk\",  \"ageHours\": 20.7, \"stale\": false, \"thresholdHours\": 48 },\n    { \"key\": \"ah_pairs_us\",    \"ageHours\": 8.6,  \"stale\": false, \"thresholdHours\": 48 },\n    { \"key\": \"trump_intraday\", \"ageHours\": 8.6,  \"stale\": false, \"thresholdHours\": 36 },\n    { \"key\": \"factors\",        \"ageHours\": 8.6,  \"stale\": false, \"thresholdHours\": 36 },\n    { \"key\": \"cn_pro\",         \"ageHours\": null, \"stale\": true,  \"thresholdHours\": 36 }, // file missing\n    { \"key\": \"earnings_vol\",   \"ageHours\": 8.6,  \"stale\": false, \"thresholdHours\": 36 },\n    { \"key\": \"options_history\",\"ageHours\": 8.6,  \"stale\": false, \"thresholdHours\": 36 },\n    { \"key\": \"fundamentals\",   \"ageHours\": 8.7,  \"stale\": false, \"thresholdHours\": 36,\n      \"note\": \"5012 symbols, snapshot 2026-09-03\" }   // or the problem list, e.g. \"index too old; 3 failed batches\"\n  ],\n  \"flow\": { \"fetched_at\": \"2026-10-20T14:02:11.000Z\", \"state\": \"live\", \"age_minutes\": 3.0, \"market_open\": true }, // Unusual Whales cache freshness (informational; not in files[]/anyStale)\n  \"anyStale\": true                         // overall status: true if any file is stale (→ HTTP 503)\n}\n```","content":{"application/json":{"schema":{"type":"object","additionalProperties":true},"example":{"checked_at":"2026-09-04T14:31:02.117Z","stale_threshold_hours":48,"aws_data_dir":"/root/data/aws","files":[{"key":"x","ageHours":0.4,"stale":false,"thresholdHours":48},{"key":"macro","ageHours":6.1,"stale":false,"thresholdHours":48},{"key":"bwe","ageHours":1.2,"stale":false,"thresholdHours":48},{"key":"events","ageHours":9.5,"stale":false,"thresholdHours":48},{"key":"earnings","ageHours":52.3,"stale":true,"thresholdHours":48},{"key":"china_market","ageHours":20.7,"stale":false,"thresholdHours":80},{"key":"hsgt","ageHours":20.9,"stale":false,"thresholdHours":80},{"key":"ah_pairs_cnhk","ageHours":20.7,"stale":false,"thresholdHours":48},{"key":"ah_pairs_us","ageHours":8.6,"stale":false,"thresholdHours":48},{"key":"trump_intraday","ageHours":8.6,"stale":false,"thresholdHours":36},{"key":"factors","ageHours":8.6,"stale":false,"thresholdHours":36},{"key":"cn_pro","ageHours":null,"stale":true,"thresholdHours":36},{"key":"earnings_vol","ageHours":8.6,"stale":false,"thresholdHours":36},{"key":"options_history","ageHours":8.6,"stale":false,"thresholdHours":36},{"key":"fundamentals","ageHours":8.7,"stale":false,"thresholdHours":36,"note":"5012 symbols, snapshot 2026-09-03"}],"flow":{"fetched_at":"2026-10-20T14:02:11.000Z","state":"live","age_minutes":3,"market_open":true},"anyStale":true}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/TooManyRequests"},"503":{"description":"At least one file is stale (`anyStale: true`) — same JSON body as the 200 response, so uptime monitors can alert on the status code alone"}}}}},"components":{"securitySchemes":{"ApiKeyHeader":{"type":"apiKey","in":"header","name":"X-API-Key"},"BearerKey":{"type":"http","scheme":"bearer"}},"responses":{"Unauthorized":{"description":"Missing or invalid API key","content":{"application/json":{"example":{"error":"Unauthorized","hint":"Provide your API key via X-API-Key header or Authorization: Bearer <key>","docs":"/api/docs"}}}},"TooManyRequests":{"description":"Rate limit exceeded (public keys only). Retry after `Retry-After` seconds.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait"}},"content":{"application/json":{"example":{"error":"Too many requests","retry_after_seconds":120}}}}}},"x-rate-limits":{"windowMinutes":5,"apiPerIp":1500,"llmPerIp":60,"llmGlobal":600}}