{"openapi":"3.1.0","info":{"title":"SonicAlpha API","description":"Podcast-sourced investment insights: discover → search → verify → synthesize.","version":"1.0"},"paths":{"/health":{"get":{"summary":"Health","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[]}},"/":{"get":{"summary":"Root","operationId":"root__get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[]}},"/llms.txt":{"get":{"summary":"Llms Txt","description":"Agent-readable docs at the llms.txt well-known path. Served from the\nrepo so a docs edit ships with the deploy that changes behavior.","operationId":"llms_txt_llms_txt_get","responses":{"200":{"description":"Successful Response","content":{"text/plain":{"schema":{"type":"string"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[]}},"/v1/insights/search":{"post":{"tags":["insights"],"summary":"Search Insights","operationId":"search_insights_v1_insights_search_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InsightSearchRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InsightSearchResponse"}}}},"422":{"description":"Invalid request (validation)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"400":{"description":"Malformed request: unknown field, bad cursor, or out-of-range parameter","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit or daily quota exceeded; includes Retry-After","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/insights/{insight_id}":{"get":{"tags":["insights"],"summary":"Get Insight","description":"Resolve one insight id directly — the id every hit carries as `id`.\n\nThe GET-per-entity leg of \"every id is resolvable\": episodes and\npodcasts already had theirs; the object customers actually store and\ncite did not. `include_quotes` attaches the verbatim passage;\n`include_similar` embeds up to 10 related insights as full objects,\nordered most-similar first (order is the contract — similarity scores\nare internal calibration and never exposed).","operationId":"get_insight_v1_insights__insight_id__get","parameters":[{"name":"insight_id","in":"path","required":true,"schema":{"type":"string","title":"Insight Id"}},{"name":"include_quotes","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Include Quotes"}},{"name":"include_similar","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Include Similar"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"description":"The insight object. `quote` is populated when include_quotes=true.","allOf":[{"$ref":"#/components/schemas/InsightHit"},{"type":"object","properties":{"similar":{"type":"array","items":{"$ref":"#/components/schemas/InsightHit"},"description":"Present only when include_similar=true: up to 10 related insights, most-similar first (ordering is the similarity contract; no scores)."}}}]}}}},"422":{"description":"Invalid request (validation)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"400":{"description":"Malformed request: unknown field, bad cursor, or out-of-range parameter","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit or daily quota exceeded; includes Retry-After","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/insights/{insight_id}/quote":{"get":{"tags":["insights"],"summary":"Insight Quote","description":"Reconstruct the verbatim transcript text for an insight.\n\nThe pipeline stores segment indices but not the underlying words —\nthose live in the gzipped VTT payload in R2. This endpoint hits R2\non every call (no cache yet); fine for click-to-expand usage, would\nwant episode-level caching if it ends up on a hot path.","operationId":"insight_quote_v1_insights__insight_id__quote_get","parameters":[{"name":"insight_id","in":"path","required":true,"schema":{"type":"string","title":"Insight Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InsightQuoteResponse"}}}},"422":{"description":"Invalid request (validation)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"400":{"description":"Malformed request: unknown field, bad cursor, or out-of-range parameter","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit or daily quota exceeded; includes Retry-After","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/insights/ask":{"post":{"tags":["insights"],"summary":"Ask Insights","description":"Research a question over the insight corpus and return a cited answer.\n\nThis is the slow, deep counterpart to POST /insights/search: multiple\nsub-searches plus an LLM synthesis instead of one ranked list.","operationId":"ask_insights_v1_insights_ask_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InsightAskRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InsightAskResponse"}}}},"422":{"description":"Invalid request (validation)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"400":{"description":"Malformed request: unknown field, bad cursor, or out-of-range parameter","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit or daily quota exceeded; includes Retry-After","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/insights/ask/stream":{"post":{"tags":["insights"],"summary":"Ask Insights Stream","description":"Streaming variant: Server-Sent Events with live progress.\n\nEach SSE `data:` line is one JSON event. Order: `stage` (retrieval) →\n`sub_query` at the moment each search is issued (+ `sub_query_result`\nwith the hit count when it completes, matched by `seq`) →\n`retrieval_done` → `sources` (full search-hit objects, render citation\ncards/player immediately) → `stage` (synthesis) → `answer_delta` tokens\n→ `done` (final answer + cost/debug). `ping` events keep the connection\nalive during quiet stretches; a terminal `error` event replaces `done`\non failure.","operationId":"ask_insights_stream_v1_insights_ask_stream_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InsightAskRequest"}}},"required":true},"responses":{"200":{"description":"Server-Sent Events: stage, sub_query, sub_query_result, retrieval_done, sources, answer_delta, done, ping, error.","content":{"text/event-stream":{"schema":{"type":"string"}}}},"422":{"description":"Invalid request (validation)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"400":{"description":"Malformed request: unknown field, bad cursor, or out-of-range parameter","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit or daily quota exceeded; includes Retry-After","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/podcasts":{"get":{"tags":["directory"],"summary":"List Podcasts","description":"Find podcasts by name. Only shows with searchable insights are\nlisted, most-covered first.","operationId":"list_podcasts_v1_podcasts_get","parameters":[{"name":"query","in":"query","required":false,"schema":{"anyOf":[{"type":"string","minLength":2,"maxLength":200},{"type":"null"}],"description":"Case-insensitive name match. Omit to browse shows ranked by insight coverage.","title":"Query"},"description":"Case-insensitive name match. Omit to browse shows ranked by insight coverage."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":25,"title":"Limit"}},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"`next_cursor` from the previous page.","title":"Cursor"},"description":"`next_cursor` from the previous page."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PodcastListResponse"}}}},"422":{"description":"Invalid request (validation)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"400":{"description":"Malformed request: unknown field, bad cursor, or out-of-range parameter","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit or daily quota exceeded; includes Retry-After","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/podcasts/{podcast_id}":{"get":{"tags":["directory"],"summary":"Get Podcast","description":"Directory card for one show. Accepts the sonic podcast id (canonical);\nlegacy vendor ids also resolve.","operationId":"get_podcast_v1_podcasts__podcast_id__get","parameters":[{"name":"podcast_id","in":"path","required":true,"schema":{"type":"string","title":"Podcast Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PodcastCard"}}}},"422":{"description":"Invalid request (validation)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"400":{"description":"Malformed request: unknown field, bad cursor, or out-of-range parameter","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit or daily quota exceeded; includes Retry-After","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/episodes":{"get":{"tags":["directory"],"summary":"List Episodes","description":"Find episodes by title, show, and/or date window — newest first.\nOnly episodes with searchable insights are listed.","operationId":"list_episodes_v1_episodes_get","parameters":[{"name":"query","in":"query","required":false,"schema":{"anyOf":[{"type":"string","minLength":2,"maxLength":200},{"type":"null"}],"description":"Case-insensitive episode-title match.","title":"Query"},"description":"Case-insensitive episode-title match."},{"name":"podcast_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Scope to one show (sonic podcast id).","title":"Podcast Id"},"description":"Scope to one show (sonic podcast id)."},{"name":"date_from","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ISO 8601; bare dates are UTC day boundaries.","title":"Date From"},"description":"ISO 8601; bare dates are UTC day boundaries."},{"name":"date_to","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ISO 8601; bare dates include the whole day.","title":"Date To"},"description":"ISO 8601; bare dates include the whole day."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":25,"title":"Limit"}},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"`next_cursor` from the previous page.","title":"Cursor"},"description":"`next_cursor` from the previous page."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EpisodeListResponse"}}}},"422":{"description":"Invalid request (validation)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"400":{"description":"Malformed request: unknown field, bad cursor, or out-of-range parameter","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit or daily quota exceeded; includes Retry-After","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/episodes/{episode_id}":{"get":{"tags":["directory"],"summary":"Get Episode","description":"Directory card for one episode — the id every insight carries as\n`episode_id` resolves here directly.","operationId":"get_episode_v1_episodes__episode_id__get","parameters":[{"name":"episode_id","in":"path","required":true,"schema":{"type":"string","title":"Episode Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EpisodeCard"}}}},"422":{"description":"Invalid request (validation)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"400":{"description":"Malformed request: unknown field, bad cursor, or out-of-range parameter","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit or daily quota exceeded; includes Retry-After","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/episodes/{episode_id}/duplicates":{"get":{"tags":["directory"],"summary":"Get Episode Duplicates","description":"Duplicate links for one episode: the canonical episode it copies (if any) and the\nepisodes that copy it. Works for episodes with no insights (a skipped twin resolves\nhere to its canonical).","operationId":"get_episode_duplicates_v1_episodes__episode_id__duplicates_get","parameters":[{"name":"episode_id","in":"path","required":true,"schema":{"type":"string","title":"Episode Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EpisodeDuplicatesResponse"}}}},"422":{"description":"Invalid request (validation)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"400":{"description":"Malformed request: unknown field, bad cursor, or out-of-range parameter","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit or daily quota exceeded; includes Retry-After","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/podcasts/{podcast_id}/episodes":{"get":{"tags":["podcasts"],"summary":"List Podcast Episodes","description":"List every episode of a podcast that has insights, newest first.","operationId":"list_podcast_episodes_v1_podcasts__podcast_id__episodes_get","parameters":[{"name":"podcast_id","in":"path","required":true,"schema":{"type":"string","title":"Podcast Id"}},{"name":"limit","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","maximum":500,"minimum":1},{"type":"null"}],"description":"Episodes per page. Client default 50; big shows have thousands.","title":"Limit"},"description":"Episodes per page. Client default 50; big shows have thousands."},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"`next_cursor` from the previous page.","title":"Cursor"},"description":"`next_cursor` from the previous page."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PodcastEpisodesResponse"}}}},"422":{"description":"Invalid request (validation)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"400":{"description":"Malformed request: unknown field, bad cursor, or out-of-range parameter","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit or daily quota exceeded; includes Retry-After","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}}},"components":{"schemas":{"EpisodeCard":{"properties":{"id":{"type":"string","title":"Id"},"title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Title"},"published_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Published At"},"duration_seconds":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Duration Seconds"},"insight_count":{"type":"integer","title":"Insight Count"},"episode_audio_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Episode Audio Url"},"episode_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Episode Url"},"episode_image_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Episode Image Url"},"podcast_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Podcast Id"},"podcast_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Podcast Name"},"podcast_image_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Podcast Image Url"},"duplicate_of":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Duplicate Of"}},"type":"object","required":["id","insight_count"],"title":"EpisodeCard"},"EpisodeDuplicatesResponse":{"properties":{"episode_id":{"type":"string","title":"Episode Id"},"duplicate_of":{"anyOf":[{"$ref":"#/components/schemas/EpisodeLink"},{"type":"null"}]},"republished_as":{"items":{"$ref":"#/components/schemas/EpisodeLink"},"type":"array","title":"Republished As","default":[]}},"type":"object","required":["episode_id"],"title":"EpisodeDuplicatesResponse"},"EpisodeLink":{"properties":{"episode_id":{"type":"string","title":"Episode Id"},"title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Title"},"published_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Published At"},"podcast_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Podcast Id"},"podcast_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Podcast Name"},"kind":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Kind"},"probability":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Probability"},"evidence":{"items":{"type":"string"},"type":"array","title":"Evidence","default":[]},"detected_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detected At"}},"type":"object","required":["episode_id"],"title":"EpisodeLink"},"EpisodeListResponse":{"properties":{"episodes":{"items":{"$ref":"#/components/schemas/EpisodeCard"},"type":"array","title":"Episodes"},"count":{"type":"integer","title":"Count"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor"}},"type":"object","required":["episodes","count"],"title":"EpisodeListResponse"},"FacetEntry":{"properties":{"value":{"type":"string","title":"Value"},"count":{"type":"integer","title":"Count"}},"type":"object","required":["value","count"],"title":"FacetEntry"},"InsightAskRequest":{"properties":{"query":{"type":"string","maxLength":1000,"minLength":8,"title":"Query","description":"The question to research."},"answer":{"type":"boolean","default":false,"title":"Answer","description":"Default returns the agent-curated evidence (`sources`) with `answer: null` in ~10-30s. Set true for a written, cited answer — adds ~15-25s."},"include_quotes":{"type":"boolean","title":"Include Quotes","description":"Attach the verbatim transcript passage to each source as `quote` — the same grounding /insights/search offers. Adds ~1-2s.","default":false}},"type":"object","required":["query"],"title":"InsightAskRequest"},"InsightAskResponse":{"properties":{"query":{"type":"string","title":"Query"},"answer":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Answer","description":"Markdown synthesis with [n] citations; null when no evidence was found."},"sources":{"type":"array","title":"Sources","items":{"$ref":"#/components/schemas/AskSource"},"description":"The agent-curated evidence set, best-first."},"took_ms":{"type":"integer","title":"Took Ms"},"debug":{"additionalProperties":true,"type":"object","title":"Debug"},"note_type":{"title":"Note Type","enum":["source_not_covered","no_evidence",null],"description":"Null when evidence was found. source_not_covered = a named show/episode is not in the corpus; no_evidence = covered ground, nothing matched the question."},"note":{"title":"Note","description":"One-sentence human explanation paired with note_type; null when evidence was found."}},"type":"object","required":["query","answer","sources","note","note_type","took_ms","debug"],"title":"InsightAskResponse"},"InsightHit":{"properties":{"id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Id"},"speaker_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Speaker Name"},"speaker_title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Speaker Title"},"mentions":{"anyOf":[{"items":{"$ref":"#/components/schemas/Mention"},"type":"array"},{"type":"null"}],"title":"Mentions"},"published_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Published At"},"podcast_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Podcast Name"},"episode_title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Episode Title"},"episode_audio_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Episode Audio Url"},"episode_image_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Episode Image Url"},"podcast_image_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Podcast Image Url"},"episode_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Episode Url"},"episode_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Episode Id"},"podcast_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Podcast Id"},"quote":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Quote"},"title":{"type":"string","title":"Title","description":"One-line headline of the extracted claim."},"text":{"type":"string","title":"Text","description":"The extracted claim — our one-paragraph paraphrase. `quote` is the verbatim transcript."},"type":{"type":"string","title":"Type","enum":["thesis","observation","data_point"],"description":"thesis = forward-looking argument, observation = qualitative read on the present, data_point = a concrete number."},"speaker_role":{"type":"string","title":"Speaker Role","enum":["operator","investor","researcher","commentator","government","unknown"],"description":"What the speaker does professionally."},"start_seconds":{"type":"number","title":"Start Seconds","description":"Position of the claim in the episode audio, seconds from the start."},"end_seconds":{"type":"number","title":"End Seconds"}},"type":"object","title":"InsightHit"},"InsightQuoteResponse":{"properties":{"insight_id":{"type":"string","title":"Insight Id"},"episode_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Episode Id"},"sonic_ingest_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sonic Ingest Id"},"segment_start":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Segment Start"},"segment_end":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Segment End"},"timestamp_start":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Timestamp Start"},"timestamp_end":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Timestamp End"},"speaker_label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Speaker Label"},"speaker_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Speaker Name"},"speaker_title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Speaker Title"},"insight_title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Insight Title"},"insight":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Insight"},"podcast_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Podcast Name"},"episode_title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Episode Title"},"podscan_episode_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Podscan Episode Id"},"quote":{"type":"string","title":"Quote"},"segments":{"items":{"$ref":"#/components/schemas/QuoteSegment"},"type":"array","title":"Segments"},"transcript_total_segments":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Transcript Total Segments"},"took_ms":{"type":"integer","title":"Took Ms"}},"type":"object","required":["insight_id","quote","segments","took_ms"],"title":"InsightQuoteResponse"},"InsightSearchRequest":{"properties":{"query":{"anyOf":[{"type":"string","maxLength":2000},{"type":"null"}],"title":"Query","description":"Natural-language query, up to 2000 characters. When present, embedded and used for vector ANN ranking unless rank_by overrides. Whitespace-only is treated as absent."},"tickers":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Tickers","description":"Filter to insights mentioning at least one of these tickers. OMIT to include untickered insights too."},"types":{"anyOf":[{"items":{"type":"string","enum":["thesis","observation","data_point"]},"type":"array"},{"type":"null"}],"title":"Types"},"mentions":{"anyOf":[{"items":{"$ref":"#/components/schemas/MentionFilter"},"type":"array"},{"type":"null"}],"title":"Mentions"},"sentiments":{"anyOf":[{"items":{"type":"string","enum":["bullish","bearish","neutral"]},"type":"array"},{"type":"null"}],"title":"Sentiments","description":"Coarse: insight has any mention with this sentiment. Set strict_ticker_sentiment_pair=true to enforce that the sentiment is on one of the requested tickers."},"speaker_roles":{"anyOf":[{"items":{"type":"string","enum":["operator","investor","researcher","commentator","government","unknown"]},"type":"array"},{"type":"null"}],"title":"Speaker Roles"},"date_from":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Date From"},"date_to":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Date To"},"ids":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Ids","description":"Filter to specific insight ids (the `id` field on each hit, format `in-...`). Use this for deep-link / refresh round-trips where the FE already knows which insights it wants."},"episode_ids":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Episode Ids","description":"Filter to specific episodes (`sonic_ingest_id`). Powers the /episode/[id] page."},"podcast_ids":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Podcast Ids","description":"Filter to all episodes of these podcasts (our minted `sonic_podcast_id`; the raw `podscan_podcast_id` is also accepted for back-compat). Resolved at request time via Mongo lookup, then expanded to `episode_ids` before the TPUF query. Powers the /pod/[id] page."},"top_k":{"type":"integer","default":25,"minimum":1,"maximum":25,"title":"Top K","description":"Results per page, up to 25. Further results are reachable via `next_cursor`."},"cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cursor","description":"Opaque pagination token from a previous response's `next_cursor`. When set, every other field is ignored — the original query's parameters are replayed server-side. Cursors expire after ~15 minutes. Client-tier keys only (internal callers page with top_k)."},"include_quotes":{"type":"boolean","title":"Include Quotes","description":"Attach the verbatim transcript passage to each hit as `quote` (same rendered text as GET /insights/{id}/quote). Transcripts are fetched from cold storage — expect roughly +0.5-1.5s per 25 results. Quotes are resolved for at most the first 100 hits; a hit whose transcript is unavailable carries `quote: null` and the search succeeds regardless.","default":false}},"type":"object","title":"InsightSearchRequest"},"InsightSearchResponse":{"properties":{"results":{"items":{"$ref":"#/components/schemas/InsightHit"},"type":"array","title":"Results"},"took_ms":{"type":"integer","title":"Took Ms"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor"},"pagination_end":{"title":"Pagination End","enum":["ranking_exhausted","end_of_results",null],"description":"Null while more pages exist. Otherwise why paging stopped: ranking_exhausted = the relevance ranking's quality boundary (narrow the query for more depth); end_of_results = the browse window is fully paged."}},"type":"object","required":["results","next_cursor","pagination_end","took_ms"],"title":"InsightSearchResponse"},"Mention":{"properties":{"company":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Company"},"ticker":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ticker"},"sentiment":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sentiment"},"mention_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Mention Id"}},"type":"object","title":"Mention","description":"One company mention on an insight. `ticker` is the canonical symbol\n(deep-links to /companies/{ticker}) or null for companies we don't track\ncanonically; `company` always carries the extracted display name."},"MentionFilter":{"properties":{"ticker":{"type":"string","maxLength":12,"minLength":1,"title":"Ticker"},"sentiment":{"anyOf":[{"type":"string","enum":["bullish","bearish","neutral"]},{"type":"null"}],"title":"Sentiment"}},"additionalProperties":false,"type":"object","required":["ticker"],"title":"MentionFilter","description":"One requested (ticker, sentiment) pair for the `mentions` filter.\nA hit matches when at least one pair holds on a SINGLE mention —\n\"bearish about NVDA\", not \"mentions NVDA and is bearish about\nsomething\". `sentiment` optional: a bare ticker pair is just a\nticker match."},"PodcastCard":{"properties":{"id":{"type":"string","title":"Id"},"podcast_ids":{"items":{"type":"string"},"type":"array","title":"Podcast Ids"},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"image_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Image Url"},"insight_count":{"type":"integer","title":"Insight Count"},"episode_count":{"type":"integer","title":"Episode Count"},"latest_episode_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Latest Episode At"}},"type":"object","required":["id","podcast_ids","insight_count","episode_count"],"title":"PodcastCard"},"PodcastEpisode":{"properties":{"episode_id":{"type":"string","title":"Episode Id"},"title":{"type":"string","title":"Title"},"image_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Image Url"},"published_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Published At"},"insight_count":{"type":"integer","title":"Insight Count"},"episode_audio_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Episode Audio Url"},"episode_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Episode Url"},"duplicate_of":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Duplicate Of"}},"type":"object","required":["episode_id","title","insight_count"],"title":"PodcastEpisode"},"PodcastEpisodesResponse":{"properties":{"podcast_id":{"type":"string","title":"Podcast Id"},"podcast_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Podcast Name"},"podcast_image_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Podcast Image Url"},"podcast_description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Podcast Description"},"total_insights":{"type":"integer","title":"Total Insights"},"episode_count":{"type":"integer","title":"Episode Count"},"episodes":{"items":{"$ref":"#/components/schemas/PodcastEpisode"},"type":"array","title":"Episodes"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor"}},"type":"object","required":["podcast_id","total_insights","episode_count","episodes"],"title":"PodcastEpisodesResponse"},"PodcastListResponse":{"properties":{"podcasts":{"items":{"$ref":"#/components/schemas/PodcastCard"},"type":"array","title":"Podcasts"},"count":{"type":"integer","title":"Count"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor"}},"type":"object","required":["podcasts","count"],"title":"PodcastListResponse"},"QuoteSegment":{"properties":{"index":{"type":"integer","title":"Index"},"speaker_label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Speaker Label"},"text":{"type":"string","title":"Text"},"timestamp_start":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Timestamp Start"},"timestamp_end":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Timestamp End"}},"type":"object","required":["index","text"],"title":"QuoteSegment"},"SimilarInsightsResponse":{"properties":{"seed":{"$ref":"#/components/schemas/InsightHit"},"comparables":{"items":{"$ref":"#/components/schemas/InsightHit"},"type":"array","title":"Comparables"},"computed_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Computed At"},"version":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Version"}},"type":"object","required":["seed","comparables"],"title":"SimilarInsightsResponse"},"AskSource":{"type":"object","title":"AskSource","description":"A search-hit insight object (same fields as /v1/insights/search results) plus `n` — the number the answer's [n] citations resolve to.","properties":{"id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Id"},"speaker_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Speaker Name"},"speaker_title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Speaker Title"},"mentions":{"anyOf":[{"items":{"$ref":"#/components/schemas/Mention"},"type":"array"},{"type":"null"}],"title":"Mentions"},"published_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Published At"},"podcast_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Podcast Name"},"episode_title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Episode Title"},"episode_audio_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Episode Audio Url"},"episode_image_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Episode Image Url"},"podcast_image_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Podcast Image Url"},"episode_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Episode Url"},"episode_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Episode Id"},"podcast_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Podcast Id"},"quote":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Quote"},"title":{"type":"string","title":"Title","description":"One-line headline of the extracted claim."},"text":{"type":"string","title":"Text","description":"The extracted claim — our one-paragraph paraphrase. `quote` is the verbatim transcript."},"type":{"type":"string","title":"Type","enum":["thesis","observation","data_point"],"description":"thesis = forward-looking argument, observation = qualitative read on the present, data_point = a concrete number."},"speaker_role":{"type":"string","title":"Speaker Role","enum":["operator","investor","researcher","commentator","government","unknown"],"description":"What the speaker does professionally."},"start_seconds":{"type":"number","title":"Start Seconds","description":"Position of the claim in the episode audio, seconds from the start."},"end_seconds":{"type":"number","title":"End Seconds"},"n":{"type":"integer","title":"N","description":"Citation number; the answer's [n] markers resolve to this source."}}},"ErrorResponse":{"type":"object","title":"ErrorResponse","required":["error"],"properties":{"error":{"type":"object","required":["type","message"],"properties":{"type":{"type":"string","enum":["invalid_request_error","authentication_error","permission_error","not_found_error","rate_limit_error","api_error"]},"message":{"type":"string"}}}}}},"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"API key"},"apiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key"}}},"security":[{"bearerAuth":[]},{"apiKeyAuth":[]}]}