The episode feed is a poll endpoint for “what’s new.” Each call returns the episodes that reached a chosen ingestion milestone since your cursor, in strict ingestion order, along with a cursor to resume from on your next poll. It’s available on every plan.Documentation Index
Fetch the complete documentation index at: https://docs.particle.pro/llms.txt
Use this file to discover all available pages before exploring further.
Which endpoint do I want?
- List episodes — browse/search the catalog by podcast, entity, company, date, etc. Ordered by publish date.
- Episode feed (this page) — poll for newly ingested episodes, resumably, on any plan.
- Episode stream — the same data pushed in real time over SSE. Enterprise.
data array of episodes plus has_more and cursor. The difference is the ordering and cursor: the feed is strictly monotonic in ingestion time with a keyset cursor, so polling never skips or re-counts episodes the way an offset list can when new rows arrive.
How polling works
Polling forward from now takes at least two calls: the first only mints a cursor; every call after it delivers episodes. (Backfilling withsince is the exception — that first call returns a backlog straight away; see Cold starts.)
- First call — omit
cursorandsince. The page is always empty — its only job is to return acursormarking “now” to poll forward from. You still pass your podcast filter (it’s required on every call), but it has no effect on this empty page: the cursor is a position in the global ingestion log, not a per-filter bookmark. - Subsequent calls — pass the
cursorfrom the previous response, and re-send the same filter (the cursor doesn’t carry it). You get every episode that reached your milestone since then, oldest-first, up tolimit, and a newcursor. - Persist the cursor and keep polling. Poll again immediately while
has_moreis true (you’re catching up); poll on your own interval oncehas_moreis false (you’re caught up).
Milestones and filters
Pick onemilestone (default transcribed) — episodes are returned once they reach that stage. The milestones work as they do for the stream, but the feed caps the podcast set it covers (see below):
| Parameter | Description |
|---|---|
milestone | discovered, transcribed (default), segmented, or fully_ingested. |
podcast_ids | Slugs or IDs to filter to (comma-separated). Hard limit of 100 per request. Unknown values are ignored; if no filter matches any podcast, the call returns 404. |
popularity_threshold | Number in (0,1); covers the most-popular podcasts at/above this popularity percentile. |
topic_ids | One or more topics (comma-separated): a slug path (e.g. sports/football/fantasy-football), ID, or ancestry hash. A topic also matches its descendants, and multiple topics are unioned. Scopes the covered podcasts to those that regularly cover the topic. Hard limit of 20 per request. Unknown topic returns 404. |
cursor | High-water mark from a prior response — the exact ingestion-log position to resume from. Omit (with no since) to start from now. |
since | First-call-only backfill anchor (ISO 8601); used only when no cursor is supplied, ignored once one is. Measured in ingestion time (when episodes entered the feed), not publish time — see Cold starts. |
limit | Max episodes per page (1–100, default 25). |
include | Heavy relations to embed in each episode (comma-separated): transcript, segments, clips, or all. Omitted by default. See Hydrate the payload. |
The feed covers at most 100 podcasts
The feed is the bounded, all-plans poll — it never fans out to an unbounded podcast set. You must supplypodcast_ids, topic_ids, popularity_threshold, or a combination (a request with none returns 422); the feed can’t be polled across every podcast. Whatever you supply, the feed covers at most 100 podcasts:
- Your
podcast_idsare always included (capped at 100 per request). popularity_thresholdthen fills any remaining slots with the most-popular podcasts above the threshold, dropping the least-popular tail to stay within 100. A low threshold can qualify many times more than 100 podcasts; the feed trims that to the top 100 by popularity — it doesn’t reject the request.topic_idsscopes that fill to one or more topics: the slots go to the most-popular podcasts that regularly cover the topic — a show that merely mentions it in passing (one fantasy-football episode out of hundreds of interviews) is excluded, while shows that genuinely focus on it are kept. Use it alone to follow the top shows in a niche, or withpopularity_thresholdto also apply a global popularity floor. The popularity threshold stays a global percentile, so for a niche topic prefertopic_idson its own.
Prefer an explicit, stable set? The feed’s That returns the podcasts where the topic carries a meaningful share of episodes and that clear a global popularity percentile. Take their IDs, pass them to the feed as
topic_ids and popularity_threshold are dynamic — each poll re-derives the covered podcasts (the most-popular shows in the topic), so the set drifts as charts and topic coverage move. To pin a fixed set instead, curate it once with the catalog endpoint and pass the result as podcast_ids:podcast_ids, and you’ll poll exactly that list — explicit and stable — rather than whatever currently tops the charts. As on the feed, popularity_threshold there is global (a percentile across all charting podcasts, not within the topic), so widen or drop it if a narrow topic returns too few podcasts.Hydrate the payload
Each episode carries only its metadata, counts, and flags (has_transcript, segment_count, clip_count) by default — the heavy relations are not shipped. To embed them and avoid a follow-up request per episode, pass include:
include value | Embeds | Available at milestone |
|---|---|---|
transcript | episode.transcript — the dialogue transcript, identical to GET /v1/podcasts/episodes/{id}/transcript?format=dialogue | transcribed |
segments | episode.segments | segmented |
clips | episode.clips | fully_ingested |
all | everything available at the chosen milestone | — |
include=transcript,clips.
A relation can only be embedded at a milestone that guarantees it. Each becomes available at the milestone above, and because milestones are ordered, you can only embed what your milestone has reached — asking for clips at milestone=transcribed returns 422. all is milestone-relative: it expands to exactly the relations your milestone guarantees, so it never conflicts (e.g. all at transcribed embeds just the transcript).
Word-level transcripts are paginated and can’t be embedded inline; fetch them from GET /v1/podcasts/episodes/{id}/transcript/words.
Example: follow five podcasts
Say you want a row in your store every time Acquired, All-In, No Priors, My First Million, or The Tim Ferriss Show drops a new episode. Pass them aspodcast_ids on every call.
podcast_ids for popularity_threshold (e.g. 0.9) — everything else is identical. Want the top shows in a topic — say fantasy football? Use topic_ids=sports/football/fantasy-football (alone, or with popularity_threshold); you poll up to the 100 most-popular shows on that topic without curating a list, and new shows that rise into it are picked up automatically.
Feed vs. stream
The feed and the stream deliver the same episodes at the same milestones with the same filters — the feed is the pull form, the stream the push form. The key difference: the feed always requires a filter and bounds its coverage to 100 podcasts, while the stream (Enterprise) can fan out to an unbounded set above a popularity floor. Use the feed when polling is simpler for you or you’re not on Enterprise; use the stream for real-time, low-latency delivery without managing a poll loop. Cursors are not interchangeable between them.Cold starts: existing data or a lost cursor
Most callers don’t begin from a blank slate — you already hold some episodes, or you lost the cursor you were persisting. The cursor is the only exact resume point, so once you have one, keep it. To (re)start without one, pick one of two clean paths:- Start at “now” and move on. Make a cursorless call for a fresh cursor and poll forward. You’ll never miss a future episode; you just won’t replay the window you were away. Reconcile that window, if you need it, with list episodes — it’s built for publish-ordered catalog backfill, which the feed’s
sinceis not. - Replay a window with
since. Setsinceto a wall-clock lower bound — the time of your last good sync, or “now minus the longest you might have been offline.” Becausesinceis ingestion time,since=<7 days ago>means everything that entered the feed in the last 7 days. Unlike the cursorless call, this first call returns that backlog right away (page throughhas_more), and you resume from the cursor it returns. Idempotent writes absorb any overlap with episodes you already have.
published_at you already hold and pass it as since. It usually works as a no-gap lower bound — an episode is ingested at or after it’s published, so anything newer lands beyond your mark — but it has two rough edges: it re-delivers back-catalog episodes that were published earlier yet ingested after your mark, and it breaks outright if any feed carries a future publish date (some do), which pushes since ahead of real ingestion times and skips episodes. A wall-clock sync time has neither problem, so reach for it first.
If you’ve lost the cursor and have no record of when you last synced, the newest published_at you hold is a reasonable last resort — it still won’t skip future episodes (barring a feed that dates an episode in the future), but you will get back episodes you already have, so make your writes idempotent and let the overlap fall away.
Related
- Episodes — browse and filter the catalog
- Episode stream — real-time SSE delivery (Enterprise)