Skip to main content
The API grows additively — new fields, parameters, and endpoints never break an existing call. Breaking changes are rare, announced ahead of time, and called out here explicitly.

October 2026

The guests behind a show’s expertise profile

GET /v1/podcasts/{id}/guests/expertise/{code} lists the guests behind one occupation or field of research on a show, most appearances first, such as GET /v1/podcasts/all-in/guests/expertise/political-science. It takes any occupation or field at any level, including SOC groups and everyday names such as doctors, and counts what the show’s profile counts, so the guests’ appearances add up to the profile’s count for the code.

Usage counts what each call draws

GET /v1/me/usage now counts billable_requests in the requests a call draws from your plan, not one per call. A transcription request that queues an episode draws 100 requests on plans billed in requests, so it now counts 100, matching what your plan’s included requests (or overage) are charged. Other calls count what their price draws, which today is 1 for every other endpoint and tool and for a transcription request answered 200. A call to an endpoint or tool your subscription does not price, and a refunded transcription request, no longer count. The new billable_basis says how the count was made: requests on plans billed in requests, calls on plans billed in dollars, where each billed call counts once, and calls_fallback when billing data could not be reached.

Failed transcription requests are refunded

A billed transcription request that ends failed is now refunded automatically, usually within half an hour: the included request, prepaid credit or overage it used is returned. The request gains refunded_at, set when it is.

Faster transcription requests, with no limit on how many

Transcription requests are transcribed concurrently, episodes of one podcast included, and most finish within a few minutes. There is no limit on how many you send. The guide now lists how long to expect by episode length.

Facts on chart entries

Entries on GET /v1/podcasts/entities/charts/{category} gain facts: the network of a TV series, the developer of a video game or software application, and the publisher of a video game, book, or periodical. Each fact links to the company’s own entity when it has one. An entity carries at most one fact of each kind, and facts is absent for every other entity.

A short semantic search on one show says when keyword_search finds it

A one-word semantic_search scoped to a show, such as semantic_search=tariffs&podcast_id=all-in, can return nothing while the show says the word many times. A short query matches long passages weakly. The empty-search diagnostics now report this as the new outcome weak_semantic_match, with a verified top-level retry_with that adds the words as keyword_search and keeps your filters. The podcast_id and episode_id remedies no longer say the show or episode has no matching dialogue when only the relevance cutoff was missed.

Fixed-value parameters accept any case and reject unknown values

A parameter that takes a fixed set of values now accepts them in any case: type=ad on GET /v1/podcasts/segments is type=AD, and bias=left is bias=LEFT. A value outside the set returns 422 naming the allowed values, where some parameters used to return an empty list. The OpenAPI spec now lists the allowed values as an enum for segment and clip type, product status, and publisher bias. GET /v1/podcasts/rankings and its movers answer 422 for a country or category_slug with no current chart, offering the closest category slugs, instead of an empty page. country=uk is accepted for gb. Entity chart slugs in the path, such as /v1/podcasts/entities/charts/Movies, also ignore case.

Retry writes safely with Idempotency-Key

The create POSTs take an optional Idempotency-Key header: alerts, webhook connections and their secret rotation, test webhooks, and transcription requests. Send one key per write and resend it on every retry. The write runs at most once, and a retry gets the first response back with Idempotent-Replayed: true, unbilled, for 24 hours. The same key with a different request returns 422 idempotency_key_reused, and a retry while the first attempt is still running returns 409 idempotency_key_in_use. These operations carry x-idempotent: true in the OpenAPI document. Requests without the header behave as before.

Check which key you’re using

GET /v1/me returns the organization, project, and plan an API key belongs to, with the key’s name and prefix. It is free: call it to confirm a key works and which project it reports to. GET /v1/podcasts/lookup and GET /v1/podcasts/episodes/lookup take links as they are: paste a show’s or episode’s Spotify, Apple Podcasts or YouTube URL, a show page on another platform, or a feed URL, and it is resolved by what it points to. platform is now optional when every identifier is a link, and links from different platforms can share a call. GET /v1/podcasts/episodes/lookup also resolves Spotify episodes: Spotify episode IDs with platform=spotify, episode URLs and spotify:episode: URIs.

Share of voice for a keyword

GET /v1/podcasts/mentions/share-of-voice takes keyword_search in place of entity_id or company_id. It splits the lines that say a word or quoted phrase by the occupations or fields of the people who said them, counting a line only when its own speaker said every word. The response carries keyword instead of entity. A keyword’s window may span at most 90 days, and one said in more than 3,000 episodes in the window is refused with ways to narrow it. Passing both a keyword and an entity returns 422.

Transcription requests

Request the transcript of any requestable episode in a podcast’s back catalogue with POST /v1/podcasts/transcription/requests. A request starts transcribing within seconds while there is capacity, and the episode keeps its ID, so once it reads transcript_status: transcribed every episode endpoint serves it. Only a request that queues an episode is billed as a transcription; requesting one again, or one another organization already queued, is billed as a lookup. GET /v1/podcasts/transcription/requests lists your requests and is not billed. transcript_not_available now points at the request endpoint in its resolve.

Expertise beside every speaker

Wherever a response names a speaker or a guest, it now says what they know. Each person carries a compact expertise: standing, seniority, and up to three of the most specific SOC 2018 occupations and ANZSRC 2020 fields, with the codes and slugs guest_occupation and guest_field take.
  • Transcript search matches and mention episodes gain speakers: the identified people speaking in their lines, matched to each line by name.
  • Episode speakers on GET /v1/podcasts/episodes/{id} and /speakers carry expertise.
  • So do the guest directory, a show’s roster and guest trends.
expertise is left out when the person has no profile yet, when their link is unsettled, when that appearance may describe someone else, or for a role profiles don’t cover, such as a moderator. See beside every speaker. Responses otherwise are unchanged.

Occupation groups and everyday names

Every occupation and guest_occupation parameter on the expertise endpoints and filters now takes SOC 2018 minor and broad groups as well as detailed occupations, so physicians (29-1210) covers all 13 physician specialties and 29-1000 covers 45 healthcare practitioner occupations. Everyday names such as doctors, medical-professionals, nurses, professors, journalists and athletes stand for the occupations people mean by them. GET /v1/people/occupations browses through the groups, and a group comes back with a new group field. Changed: GET /v1/people/occupations?parent= a SOC major group, such as 29-0000, now lists that group’s minor groups rather than its detailed occupations, and each minor group lists its broad groups. To reach the detailed occupations, keep browsing with parent, or pass the group itself to an occupation filter, which matches everything under it.

Words that name a field or a capacity, and guest_capacity

Words with no useful SOC 2018 occupation now filter by the field of research or capacity they name. guest_occupation=oncologists keeps guests in field 3211 Oncology, investors and bankers reach the finance fields, and founders, politicians, pundits and influencers reach the capacities executive_founder, public_official, journalist_analyst and creator_entertainer. GET /v1/people/fields?q= finds a field by these words too. A new guest_capacity filter on GET /v1/podcasts/episodes, its timeseries, transcript search and both mention endpoints keeps guests who know their field in that capacity; on the timeseries it narrows a subject rather than being one. The podcast list accepts a word for a field but rejects one for a capacity, since show profiles count guests by code.

Current team for NFL, MLB and NHL players

The sports object on GET /v1/people/{id} and GET /v1/podcasts/guests/{id} carries current_team for NFL, MLB and NHL players: the team the league’s current roster lists them on, with its entity_slug, the league, and as_of, when the roster was read. Rosters are read daily, so a trade shows up the next day. A player who is not on one of those rosters, including anyone in another league, has no current_team, and neither does an NHL player who has changed teams during the season, since the NHL feed does not say which team is current; a team in teams with no end_year is not proof the player is still there.

Speaker expertise

Podcast hosts, guests, panelists and correspondents now have expertise profiles: their occupations (SOC 2018, with ISCO-08 equivalents) and fields of research (ANZSRC 2020), how established their standing is, how senior they are, whether they still practice, and the credential that establishes it, quoted from the public record. Each guest appearance is read too: whether the guest spoke within their field, whether the appearance was mainly promotional, and how the show introduced them.
  • People: GET /v1/people/expertise finds the people listed under an occupation or a field, and GET /v1/people/{id}/expertise returns one person’s profile. GET /v1/people/occupations and GET /v1/people/fields, each with a /{code} variant, list the codes you can filter by.
  • Who is being booked: GET /v1/people/occupations/{code}/guests and GET /v1/people/fields/{code}/guests rank the most-booked guests in an occupation or field; the matching /podcasts endpoints rank the shows that book them.
  • Episodes and shows: GET /v1/podcasts/episodes/{id}/expertise lists an episode’s guests with their profiles and how each appearance reads. GET /v1/podcasts/{id}/guests/expertise profiles a show’s guests, and the podcast list filters by it with guest_occupation, guest_field, min_established_guest_share, min_in_field_guest_share, max_promotional_guest_share and min_analyzed_guest_appearances.
  • Filters: the episode list, transcript search, mentions and their timeseries take guest_occupation, guest_field, guest_standing, guest_seniority, guest_practicing and guest_in_field. The guest directory takes occupation, field, standing, seniority, practicing, capacity and employer, and a guest’s appearances take in_field and exclude_promotional.
  • Share of voice: GET /v1/podcasts/mentions/share-of-voice splits the lines that mention a company, person or topic by the occupations or fields of the people who said them.

A person’s current role prefers a titled one

When a person holds several current roles, current_role on GET /v1/people/{id} and the guest endpoints, and the title and company on an episode entity’s entity.person, now come from a titled role before an untitled one, then from the most recent start. Untitled company affiliations used to take precedence over a titled executive role because they were recorded more recently, so a chief executive could read as an untitled affiliation with no title. The episode stream, the feed and the episode archive now name the same role as GET /v1/podcasts/episodes/{id}/entities for every person.

Sports history on people

GET /v1/people/{id} and GET /v1/podcasts/guests/{id} return a sports object for athletes, coaches and other sports figures: the sports they play, the teams they played for or coached (role is athlete or coach, with start_year, end_year, and the leagues the team plays in today), and leagues recorded on the person directly. Each team and league carries an entity_slug for GET /v1/entities/{id}. A missing end_year means no end is recorded, which does not by itself mean the person is still on the team. People with no sports history are unchanged.

Each segment listed once

GET /v1/podcasts/episodes/{id}/segments, GET /v1/podcasts/segments and GET /v1/podcasts/segments/{id} no longer repeat an ad segment once for every ad read in its break. The repeats could push an episode’s last segments out of its segment list, which now returns every segment. An ad segment’s read_type is now always its first ad read’s. Paging through GET /v1/podcasts/segments no longer repeats or skips segments created at the same moment, such as an episode’s segments, which are all written at once.

Eight more sports charts

GET /v1/podcasts/entities/charts lists eight more sports charts: wnba, college-basketball, formula-1, nascar, and pro-wrestling for one league each, and soccer, golf, and tennis for a whole sport. They rank the way the league charts do; see League chart entries. On golf and tennis, the majors and Grand Slams chart alongside the players. The new charts publish 7d only, and all-sports now pools all thirteen sports charts. all-sports also applies the league charts’ top-10 rules now, so a league organization or a member the league shares with a neighbor sits directly below its top 10, and it measures reach against each league and the whole pool together.

Zero data retention

Zero data retention is available per organization on request: Particle keeps that each API and MCP request happened (time, endpoint, status, duration, size, organization, project, and credential) but not its contents. Organization settings in the platform dashboard shows whether it is on and how to request it. Alerts and Radar are not covered.

Speaker names follow the show’s own spelling

A speaker’s name is now spelled the way the show’s own title or notes print it, rather than the way speech-to-text heard it: a host who introduces herself as “Kristen Plake”, transcribed “Kristen Plaik”, is labelled Kristen Plake wherever the podcast’s description says so. The spelling is the same on every transcript line’s speaker, in the line text where the name is spoken, on word-level transcripts, in an episode’s speakers, and on the linked person. When a person’s spelling is corrected, their slug changes to match (kristen-plake); the old slug keeps resolving to the same person, so stored links and integrations keep working. Episodes transcribed earlier are corrected too, across the same fields, once a later episode or the show’s notes settle the spelling.

People and companies on episode entities

Entities embedded in episodes by the episode stream, the episode feed and the episode archive now carry entity.person or entity.company when the entity is a known person or company, exactly as GET /v1/podcasts/episodes/{id}/entities returns them, so a mention joins to the person or company it names by id.

Show archive

GET /v1/podcasts/archive/download returns every podcast we cover as one gzip-compressed JSON Lines file: each show exactly as GET /v1/podcasts/{id} returns it with every section, plus its external links with their attributes. It is rebuilt twice a day, answers 304 to an unchanged If-None-Match, and comes with the episode archive.

Steadier episode payloads and fuller mention lines

Episodes delivered by the episode stream, the episode feed and the episode archive no longer carry the podcast’s popularity on the podcast inside their segments’ and clips’ episode references. Popularity is re-ranked daily, so a stored copy goes stale overnight; read it from GET /v1/podcasts/{id} when you need it. Other endpoints are unchanged. An entity’s mention_lines now also lists the lines that use the entity’s own name when extraction recognized it under another form: an entity “Pope” annotated as “pontiff” now includes the line that says “the Pope”.

Entities and ads in the episode stream

The episode stream and the episode feed embed entities and ads at the fully_ingested milestone, by name or with include=all, in the same shape as episode archive records. fully_ingested now waits for entity extraction and ad analysis as well as clips, so the episodes it delivers carry all three; this usually adds under a minute.

Entities and ads in the episode archive

Episode archive records carry two more relations: entities, the knowledge graph entities discussed in the episode, each with its salience, its number of mentions and the transcript lines that mention it; and ads, the advertising spots detected, in playback order. Narrow them away with include like the others. They describe the episode itself; data that changes over time, such as an entity’s linked person or a sponsor company’s profile, is left to the entity and company endpoints.

Episode archive

The episode archive delivers every transcribed episode we hold as gzip-compressed JSON Lines files, one per month or day of publication, for loading our full history before following the episode stream. Each line is an episode exactly as the stream sends it with include=all. The archive is updated daily: late episodes, imported back catalogues and corrections are folded into the days they belong to, and updated_since on the listing returns only the days that changed, so a copy can be kept current. Episodes can also be replayed over Server-Sent Events. Available by arrangement; organizations without access receive 403 episode_archive_not_enabled.

Ad segments are no longer repeated

With include=segments, the episode stream and the episode feed returned an ad break once per ad read in it, so an episode could list the same segment several times and more segments than its segment_count. Each segment now appears once. When a break holds several ads, its read_type is that of the first ad recorded, where before it varied between requests. Episode topics also come back in a fixed order now.

September 2026

Corpus totals by transcript status

GET /v1/podcasts/stats adds episodes.totals: how many episodes are transcribed, and how many more are requestable in podcasts’ back catalogues, discovered but not transcribed, with totals_counted_at. Coverage from GET /v1/podcasts/{id}?include=coverage now reads from the same hourly counts. It answers instantly for any show, gains counted_at, and reports back_catalogue only once the counts include it.

Untranscribed episodes say so, and queued

A back-catalogue episode’s transcript, segments, speakers, entities, topics, ads, clips and related episodes now answer 409 transcript_not_available instead of 404: the episode exists, but has not been transcribed. One whose audio cannot be fetched answers 422 episode_audio_unavailable, since it never will be. An ID that matches nothing is still 404. transcript_status gains queued, for a back-catalogue episode whose transcript has been requested and is waiting to be transcribed; coverage counts it too, and transcript_status=requestable still lists it.

Coverage: how much of a show’s history is transcribed

GET /v1/podcasts/{id}?include=coverage adds a coverage section: every episode we know of (discovered_episode_count), split by transcript_status and by publication year, plus back_catalogue, which says when the show’s back catalogue was imported and whether it is complete. It is absent until the import has run. See transcript status and the back catalogue.

Transcript status and the back catalogue

Every episode now carries transcript_status: transcribed, processing, requestable or unavailable (one that can never be transcribed, such as one whose audio is gone). A show’s older episodes, which we discover in its feed but have not transcribed, are its back catalogue. The episode lists leave them out unless you ask: pass podcast_id and transcript_status=any, untranscribed or requestable to GET /v1/podcasts/episodes or GET /v1/podcasts/{id}/episodes; transcript_status=transcribed replaces has_transcript=true. GET /v1/podcasts/episodes/{id} returns a back-catalogue episode by id, and episode lookup now resolves a back-catalogue episode’s Apple Podcasts ID, guid or PodcastIndex ID instead of missing, with has_transcript and transcript_status on every result. Responses without transcript_status are unchanged.

Podcast lookup ignores identifier case and accepts platform=x

GET /v1/podcasts/lookup now matches identifiers regardless of case, so platform=twitter&identifier=thepeelpod finds the podcast whose X handle is ThePeelPod. Each result still echoes the spelling you sent, and an identifier stored in exactly your casing wins over one that differs only by case, so an ID that resolved before resolves to the same podcast now. platform=rss and platform=website keep matching URLs exactly. The platform parameter is case-insensitive too, and x is accepted as an alias for twitter there and on GET /v1/entities/lookup.

Sports and entertainment entity charts

GET /v1/podcasts/entities/charts lists seven more charts. Five league charts, nfl, nba, mlb, nhl, and college-football, rank the players, teams, coaches, and events of one league together, by a blend of reach and 52-day elevation; see League chart entries. Two composites, all-sports and all-entertainment, pool the league charts and the tv-series, movies, and video-games charts, and rank the way all does. Each directory row gains group (overall, entertainment, or sports), for building tabs without a hard-coded chart list, and windows, the windows that chart publishes. The composites publish 7d only.

Keyword alerts

Alerts gain a third kind, KEYWORD_MENTION, that watches a phrase instead of an entity — for a startup whose brand differs from its legal name, a product, a drug, or any name the entity resolvers don’t know. Create one with kind: "KEYWORD_MENTION" and keyword in place of entities, and set description to say what the phrase means so same-name mentions of something else are filtered out. The phrase matches the way a double-quoted phrase in keyword search does, in topic-discussion and interview segments only. Preview, backfill, filters, digests, Slack and webhooks work as they do for entity alerts. A phrase that matched more than 700 podcast episodes in the past week is rejected as too broad on create, edit and preview. entities is now required only for entity kinds; alerts, matches and delivery payloads carry a keyword field on keyword alerts. See Alerts.

Episode search: format=compact for screening

GET /v1/podcasts/episodes/search takes format=compact. The page carries the same ranked matches with windows and clips omitted, and skips the transcript load and line scoring that produce them, so it is a fraction of the size and returns sooner. Each match keeps its episode, its segment (id, title, description, summary, bounds), and its match score; ranking, ordering, has_more, and cursor are unchanged. It is for sweeping many companies, themes, or dates and fetching dialogue only for the matches worth reading. The default format is unchanged; windows is now documented as optional in the schema because the compact page omits it. On GET /v1/podcasts/mentions, every is_mention line now carries segment_id, the segment containing that line. A window’s segment is where the window opens, context_lines before its first mention, so a mention in a segment’s opening lines was only citable to the previous segment; the line-level id is the citation.

Sponsors resolve to companies by declared domain, then by ad majority

Every advertising surface that links a sponsor to a company — sponsor leaderboards, per-podcast and per-publisher advertising, sponsor detail and placements, and sponsor prospecting — now resolves the link from the sponsor’s declared domain when it maps to one live company, else from the company most of its ads that aired in the last 180 days were attributed to, else from the all-time majority. Previously the company on the sponsor’s single newest ad won, so one mis-attributed read could relabel a sponsor for every surface. Prospecting rows gain company_contested, true when the recent majority is under 60%, when the declared domain does not map to a company, when a mapped domain is contradicted by an ad majority of 60% or more, and for every all-time fallback; a weak disagreement with a mapped domain is not contested; such rows keep their best-guess company but no longer carry contacts.

x402 discovery lists every MCP tool price

/.well-known/x402.json on both hosts now itemizes the MCP server’s per-tool prices under mcp.tools — one row per payable tool with its name, summary, price and network, next to the REST endpoint rows — and the MCP host’s manifest at mcp.particle.pro/.well-known/x402 carries the same list under tools on its MCP resource (the API host’s manifest lists only its own REST resources). The mcp.price range summary stays for readers that take a single string. GET /v1/podcasts/{id}/recommendations/sponsors (and include=recommended_sponsors on podcast detail and particle_podcast_resolve) now folds sponsor records that resolve to the same company or name into one row, cites the most related venues in via rather than the heaviest buyers, counts a publisher’s sibling feeds once, and caps the weight of ad volume on any one venue. Scores spread across the range again, so band separates advertisers present across several related publishers from single-venue overlaps; exclude_top_advertisers remains the way to look past the largest programmatic advertisers. GET /v1/podcasts/{id}/related, include=related on podcast detail, and particle_podcast_list_related now list only shows above a relatedness floor, so a show with too few embedded episodes gets a short or empty list instead of fifty near-zero “weak” rows. Feeds the screens flag as machine-generated or farmed (synthetic voice and script, text read by a synthetic voice, syndication spam, brand impersonation) are never listed as related shows, and a publisher’s duplicate feeds of one show — an “all shows” aggregate, a regional edition — appear once. An empty first page carries a coverage note (not_computed, none_above_floor or filtered_out) so a client can tell “not yet” from “nothing” from “your filters”. Recommended guests and sponsors, which read the same related sets, inherit the floor and the screening.

Related episodes: one story, many shows

GET /v1/podcasts/episodes/{id}/related and particle_podcast_list_related_episodes now carry at most two episodes per show, collapse the same content republished on another feed (including the query episode itself on a sibling feed) to one row, and exclude shows the AI and farm screens have flagged as machine-generated, machine-read, syndication spam or brand impersonation. Episodes of shows removed from the catalogue no longer appear with an empty podcast.

Pitch lists seed from bookings, once per publisher

GET /v1/podcasts/guests/{id}/recommendations/podcasts, and include=recommended_podcasts on particle_podcast_get_guest, now seed from the shows that booked the person as a guest or panelist; segments a correspondent files for their own newsroom no longer count, and a publisher’s sibling feeds count as one venue. A Bloomberg anchor’s pitch list was Bloomberg’s other feeds, and one appearance on an outlet with a republication feed scored as two. Scores and via follow: via still lists every seed show, strongest first.

Entity charts leave beta

GET /v1/podcasts/entities/charts and GET /v1/podcasts/entities/charts/{category} are generally available at the standard tier. Each day we publish an edition of five charts. The per-category mention charts tv-series, movies, and video-games rank entities by the distinct podcasts that named them in ordinary dialogue; the composite all draws on a wider curated pool of entity types (people, books, music, brands, organizations, sports, events, products, and more) and ranks by a blend of that reach and 52-day elevation, so its rank does not follow podcast_count; guests (source: guest_appearances) ranks people by the distinct shows they appeared on. Every entry carries its movement since the previous edition. Mention-chart entries on 7d editions also carry a momentum classification with the window averages behind it (signals is omitted on the guests chart and on 24h editions), and any entry may carry a one-line why, usually backed by a verbatim quote (quote is omitted when none was captured) — why itself is omitted when there is nothing notable to explain. While the quote’s source episode still exists (why.verifiable), the receipt carries the episode and the offsets to play it from; once it has been deleted, the episode fields are omitted and only the text and quote remain. Guest entries carry their in-window appearances instead, plus a highlight_clip when one of those appearances has a scored clip. include=timeseries adds each entry’s daily history as dated buckets; include=driven_by groups derivative entries under the entry driving them; sort=acceleration surfaces the fastest risers from anywhere in a mention chart. The category charts are volume-ranked, not acceleration-ranked.

Agent onboarding playbook and a complete endpoint map

api.particle.pro/agents.md (also at mcp.particle.pro/agents.md, and as the Agent onboarding page) is the playbook an agent follows to connect a client over MCP, the REST API, or x402 and run a first live query: which surface to pick, the configuration for each client, the restart most clients need before new tools appear, and what to do when something breaks. Paste “Read https://api.particle.pro/agents.md and connect me to Particle” into any coding agent. api.particle.pro/llms.txt maps every public endpoint and MCP tool to its documentation page and keyless per-call price, with the conventions (identifiers, pagination, include=, time, language, rate limits, errors) in one file. It is rendered from the endpoint and tool catalogs, so it is complete by construction. The docs index at docs.particle.pro/llms.txt now lists every page, and the agent conventions it opens with are the same text the MCP server sends on initialize. The /.well-known/x402 manifest links to all three, and the API reference introduction gathers the conventions in one table, including the rate limit: 10,000 requests per minute per organization. GET /v1/podcasts/{id}/recommendations/guests, and include=recommended_guests on podcast detail and particle_podcast_resolve, no longer reward how often a person recurs on a single related show, and a publisher’s sibling feeds now count once. Both mechanisms had let a news outlet’s own correspondents — who file segments on their newsroom’s feeds every week — outrank guests that several independent shows had chosen. Scores spread across the range instead of piling up near 1.0 and ties rank deterministically; because one publisher counts once, strong is reachable only for people booked by at least two related publishers.

Pay per request with x402 — keyless access for agents

Every standard and premium /v1/* endpoint now accepts an x402 USDC micropayment in place of an API key. A keyless request returns 402 with the payment requirements in a PAYMENT-REQUIRED header and a payment_required body; the agent signs the transfer, retries with PAYMENT-SIGNATURE, and receives the response plus a PAYMENT-RESPONSE receipt. Every price is a whole number of cents per call, from $0.01, and is listed per endpoint and per MCP tool at api.particle.pro/llms.txt; failed requests are never charged. Nothing changes for keyed traffic: any presented credential — even an invalid one — takes the normal authentication path. Free endpoints stay free, and alerts and enterprise surfaces still require an account.

Buy-side recommendations for advertisers

GET /v1/companies/{id}/podcast/recommendations (premium) lists the shows a company could advertise on next: every show it has never bought, ranked by relatedness to the shows its sponsors already run ads on, each weighted by how much it advertises there. include=via attaches the company’s own shows behind each pick; min_score and language filter. In the temporal holdout it recovered advertisers’ later buys at 13× the rate of category-by-popularity. MCP: include: ["podcast_recommendations"] on particle_company_get. GET /v1/podcasts/{id}/recommendations/sponsors gains include=contacts — up to three people at each recommended sponsor’s company most likely to own a sponsorship decision (marketing, partnerships, brand, media and advertising, communications, growth, then other executives), each with a title, a role class and a LinkedIn URL when known — and exclude_top_advertisers=N, which drops the N most active advertisers corpus-wide so the list shows the long tail where it is strongest. GET /v1/companies/{id}/people gains role= with the same classes (plus other), matching the classifier’s first-match rule. GET /v1/podcasts/{id}/related and particle_podcast_list_related gain catalogue filters: language, publishing_status, suitability_tier, min_popularity and exclude_same_publisher, applied to the stored ranking with pagination over the filtered list.

GET /v1/podcasts/episodes/lookup — resolve a hosting platform’s episode ID

platform=megaphone, omny, acast and art19 resolve a podcast host’s own episode ID. The ID is read from the episode’s audio URL, so coverage is simply whether that episode is served by that host, and an episode resolves under exactly one of them. Pass the bare ID rather than the audio URL: publishers route audio through arbitrary stacks of tracking prefixes, so the URL you hold may not resemble the one we stored, but the ID inside it is stable.

GET /v1/podcasts/episodes/lookup — resolve an RSS guid or PodcastIndex episode ID

platform=guid resolves the <guid> an episode carries in its publisher’s feed — the podcast ecosystem’s canonical episode identifier, and the broadest coverage of any identifier on this endpoint at roughly 89% of episodes. Guids are matched exactly as supplied and never parsed, since publishers use everything from UUIDs to permalink URLs. A guid identifies an episode within its feed rather than globally, so for the ~1.4% that reach more than one episode the lookup returns the one on the most popular podcast, deterministically. platform=podcastindex resolves a PodcastIndex episode ID. These are numeric and unique, so no tie-breaking applies; a non-numeric value is reported as a miss rather than failing the whole request.

GET /v1/podcasts/{id}/related — shows like this show, with the reasons

A new standard-tier GET /v1/podcasts/{id}/related returns the shows most related to a podcast, best first. Each result carries a calibrated score in (0, 1], a coarse band — strong (same beat and audience), moderate (overlapping subject or audience), weak (a loose connection) — and, with include=basis, a basis object naming the signals behind the pairing: content_similarity of recent episodes, topic_overlap with the top shared_topics, shared_guest_count with named shared_guests, same_publisher, shared_sponsor_count, and shared_category_count. Branch on band; the score’s exact thresholds may be recalibrated as the ranker improves. Related sets are precomputed per show from its embedded transcripts, topic profile, guest roster, network, and advertisers, restricted to the show’s language, and refreshed as new episodes land, so the endpoint is a fast page read. A show whose set has not been computed yet returns 200 with an empty data array, not 404. GET /v1/podcasts/{id} gains include=related for the five most related shows inline in lean form. The ranker was validated against Apple’s “You Might Also Like” lists, a temporal shared-guest holdout, and a calibrated editorial judge; the measured recall and precision figures are published alongside the methodology once the full-catalogue evaluation completes.

GET /v1/podcasts/episodes/{id}/related — episodes elsewhere covering the same story or subject

A new standard-tier GET /v1/podcasts/episodes/{id}/related returns episodes from other shows that cover the same story or subject as an episode, best first. Each result carries a score, a band, and with include=basis the reasons: content similarity, shared entities, shared topics, a shared news story, shared guests, and days apart. published_within_days narrows to a recency window (news-style use) and same_podcast=true opts the show’s own episodes in. Results are computed live from the episode’s content centroid and reranked on the structured signals, then cached, so first calls are slower than repeats. MCP: particle_podcast_list_related_episodes.

GET /v1/podcasts/{id}/recommendations/sponsors — advertisers that buy shows like this one

A new premium-tier GET /v1/podcasts/{id}/recommendations/sponsors is the prospecting list for a show selling its own inventory: sponsors that run on the podcast’s related shows but not on this one, ranked by how related those venues are, how much the sponsor buys there, and how recently. Each row carries the sponsor with its linked company (the path to people to contact), a score and band, shared_show_count, total_ads, last_ad_at, and with include=via the related shows that run it. active_since keeps only sponsors still buying after a date. The podcast detail embeds the first five with include=recommended_sponsors. MCP: include: ["recommended_sponsors"] on particle_podcast_resolve.

GET /v1/podcasts/guests/{id}/recommendations/podcasts and GET /v1/podcasts/{id}/recommendations/guests — the pitch list and the booking pipeline

Two standard-tier views derived from related sets and guest rosters. GET /v1/podcasts/guests/{id}/recommendations/podcasts lists shows a person has not appeared on, ranked by relatedness to the shows they have, with include=via naming the person’s own shows behind each recommendation. GET /v1/podcasts/{id}/recommendations/guests lists people who have guested on the show’s related shows but never on this one, with circuit regulars excluded and include=via naming the venues. Both return 200 with an empty data array until the relevant related sets have been computed. The podcast detail embeds the first five recommended guests with include=recommended_guests.

GET /v1/podcasts/episodes/lookup — resolve a YouTube video ID

platform=youtube now resolves a YouTube video to the episode it belongs to, alongside the existing apple / itunes support. Pass a bare video ID or a full URL — watch?v=, youtu.be/, /live/, /shorts/ and /embed/ forms are all accepted. Coverage reflects the videos we have already discovered for an episode (feed links, Podcasting 2.0 alternate enclosures, Media RSS, show-note URLs, channel matching) rather than a per-episode lookup against YouTube, so every video we hold resolves — there is no partial-window caveat like Apple’s. Where one video is attached to several episodes, the lookup returns the episode whose video came from the most precise discovery method — a feed enclosure or a runtime-validated channel match ahead of a URL scraped from show notes — with podcast popularity breaking ties, so repeated calls agree.

August 2026

GET /v1/podcasts/episodes/lookup — resolve an Apple Podcasts episode ID

A new standard-tier GET /v1/podcasts/episodes/lookup turns an Apple Podcasts episode identifier — the ?i= value in an Apple Podcasts URL — into a Particle episode. It is the episode-level counterpart to GET /v1/podcasts/lookup and takes the same shape: one platform, up to 100 identifiers, one echoed result per input, with the episode key omitted for identifiers that don’t resolve. Each hit carries the parent podcast, so one call tells you both the episode and its show. A full Apple Podcasts episode URL is accepted in place of a bare ID. Coverage is partial by design: Apple only publishes episode IDs through a show’s catalog listing, which is capped at that show’s 200 most recent episodes, so deep back-catalog episodes have no Apple ID to match. Recent episodes and small-to-mid shows resolve almost always. Treat a miss as a normal outcome and fall back to GET /v1/podcasts/episodes/search.

GET /v1/podcasts/advertising/leaderboard/preview — the leaderboard’s open teaser

A new standard-tier GET /v1/podcasts/advertising/leaderboard/preview returns the top 10 advertising sponsors of the trailing 7 days for a chosen metric. Each row carries movement versus the equivalent 7-day window ending 30 days ago, in the same previous_rank / movement / rank_change vocabulary as the entity charts. Every caller gets the same board regardless of plan, so it needs no premium access — use it for public or signed-out surfaces, and GET /v1/podcasts/advertising/leaderboard (premium) for deeper pages, other time windows, and company or publisher filtering.

GET /v1/entities/search — new contained match quality

match_quality gains a value: contained marks a result whose whole name appears inside a longer query — a sign the query names that entity plus something else (spacex elon musk returns both Elon Musk and SpaceX as contained). These results rank alongside strong matches with popularity breaking ties, but don’t auto-select one: the unmatched remainder of the query usually names a second entity or a qualifier, so let the user pick. Existing integrations that only branch on exact are unaffected.

July 2026

Speaker roles — a documented, closed set (breaking change)

role on a speaker is now guaranteed to be one of fourteen values, listed in full under Episodes → Speaker roles. Previously the field could carry any of ~130 values, including one-offs and typos produced during transcript analysis; those are now normalized into the documented buckets on the way out (a REPORTER is served as CORRESPONDENT, a CALLER as AUDIENCE).
  • The episode list endpoints now apply the same default role filter as the detail endpoint. GET /v1/podcasts/episodes and GET /v1/podcasts/{id}/episodes previously embedded every speaker, while GET /v1/podcasts/episodes/{id} returned only conversation participants — the same episode gave two different answers. Both now return HOST, GUEST, PANELIST and CORRESPONDENT. If you were reading advertisers or soundbite speakers out of a list response, read them from GET /v1/podcasts/episodes/{id}/speakers?role=… instead.
  • role accepts every documented value. soundbite_speaker, narrator, announcer, voice_actor, moderator, audience, producer and other are now valid filter values; previously several roles appeared in responses but could not be filtered for.
  • role=unknown now works as documented. It returns speakers we could not identify, whether the underlying row records that explicitly or carries no role at all. It previously matched only the latter, missing almost every such speaker.
  • Role values are case-insensitive. ?role=HOST, ?role=host and ?role=HoSt are equivalent, on the REST API, in MCP tool arguments, and in alert speaker_roles. Responses continue to use uppercase. Alert speaker_roles previously rejected lowercase with a 422.
  • CO_HOST is retired. It was never produced after May 2026 and is now folded into HOST. Filtering on co_host is no longer accepted; use host, which covers both.

POST /v1/alerts/{id}/test-webhook — send a test webhook

A new POST /v1/alerts/{id}/test-webhook fires a synthetic alert.match.created delivery to every webhook connection on an alert, so you can verify your endpoint — receipt, signature, and parsing — without waiting for a real match. The payload is byte-for-byte shaped like a live delivery and carries the alert’s real id, title, kind, and first watched entity, with illustrative sample match content.
  • The webhook envelope gains a test boolean, set to true only on these test deliveries and absent on real matches, so your handler can exercise its parse-and-verify path while skipping real side effects. Existing consumers are unaffected — real matches never carry the field.
  • Nothing is persisted: a test never appears in the alert’s delivery log. The response reports each connection’s outcome (delivered, status_code, duration_ms, error); a non-2xx or unreachable endpoint comes back as delivered: false while the request itself still returns 200.
See Alerts → Webhooks → Send a test event.

June 2026

Alert filters — narrow what an alert surfaces

POST /v1/projects/{projectId}/alerts and PATCH /v1/alerts/{id} now accept a filters object that narrows matches after detection — applied identically to the matches list, the realtime email, and the daily/weekly digests. Four axes:
  • languages — restrict to source episodes in specific BCP-47-like language tags (e.g. ["en", "pt-BR", "zh-Hant"]). Empty (the default) keeps every language.
  • relevance — EVERYTHING (default) returns on-target and incidental matches; RELEVANT narrows to on-target only, dropping passing mentions.
  • source_popularity — ANY (default) keeps every match; POPULAR keeps only matches from the top 5% of currently-charting podcasts.
  • speaker_roles — PODCAST_SPEAKER alerts only. Replaces the default appearance set (GUEST, PANELIST, CORRESPONDENT, AUDIENCE, SOUNDBITE_SPEAKER — HOST excluded). Sending this on an ENTITY_MENTION alert returns 422.
A PATCH with filters replaces the alert’s filter set wholesale — there’s no per-axis merge, so to add languages to an alert that already has relevance: "RELEVANT" you must send both. Send "filters": {} to clear every filter at once, or omit filters from the patch body to leave the existing set unchanged. Filters that narrow what gets detected (languages, speaker_roles) also gate the post-create historical backfill, so older matches that wouldn’t have passed the current filter were never stored — broadening either axis later won’t surface them. relevance and source_popularity are applied at read/delivery time, so the matches list and emails immediately reflect the change. Existing alerts keep their current behavior (no filters → every match surfaced). See Alerts → Filtering matches for the full spec and examples.

GET /v1/entities/search — purpose-built entity search (breaking change)

A new GET /v1/entities/search ranks people, companies, and knowledge graph entities for a free-text query — a name, partial name, nickname, stock ticker, @handle, or website domain — in one call. Each result is a discriminated union: type (person, company, or knowledge_graph_entity) says which kind it is, and exactly one of person/company/knowledge_graph_entity holds the matched record in the same compact shape those resources return elsewhere. Alongside it, match_quality and a mentions object (podcast_episodes, news_articles) help confirm the hit. Take the embedded record’s id (or slug) to the matching resource endpoint, or its id to alert creation.
  • The q= parameter on GET /v1/entities has been removed. Move name search to /v1/entities/search — passing q to GET /v1/entities now returns a 422 directing you there. GET /v1/entities still lists the most-mentioned entities and filters by type, podcast_id, or ids.
  • Search results are union-shaped (a person, company, or knowledge graph entity) rather than the always-an-entity shape GET /v1/entities returns — a match may be a person or company with no standalone entity, and vice versa.

GET /v1/podcasts/search now searches podcasts (breaking change)

As announced, GET /v1/podcasts/search now does what its name says: it searches podcasts by name and returns a ranked, paginated list of podcast objects. See Search.
  • If you used it to search episode dialogue (semantic_search / keyword_search), migrate to GET /v1/podcasts/episodes/search — the parameters and response shape are identical to what /v1/podcasts/search returned before this change.
  • GET /v1/podcasts?q=… keeps working and returns the same results as the new /v1/podcasts/search — it remains supported for backwards compatibility, but /v1/podcasts/search is now the canonical way to search podcasts, and the q parameter on /v1/podcasts may be retired in a future version.
  • Podcast search is metered as a standard endpoint — searching the catalog at /v1/podcasts/search now costs the same as /v1/podcasts.

Missing something? Email api@particle.pro.