GET /v1/podcasts/episodes/search finds dialogue inside podcast episodes. Each result is a segment of an episode, returned with bounded transcript windows centered on the highest-relevance dialogue lines (flagged is_match: true) and any highlight clips that overlap the segment.
Available to MCP agents as
particle_podcast_search_transcripts.- Where have founders talked about regretting how much they raised, in their own words?
- Which segments mention a specific product name or ticker, spoken exactly?
- What did guests on one show say about a topic in the last 30 days?
When to use Episode search vs Mentions
The Particle podcast surface ships two complementary dialogue-search endpoints. Pick by what you’re really asking.
Episode search ranks segments by relevance. Mentions returns episodes with all of their mention windows in time order. Different shapes, different jobs.
semantic_search — search by meaning
Vector search is the right tool when the surface words in dialogue might not match the surface words in your query. Two speakers can discuss the same idea using totally different vocabulary, and a lexical engine misses both. Express your query the way you’d describe the topic to a colleague — full sentences are welcome.
semantic_search is not good at:
keyword_search — search by exact tokens (BM25)
Use this when the exact form of a token matters: company tickers, drug names, model numbers, hashtags. Tokens are matched after the same normalization the index applies (lowercased, English tokenizer, no stemming); punctuation splits tokens, and single-letter tokens without a digit are dropped, so spell terms out rather than abbreviating to one character. Multi-word queries are matched as a bag of tokens, ranked by BM25.
Every token you type must appear somewhere in the matched passage. That is what makes results trustworthy — a hit is guaranteed to contain your terms — but it also means a loose pile of related words that never co-occur in one passage returns nothing. Pass keyword_match=ranked for that case and the words steer relevance instead of excluding anything.
To require an exact ordered phrase rather than independent tokens, wrap it in double quotes — keyword_search="machine learning". Multiple quoted phrases must all appear ("central bank" "interest rates"). Quoted phrases filter under both modes, so to relax a phrase remove its quotes rather than switching mode.
A quoted phrase requires those words adjacent and in order in the segment’s spoken dialogue. A speaker’s own name is not indexed as dialogue — searching "Graham Duncan" returns segments where the name appears in what was said, not every segment he speaks in. Use entity_id or /v1/podcasts/mentions to find a person’s appearances instead. Adjacency is measured across the segment’s dialogue as a whole, so in rare cases a phrase can straddle the break between two consecutive lines.
There is no boolean OR — a bare OR is matched as an ordinary word. Send alternatives as separate requests.
Hybrid: semantic_search + keyword_search
When you want to rank by an idea and a specific term at once, send both. Each leg runs independently — vector similarity and BM25 — and the two result sets are fused via reciprocal rank fusion. Under the default keyword_match=required the fusion is an intersection: your keyword tokens filter both legs, so a segment cannot surface on vector similarity alone if it never says what you typed. Pass keyword_match=ranked to make it a union instead, where an unquoted keyword only boosts relevance and a strong vector match can surface without it. Quoted phrases are hard filters under both modes, so every result contains them no matter which leg it came from.
Scoping a ranked search to an entity
entity_id and company_id here are filters — they narrow ranked candidates to episodes featuring the resolved entity. The ranking still comes from semantic_search / keyword_search. To read every line about an entity, use Mentions instead.
Lines a person said: speaker
entity_id scopes a search to episodes featuring a person, so a match can be the host quoting them, or a clip of them played on someone else’s show. speaker answers the narrower question, did they say it: it keeps only segments in which the person speaks a line, and only their lines can be a window’s is_match. Pass a person slug or ID from GET /v1/entities/search; a person’s entity slug works too.
- Their own words. With
keyword_search, the person’s lines in the segment must carry every quoted phrase, and every unquoted term underkeyword_match=required. A phrase may run across their consecutive lines, since transcription splits a sentence across lines, but never into someone else’s line. - The original, not the replays. Only speaking roles count (host, guest, panelist, correspondent). A soundbite of the person played on another show is not them speaking, so the search above returns the interview where they said it and leaves out the shows that replayed it. Drop
speakerto see who repeated it. - An empty page is not proof. Retrieval narrows to the person’s episodes before the line check runs, so a line of theirs can rank below other segments of those episodes and be missed. Narrow with
podcast_id,episode_id,sinceoruntil, or trysemantic_search, before concluding they never said it. format=compactreturns the same matches withoutwindows, but still loads transcripts to check whose lines they are, so it is not as fast as an unfiltered compact page.
speaker combines with entity_id/company_id (lines they spoke in episodes featuring another entity), but not with the guest_ filters.
Lines by speaker role
speaker_role keeps only lines whose speaker held one of the roles in the episode: guest, panelist, host, correspondent, or speaker for any of them, comma-separated and case-insensitive. speaker_role=guest,panelist leaves out the hosts’ lines, such as a news show’s scripted reads:
- Every match is in a role you named. Every
is_matchline is spoken in one of the roles, by therolethe line carries, and a segment is returned only when one of its lines is. Withkeyword_search, those lines must say it. - No default. Omit
speaker_roleto keep every speaker’s lines. When it names roles, the page echoes them inspeaker_roles. - Not
role.roleis howentity_idorcompany_idrelates to the episode;speaker_roleis whose lines count. They combine:entity_id=sam-altman&role=guest&speaker_role=hostis the hosts’ lines in episodes where Sam Altman is a guest. - With
speaker, it keeps that person’s lines in those roles, and returns422when they have never spoken in any of them, naming the roles they hold. - With the
guest_filters, which count their guests’ lines as a guest or panelist, it must nameguestorpanelist; otherwise it returns422.
Response
segment_types is the segment types the page was searched in: the type you passed, or by default TOPIC_DISCUSSION and INTERVIEW, the segments where the conversation happens. Intros, outros, ad reads, transitions and personal banter are left out unless type names one. speaker_roles appears beside it when speaker_role named any.
windows is bounded — never the whole segment. Each window centers on one or more high-relevance lines (flagged is_match: true) padded with surrounding context. A single segment can produce multiple non-overlapping windows when the top-scored lines are far apart inside it. When the line-scoring path can’t pinpoint a match (e.g., a degraded embedding service or no individual line scored above zero), the window falls back to the segment’s opening lines and is flagged is_preview: true.
Lines in topic-discussion and interview segments carry speaker_statement_characteristics: labels for how the line is said (its sentiment, certainty, specificity and temporal orientation). See Speaker statement characteristics.
Pass the same labels as speaker_statement_characteristics[<axis>]=<labels> to keep only lines said a certain way: every is_match line then carries labels the filter accepts. It takes the topic as semantic_search and cannot be combined with a quoted keyword_search phrase or keyword_match=ranked. With no topic at all, scoped by episode_id, speaker or a guest_ filter, it returns the strongest lines said that way, at most 50 (match.source is characteristics). With speaker or the guest_ filters, only the lines the selected people say count: guest_occupation=cardiologists with speaker_statement_characteristics[temporal_orientation]=future finds what cardiologists predicted. See Filtering by characteristics.
With format=compact, windows and clips are absent from every match and the rest of the shape is unchanged — the segment’s description and summary stand in for the dialogue when deciding whether to read it.
match.source is semantic, keyword, hybrid, or characteristics (a search without a topic, which has no relevance_score) — branch on it when rendering. clips is omitted when no highlight clip overlaps the segment. The page-level entity block appears when an entity_id or company_id filter was provided and resolved successfully — for company_id it is the company’s linked entity — and a company block additionally appears alongside it when the filter was a company_id. A reference that can’t be resolved is rejected with 422 unresolved_reference, naming the parameter and the value that failed and pointing at the endpoint that turns your text into a usable slug — an unresolvable filter is a typo to fix, not an empty result to render. A company_id that resolves to a company we hold no linked entity for is different: the request was valid and there is simply nothing to filter on, so it returns empty data and still echoes the company block (without entity) so you can render what you matched.
Filters
When a search comes back empty
An empty page is ambiguous on its own: it could mean the corpus holds nothing on your topic, or that one filter was too narrow. So when the first page of a search comes back empty and we can attribute why, the response carries adiagnostics block that says which it was.
Treat it as advisory — check whether it is present rather than assuming it. It
is absent whenever an empty page has nothing we can honestly attribute: paging
past the end of a result set (an empty page after a cursor is ordinary
exhaustion, not a filter problem), a company_id that resolves to a company
with no linked entity, a language filter we cannot reproduce in the probes,
and the case where ranked matches were found but dropped on the way out — a
data-consistency problem on our side rather than something your request can fix.
We would rather return nothing than name a filter that is not the cause.
outcome is one of:
Each entry in
filters reports one filter your request applied. remedy says
why that filter excluded everything, and retry_with is the parameter change to
apply to the same request — an empty value means remove that parameter. Where
one parameter cannot be cleared on its own, retry_with names every parameter
that has to change together.
Entries marked emptied_results were verified by re-running your search with
that one filter removed and everything else held fixed, so their retry_with is
a change already known to return results rather than a guess.
An entry marked unmeasured is one whose re-run did not finish in time, which
can happen with a quoted phrase of common words: whether removing it alone
returns results is unknown, not false. The diagnostics still name any filter
that was measured to empty the results, and are left out when none was.
Under weak_semantic_match, the top-level retry_with (for example
{"keyword_search": "tariffs"}, plus "keyword_match": "required" if you sent
ranked) is verified the same way and leads next_steps. Prefer it: removing podcast_id may also return results, but from
other shows. An empty semantic_search page on one show means only that nothing
there cleared the relevance cutoff, not that the show never discusses the topic.
Pagination
Standardlimit (1–100, default 25) + opaque cursor. Pass the cursor from the previous response back as ?cursor=… to fetch the next page. Cursors are opaque — don’t parse them.
Related
- Podcast search — find a podcast by name, not dialogue inside episodes.
- Mentions — every line where a person or company is mentioned, episode-grouped.
- Transcripts → mentions in one episode — every entity mentioned in a single episode.
- Episodes — episode-level recall when you don’t need dialogue.