> ## 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.

# Changelog

> What's new on the Particle Pro REST API — new endpoints and capabilities, and anything that could affect an existing integration.

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.

## September 2026

### Pay per request with x402 — keyless access for agents

Every standard and premium `/v1/*` endpoint now accepts an [x402](/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`](/errors/payment_required) body; the agent signs the transfer, retries with `PAYMENT-SIGNATURE`, and receives the response plus a `PAYMENT-RESPONSE` receipt. Prices are one cent per unit of cost weight ($0.01 standard, up to $0.03 premium); 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`](/podcasts/advertising#shows-a-company-could-advertise-on-next) (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`.

### Sponsor prospecting: contacts and the long tail; related-show filters

[`GET /v1/podcasts/{id}/recommendations/sponsors`](/podcasts/advertising#sponsors-a-podcast-could-pitch) 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`](/companies/overview) gains `role=` with the same classes (plus `other`), matching the classifier's first-match rule. [`GET /v1/podcasts/{id}/related`](/podcasts/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`](/podcasts/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}`](/podcasts/related#the-five-most-related-shows-inline) 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`](/podcasts/related-episodes) 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`](/podcasts/advertising#sponsors-a-podcast-could-pitch) 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`](/podcasts/guests#recommended-podcasts-for-a-guest) 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`](/podcasts/guests#recommended-guests-for-a-podcast) 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`](/podcasts/lookup#look-up-an-episode-by-apple-podcasts-episode-id) 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`](/podcasts/lookup) and takes the same shape: one `platform`, up to 100 `identifier`s, 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`](/podcasts/episode-search).

### `GET /v1/podcasts/advertising/leaderboard/preview` — the leaderboard's open teaser

A new standard-tier [`GET /v1/podcasts/advertising/leaderboard/preview`](/api-reference/podcast-advertising/get-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`](/api-reference/podcast-advertising/get-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](/podcasts/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`](/api-reference/podcast-episodes/list-episodes) and
  [`GET /v1/podcasts/{id}/episodes`](/api-reference/podcasts/list-episodes-for-a-podcast)
  previously embedded *every* speaker, while
  [`GET /v1/podcasts/episodes/{id}`](/api-reference/podcast-episodes/get-an-episode) 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=…`](/api-reference/podcast-episodes/list-speakers-in-an-episode)
  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`](/api-reference/alerts/send-a-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](/alerts/webhooks#send-a-test-event).

## June 2026

### Alert filters — narrow what an alert surfaces

[`POST /v1/projects/{projectId}/alerts`](/api-reference/alerts/create-an-alert) and [`PATCH /v1/alerts/{id}`](/api-reference/alerts/update-an-alert) 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](/alerts/overview#filtering-matches) for the full spec and examples.

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

A new [`GET /v1/entities/search`](/api-reference/entities/search-entities) 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](/podcasts/search).

* **If you used it to search episode dialogue** (`semantic_search` / `keyword_search`), migrate to [`GET /v1/podcasts/episodes/search`](/podcasts/episode-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](mailto:api@particle.pro).
