Server definition
- Hash
- sha256:ef8d5d39740f923905e4fc05f1af1e8750bb83a23f1592834e769c41b4c4a36b
- What it is
- What a remote MCP server returned when asked what it offers: 28 tools
The blob, as servednamed by its sha256
{
"instructions": "Particle exposes podcast intelligence — podcasts, episodes, transcripts, guests, chart rankings, advertising, listener ratings, and political-bias and brand-suitability analytics — alongside company and people data and a topic taxonomy, as one connected knowledge graph. Start from a resolve tool to turn a free-text name into a slug, then use the get/list/search tools to pull and traverse data. Call `particle_catalog` to discover the full surface — every category and tool with its expand options and per-call price, and full input schemas per category; every public tool is callable by name even if tools/list did not advertise it.\n\nConventions a tool list alone hides:\n\n- **Pick the right surface.** Building an agent loop (Claude Code, Cursor, Codex, ChatGPT, a custom MCP client)? Use the MCP server at `https://mcp.particle.pro` — slug-first inputs, bundled responses, markdown output. Building a service (server-to-server, scheduled jobs)? Use the REST API at `https://api.particle.pro` with an API key — typed JSON, OpenAPI schema. Building a frontend? Route calls through your own backend so the API key stays server-side — never embed keys in browser or mobile code. Connecting a client for someone? Follow `https://api.particle.pro/agents.md`. The endpoint and tool map with per-call prices is `https://api.particle.pro/llms.txt`.\n- **No API key? Pay per request.** Every billable REST endpoint and `POST /mcp` accept an x402 USDC micropayment (Base) in place of a credential: a keyless call returns `402` with the payment requirements in the `PAYMENT-REQUIRED` header; sign the transfer and retry with `PAYMENT-SIGNATURE`, and the receipt comes back in `PAYMENT-RESPONSE`. Each REST call and each MCP `tools/call` costs its per-call price, a whole number of cents from $0.01 (MCP `initialize` and `tools/list` are free, and `particle_catalog` lists every tool with its price); a request whose every call fails is never charged, while a batch with any successful call settles in full. Free endpoints stay free; alerts and enterprise surfaces still need an account. Sending any credential — even an invalid one — takes the normal auth path instead. Discovery: `/.well-known/x402.json` on api.particle.pro and mcp.particle.pro lists the payable endpoints and terms, and every payable MCP tool with its per-call price under `mcp.tools`; the `/.well-known/x402` manifest on each host lists that host's own resources, so only the MCP host's carries the per-tool list; every payable operation in https://api.particle.pro/openapi.json carries `x-payment-info` and a `402` response; each 402 carries a Bazaar declaration, so the endpoints are searchable in the Coinbase x402 Bazaar. See https://docs.particle.pro/x402.md.\n- **Tools are lean by default and expand.** Most MCP tools return a minimal payload and opt into richer sections via an `include` array (e.g. a company's people, products, and competitors; a person's roles and podcast appearances) or a `mode`/`format` switch — a tool does far more than its name implies. Check the tool's input schema before assuming a capability is missing.\n- **Responses form a graph; slugs are edges.** A slug a tool returns (person, company, podcast, episode, publisher, guest) is a valid input to the other tools. Resolve a free-text name once (`particle_entity_resolve`, `particle_person_resolve`, `particle_company_resolve`, `particle_podcast_resolve`), then traverse: company → its people → a person's podcast appearances → that episode's transcript and the entities named in it. Never guess or fabricate a slug — resolve it. Slugs are short handles Particle assigns, not names slugified: 20VC is `the-twenty-minute-vc`, Lenny's Podcast is `lennys`, All-In is `all-in`. Take them from responses; a constructed slug returns 404, and trying other spellings never resolves it. A podcast slug is not an episode id: list the show's episodes to get one.\n- **Choose search by intent.** `particle_podcast_search_transcripts` finds dialogue *about* a topic (semantic, keyword, or hybrid; relevant clips arrive inline on matches). `particle_podcast_find_mentions` finds lines *naming* a resolved entity — a person, company, or any other resolvable slug (places, organizations, events, concepts) — its default `format=\"summary\"` previews the first ~10 mention lines per episode for scanning; call back with `format=\"detail\"` and an `episode_slug` for an episode's complete mention set with context. Both take `format=\"compact\"` for screening many results (a fan-out over companies, themes, or dates) before reading any: a compact search match is its identity rows, segment id, relevance score, and the segment's one-line description, with no dialogue; a compact mentions episode is its mention count, the strings the entity was mentioned as, and the segments carrying the mentions, with no lines. Cite by segment id and read only the survivors. `particle_podcast_list_episodes` is filter-driven episode discovery (podcast, person, company, language, date, duration). Don't put an entity name in `semantic_search` — that's a topic field; resolve the name and pass it as a `person_slug`/`company_slug`/`entity_slug` filter, or use `particle_podcast_find_mentions`. For \"how often over time\" questions, use `particle_podcast_get_episode_timeseries` instead of paging search or mentions once per period. For \"shows like this show\", use `particle_podcast_list_related` (add `include: [\"basis\"]` for the reasons) or `include: [\"related\"]` on `particle_podcast_resolve` — not a topic or semantic search; for \"who else covered this episode\", `particle_podcast_list_related_episodes` or `include: [\"related\"]` on `particle_podcast_get_episode`. For where a guest could appear next, use `include: [\"recommended_podcasts\"]` on `particle_podcast_get_guest`; for who a show could book, `include: [\"recommended_guests\"]` on `particle_podcast_resolve`; for advertisers a show could pitch, `include: [\"recommended_sponsors\"]` on `particle_podcast_resolve` (premium). Within a search, `semantic_search` carries the idea (a sentence, paraphrase-tolerant) while `keyword_search` carries words that must be literally spoken — every word must occur in the same passage, so keep it to one or two exact tokens and never put a sentence there; `keyword_match=\"ranked\"` relaxes it to a relevance hint. Add filters only after a broad query shows the topic has coverage: when filters empty a search, the error names the parameter responsible and the retry to make, so act on it rather than re-issuing variations.\n- **Not every episode is transcribed.** Episodes carry `transcript_status`. A show's older episodes, discovered in its feed but not transcribed, are its back catalogue: `particle_podcast_list_episodes` lists them only when you pass `transcript_status` with `podcast_slug`, and they read `requestable`, or `queued` once someone has requested the transcript. An episode without a transcript has no speakers, entities, segments or clips, so its empty lists say nothing about who was on it or what it covered — and counts over a show's episodes cover only its transcribed ones. `include: [\"coverage\"]` on `particle_podcast_resolve` says how many of a show's episodes exist and how many are transcribed, by year.\n- **Most tools are read-only; the `particle_alert_*` tools are the writable exception.** They create and manage alerts that each watch a single entity for podcast mentions or speaker appearances, and they act on the one project your credential is scoped to — no project parameter, no way to reach another project's alerts. Resolve a name to a slug, optionally preview match frequency with `particle_alert_preview`, then `particle_alert_create` to start watching it. When a name has no slug, or its entity has no podcast coverage, watch the phrase instead: `kind: \"KEYWORD_MENTION\"` with `keyword` in place of `entities`, and a `description` saying what the phrase means. Alerts accept a persistent `filters` object on create and update — narrow what gets surfaced by language, relevance (`EVERYTHING`|`RELEVANT`), source popularity (`ANY`|`POPULAR`), and (for `PODCAST_SPEAKER` alerts) `speaker_roles`. `particle_alert_create`/`particle_alert_update` mutate and `particle_alert_delete` is a destructive soft delete.\n- **Discovery is free; execution is metered.** A bare connection advertises the default categories, but every public tool is callable by name. `particle_catalog` (free) lists every category and tool with expand options, plus full input schemas per category; opt-in categories advertise on `tools/list` via the MCP URL, e.g. `?include=podcast_advertising,podcast_publishers,podcast_ratings,podcast_bias,podcast_suitability` (use these canonical tokens — unknown values are silently ignored).\n- **Errors course-correct.** Tool errors lead with a stable `**Error code:** <slug>` line and a suggestion naming the next call (404 → re-resolve the slug; 400/422 → check the schema via `particle_catalog`). REST errors are RFC 9457 `application/problem+json` with a stable `error_code` and, when there is a self-service fix, a `resolve` object naming the action, URL, method, and endpoint; a 404 on a slug means re-resolve it, a 422 names the parameter at fault, and a 429 carries `Retry-After`. Follow the suggestion instead of blind-retrying, and never retry-loop an `internal_error`.\n- **Reading these docs?** Append `.md` to any docs URL for raw markdown (e.g. `https://docs.particle.pro/mcp/overview.md`), fetch the entire corpus from `https://docs.particle.pro/llms-full.txt`, or start from the page index at `https://docs.particle.pro/llms.txt`.\n\n- **Render selected evidence only when a visual helps.** After retrieving Particle data, call `particle_radar_render_cards` directly with a title and up to six episode, podcast, company, or person cards. Use only evidence and canonical HTTPS source URLs returned by the data tools; do not invent links or pass credentials. The opt-in `radar` category (also in `include=all`) is free presentation, with no data fetch or storage. The renderer owns an MCP Apps UI resource and returns structured cards plus a complete Markdown fallback. Other data tools keep their existing output formats; `particle_call` provides the renderer's text fallback only.",
"tools": [
{
"description": "Create an alert that watches a single entity and emails you whenever it is mentioned on a podcast episode (kind=ENTITY_MENTION) or appears as a speaker (kind=PODCAST_SPEAKER). Pass the entity slug from a resolve tool — resolve a name with particle_entity_resolve, then create the alert with the slug it returns. An alert watches exactly one entity; to cover several entities, call this tool once per entity.\n\nWhen a name has no entity slug, or its entity has no podcast coverage (a startup known by a brand that differs from its legal name, a product, a drug, a code word), create a kind=KEYWORD_MENTION alert with `keyword` instead of `entities`. It fires whenever the phrase is spoken — the match particle_podcast_search_transcripts makes for a double-quoted keyword_search phrase (words adjacent and in order), not the looser unquoted match — so also set `description` to say what the phrase means; that is how same-name mentions of something else are filtered out.\n\nUse the optional `filters` object to narrow what gets surfaced on every channel (matches list, realtime email, daily/weekly digest). Four independent axes: `languages` (BCP-47-like tags like ['en','pt-BR'] — empty means all languages), `relevance` (EVERYTHING returns on-target + incidental matches, RELEVANT narrows to on-target only — dropping passing mentions), `source_popularity` (ANY keeps every source, POPULAR keeps only matches from podcasts in the top 5% by chart popularity), and `speaker_roles` (PODCAST_SPEAKER alerts only — REPLACES the default appearance set GUEST/PANELIST/CORRESPONDENT/AUDIENCE/SOUNDBITE_SPEAKER; sending it on an ENTITY_MENTION alert errors with unprocessable_entity).\n\nBilling: creating an active alert can move an eligible organization's subscription from its credit/trial phase to paid fixed-fee billing. Explain this possible billing change and obtain the user's explicit confirmation before creating an active alert. Creating a paused alert (is_active=false) does not trigger this billing transition.\n\nAfter creation the alert immediately backfills matches from the past week (visible via particle_alert_list_matches) without sending emails for them. To see what an alert would catch BEFORE committing, use particle_alert_preview first. The created alert's id feeds particle_alert_get, particle_alert_update, particle_alert_delete, and particle_alert_list_matches.\n\nAlerts are not covered by zero data retention: the alert's definition and its matched results are stored as part of the alerts feature.",
"inputSchema": {
"properties": {
"delivery_cadence": {
"description": "How often matches are emailed: REALTIME (default, one email per match), DAILY (one bundled email each morning), or WEEKLY (one bundled email Monday).",
"enum": [
"REALTIME",
"DAILY",
"WEEKLY"
],
"type": "string"
},
"description": {
"description": "Optional longer description of what the alert is for.",
"type": "string"
},
"entities": {
"description": "The entity to watch, as a single slug from the resolve tools (particle_entity_resolve, particle_person_resolve, particle_company_resolve). Person, company, and place/other (knowledge-graph) slugs are all accepted; the resolved type is echoed back in the response. Exactly one for ENTITY_MENTION and PODCAST_SPEAKER — an alert watches a single entity, so create one alert per entity. Omit for KEYWORD_MENTION.",
"items": {
"type": "string"
},
"maxItems": 1,
"minItems": 1,
"type": "array"
},
"filters": {
"description": "Persistent narrowing applied to every surface the alert produces (matches list, realtime email, daily/weekly digest). Omit for no filters — every detected match is surfaced. See AlertFiltersInput for the four axes (languages, relevance, source_popularity, speaker_roles).",
"properties": {
"languages": {
"description": "Primary language tags the source episode must be in: a 2-3 letter primary tag (e.g. 'en', 'de', 'sma'). A region or script subtag (e.g. 'pt-BR', 'zh-Hant') is accepted for readability but matching is on the PRIMARY tag only — 'pt-BR' surfaces every Portuguese episode regardless of region, and 'pt-BR' + 'pt-PT' collapse to one. Empty or missing means all languages are surfaced. Case-insensitive; echoed in canonical primary-tag form.",
"items": {
"type": "string"
},
"type": "array"
},
"relevance": {
"description": "EVERYTHING (default) returns both on-target and incidental matches — the watched entity is correctly identified in both, only the depth of discussion differs. RELEVANT narrows to on-target only: matches where the watched entity is the subject being discussed, dropping passing mentions. Wrong-entity matches (name collisions) are globally suppressed before any filter runs.",
"enum": [
"EVERYTHING",
"RELEVANT"
],
"type": "string"
},
"source_popularity": {
"description": "ANY (default) keeps matches from every source. POPULAR keeps only matches whose source podcast scores in the top 5% by chart-popularity percentile (Podcast.Popularity >= 0.95; cume_dist over current chart entries, multi-region weighted). Podcasts that aren't currently charting drop out.",
"enum": [
"ANY",
"POPULAR"
],
"type": "string"
},
"speaker_roles": {
"description": "PODCAST_SPEAKER alerts only — sending this on an ENTITY_MENTION alert returns an unprocessable_entity error. REPLACES (not intersects with) the default appearance set. Default when omitted is GUEST, PANELIST, CORRESPONDENT, AUDIENCE, SOUNDBITE_SPEAKER — HOST is excluded because hosting the show isn't an appearance. Setting ['HOST'] flips that; setting ['GUEST'] alone narrows further. Input casing is not significant — 'guest' and 'GUEST' are the same value — and the filter is stored and returned in canonical uppercase form. Raw STT/LLM labels like CALLER or REPORTER are not accepted.",
"items": {
"enum": [
"HOST",
"GUEST",
"PANELIST",
"CORRESPONDENT",
"AUDIENCE",
"SOUNDBITE_SPEAKER"
],
"type": "string"
},
"type": "array"
}
},
"type": "object"
},
"is_active": {
"description": "Whether the alert produces matches. Defaults to true. Set false to create it paused.",
"type": "boolean"
},
"keyword": {
"description": "KEYWORD_MENTION alerts only (and required for them): the phrase to watch, e.g. Lightfield. Matches like a double-quoted keyword_search phrase — the words adjacent and in order, ignoring case and punctuation, on whole words — in topic-discussion and interview segments, never ad reads, intros, or outros. Also set description to say what the phrase means (e.g. 'Lightfield, the AI-native CRM'); it is how same-name mentions of something else get filtered out. A phrase that matched more than 700 podcast episodes in the past week is rejected as too broad.",
"maxLength": 100,
"type": "string"
},
"kind": {
"description": "What signal to watch for. ENTITY_MENTION (default) fires whenever a watched entity is mentioned on a podcast episode. PODCAST_SPEAKER fires only when a watched person is themself an identified speaker (guest/panelist/correspondent/audience). KEYWORD_MENTION fires whenever keyword is spoken — use it when the name has no entity slug (a resolve tool finds nothing, or the right entity has no episodes). Kind is fixed at creation.",
"enum": [
"ENTITY_MENTION",
"PODCAST_SPEAKER",
"KEYWORD_MENTION"
],
"type": "string"
},
"notifications": {
"description": "Email addresses to notify. Each must already be verified for your organization (or belong to an org member). When omitted, defaults to your account email if available; otherwise pass at least one.",
"items": {
"type": "string"
},
"type": "array"
},
"output_format": {
"description": "Output serialization. 'markdown' (default) returns the LLM-facing rendering. 'json' returns the structured payload as JSON text — use only for programmatic chaining where exact field extraction matters; the JSON shape is larger and noisier for an LLM to read.",
"enum": [
"markdown",
"json"
],
"type": "string"
},
"title": {
"description": "Human-readable title for the alert (e.g. 'OpenAI mentions').",
"type": "string"
}
},
"required": [
"title"
],
"type": "object"
},
"name": "particle_alert_create",
"outputSchema": null
},
{
"description": "Delete an alert. This is a soft delete: the alert stops producing matches and disappears from particle_alert_list, but its past matches and deliveries are retained for audit. To pause an alert instead of removing it, use particle_alert_update with is_active=false.",
"inputSchema": {
"properties": {
"alert_id": {
"description": "Alert id to delete.",
"type": "string"
}
},
"required": [
"alert_id"
],
"type": "object"
},
"name": "particle_alert_delete",
"outputSchema": null
},
{
"description": "Fetch a single alert's full configuration — title, kind, cadence, watched entities (with names), notification emails, and any active filters (languages, relevance, source_popularity, speaker_roles). The `filters` section is omitted when the alert carries none. By default the response is just the configuration; request include=['matches'] to embed the most recent matches it has caught and include=['deliveries'] for the email audit log. For the full, paginated match history with transcript excerpts, use particle_alert_list_matches.",
"inputSchema": {
"properties": {
"alert_id": {
"description": "Alert id from particle_alert_list or particle_alert_create.",
"type": "string"
},
"include": {
"description": "Optional sections to embed: 'matches' for the most recent matches the alert has caught, 'deliveries' for the email delivery audit log.",
"items": {
"enum": [
"matches",
"deliveries"
],
"type": "string"
},
"type": "array"
},
"match_limit": {
"description": "How many recent matches to embed when include=matches (1-25, default 5). Use particle_alert_list_matches for full pagination and transcript windows.",
"maximum": 25,
"minimum": 1,
"type": "integer"
},
"output_format": {
"description": "Output serialization. 'markdown' (default) returns the LLM-facing rendering. 'json' returns the structured payload as JSON text — use only for programmatic chaining where exact field extraction matters; the JSON shape is larger and noisier for an LLM to read.",
"enum": [
"markdown",
"json"
],
"type": "string"
}
},
"required": [
"alert_id"
],
"type": "object"
},
"name": "particle_alert_get",
"outputSchema": null
},
{
"description": "List the alerts in your project, newest first. Each entry carries the alert `id` — feed it into particle_alert_get for full configuration, particle_alert_list_matches for what it has caught, or particle_alert_update / particle_alert_delete to manage it.",
"inputSchema": {
"properties": {
"cursor": {
"description": "Opaque pagination cursor from a previous response's cursor field.",
"type": "string"
},
"limit": {
"description": "Alerts per page (1-100, default 25).",
"maximum": 100,
"minimum": 1,
"type": "integer"
},
"output_format": {
"description": "Output serialization. 'markdown' (default) returns the LLM-facing rendering. 'json' returns the structured payload as JSON text — use only for programmatic chaining where exact field extraction matters; the JSON shape is larger and noisier for an LLM to read.",
"enum": [
"markdown",
"json"
],
"type": "string"
}
},
"type": "object"
},
"name": "particle_alert_list",
"outputSchema": null
},
{
"description": "List the matches an alert has caught, newest first — the payoff of an alert. Each match names the watched entity and the podcast episode it was detected on (with episode and podcast slugs that feed particle_podcast_get_episode and particle_podcast_resolve). Use view=detailed to include the transcript excerpts around each mention, and after/before to scope to a date range. Backfilled matches (from the past-week sweep at creation) are flagged and never triggered an email.",
"inputSchema": {
"properties": {
"after": {
"description": "Only matches detected on or after this ISO date (e.g. 2026-05-01).",
"type": "string"
},
"alert_id": {
"description": "Alert id whose matches to list.",
"type": "string"
},
"before": {
"description": "Only matches detected on or before this ISO date.",
"type": "string"
},
"cursor": {
"description": "Opaque pagination cursor from a previous response's cursor field.",
"type": "string"
},
"limit": {
"description": "Matches per page (1-100, default 25).",
"maximum": 100,
"minimum": 1,
"type": "integer"
},
"output_format": {
"description": "Output serialization. 'markdown' (default) returns the LLM-facing rendering. 'json' returns the structured payload as JSON text — use only for programmatic chaining where exact field extraction matters; the JSON shape is larger and noisier for an LLM to read.",
"enum": [
"markdown",
"json"
],
"type": "string"
},
"view": {
"description": "Detail level. 'summary' (default) returns each match's entity, episode, and counts. 'detailed' also includes the transcript excerpt windows around each mention.",
"enum": [
"summary",
"detailed"
],
"type": "string"
}
},
"required": [
"alert_id"
],
"type": "object"
},
"name": "particle_alert_list_matches",
"outputSchema": null
},
{
"description": "Preview how often an alert would fire BEFORE creating it. Sweeps the past N days (default 7, max 30) for the given entity (or keyword, for kind=KEYWORD_MENTION) and returns the total match count, a per-day breakdown, and a small sample of the most recent matches with episode context. Use this to size an alert (REALTIME vs DAILY vs WEEKLY cadence) or to confirm the entity slug watches the right thing, then call particle_alert_create with the same entity slug (for KEYWORD_MENTION, the same keyword instead). Pass the same `filters` you plan to save so the estimate matches what the alert would surface — the languages and speaker_roles axes narrow the sweep; relevance and source_popularity are read-time projections that don't, so the count is an upper bound when relevance=RELEVANT. Starts a background sweep and caches its progress and results; it does not create an alert or send notifications.",
"inputSchema": {
"properties": {
"entities": {
"description": "The entity to preview, as a single slug (from the resolve tools), same as particle_alert_create.entities — exactly one for entity kinds, omitted for KEYWORD_MENTION.",
"items": {
"type": "string"
},
"maxItems": 1,
"minItems": 1,
"type": "array"
},
"filters": {
"description": "Same as particle_alert_create.filters. Pass the filters you intend to save so the estimate reflects what the alert would actually surface. Only languages and speaker_roles narrow the historical sweep; relevance and source_popularity are read-time projections that don't run on historical episodes, so setting them leaves the count unchanged (the estimate is an upper bound when relevance=RELEVANT).",
"properties": {
"languages": {
"description": "Primary language tags the source episode must be in: a 2-3 letter primary tag (e.g. 'en', 'de', 'sma'). A region or script subtag (e.g. 'pt-BR', 'zh-Hant') is accepted for readability but matching is on the PRIMARY tag only — 'pt-BR' surfaces every Portuguese episode regardless of region, and 'pt-BR' + 'pt-PT' collapse to one. Empty or missing means all languages are surfaced. Case-insensitive; echoed in canonical primary-tag form.",
"items": {
"type": "string"
},
"type": "array"
},
"relevance": {
"description": "EVERYTHING (default) returns both on-target and incidental matches — the watched entity is correctly identified in both, only the depth of discussion differs. RELEVANT narrows to on-target only: matches where the watched entity is the subject being discussed, dropping passing mentions. Wrong-entity matches (name collisions) are globally suppressed before any filter runs.",
"enum": [
"EVERYTHING",
"RELEVANT"
],
"type": "string"
},
"source_popularity": {
"description": "ANY (default) keeps matches from every source. POPULAR keeps only matches whose source podcast scores in the top 5% by chart-popularity percentile (Podcast.Popularity >= 0.95; cume_dist over current chart entries, multi-region weighted). Podcasts that aren't currently charting drop out.",
"enum": [
"ANY",
"POPULAR"
],
"type": "string"
},
"speaker_roles": {
"description": "PODCAST_SPEAKER alerts only — sending this on an ENTITY_MENTION alert returns an unprocessable_entity error. REPLACES (not intersects with) the default appearance set. Default when omitted is GUEST, PANELIST, CORRESPONDENT, AUDIENCE, SOUNDBITE_SPEAKER — HOST is excluded because hosting the show isn't an appearance. Setting ['HOST'] flips that; setting ['GUEST'] alone narrows further. Input casing is not significant — 'guest' and 'GUEST' are the same value — and the filter is stored and returned in canonical uppercase form. Raw STT/LLM labels like CALLER or REPORTER are not accepted.",
"items": {
"enum": [
"HOST",
"GUEST",
"PANELIST",
"CORRESPONDENT",
"AUDIENCE",
"SOUNDBITE_SPEAKER"
],
"type": "string"
},
"type": "array"
}
},
"type": "object"
},
"keyword": {
"description": "Same as particle_alert_create.keyword — required for KEYWORD_MENTION. A phrase that matched more than 700 podcast episodes in the past week is rejected as too broad, as on create.",
"maxLength": 100,
"type": "string"
},
"kind": {
"description": "Signal to preview. Defaults to ENTITY_MENTION.",
"enum": [
"ENTITY_MENTION",
"PODCAST_SPEAKER",
"KEYWORD_MENTION"
],
"type": "string"
},
"output_format": {
"description": "Output serialization. 'markdown' (default) returns the LLM-facing rendering. 'json' returns the structured payload as JSON text — use only for programmatic chaining where exact field extraction matters; the JSON shape is larger and noisier for an LLM to read.",
"enum": [
"markdown",
"json"
],
"type": "string"
},
"window_days": {
"description": "How many days back to sweep (1-30, default 7).",
"maximum": 30,
"minimum": 1,
"type": "integer"
}
},
"type": "object"
},
"name": "particle_alert_preview",
"outputSchema": null
},
{
"description": "Update an existing alert. Only the fields you pass change; the entities and notifications lists, when provided, replace the whole set (pass a single entity slug from the resolve tools, same as particle_alert_create — an alert watches exactly one entity). A KEYWORD_MENTION alert takes a new `keyword` instead of `entities`. Use is_active to pause or resume an alert without deleting it. An alert's kind is fixed at creation — to change it, create a new alert.\n\nThe optional `filters` object replaces the alert's filter set wholesale — omit to leave the existing filters unchanged, send {} to clear all filters. Same four axes as particle_alert_create.filters: `languages`, `relevance` (EVERYTHING/RELEVANT), `source_popularity` (ANY/POPULAR), and `speaker_roles` (PODCAST_SPEAKER alerts only — sending it on an ENTITY_MENTION alert returns unprocessable_entity).\n\nAlerts are not covered by zero data retention: the alert's definition and its matched results are stored as part of the alerts feature.",
"inputSchema": {
"properties": {
"alert_id": {
"description": "Alert id to update.",
"type": "string"
},
"delivery_cadence": {
"description": "New delivery cadence.",
"enum": [
"REALTIME",
"DAILY",
"WEEKLY"
],
"type": "string"
},
"description": {
"description": "New description.",
"type": "string"
},
"entities": {
"description": "Replacement watch target as a single entity slug (from the resolve tools). When provided, replaces the entire existing watch list — exactly one entity; omit to leave entities unchanged. Not accepted on KEYWORD_MENTION alerts.",
"items": {
"type": "string"
},
"maxItems": 1,
"minItems": 1,
"type": "array"
},
"filters": {
"description": "Replace the alert's filter set wholesale. Omit to leave the existing filters unchanged; send an empty object {} to clear all filters. Same four axes as particle_alert_create.filters (languages, relevance, source_popularity, speaker_roles).",
"properties": {
"languages": {
"description": "Primary language tags the source episode must be in: a 2-3 letter primary tag (e.g. 'en', 'de', 'sma'). A region or script subtag (e.g. 'pt-BR', 'zh-Hant') is accepted for readability but matching is on the PRIMARY tag only — 'pt-BR' surfaces every Portuguese episode regardless of region, and 'pt-BR' + 'pt-PT' collapse to one. Empty or missing means all languages are surfaced. Case-insensitive; echoed in canonical primary-tag form.",
"items": {
"type": "string"
},
"type": "array"
},
"relevance": {
"description": "EVERYTHING (default) returns both on-target and incidental matches — the watched entity is correctly identified in both, only the depth of discussion differs. RELEVANT narrows to on-target only: matches where the watched entity is the subject being discussed, dropping passing mentions. Wrong-entity matches (name collisions) are globally suppressed before any filter runs.",
"enum": [
"EVERYTHING",
"RELEVANT"
],
"type": "string"
},
"source_popularity": {
"description": "ANY (default) keeps matches from every source. POPULAR keeps only matches whose source podcast scores in the top 5% by chart-popularity percentile (Podcast.Popularity >= 0.95; cume_dist over current chart entries, multi-region weighted). Podcasts that aren't currently charting drop out.",
"enum": [
"ANY",
"POPULAR"
],
"type": "string"
},
"speaker_roles": {
"description": "PODCAST_SPEAKER alerts only — sending this on an ENTITY_MENTION alert returns an unprocessable_entity error. REPLACES (not intersects with) the default appearance set. Default when omitted is GUEST, PANELIST, CORRESPONDENT, AUDIENCE, SOUNDBITE_SPEAKER — HOST is excluded because hosting the show isn't an appearance. Setting ['HOST'] flips that; setting ['GUEST'] alone narrows further. Input casing is not significant — 'guest' and 'GUEST' are the same value — and the filter is stored and returned in canonical uppercase form. Raw STT/LLM labels like CALLER or REPORTER are not accepted.",
"items": {
"enum": [
"HOST",
"GUEST",
"PANELIST",
"CORRESPONDENT",
"AUDIENCE",
"SOUNDBITE_SPEAKER"
],
"type": "string"
},
"type": "array"
}
},
"type": "object"
},
"is_active": {
"description": "Pause (false) or resume (true) the alert.",
"type": "boolean"
},
"keyword": {
"description": "Replacement phrase for a KEYWORD_MENTION alert; omit to leave it unchanged. Not accepted on other kinds. Matches already recorded keep the phrase they fired on.",
"maxLength": 100,
"type": "string"
},
"notifications": {
"description": "Replacement notification emails. When provided, replaces the entire existing set; omit to leave them unchanged.",
"items": {
"type": "string"
},
"type": "array"
},
"output_format": {
"description": "Output serialization. 'markdown' (default) returns the LLM-facing rendering. 'json' returns the structured payload as JSON text — use only for programmatic chaining where exact field extraction matters; the JSON shape is larger and noisier for an LLM to read.",
"enum": [
"markdown",
"json"
],
"type": "string"
},
"title": {
"description": "New title.",
"type": "string"
}
},
"required": [
"alert_id"
],
"type": "object"
},
"name": "particle_alert_update",
"outputSchema": null
},
{
"description": "Dispatch any public Particle tool by name. Compatibility fallback for harnesses that block calling tools that weren't advertised on tools/list — every public Particle tool is executable by name, so prefer calling discovered tools directly when your harness allows it. Identical metering and plan gating apply either way. Use particle_catalog to discover tool names and input schemas.",
"inputSchema": {
"properties": {
"arguments": {
"description": "Arguments object for the target tool, matching its input schema.",
"type": "object"
},
"tool": {
"description": "Flat name of any public Particle tool (e.g. 'particle_podcast_get_episode'). Discover names and schemas with particle_catalog.",
"type": "string"
}
},
"required": [
"tool"
],
"type": "object"
},
"name": "particle_call",
"outputSchema": null
},
{
"description": "Browse the full Particle tool catalog. Your tools/list shows only the default categories, but EVERY public Particle tool is callable by name regardless of what was advertised — call this tool to discover the rest.\n\nWithout arguments: the categorical menu (every category with tool names, one-line summaries, and an `↳` line listing each tool's expand options). With `category`: the full input schema for each of that category's tools, ready to call.\n\nTwo conventions the one-line summaries don't convey, so read tools through this lens:\n- Tools are lean by default and EXPAND. Most return a minimal payload and opt into richer sections via an `include` array (e.g. a company's people, products, and competitors; a person's roles and podcast appearances) or change behavior via a `mode`/`format` switch. The `↳` line names these — a tool does far more than its summary alone implies.\n- Responses are a graph; slugs are edges. A slug a tool returns (person, company, podcast, episode, publisher, guest) is a valid input to the other tools, so you resolve once and then traverse: company → its people → a person's podcast appearances → that episode's transcript and every entity in it.\n\nCategories on offer:\n- `system` (always-on): Discovery meta-tools: browse the full tool catalog and call any tool by name.\n- `podcasts` (default): Resolve podcasts, list and fetch episodes, search transcripts, and find entity mentions.\n- `people` (default): Resolve people and entities to canonical handles and fetch person profiles.\n- `companies` (default): Resolve companies and fetch company profiles with people, products, and competitors.\n- `topics` (default): Browse the hierarchical topic taxonomy used to classify podcast episodes.\n- `podcast_rankings` (default): Podcast chart rankings: current charts, movers, and ranking history.\n- `podcast_guests` (default): Podcast guest directory, trending guests, and per-guest appearance profiles.\n- `podcast_advertising` (opt-in): Podcast advertising intelligence: sponsor rosters, ad presence, and sponsor leaderboards.\n- `podcast_publishers` (opt-in): Podcast publisher profiles with their shows, bias profile, and suitability profile.\n- `podcast_ratings` (opt-in): Listener review ratings for podcasts: summaries and recent rating lists.\n- `podcast_bias` (opt-in): Corpus-wide political-bias views: publisher leaderboards and publishers by bias result.\n- `podcast_suitability` (opt-in): Corpus-wide GARM brand-suitability views: publisher leaderboards and category exposure.\n- `alerts` (default): Create and manage alerts that watch entities for podcast mentions or speaker appearances, preview match frequency, and review the matches an alert has caught.\n- `radar` (opt-in): Display selected research results as embedded Radar cards, with a Markdown fallback. Rendering is free and does not fetch data.\n\nOpt-in categories can also be advertised on tools/list by adding `?include=<category>` (comma-separated, or `all`) to the connection URL, or the X-Particle-Include header. `?exclude=` hides default categories; `?tools=<name,...>` pins the advertised list to exact tools instead. Discovery is free; tool execution is metered and plan-gated as usual.",
"inputSchema": {
"properties": {
"category": {
"description": "Exposure category name (e.g. 'podcast_bias'). When set, the response includes the FULL input schema for every tool in that category — call this before invoking a tool you haven't seen advertised. When omitted, returns the categorical menu: every category with its tools and one-line summaries.",
"type": "string"
},
"output_format": {
"description": "Output serialization. 'markdown' (default) returns the LLM-facing rendering. 'json' returns the structured payload as JSON text — use only for programmatic chaining where exact field extraction matters; the JSON shape is larger and noisier for an LLM to read.",
"enum": [
"markdown",
"json"
],
"type": "string"
}
},
"type": "object"
},
"name": "particle_catalog",
"outputSchema": null
},
{
"description": "Return a bundled profile for one company: identifiers (slug, ticker, domain, CIK, QID, linked entity), name, and description.\n\nRequest optional sections via `include`: 'people' for current leadership and notable people (person slugs feed `particle_person_get`), 'products' for the three-level product hierarchy, 'competitors' for the competitor list, 'external_links' for the company's LinkedIn, social profiles, domain, Wikidata QID, SEC CIK and tickers. The default response is lean — include only what you need.\n\nFor sponsor/advertising analytics on this company, use `particle_company_get_podcast_ad_presence` instead.",
"inputSchema": {
"properties": {
"company_slug": {
"description": "Company identifier — accepts slug (e.g. 'nvidia'), domain (e.g. 'nvidia.com'), or canonical ID. If you already know the domain you can call this tool directly without first running particle_company_resolve.",
"type": "string"
},
"include": {
"description": "Optional response sections: 'people' (current leadership and notable people), 'products' (three-level product hierarchy), 'competitors' (competitor list), 'podcast_recommendations' (the ten podcasts the company could advertise on next, with the shows it already buys that led there; premium), 'external_links' (LinkedIn, social profiles, domain, Wikidata QID, SEC CIK and tickers). Default response is lean — request only what you need.",
"items": {
"enum": [
"people",
"products",
"competitors",
"podcast_recommendations",
"external_links"
],
"type": "string"
},
"type": "array"
},
"product_status": {
"description": "Comma-separated lifecycle filter for include=products (e.g. 'active' or 'active,announced'). Allowed values: active, announced, discontinued, rumored. Defaults to 'active'.",
"type": "string"
}
},
"required": [
"company_slug"
],
"type": "object"
},
"name": "particle_company_get",
"outputSchema": null
},
{
"description": "Resolve a company by free-text name, ticker, SEC CIK, Wikidata QID, or domain. Returns candidates with the agent-facing identifier (`slug`, falling back to `domain` or `id`) you should pass to `particle_company_get`, `particle_company_get_podcast_ad_presence`, `particle_podcast_find_mentions` (as `company_slug`), or `particle_podcast_list_episodes`.\n\nAt least one identifier is required. Multiple are ANDed together — useful for disambiguating (e.g. ticker plus a name hint). For people or other knowledge-graph entities (not companies) use `particle_entity_resolve` instead.",
"inputSchema": {
"properties": {
"cik": {
"description": "SEC Central Index Key (e.g. '0000320193'). Comma-separated for bulk.",
"type": "string"
},
"domain": {
"description": "Company website domain (e.g. 'apple.com'). Comma-separated for bulk.",
"type": "string"
},
"limit": {
"description": "Maximum candidates to return (1-25, default 5).",
"maximum": 25,
"minimum": 1,
"type": "integer"
},
"qid": {
"description": "Wikidata QID (e.g. 'Q312'). Comma-separated for bulk.",
"type": "string"
},
"query": {
"description": "Free-text company name (case-insensitive). Use for human-typed names.",
"type": "string"
},
"ticker": {
"description": "Stock ticker symbol (e.g. 'NVDA', 'AAPL'). Comma-separated for multi-ticker lookup.",
"type": "string"
}
},
"type": "object"
},
"name": "particle_company_resolve",
"outputSchema": null
},
{
"description": "One knowledge-graph entity's profile: name, kind, description, and Wikipedia link. Use it to confirm what a slug from `particle_entity_resolve` actually refers to — especially for the long tail that isn't a person or company (places, organizations, events, products, concepts).\n\nWhen the entity is a linked person or company the response carries the person_slug / company_slug — prefer `particle_person_get` / `particle_company_get` for those, which return the full profiles. Entity slugs feed `particle_podcast_find_mentions`, `particle_podcast_get_episode_timeseries`, and the alert tools.",
"inputSchema": {
"properties": {
"entity_slug": {
"description": "Knowledge-graph entity slug or encoded ID from particle_entity_resolve, episode entity listings, or mention payloads (e.g. 'germany', 'bitcoin').",
"type": "string"
},
"output_format": {
"description": "Output serialization. 'markdown' (default) returns the LLM-facing rendering. 'json' returns the structured payload as JSON text — use only for programmatic chaining where exact field extraction matters; the JSON shape is larger and noisier for an LLM to read.",
"enum": [
"markdown",
"json"
],
"type": "string"
}
},
"required": [
"entity_slug"
],
"type": "object"
},
"name": "particle_entity_get",
"outputSchema": null
},
{
"description": "Resolve any named thing — person, company, place, or other entity — by free-text name in one union search. Each candidate carries a `type` and the canonical `slug` for that type:\n - `person`: the canonical person slug. Feed it into `particle_person_get`, every `person_slug` parameter (`particle_podcast_find_mentions`, `particle_podcast_search_transcripts`, `particle_podcast_list_episodes`), or `particle_podcast_get_guest`'s `guest_slug`.\n - `company`: the canonical company slug. Feed it into `particle_company_get` and every `company_slug` parameter.\n - `place`/`other`: a bare entity slug. Feed it into the `entity_slug` parameter on `particle_podcast_find_mentions`, `particle_podcast_search_transcripts`, and `particle_podcast_list_episodes` to filter by that entity.\n\nUse this first whenever you only have a name and don't know what kind of thing it names. If you already know it's a person, `particle_person_resolve` ranks people only; for companies with a known ticker, domain, CIK, or QID, `particle_company_resolve` has more identifier surface.\n\nFor bulk resolution, pass a comma-separated `query` (e.g. \"sam altman, nvidia, davos\") — each name is resolved independently in a single call and `limit` applies per query.",
"inputSchema": {
"properties": {
"limit": {
"description": "Maximum candidates per query (1-10, default 5).",
"maximum": 10,
"minimum": 1,
"type": "integer"
},
"query": {
"description": "Free-text name(s) of a person, organization, place, or company to resolve (e.g. 'sam altman', 'nvidia'). Case-insensitive. Comma-separated for bulk lookup (e.g. 'sam altman, kara swisher, marc andreessen') — each query is resolved independently and grouped in the response.",
"type": "string"
}
},
"required": [
"query"
],
"type": "object"
},
"name": "particle_entity_resolve",
"outputSchema": null
},
{
"description": "Return a person's profile: name, current role, and bio, keyed by the canonical person slug from `particle_person_resolve`.\n\nRequest optional sections via `include`: 'external_links' for LinkedIn/Wikipedia/social profiles, 'podcast_appearances' for their most recent podcast appearances (episode and podcast slugs included for follow-up calls), 'companies' for the full role history. The default response is lean.\n\nFor podcast-guest analytics (appearance stats, suitability exposure, co-appearance graph) use `particle_podcast_get_guest` with the same slug.",
"inputSchema": {
"properties": {
"include": {
"description": "Optional response sections: 'external_links' (LinkedIn, Wikipedia, social profiles), 'podcast_appearances' (recent podcast appearances with episode and podcast slugs), 'companies' (the full role history, current role included and marked). Default response is lean — request only what you need.",
"items": {
"enum": [
"external_links",
"podcast_appearances",
"companies"
],
"type": "string"
},
"type": "array"
},
"output_format": {
"description": "Output serialization. 'markdown' (default) returns the LLM-facing rendering. 'json' returns the structured payload as JSON text — use only for programmatic chaining where exact field extraction matters; the JSON shape is larger and noisier for an LLM to read.",
"enum": [
"markdown",
"json"
],
"type": "string"
},
"person_slug": {
"description": "Canonical person slug from particle_person_resolve (e.g. 'sam-altman'), or the encoded person ID.",
"type": "string"
}
},
"required": [
"person_slug"
],
"type": "object"
},
"name": "particle_person_get",
"outputSchema": null
},
{
"description": "Resolve a person by free-text name. Returns ranked candidates with the canonical person `slug` — the stable handle accepted by `particle_person_get`, by every `person_slug` parameter (`particle_podcast_find_mentions`, `particle_podcast_search_transcripts`, `particle_podcast_list_episodes`), and by `particle_podcast_get_guest`'s `guest_slug`.\n\nFor bulk resolution, pass a comma-separated `query` — each name resolves independently in one call.\n\nFor organizations, places, or mixed/unknown entity kinds use `particle_entity_resolve`; for companies with a known ticker or domain use `particle_company_resolve`.",
"inputSchema": {
"properties": {
"limit": {
"description": "Maximum candidates per query (1-10, default 5).",
"maximum": 10,
"minimum": 1,
"type": "integer"
},
"output_format": {
"description": "Output serialization. 'markdown' (default) returns the LLM-facing rendering. 'json' returns the structured payload as JSON text — use only for programmatic chaining where exact field extraction matters; the JSON shape is larger and noisier for an LLM to read.",
"enum": [
"markdown",
"json"
],
"type": "string"
},
"query": {
"description": "Free-text person name (e.g. 'sam altman'). Case-insensitive. Comma-separated for bulk lookup — each name is resolved independently and grouped in the response.",
"type": "string"
}
},
"required": [
"query"
],
"type": "object"
},
"name": "particle_person_resolve",
"outputSchema": null
},
{
"description": "Find dialogue lines where a specific person or company is named in podcast transcripts.\n\n## Three response modes\n\n**`format=\"summary\"` (default, wide scan).** Returns up to `limit` episodes (reverse-chronological), each with metadata + the first 10 mention-only lines (just the lines naming the entity, no surrounding dialogue). Use this to see *what's been said across episodes* and decide which episodes are worth reading in full. Paginate older episodes with `cursor`.\n\n**`format=\"detail\"` (narrow drill-in).** Requires `episode_slug`. Returns the full mention windows with `context_lines` of surrounding dialogue around each mention. Pass one slug for a single episode, or up to 10 comma-separated slugs (e.g. `episode_slug=\"all-in-200,all-in-201,all-in-202\"`) to multi-get several episodes in one call. `limit`/`cursor` don't apply.\n\n**`format=\"compact\"` (screening).** The same episodes as summary, each with its mention count, the strings it was mentioned as, and the segments carrying the mentions (id, title, type, first mention time) — no dialogue lines at all, the smallest shape. `limit` and `cursor` page it exactly as summary. Use it to fan out over many entities or a long date range and decide where to read; the segment ids are citations, and `format=\"detail\"` with the episode slug reads the lines.\n\n## Workflow\n\nTwo patterns, depending on what you already know:\n\n- **No specific episode in mind:** call `format=\"summary\"` first to scan, then call `format=\"detail\"` with the slug(s) of the episodes worth reading in full. For most questions (sentiment, recurring themes, who said what when), summary alone has enough signal and the second call isn't needed.\n\n- **Already have the episode slug** (e.g. user mentioned the episode by name, or you have it from another tool like `particle_podcast_get_episode` or `particle_podcast_search_transcripts`): skip summary entirely and call `format=\"detail\"` with `episode_slug` directly.\n\n## Examples\n\n*Wide scan, then drill in:* User asks \"what has All-In said about OpenAI recently?\". Call `format=\"summary\"`, `company_slug=\"openai\"`, `podcast_slug=\"all-in\"`, `since=\"2025-11-01\"`, `limit=20`. Read the mention lines per episode; if 2-3 episodes have substantive discussion, call `format=\"detail\"`, `episode_slug=\"slug1,slug2,slug3\"` for full context in one round-trip.\n\n*Direct drill-in:* User says \"In All-In #200 they discuss OpenAI's strategy — pull the full quotes\". Call `format=\"detail\"`, `episode_slug=\"all-in-200\"`, `company_slug=\"openai\"` directly — no summary needed.\n\n## When NOT to use this tool\n\nFor dialogue that *discusses* a topic without naming a specific person or company (paraphrase-tolerant search), use `particle_podcast_search_transcripts` instead — that one ranks segments by relevance to a free-text query.\n\n## Required inputs\n\nOne of `person_slug`, `company_slug`, or `entity_slug` is required: `person_slug` for a person, `company_slug` for a company, `entity_slug` for any other knowledge-graph entity (places, organizations, events, concepts). Resolve a name to a slug first with `particle_person_resolve`, `particle_company_resolve`, or `particle_entity_resolve`. Slugs are case-insensitive on input.",
"inputSchema": {
"properties": {
"company_slug": {
"description": "Company slug, domain, or canonical ID (e.g. 'nvidia' or 'nvidia.com'). Resolves to the company's linked entity.",
"type": "string"
},
"context_lines": {
"description": "Surrounding dialogue lines around each mention (1-20, default 2). Detail mode only — ignored in summary and compact.",
"maximum": 20,
"minimum": 1,
"type": "integer"
},
"cursor": {
"description": "Opaque pagination cursor from a previous summary or compact response's cursor field. Summary and compact modes.",
"type": "string"
},
"entity_slug": {
"description": "Knowledge-graph entity slug from particle_entity_resolve for the long tail that isn't a person or company — places, organizations, events, products, concepts (e.g. 'germany'). Use person_slug for people and company_slug for companies.",
"type": "string"
},
"episode_slug": {
"description": "Episode slug(s) or canonical ID(s). For format='detail', required: pass one slug for a single drill-in or up to 10 comma-separated slugs (e.g. 'all-in-200,all-in-201,all-in-202') for a multi-episode drill-in in one call. For format='summary' or 'compact', optional filter to one episode.",
"type": "string"
},
"format": {
"description": "Response shape. 'summary' (default) returns many episodes (reverse chron) with metadata plus the first few mention-only lines per episode — use this to scan and pick episodes to drill into. 'detail' requires episode_slug and returns one episode's full mention windows with surrounding dialogue context. 'compact' returns the same episodes as summary with the mention count and the segments carrying the mentions (id, title, type, first mention time) and no dialogue — the smallest shape, for screening many entities before reading any.",
"enum": [
"summary",
"detail",
"compact"
],
"type": "string"
},
"language": {
"description": "Restrict to episodes of podcasts in this language — ISO 639-1 code (e.g. 'fr'). Matches the podcast's primary language subtag, so 'fr' covers 'fr-FR'.",
"type": "string"
},
"limit": {
"description": "Episodes per page (1-50, default 10). Summary and compact modes — detail returns one episode regardless.",
"maximum": 50,
"minimum": 1,
"type": "integer"
},
"output_format": {
"description": "Output serialization. 'markdown' (default) returns the LLM-facing rendering. 'json' returns the structured payload as JSON text — use only for programmatic chaining where exact field extraction matters; the JSON shape is larger and noisier for an LLM to read.",
"enum": [
"markdown",
"json"
],
"type": "string"
},
"person_slug": {
"description": "Person slug or encoded person ID from particle_person_resolve, particle_entity_resolve, or the guest tools (e.g. 'sam-altman'). One of person_slug, company_slug, or entity_slug is required.",
"type": "string"
},
"podcast_slug": {
"description": "Restrict mentions to a single podcast by slug, internal ID, or numeric iTunes ID. A particle.pro or Radar show link also works.",
"type": "string"
},
"role": {
"description": "Constrain how the entity participates: guest, host, panelist, correspondent, or mention.",
"enum": [
"guest",
"host",
"panelist",
"correspondent",
"mention"
],
"type": "string"
},
"since": {
"description": "Only episodes published on or after this ISO 8601 date (e.g. 2025-01-01).",
"type": "string"
},
"until": {
"description": "Only episodes published on or before this ISO 8601 date.",
"type": "string"
}
},
"type": "object"
},
"name": "particle_podcast_find_mentions",
"outputSchema": null
},
{
"description": "Return a bundled overview of one podcast episode: title, podcast, speakers (with entity slugs), top mentioned entities, and segment/clip counts.\n\nBy default the response is lean — counts plus the top mentioned entities. Request optional sections via `include`: 'segments' for the structural outline with timestamps, 'entities' for the complete entity list, 'clips' for engagement-ranked highlight clips, 'topics' for topic classifications with slugs, or 'transcript' for the dialogue transcript (narrow it by speaker or time range via `transcript_speaker` / `transcript_start` / `transcript_end` — full transcripts are large).\n\nFor \"every line about X in this episode\" use `particle_podcast_find_mentions` with `episode_slug` instead — that returns the dialogue around each mention with `is_mention` flags. For the ad reads inside the episode use `particle_podcast_get_episode_ads` (premium).",
"inputSchema": {
"properties": {
"episode_slug": {
"description": "Episode slug or canonical ID. A particle.pro or Radar episode link also works.",
"type": "string"
},
"include": {
"description": "Optional response sections: 'transcript' (bounded dialogue transcript — large for long episodes; narrow it with the transcript_* sub-params), 'segments' (structural outline with timestamps), 'entities' (complete mentioned-entity list instead of the top 20), 'clips' (engagement-ranked highlight clips), 'topics' (topic classifications with slugs), 'related' (the five episodes from OTHER shows most related to this one — slug, show, score, band; for the full ranked list with the basis behind each match call particle_podcast_list_related_episodes). Default response is lean — request only what you need.",
"items": {
"enum": [
"transcript",
"segments",
"entities",
"clips",
"topics",
"related"
],
"type": "string"
},
"type": "array"
},
"transcript_end": {
"description": "Transcript end clip in seconds.",
"type": "number"
},
"transcript_format": {
"description": "Transcript format for include=transcript. Defaults to text.",
"enum": [
"dialogue",
"text",
"srt"
],
"type": "string"
},
"transcript_speaker": {
"description": "Filter the transcript to one speaker (name or entity slug).",
"type": "string"
},
"transcript_start": {
"description": "Transcript start clip in seconds.",
"type": "number"
}
},
"required": [
"episode_slug"
],
"type": "object"
},
"name": "particle_podcast_get_episode",
"outputSchema": null
},
{
"description": "Time-bucketed episode counts — the purpose-built answer to \"how often is X discussed over time\". Counts episodes matching the same filters as `particle_podcast_list_episodes` (person, company, entity, podcast, keyword, language, duration, transcript availability) per day, week, or month, plus range totals. `keyword_search` additionally counts matching transcript segments per bucket (exact counts); `semantic_search` does the same by meaning, with the same similarity threshold as `particle_podcast_search_transcripts` (lower bounds for pathologically broad queries), and requires `published_after`. The two cannot be combined.\n\nUse this for appearance, publication, or topic trend lines instead of paging `particle_podcast_list_episodes`, `particle_podcast_find_mentions`, or `particle_podcast_search_transcripts` once per period. Buckets are UTC-aligned, zero-filled, and Monday-aligned for weeks; ranges are capped at 1000 buckets. At least one of podcast_slug, person_slug, company_slug, entity_slug, keyword_search, or semantic_search is required.",
"inputSchema": {
"properties": {
"company_slug": {
"description": "Company slug, domain, or ID. Resolves to the linked entity.",
"type": "string"
},
"entity_slug": {
"description": "Knowledge-graph entity slug from particle_entity_resolve for the long tail that isn't a person or company (e.g. 'germany'). Use person_slug for people and company_slug for companies.",
"type": "string"
},
"has_transcript": {
"description": "Only count episodes with a completed transcript.",
"type": "boolean"
},
"interval": {
"description": "Bucket width. Weeks start on Monday; all buckets are UTC-aligned. Defaults to week.",
"enum": [
"day",
"week",
"month"
],
"type": "string"
},
"keyword_search": {
"description": "Keyword filter over transcript content. Double-quoted substrings must appear as exact phrases; unquoted terms must all appear in one transcript segment. Adds per-bucket mention counts to the response. Cannot be combined with semantic_search.",
"type": "string"
},
"language": {
"description": "Restrict to episodes of podcasts in this language — ISO 639-1 code (e.g. 'fr'). Matches the podcast's primary language subtag, so 'fr' covers 'fr-FR'.",
"type": "string"
},
"max_duration": {
"description": "Maximum episode duration in seconds.",
"minimum": 0,
"type": "integer"
},
"min_duration": {
"description": "Minimum episode duration in seconds.",
"minimum": 0,
"type": "integer"
},
"output_format": {
"description": "Output serialization. 'markdown' (default) returns the LLM-facing rendering. 'json' returns the structured payload as JSON text — use only for programmatic chaining where exact field extraction matters; the JSON shape is larger and noisier for an LLM to read.",
"enum": [
"markdown",
"json"
],
"type": "string"
},
"person_slug": {
"description": "Person slug or encoded person ID (e.g. 'sam-altman'). Counts episodes featuring the person as a speaker — and, when the person has a linked knowledge-graph entity, episodes that mention them.",
"type": "string"
},
"podcast_slug": {
"description": "Podcast slug, internal ID, or numeric iTunes ID. Restrict to one podcast. A particle.pro or Radar show link also works.",
"type": "string"
},
"published_after": {
"description": "Inclusive range start as an ISO 8601 date or date-time. Omit to aggregate all time.",
"type": "string"
},
"published_before": {
"description": "Range end as an ISO 8601 date or date-time. Defaults to now.",
"type": "string"
},
"role": {
"description": "Role filter when person_slug, company_slug, or entity_slug is set.",
"enum": [
"guest",
"host",
"panelist",
"correspondent",
"mention"
],
"type": "string"
},
"semantic_search": {
"description": "Vector-similarity filter by meaning over transcript content — the counting twin of particle_podcast_search_transcripts' semantic_search, using the same similarity threshold. Describe the topic the way you'd say it to a colleague; paraphrase tolerant. Adds per-bucket mention counts to the response. Requires published_after (ranges up to ~2 years). Cannot be combined with keyword_search.",
"type": "string"
}
},
"type": "object"
},
"name": "particle_podcast_get_episode_timeseries",
"outputSchema": null
},
{
"description": "A guest's podcast-appearance profile: lifetime stats (appearances, distinct podcasts, first/last appearance) plus their most frequent podcasts. Guests are people — the same slug works with `particle_person_get` for the biographical profile.\n\nRequest optional sections via `include`: 'appearances' for the most recent episode appearances (episode and podcast slugs included for follow-up calls), 'podcasts' for the per-podcast rollup, 'suitability' for brand-suitability exposure across the podcasts they appear on, 'recommended_podcasts' for the five shows they could plausibly appear on next — shows related to the ones they have guested on, minus those, with the venues behind each pick (the pitch list; branch on each row's band).\n\nReturns not_found for people who exist but have never appeared on a podcast — use `particle_person_get` for those.",
"inputSchema": {
"properties": {
"guest_slug": {
"description": "Person slug (e.g. 'sam-altman') from particle_podcast_list_guests, particle_person_resolve, or particle_entity_resolve.",
"type": "string"
},
"include": {
"description": "Optional response sections: 'appearances' (most recent episode appearances with episode/podcast slugs), 'podcasts' (per-podcast rollup of where they appear), 'suitability' (brand-suitability exposure across the podcasts they appear on), 'recommended_podcasts' (the five shows they could plausibly appear on next — shows related to the ones they have guested on, minus those, each with the venues that led there; the pitch list). Default response is the profile + lifetime stats.",
"items": {
"enum": [
"appearances",
"podcasts",
"suitability",
"recommended_podcasts"
],
"type": "string"
},
"type": "array"
},
"output_format": {
"description": "Output serialization. 'markdown' (default) returns the LLM-facing rendering. 'json' returns the structured payload as JSON text — use only for programmatic chaining where exact field extraction matters; the JSON shape is larger and noisier for an LLM to read.",
"enum": [
"markdown",
"json"
],
"type": "string"
}
},
"required": [
"guest_slug"
],
"type": "object"
},
"name": "particle_podcast_get_guest",
"outputSchema": null
},
{
"description": "Podcast chart rankings from Apple Podcasts and Spotify, in four modes:\n - `chart` (default): the current chart for a source/country/category slot, or — with `podcast_slug` — every chart slot that podcast currently holds.\n - `movers`: the biggest rank changes over `window_days` (risers, fallers, debuts, exits).\n - `history`: past snapshots for a chart slot, or — with `podcast_slug` — one podcast's chart history over time.\n - `slots`: the valid slot values — every source, country, and category_slug with live chart data — so filter values are discovered, not guessed. `source` narrows the country/category listings; other filters are ignored.\n\nEach row carries the matched `podcast_slug` when the chart entry is in the catalog — feed it into `particle_podcast_resolve` or any podcast tool. For a single podcast's at-a-glance chart presence, `particle_podcast_resolve` with `include: [\"rankings\"]` is one call instead of two.",
"inputSchema": {
"properties": {
"category_slug": {
"description": "Category slug (e.g. 'comedy', 'business'). Omit for the overall chart.",
"type": "string"
},
"change": {
"description": "Mode=movers only: filter by change type. Defaults to all.",
"enum": [
"all",
"up",
"down",
"new",
"exit"
],
"type": "string"
},
"country": {
"description": "ISO 3166-1 alpha-2 country code (e.g. 'us', 'gb', 'jp'). Defaults to us.",
"type": "string"
},
"cursor": {
"description": "Opaque pagination cursor from a previous response. Not supported by mode=movers.",
"type": "string"
},
"limit": {
"description": "Rows per page (1-100, default 25).",
"maximum": 100,
"minimum": 1,
"type": "integer"
},
"mode": {
"description": "What to return. 'chart' (default): the current chart — or, with podcast_slug, every chart slot the podcast currently holds. 'movers': biggest rank changes over window_days (risers, fallers, debuts, exits). 'history': past chart snapshots for a slot — or, with podcast_slug, one podcast's chart history. 'slots': the valid chart-slot values — every source, country, and category_slug with live data — for discovering filter values before a chart/movers/history call.",
"enum": [
"chart",
"movers",
"history",
"slots"
],
"type": "string"
},
"output_format": {
"description": "Output serialization. 'markdown' (default) returns the LLM-facing rendering. 'json' returns the structured payload as JSON text — use only for programmatic chaining where exact field extraction matters; the JSON shape is larger and noisier for an LLM to read.",
"enum": [
"markdown",
"json"
],
"type": "string"
},
"podcast_slug": {
"description": "Podcast slug, internal ID, or numeric iTunes ID. With mode=chart: that podcast's current chart appearances across every slot. With mode=history: that podcast's chart history. A particle.pro or Radar show link also works.",
"type": "string"
},
"since": {
"description": "Mode=history only: only snapshots captured on or after this ISO 8601 timestamp.",
"type": "string"
},
"source": {
"description": "Ranking source platform. Defaults to apple.",
"enum": [
"apple",
"spotify"
],
"type": "string"
},
"until": {
"description": "Mode=history only: only snapshots captured on or before this ISO 8601 timestamp.",
"type": "string"
},
"window_days": {
"description": "Mode=movers only: comparison window in days (1-30, default 1 = vs. yesterday).",
"maximum": 30,
"minimum": 1,
"type": "integer"
}
},
"type": "object"
},
"name": "particle_podcast_get_rankings",
"outputSchema": null
},
{
"description": "Browse AI-extracted highlight clips across the catalog, ranked by engagement potential — the shareable moments. Filter by podcast, episode, clip type (FUNNY, CONTROVERSIAL, INSIGHTFUL, ...), minimum engagement score, or speaker — `speaker` takes a person slug and returns only clips of that person talking ('an insightful Sam Altman clip').\n\nPass `clip_id` for one clip's full detail (description, social-hook intro, speaker, audio URL), plus `include: [\"transcript\"]` for its dialogue.\n\nFor text-based clip discovery — finding clips about a topic or entity — use `particle_podcast_search_transcripts` instead: matching clips arrive inline on each search result. Episode slugs on every row feed `particle_podcast_get_episode`.",
"inputSchema": {
"properties": {
"clip_id": {
"description": "Return one clip's full detail instead of a listing. Clip IDs come from this tool, particle_podcast_get_episode with include=clips, and search-result overlapping clips.",
"type": "string"
},
"cursor": {
"description": "Opaque pagination cursor from a previous response.",
"type": "string"
},
"episode_slug": {
"description": "Restrict the listing to one episode (slug or ID). A particle.pro or Radar episode link also works.",
"type": "string"
},
"include": {
"description": "With clip_id only: 'transcript' attaches the clip's dialogue transcript.",
"items": {
"enum": [
"transcript"
],
"type": "string"
},
"type": "array"
},
"limit": {
"description": "Clips per page (1-50, default 10).",
"maximum": 50,
"minimum": 1,
"type": "integer"
},
"min_engagement": {
"description": "Minimum engagement potential score (0-100). Above 70 is typical for a strong clip.",
"maximum": 100,
"minimum": 0,
"type": "integer"
},
"output_format": {
"description": "Output serialization. 'markdown' (default) returns the LLM-facing rendering. 'json' returns the structured payload as JSON text — use only for programmatic chaining where exact field extraction matters; the JSON shape is larger and noisier for an LLM to read.",
"enum": [
"markdown",
"json"
],
"type": "string"
},
"podcast_slug": {
"description": "Restrict the listing to one podcast (slug, internal ID, or numeric iTunes ID). A particle.pro or Radar show link also works.",
"type": "string"
},
"speaker": {
"description": "Restrict the listing to clips whose primary speaker is this person — a person slug (e.g. 'sam-altman' from particle_person_resolve), a knowledge-graph entity slug for the same person, or an ID.",
"type": "string"
},
"type": {
"description": "Clip type filter.",
"enum": [
"SPICY",
"CONTROVERSIAL",
"EMOTIONAL",
"FUNNY",
"SHOCKING",
"INSIGHTFUL",
"INFORMATIVE",
"EDUCATIONAL",
"PHILOSOPHICAL",
"AHA_MOMENT",
"NOTABLE_LINE",
"BEST_STORY",
"DEBATE_DISAGREEMENT"
],
"type": "string"
}
},
"type": "object"
},
"name": "particle_podcast_list_clips",
"outputSchema": null
},
{
"description": "List episodes across the catalog with rich filters: by podcast, person, company, language, date range, duration, or transcript availability.\n\nBy default it lists the episodes we have ingested. Pass `transcript_status` with `podcast_slug` to reach the show's back catalogue — older episodes discovered in its feed but not transcribed, marked requestable. Their transcript, segments, speakers and entities do not exist until one is requested.\n\nUse this for episode-level discovery when you only need metadata (title, duration, speakers, counts). For dialogue around a person in any episode, use `particle_podcast_find_mentions`. For ranked retrieval by topic, use `particle_podcast_search_transcripts`.",
"inputSchema": {
"properties": {
"company_slug": {
"description": "Company slug, domain, or ID. Resolves to the linked entity.",
"type": "string"
},
"cursor": {
"description": "Opaque pagination cursor from a previous response.",
"type": "string"
},
"entity_slug": {
"description": "Knowledge-graph entity slug from particle_entity_resolve for the long tail that isn't a person or company — places, organizations, events, products, concepts (e.g. 'germany'). Use person_slug for people and company_slug for companies.",
"type": "string"
},
"has_transcript": {
"description": "Only include episodes with a completed transcript. Superseded by transcript_status=transcribed; do not pass both.",
"type": "boolean"
},
"language": {
"description": "Restrict to episodes of podcasts in this language — ISO 639-1 code (e.g. 'fr'). Matches the podcast's primary language subtag, so 'fr' covers 'fr-FR'.",
"type": "string"
},
"limit": {
"description": "Episodes per page (1-50, default 10).",
"maximum": 50,
"minimum": 1,
"type": "integer"
},
"max_duration": {
"description": "Maximum episode duration in seconds.",
"minimum": 0,
"type": "integer"
},
"min_duration": {
"description": "Minimum episode duration in seconds.",
"minimum": 0,
"type": "integer"
},
"output_format": {
"description": "Output serialization. 'markdown' (default) returns the LLM-facing rendering. 'json' returns the structured payload as JSON text — use only for programmatic chaining where exact field extraction matters; the JSON shape is larger and noisier for an LLM to read.",
"enum": [
"markdown",
"json"
],
"type": "string"
},
"person_slug": {
"description": "Person slug or encoded person ID from particle_person_resolve, particle_entity_resolve, or the guest tools (e.g. 'sam-altman'). Episodes featuring or mentioning the person.",
"type": "string"
},
"podcast_slug": {
"description": "Podcast slug, internal ID, or numeric iTunes ID. Restrict to one podcast. A particle.pro or Radar show link also works.",
"type": "string"
},
"published_after": {
"description": "ISO 8601 date or date-time.",
"type": "string"
},
"published_before": {
"description": "ISO 8601 date or date-time.",
"type": "string"
},
"role": {
"description": "Role filter when person_slug or company_slug is set.",
"enum": [
"guest",
"host",
"panelist",
"correspondent",
"mention"
],
"type": "string"
},
"transcript_status": {
"description": "Filter by where episodes stand on the way to a transcript. Omitted lists the episodes we have ingested. any, untranscribed and requestable also list the podcast's back catalogue — episodes discovered in its feed but not transcribed — and require podcast_slug: any lists every discovered episode, untranscribed those without a transcript, requestable back-catalogue episodes whose transcript can be requested. transcribed lists episodes with a transcript.",
"enum": [
"any",
"transcribed",
"untranscribed",
"requestable"
],
"type": "string"
}
},
"type": "object"
},
"name": "particle_podcast_list_episodes",
"outputSchema": null
},
{
"description": "Browse podcast guests across the catalog, in two opinionated modes:\n - `directory` (default): the guest directory ranked by lifetime appearances (guests with 2+ appearances).\n - `trends`: who's making the rounds right now — guests with appearances on 2+ distinct podcasts in the last 30 days, which surfaces cross-show press tours rather than show regulars. The press-tour shape is enforced: every in-window appearance must be on a different podcast, each needs 5+ minutes of identified speaking time, mononymous catch-all people are excluded, and the in-window rate must be a 2x spike over the guest's lifetime baseline.\n\n`podcast_slug` switches the directory to one show's roster: every guest who has appeared on that podcast, ranked by appearances on the show (one-off guests included). `topic_slug` narrows either corpus mode to guests appearing on episodes about that topic. Guest slugs ARE person slugs — feed them into `particle_podcast_get_guest` for the appearance profile or `particle_person_get` for the person profile.",
"inputSchema": {
"properties": {
"cursor": {
"description": "Opaque pagination cursor from a previous response.",
"type": "string"
},
"limit": {
"description": "Guests per page (1-50, default 20).",
"maximum": 50,
"minimum": 1,
"type": "integer"
},
"mode": {
"description": "What to return. 'directory' (default): the guest directory ranked by lifetime appearances. 'trends': guests trending right now — appearances on 2+ distinct podcasts in the last 30 days (cross-show press tours, not regulars).",
"enum": [
"directory",
"trends"
],
"type": "string"
},
"output_format": {
"description": "Output serialization. 'markdown' (default) returns the LLM-facing rendering. 'json' returns the structured payload as JSON text — use only for programmatic chaining where exact field extraction matters; the JSON shape is larger and noisier for an LLM to read.",
"enum": [
"markdown",
"json"
],
"type": "string"
},
"podcast_slug": {
"description": "Return one show's guest roster instead of the corpus directory: every guest who has appeared on this podcast, ranked by appearances on the show (no lifetime-appearance floor). Slug from particle_podcast_resolve. Only valid with the default directory mode. A particle.pro or Radar show link also works.",
"type": "string"
},
"topic_slug": {
"description": "Restrict to guests with appearances on episodes classified under this topic (slug from particle_topic_browse, e.g. 'technology/artificial-intelligence'). Ignored when podcast_slug is set.",
"type": "string"
}
},
"type": "object"
},
"name": "particle_podcast_list_guests",
"outputSchema": null
},
{
"description": "List the shows most related to a podcast, best first — \"shows like this show\". Each result carries the related show's slug, a calibrated score in (0,1], and a coarse band (strong: same beat and audience; moderate: overlapping subject or audience; weak: a loose connection) to branch on. Add `include: [\"basis\"]` to see WHY each pair is related: content similarity of recent episodes, shared topics, shared guests (named), same publisher, shared sponsors — use it to explain a recommendation or to keep only pairs related for the reason you care about (shared guests for booking, content for media planning).\n\nRelated sets are precomputed per show from its transcripts, topic profile, guest roster, network and advertisers, restricted to the show's language. Only shows above a relatedness floor are listed, machine-generated and farmed feeds are never listed, and a publisher's duplicate feeds of one show appear once. An empty FIRST page is not an error: its `coverage` says whether the set is not computed yet, nothing cleared the floor, or the request's filters and the default policy removed everything; an empty page reached through a cursor is simply the end of the list.\n\nNot a topic browser: for shows that COVER a topic use `particle_podcast_resolve` with `topic_slug`. Not a guest lookup: for where a person has appeared use `particle_podcast_get_guest`. Not advertiser co-occurrence: use `particle_podcast_get_sponsors`. Every related show's slug feeds `particle_podcast_resolve`, `particle_podcast_list_episodes` and the other podcast tools; person slugs in the basis feed `particle_podcast_get_guest`, topic slugs feed `particle_podcast_resolve`'s `topic_slug`. For the five most related shows inline on a resolve, pass `include: [\"related\"]` to `particle_podcast_resolve` instead of calling this tool.",
"inputSchema": {
"properties": {
"cursor": {
"description": "Opaque pagination cursor from a previous response.",
"type": "string"
},
"exclude_same_publisher": {
"description": "Drop shows from the source show's own publisher.",
"type": "boolean"
},
"include": {
"description": "Optional response sections. 'basis' attaches, per result, the signals that make the two shows related: content similarity of recent episodes, shared topics, shared guests (named), same publisher, shared sponsors. Off by default; opt in when you need to explain or filter by the reason.",
"items": {
"enum": [
"basis"
],
"type": "string"
},
"type": "array"
},
"language": {
"description": "Only shows in this language: an ISO 639-1 code such as 'en' or 'es'.",
"type": "string"
},
"limit": {
"description": "Results per page (1-50, default 10).",
"maximum": 50,
"minimum": 1,
"type": "integer"
},
"min_popularity": {
"description": "Only shows at or above this popularity percentile (0-1]. A floor above 0 excludes non-charting shows; 0 applies no floor.",
"maximum": 1,
"minimum": 0,
"type": "number"
},
"min_score": {
"description": "Drop results below this fused score (0-1]. Prefer branching on each result's band (strong / moderate / weak); score thresholds may be recalibrated as the ranker improves.",
"maximum": 1,
"minimum": 0,
"type": "number"
},
"output_format": {
"description": "Output serialization. 'markdown' (default) returns the LLM-facing rendering. 'json' returns the structured payload as JSON text — use only for programmatic chaining where exact field extraction matters; the JSON shape is larger and noisier for an LLM to read.",
"enum": [
"markdown",
"json"
],
"type": "string"
},
"podcast_slug": {
"description": "The source podcast — slug (e.g. 'all-in' from particle_podcast_resolve), internal ID, or numeric iTunes ID. A particle.pro or Radar show link also works.",
"type": "string"
},
"publishing_status": {
"description": "Only shows that released an episode in the last 90 days ('active') or did not ('dormant'); shows with no known episode date match neither.",
"enum": [
"active",
"dormant"
],
"type": "string"
},
"suitability_tier": {
"description": "Only shows whose latest brand-suitability tier is this value; never-assessed shows are excluded.",
"enum": [
"SAFE",
"LIMITED",
"SENSITIVE",
"UNSAFE"
],
"type": "string"
}
},
"type": "object"
},
"name": "particle_podcast_list_related",
"outputSchema": null
},
{
"description": "Episodes from OTHER shows that cover the same story or subject as a given episode, best first — a live nearest-neighbour search over episode content, reranked on shared salient entities, shared topics and a shared news story. Each row carries a calibrated score and a band (strong / moderate / weak) to branch on; pass `include: [\"basis\"]` to see the signals behind every match. Each show contributes at most two episodes, the same content republished on another feed is collapsed to one row, and feeds the screens flag as machine-made or syndication spam are excluded.\n\nUse it when you already have an episode and want its coverage elsewhere ('who else covered this?'). Add `published_within_days` (7–30) to keep to the same news cycle; `same_podcast: true` admits the show's own episodes, which are otherwise excluded.\n\nDo NOT use it to find dialogue about a topic — that is `particle_podcast_search_transcripts` — nor to find every line naming an entity, which is `particle_podcast_find_mentions`. Episode slugs on every row feed `particle_podcast_get_episode`; podcast slugs feed `particle_podcast_resolve`.",
"inputSchema": {
"properties": {
"cursor": {
"description": "Opaque pagination cursor from a previous response.",
"type": "string"
},
"episode_slug": {
"description": "Episode slug or ID (from particle_podcast_list_episodes, particle_podcast_get_episode, or a search result). A particle.pro or Radar episode link also works.",
"type": "string"
},
"include": {
"description": "'basis' attaches, per result, the signals behind the match: content similarity, shared entities with names, shared topics, a shared news story, shared guests, days apart.",
"items": {
"enum": [
"basis"
],
"type": "string"
},
"type": "array"
},
"limit": {
"description": "Results per page (1-50, default 10).",
"maximum": 50,
"minimum": 1,
"type": "integer"
},
"output_format": {
"description": "Output serialization. 'markdown' (default) returns the LLM-facing rendering. 'json' returns the structured payload as JSON text — use only for programmatic chaining where exact field extraction matters; the JSON shape is larger and noisier for an LLM to read.",
"enum": [
"markdown",
"json"
],
"type": "string"
},
"published_within_days": {
"description": "Only episodes published within this many days of the query episode, on either side. Omit for no window. Use 7–30 for 'who else covered this story'.",
"maximum": 3650,
"minimum": 0,
"type": "integer"
},
"same_podcast": {
"description": "Admit episodes of the same show. Off by default — a show's own episodes are its episode list (particle_podcast_list_episodes), not its related content.",
"type": "boolean"
}
},
"required": [
"episode_slug"
],
"type": "object"
},
"name": "particle_podcast_list_related_episodes",
"outputSchema": null
},
{
"description": "Find a podcast by free-text title, exact slug, iTunes ID, or RSS feed URL. Returns slug, title, episode count, bias, and the top recurring speakers (with entity slugs). Use the slug as the agent-facing handle to feed into other podcast tools (`particle_podcast_find_mentions`, `particle_podcast_list_episodes`, `particle_podcast_get_sponsors`).\n\nFree-text matching is forgiving — typos, missing or extra words, and pasted episode titles all work. Results are ordered best-match-first; text matches carry a `match_quality` field, and an empty list means the catalog has no plausible candidate.\n\nWith all identifiers omitted, returns the most recently updated podcasts — useful for browsing the catalog when you don't have a name in mind. Narrow free-text browsing with `topic_slug` (topic concentration, descendants included), `suitability_tier`, or `min_popularity` (global popularity percentile over charting podcasts).\n\nOptional hydrations attach extra data to each result in the same call:\n - `include: [\"external_links\"]`: third-party platform presences (directories, social profiles, video channels, publisher websites) with resolved URLs and audience metrics.\n - `include: [\"suitability\"]`: per-category brand-suitability breakdown (12 categories with prevalence, treatment, derived risk level, reasoning, and evidence excerpts) — premium-grade data, requires a plan with premium endpoints. The high-level `suitability_tier` enum (SAFE / LIMITED / SENSITIVE / UNSAFE) is rendered on every result without opt-in.\n - `include: [\"ratings_summary\"]`: listener-review aggregate (average stars, count, per-platform breakdown).\n - `include: [\"bias\"]`: full political-bias analysis (the high-level bias enum is always rendered without opt-in).\n - `include: [\"rankings\"]`: current chart positions across sources/countries/categories — premium-grade data, requires a plan with premium endpoints. For movers and history use `particle_podcast_get_rankings`.\n - `include: [\"format\"]`: the show's format profile — how often episodes feature guests, detected production formats (interview, panel, call_in, solo_narrated), ad and video presence, episode-length distribution, publishing cadence, and the publish-day pattern.\n - `recent_episodes: N`: inline this many of each result's most recent episodes (slug, title, published_at, duration) — skip the follow-up `particle_podcast_list_episodes` call when you only need the most recent tail.",
"inputSchema": {
"properties": {
"include": {
"description": "Optional non-parameterized hydrations to attach to each result. 'external_links' adds third-party platform presences (directories, social profiles, video channels, publisher websites). 'suitability' adds the per-category brand-suitability breakdown (premium-grade data — requires a plan with premium endpoints). 'ratings_summary' adds the listener-review aggregate. 'bias' adds the full political-bias analysis. 'rankings' adds current chart positions (premium-grade data — requires a plan with premium endpoints). 'format' adds the format profile (guest frequency, interview/panel/call-in/solo formats, ads, video, episode length, cadence, publish days). 'related' adds the five most related shows (slug, score, band) — for the full ranked list with the basis behind each pair call particle_podcast_list_related. 'recommended_guests' adds the five guests the show could book next — people who have guested on its related shows but never on it (person slug, score, band, and the related shows that booked them); the booking pipeline. 'recommended_sponsors' adds the five advertisers the show could pitch — sponsors that run on its related shows but not on it (sponsor, linked company, score, band, ads and most recent ad across those shows, and which shows); the prospecting list (premium-grade data — requires a plan with premium endpoints). 'coverage' adds how much of the show's history we know of and have transcribed: discovered episodes by transcript status and by year, and whether its back catalogue (older episodes discovered in its feed but not transcribed) has been imported and is complete. The high-level suitability_tier and bias enums are always rendered without opt-in. Off by default; opt in only when needed.",
"items": {
"enum": [
"external_links",
"suitability",
"ratings_summary",
"bias",
"rankings",
"format",
"related",
"recommended_guests",
"recommended_sponsors",
"coverage"
],
"type": "string"
},
"type": "array"
},
"itunes_id": {
"description": "Numeric Apple Podcasts / iTunes ID (e.g. '1502871393'). Resolves directly to the matching podcast.",
"type": "string"
},
"limit": {
"description": "Maximum candidates to return (1-25, default 5).",
"maximum": 25,
"minimum": 1,
"type": "integer"
},
"min_popularity": {
"description": "Restrict candidates to podcasts whose global popularity percentile is at least this value (0-1]. Popularity is a cume_dist ranking over currently-charting podcasts; non-charting podcasts are excluded when set. Omit or 0 to disable.",
"maximum": 1,
"minimum": 0,
"type": "number"
},
"query": {
"description": "Free-text search across podcast titles and descriptions (case-insensitive partial match). Omit to fall back to the most recently updated podcasts.",
"type": "string"
},
"recent_episodes": {
"description": "Inline this many of each result's most recent episodes (slug, title, published_at, duration). 0 (default) means none — call particle_podcast_list_episodes if you need more than the inline tail. Capped at 25.",
"maximum": 25,
"minimum": 0,
"type": "integer"
},
"rss_url": {
"description": "Canonical RSS feed URL. Resolves directly to the matching podcast.",
"type": "string"
},
"slug": {
"description": "Exact slug match for a known handle (e.g. 'all-in').",
"type": "string"
},
"suitability_tier": {
"description": "Filter candidates by brand-suitability tier. Podcasts without a suitability analysis are excluded when set.",
"enum": [
"SAFE",
"LIMITED",
"SENSITIVE",
"UNSAFE"
],
"type": "string"
},
"topic_slug": {
"description": "Filter candidates by topic (slug from particle_topic_browse, e.g. 'technology/artificial-intelligence'). Matches podcasts where the topic — or any of its descendants — accounts for a meaningful share of episodes, ranked by concentration.",
"type": "string"
}
},
"type": "object"
},
"name": "particle_podcast_resolve",
"outputSchema": null
},
{
"description": "Search the podcast catalog by what is said in episodes — by meaning (`semantic_search`), by exact phrase (`keyword_search`), or both at once (hybrid ranking). This is THE way to retrieve relevant dialogue, segments, and clips: each result is one segment of one episode with bounded transcript windows pinpointing the highest-relevance lines, plus any highlight clips that overlap the segment inline on the match.\n\nSegments partition an episode's transcript — where start_line and end_line are present, every spoken line belongs to exactly one segment and one segment's end_line + 1 is the next one's start_line. They are contiguous in transcript lines, not in wall-clock seconds: the seconds between one segment's end_seconds and the next's start_seconds contain no transcribed speech. These matches do not carry the line ranges themselves — fetch them with `particle_podcast_get_episode` and `include: [\"segments\"]`, where their absence marks an episode segmented by an earlier version, a small share of which do leave lines uncovered. Clips are sparse, engagement-ranked highlights that overlap some segments. There is no separate clip-search tool — relevant clips arrive on these matches, and a known episode's full clip list is `particle_podcast_get_episode` with `include: [\"clips\"]`.\n\nA match window defaults to one line of context around each matched line; raise `context` to widen windows in place instead of fetching the full transcript.\n\n**Screening many results?** Pass `format: \"compact\"`. Each match then carries only its identity — episode and podcast slugs, segment id and bounds, segment type, the segment's one-line description, and the relevance score — with no dialogue or clips, at a fraction of the size and latency of the default. Fan out compact searches over companies, themes, or dates, decide which segments matter, then read dialogue only for those: `particle_podcast_get_episode` with `include: [\"transcript\"]` and `transcript_start`/`transcript_end` set to the segment's bounds, or this tool again with `episode_slug` narrowed to that episode.\n\nUse this for \"find dialogue *about* a topic\". For \"every line *naming* a person or company\" use `particle_podcast_find_mentions` instead — `person_slug` and `company_slug` here narrow ranked results, they don't drive the ranking.\n\n**Choosing your query.** At least one of `semantic_search` or `keyword_search` is required, and they do different jobs:\n- `semantic_search` carries the *idea*. Write it as a sentence describing what should be discussed, in the vocabulary a speaker would use. It is paraphrase-tolerant, so it finds the topic however it happens to be worded.\n- `keyword_search` carries words that must be *literally spoken*. Every word must occur in the same passage, so it is for one or two exact tokens — a ticker, a product name — not for a description. Putting a sentence here returns nothing.\n- Use both when a topic must also contain an exact term. The result is their intersection, which is narrow by design; if that comes back empty, `keyword_match: \"ranked\"` relaxes the keyword side to a relevance hint.\n\n**Do not put a name in `semantic_search`.** Resolve it (`particle_person_resolve`, `particle_company_resolve`, `particle_entity_resolve`) and pass the slug — searching for \"Sam Altman\" as text finds passages that *sound like* him, while `person_slug` finds the episodes actually featuring him.\n\n**Start broad, then narrow.** Every filter compounds, and each one can silently remove all results. Issue the query with `semantic_search` alone first, then add filters once you know the topic has coverage. If a search returns nothing because of your filters, the error names the specific parameter responsible and the retry to make — act on it rather than re-issuing variations of the same query.\n\n**Note on `role`.** It describes how someone relates to the episode: `guest`/`host`/`panelist`/`correspondent` mean they *spoke*, `mention` means they were *talked about*. Omitting `role` covers both and is almost always what you want.",
"inputSchema": {
"properties": {
"company_slug": {
"description": "Company slug, domain, or ID. Resolves to the company's linked entity and applies as a filter.",
"type": "string"
},
"context": {
"description": "Lines of surrounding dialogue around each matched line (1-15, default 1). Widens each match window in place — use a larger value instead of fetching the full transcript when a match needs more context. Ignored when format is compact.",
"maximum": 15,
"minimum": 1,
"type": "integer"
},
"cursor": {
"description": "Opaque pagination cursor from a previous response.",
"type": "string"
},
"entity_slug": {
"description": "Knowledge-graph entity slug from particle_entity_resolve for the long tail that isn't a person or company — places, organizations, events, products, concepts (e.g. 'germany'). Use person_slug for people and company_slug for companies.",
"type": "string"
},
"entity_type": {
"description": "Narrow to dialogue in episodes that mention any entity of this category — e.g. 'book', 'company', 'movie', 'school'. Use for 'discussions of X that reference some book'. Ignored when person_slug/company_slug/entity_slug names a specific entity, which is strictly narrower. Categories come from particle_catalog.",
"type": "string"
},
"episode_slug": {
"description": "Filter to a specific episode by slug or ID. A particle.pro or Radar episode link also works.",
"type": "string"
},
"format": {
"description": "Response shape. 'full' (default) carries each match's bounded dialogue windows and overlapping clips. 'compact' returns the same ranked matches with no dialogue — episode and podcast slugs, segment id and bounds, segment type, the segment's one-line description, and the relevance score — at a fraction of the size and latency, because the transcript load and line scoring are skipped. Use it to screen many results (a fan-out over companies, themes, or dates) and read dialogue only for the survivors.",
"enum": [
"full",
"compact"
],
"type": "string"
},
"keyword_match": {
"description": "How UNQUOTED keyword_search words are applied. 'required' (default) excludes any passage missing one of them, which also makes a hybrid call an intersection with semantic_search. Switch to 'ranked' when keyword_search is a loose bag of related words that will not co-occur — then those words only steer relevance. Quoted phrases still filter in both modes: to relax a phrase, remove its quotes rather than switching mode.",
"enum": [
"required",
"ranked"
],
"type": "string"
},
"keyword_search": {
"description": "Words that must literally be spoken. Use for exact tokens a paraphrase would miss — tickers, product names, drug names, model numbers. Every word must appear in the same passage (see keyword_match), so keep it to the one or two words that must be said and put the rest of the idea in semantic_search. Wrap words in double quotes to also require them adjacent and in order in the segment's spoken dialogue — only for short exact strings, never for a sentence. A quoted name matches segments where the name appears in the dialogue, not segments that person speaks in; use person_slug or particle_podcast_find_mentions for a person's appearances. There is no boolean OR: 'a OR b' requires the literal word 'OR', so issue one call per alternative.",
"maxLength": 500,
"type": "string"
},
"language": {
"description": "Restrict to episodes of podcasts in this language — ISO 639-1 code (e.g. 'fr'). Matches the podcast's primary language subtag, so 'fr' covers 'fr-FR'.",
"type": "string"
},
"limit": {
"description": "Results per page (1-50, default 10).",
"maximum": 50,
"minimum": 1,
"type": "integer"
},
"output_format": {
"description": "Output serialization. 'markdown' (default) returns the LLM-facing rendering. 'json' returns the structured payload as JSON text — use only for programmatic chaining where exact field extraction matters; the JSON shape is larger and noisier for an LLM to read.",
"enum": [
"markdown",
"json"
],
"type": "string"
},
"person_slug": {
"description": "Person slug or encoded person ID from particle_person_resolve, particle_entity_resolve, or the guest tools (e.g. 'sam-altman'). Filters results to dialogue featuring this person. For 'every line about X' use particle_podcast_find_mentions instead.",
"type": "string"
},
"podcast_slug": {
"description": "Podcast slug, internal ID, or numeric iTunes ID. A particle.pro or Radar show link also works.",
"type": "string"
},
"role": {
"description": "How the entity must relate to the episode. Speaking roles: 'guest', 'host', 'panelist', 'correspondent', or 'speaker' for any of them. 'mention' means the entity is talked about rather than speaking. Omit to match both — usually what you want.",
"enum": [
"guest",
"host",
"panelist",
"correspondent",
"speaker",
"mention"
],
"type": "string"
},
"segment_type": {
"description": "Segment type filter.",
"enum": [
"INTRO",
"PERSONAL_BANTER",
"TOPIC_DISCUSSION",
"INTERVIEW",
"TRANSITION",
"AD",
"OUTRO"
],
"type": "string"
},
"semantic_search": {
"description": "Vector-similarity search by meaning. Express the query the way you'd describe the topic to a colleague — paraphrase tolerant. Combine with keyword_search for hybrid ranking. Describe a topic, not a name: to find a specific person/company/entity, filter with person_slug / company_slug / entity_slug (or use particle_podcast_find_mentions for every line about them) — and for an exact token like a ticker, use keyword_search.",
"maxLength": 500,
"type": "string"
},
"since": {
"description": "Only segments from episodes published on or after this ISO 8601 date.",
"type": "string"
},
"sort": {
"description": "Sort order. Defaults to relevance.",
"enum": [
"relevance",
"recency"
],
"type": "string"
},
"until": {
"description": "Only segments from episodes published on or before this ISO 8601 date.",
"type": "string"
}
},
"type": "object"
},
"name": "particle_podcast_search_transcripts",
"outputSchema": null
},
{
"description": "Navigate the topic taxonomy. Without `parent_slug`, returns the top-level roots (Politics, Business, Technology, etc.). With `parent_slug` set, returns the direct children of that topic. Topic slugs use a `parent/child` convention (e.g. `politics/elections`) and let agents browse the hierarchy to find well-named categories.",
"inputSchema": {
"properties": {
"cursor": {
"description": "Opaque pagination cursor from a previous response.",
"type": "string"
},
"limit": {
"description": "Topics per page (1-100, default 50).",
"maximum": 100,
"minimum": 1,
"type": "integer"
},
"output_format": {
"description": "Output serialization. 'markdown' (default) returns the LLM-facing rendering. 'json' returns the structured payload as JSON text — use only for programmatic chaining where exact field extraction matters; the JSON shape is larger and noisier for an LLM to read.",
"enum": [
"markdown",
"json"
],
"type": "string"
},
"parent_slug": {
"description": "Topic slug or ID. Returns the direct children of this topic. Omit for top-level roots (Politics, Business, Technology, etc.).",
"type": "string"
}
},
"type": "object"
},
"name": "particle_topic_browse",
"outputSchema": null
}
]
}Verify it yourself
curl -s https://api.teppi.xyz/v1/evidence/sha256:ef8d5d39740f923905e4fc05f1af1e8750bb83a23f1592834e769c41b4c4a36b | sha256sum