# Search Episode Transcript

> Pod Engine API endpoint reference, optimized for LLM consumption.
> Auto-generated from the source-of-truth endpoint registry.
>
> Category: Search
> Source: https://www.podengine.ai/api-docs/transcript-passages.md
> Interactive HTML docs: https://www.podengine.ai/api-docs/transcript-passages
> Full API reference (all endpoints): https://www.podengine.ai/api-docs/llms.txt

**Base URL:** `https://api.podengine.ai`
**Auth:** Requires a Bearer token: `Authorization: Bearer YOUR_API_KEY`

---

#### POST /api/v1/search/episodes/:episodeId/transcript
**Search Episode Transcript**

Search within the transcript of the episode identified by episodeId in the URL. Send the original searchOptions, pageSize and optional cursor in the body; do not repeat episodeId in the body. Count and browse matching passages for that episode. The best passage is separate from paginated additional passages. Counts are exact (eq) or lower bounds (gte); at most 100 passages can be browsed. Initial episode totals still count episodes. Reuse nextCursor with the same episode and query; HTTP 409 means restart without the cursor. Passages use the same Elasticsearch analyzer and sentence boundaries as matchSnippet; nearby terms in one passage count once. Highlighting failures, including transcripts exceeding the index analysis limit, are errors, never zero matches or silently partial counts.

```bash
curl -X POST "https://api.podengine.ai/api/v1/search/episodes/316bcb29-3017-46d1-88ee-af5a6505a484/transcript" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d '{
  "searchOptions": {
    "searchTerms": [
      {
        "searchTerm": "artificial intelligence",
        "searchType": "text",
        "searchTargets": [
          "transcript"
        ],
        "searchTermOptions": {
          "matchMode": "must",
          "phraseMatch": true
        }
      }
    ]
  },
  "pageSize": 5
}'
```

**Path parameters schema:**
```json
{
  "type": "object",
  "properties": {
    "episodeId": {
      "type": "string",
      "format": "uuid",
      "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
      "description": "Pod Engine UUID of the episode."
    }
  },
  "required": [
    "episodeId"
  ],
  "additionalProperties": false
}
```

**Request body schema:**
```json
{
  "type": "object",
  "properties": {
    "searchOptions": {
      "type": "object",
      "properties": {
        "cursor": {
          "nullable": true,
          "description": "Pagination cursor for fetching the next page of results. This cursor is stateless and does not expire. However, because it uses search_after pagination, results may be inconsistent if the underlying data changes between requests (e.g., you may see duplicate or missing results if documents are added/removed).",
          "type": "string"
        },
        "pageSize": {
          "description": "Number of results to return per page",
          "type": "integer",
          "minimum": 1,
          "maximum": 1000
        },
        "project": {
          "description": "Details about any Project this search is associated with. If included will filter out podcasts related to the project.",
          "type": "object",
          "properties": {
            "projectId": {
              "type": "string",
              "description": "The Pod Engine Project ID"
            },
            "excludeIgnoredPodcasts": {
              "description": "If true, will not include any podcasts marked as ignored on this project in the results.",
              "type": "boolean"
            },
            "excludePodcastsOnList": {
              "description": "If true, will not include any podcasts on the project list in the results.",
              "type": "boolean"
            },
            "includePodcastRelevancy": {
              "description": "If the podcast relevancy should be included with the search results. Note that including this will limit your search results to 20 total and also slow down the search results response",
              "type": "boolean"
            },
            "excludeRelvancyValues": {
              "description": "If provided, will exclude podcasts with these relevancy values. Valid values are 1-5. Note this is a server side filter and will only work if includePodcastRelevancy is set.",
              "type": "array",
              "items": {
                "type": "number",
                "minimum": 1,
                "maximum": 5
              }
            },
            "forceRecalculateRelevancy": {
              "description": "If true, will force a recalculation of the podcast relevancy values for this search. Note this will slow down the search results response and should only be used if you believe the relevancy values are out of date.",
              "type": "boolean"
            }
          },
          "required": [
            "projectId"
          ],
          "additionalProperties": false
        },
        "projectId": {
          "description": "[DEPRECATED] Project ID to associate the search with",
          "type": "string"
        },
        "searchTerms": {
          "description": "Terms to search for in the episode title, the episode description, the transcript, or the parent podcast's title and description.",
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "searchTerm": {
                "type": "string",
                "minLength": 2,
                "description": "The text to look for. Wrapping it in single or double quotes forces a phrase match, the same as setting searchTermOptions.phraseMatch."
              },
              "searchType": {
                "type": "string",
                "enum": [
                  "text"
                ],
                "description": "How to match the term. 'text' runs keyword matching against each of the searchTargets."
              },
              "searchTargets": {
                "type": "array",
                "items": {
                  "type": "string",
                  "enum": [
                    "podcast-title",
                    "podcast-description",
                    "episode-title",
                    "episode-description",
                    "transcript"
                  ]
                },
                "description": "Which fields to match this term against. A result matches if the term hits any one of its targets, so listing several widens the search rather than narrowing it."
              },
              "searchTermOptions": {
                "default": {
                  "matchMode": "optional",
                  "phraseMatch": false,
                  "fuzzy": false
                },
                "type": "object",
                "properties": {
                  "matchMode": {
                    "default": "optional",
                    "description": "How this term combines with the others. 'optional' (the default) lets the term raise a result's relevance without being required, though a result still has to match at least one term. 'must' requires it. 'not' excludes anything it matches.",
                    "type": "string",
                    "enum": [
                      "must",
                      "not",
                      "optional"
                    ]
                  },
                  "phraseMatch": {
                    "description": "Whether to match the search term as a phrase",
                    "type": "boolean"
                  },
                  "slop": {
                    "description": "How many words apart tokens can appear in a document to be considered a match",
                    "type": "number"
                  },
                  "fuzzy": {
                    "description": "Whether to use fuzzy matching",
                    "type": "boolean"
                  }
                },
                "required": [
                  "matchMode"
                ],
                "additionalProperties": false,
                "description": "How this term is matched: whether it is required, treated as a phrase, or matched loosely."
              }
            },
            "required": [
              "searchTerm",
              "searchType",
              "searchTargets",
              "searchTermOptions"
            ],
            "additionalProperties": false
          }
        },
        "excludeItunesGenres": {
          "description": "iTunes genres to exclude from results",
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "excludePodcastIds": {
          "description": "Specific podcast IDs to exclude from results",
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "excludeEpisodeIds": {
          "description": "Specific episode IDs to exclude from results",
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "explicit": {
          "description": "Filter by explicit content rating",
          "type": "string",
          "enum": [
            "clean",
            "explicit"
          ]
        },
        "foundedSince": {
          "description": "Filter podcasts founded after this date"
        },
        "hasPrimaryContact": {
          "description": "Filter podcasts that have primary contact information",
          "type": "boolean"
        },
        "hasSocials": {
          "description": "Filter podcasts with specific social media presence from beehiiv, behance, bluesky, discord, facebook, github, instagram, linkedin, linktree, mastodon, medium, patreon, pinterest, reddit, snapchat, soundcloud, substack, threads, tiktok, tumblr, twitch, twitter, vimeo, whatsapp, youtube",
          "type": "array",
          "items": {
            "type": "string",
            "enum": [
              "beehiiv",
              "behance",
              "bluesky",
              "discord",
              "facebook",
              "github",
              "instagram",
              "linkedin",
              "linktree",
              "mastodon",
              "medium",
              "patreon",
              "pinterest",
              "reddit",
              "snapchat",
              "soundcloud",
              "substack",
              "threads",
              "tiktok",
              "tumblr",
              "twitch",
              "twitter",
              "vimeo",
              "whatsapp",
              "youtube"
            ]
          }
        },
        "includeItunesGenres": {
          "description": "iTunes genres to include in results",
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "includePodcastIds": {
          "description": "Specific podcast IDs to include in results",
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "languages": {
          "description": "Filter podcasts by language",
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "lastEpisodeDateSince": {
          "description": "Filter podcasts with episodes published after this date"
        },
        "maxTotalEpisodes": {
          "description": "Maximum number of total episodes",
          "type": "integer",
          "minimum": 1,
          "maximum": 9007199254740991
        },
        "minCastboxPlays": {
          "description": "Minimum number of Castbox plays",
          "type": "integer",
          "minimum": 1,
          "maximum": 9007199254740991
        },
        "minCastboxSubscribers": {
          "description": "Minimum number of Castbox subscribers",
          "type": "integer",
          "minimum": 1,
          "maximum": 9007199254740991
        },
        "minItunesRating": {
          "description": "Minimum iTunes rating score",
          "type": "number",
          "minimum": 0,
          "maximum": 5
        },
        "minItunesRatingCount": {
          "description": "Minimum number of iTunes ratings",
          "type": "integer",
          "minimum": 1,
          "maximum": 9007199254740991
        },
        "minSpotifyRating": {
          "description": "Minimum Spotify rating score",
          "type": "number",
          "minimum": 0,
          "maximum": 5
        },
        "minSpotifyRatingCount": {
          "description": "Minimum number of Spotify ratings",
          "type": "integer",
          "minimum": 1,
          "maximum": 9007199254740991
        },
        "minTotalEpisodes": {
          "description": "Minimum number of total episodes",
          "type": "integer",
          "minimum": 1,
          "maximum": 9007199254740991
        },
        "podcastCountries": {
          "description": "Filter podcasts by country of origin using ISO 3166-1 alpha-2 codes, for example us",
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "podcastHasGuests": {
          "description": "Filter podcasts that feature guests",
          "type": "boolean"
        },
        "podcastAuthorityScore": {
          "description": "Filter podcasts by their Authority Score",
          "type": "object",
          "properties": {
            "authorityScore": {
              "description": "Restrict results to podcasts whose overall Authority Score falls in this range. The overall score combines the quality, engagement, social and YouTube components; podcasts we have not scored are excluded. See https://www.podengine.ai/podcast-authority-score.",
              "type": "object",
              "properties": {
                "min": {
                  "nullable": true,
                  "description": "Inclusive lower bound for the overall Authority Score. Null or omitted leaves the range open at the bottom.",
                  "type": "number",
                  "minimum": 0,
                  "maximum": 100
                },
                "max": {
                  "nullable": true,
                  "description": "Inclusive upper bound for the overall Authority Score. Null or omitted leaves the range open at the top.",
                  "type": "number",
                  "minimum": 0,
                  "maximum": 100
                }
              },
              "required": [
                "min",
                "max"
              ],
              "additionalProperties": false
            },
            "qualityScore": {
              "description": "Restrict results by the quality component of the Authority Score, which rates how well the show is produced and maintained: release consistency, episode length, artwork, show notes, transcript coverage, contact details and RSS completeness. Podcasts we have not scored are excluded.",
              "type": "object",
              "properties": {
                "min": {
                  "nullable": true,
                  "description": "Inclusive lower bound for the quality score. Null or omitted leaves the range open at the bottom.",
                  "type": "number",
                  "minimum": 0,
                  "maximum": 100
                },
                "max": {
                  "nullable": true,
                  "description": "Inclusive upper bound for the quality score. Null or omitted leaves the range open at the top.",
                  "type": "number",
                  "minimum": 0,
                  "maximum": 100
                }
              },
              "required": [
                "min",
                "max"
              ],
              "additionalProperties": false
            },
            "engagementScore": {
              "description": "Restrict results by the engagement component of the Authority Score, which rates how strongly listeners respond to the show: review counts and ratings, recent Apple US chart position, and Castbox plays and subscribers. Podcasts we have not scored are excluded.",
              "type": "object",
              "properties": {
                "min": {
                  "nullable": true,
                  "description": "Inclusive lower bound for the engagement score. Null or omitted leaves the range open at the bottom.",
                  "type": "number",
                  "minimum": 0,
                  "maximum": 100
                },
                "max": {
                  "nullable": true,
                  "description": "Inclusive upper bound for the engagement score. Null or omitted leaves the range open at the top.",
                  "type": "number",
                  "minimum": 0,
                  "maximum": 100
                }
              },
              "required": [
                "min",
                "max"
              ],
              "additionalProperties": false
            },
            "socialScore": {
              "description": "Restrict results by the social component of the Authority Score, which rates follower counts across the show's linked Facebook, Instagram, LinkedIn, TikTok and Twitter accounts. Podcasts we have not scored are excluded.",
              "type": "object",
              "properties": {
                "min": {
                  "nullable": true,
                  "description": "Inclusive lower bound for the social score. Null or omitted leaves the range open at the bottom.",
                  "type": "number",
                  "minimum": 0,
                  "maximum": 100
                },
                "max": {
                  "nullable": true,
                  "description": "Inclusive upper bound for the social score. Null or omitted leaves the range open at the top.",
                  "type": "number",
                  "minimum": 0,
                  "maximum": 100
                }
              },
              "required": [
                "min",
                "max"
              ],
              "additionalProperties": false
            },
            "youtubeScore": {
              "description": "Restrict results by the YouTube component of the Authority Score, which rates the channel's subscribers, total views and most-viewed video. Podcasts we have not scored are excluded.",
              "type": "object",
              "properties": {
                "min": {
                  "nullable": true,
                  "description": "Inclusive lower bound for the YouTube score. Null or omitted leaves the range open at the bottom.",
                  "type": "number",
                  "minimum": 0,
                  "maximum": 100
                },
                "max": {
                  "nullable": true,
                  "description": "Inclusive upper bound for the YouTube score. Null or omitted leaves the range open at the top.",
                  "type": "number",
                  "minimum": 0,
                  "maximum": 100
                }
              },
              "required": [
                "min",
                "max"
              ],
              "additionalProperties": false
            }
          },
          "additionalProperties": false
        },
        "podcastAudienceEstimatedMonthlyListeners": {
          "description": "Filter by the published estimated monthly listeners. Bounds are inclusive; omit min or max for an open-ended range. Podcasts without an estimate are excluded. Estimates under 1,000 (bucket 0-1K) match as 1 to 999 even though they read 1,000. Example: { \"min\": 10000, \"max\": 100000 }.",
          "type": "object",
          "properties": {
            "min": {
              "description": "Inclusive minimum estimated monthly listeners",
              "type": "integer",
              "minimum": 0,
              "maximum": 9007199254740991
            },
            "max": {
              "description": "Inclusive maximum estimated monthly listeners",
              "type": "integer",
              "minimum": 0,
              "maximum": 9007199254740991
            }
          },
          "additionalProperties": false
        },
        "sortOrder": {
          "description": "Specify how episode results should be sorted",
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "field": {
                "type": "string",
                "enum": [
                  "relevance",
                  "recentActivity",
                  "title",
                  "appleReviews",
                  "spotifyReviews"
                ],
                "description": "Episode sort: relevance, publication date (recentActivity), title, Apple rating or Spotify rating. Audience, YouTube, authority and Castbox sorts are only available for podcast searches."
              },
              "direction": {
                "description": "Sort direction. Defaults to 'asc' for 'title' and 'desc' for every other field.",
                "type": "string",
                "enum": [
                  "asc",
                  "desc"
                ]
              },
              "nullOrder": {
                "description": "Where results that have no value for this field go. Defaults to 'last'. Ignored for 'relevance' and the podcast-only metrics.",
                "type": "string",
                "enum": [
                  "first",
                  "last"
                ]
              }
            },
            "required": [
              "field"
            ],
            "additionalProperties": false
          }
        },
        "includeMatchSnippet": {
          "description": "Return matchSnippet: the single best matching transcript passage, approximately 400 characters with <em> tags around matches. Requires a positive text term targeting transcript. Omitted when no transcript passage matches. When enabled, transcriptHighlights contains this same single passage; transcriptHighlightLength is ignored.",
          "type": "boolean"
        },
        "includeTranscriptSnippet": {
          "description": "Return the opening of each matching episode transcript alongside the result, as transcriptTextSnippet. Episodes with no transcript return null.",
          "type": "boolean"
        },
        "transcriptSnippetLength": {
          "description": "How many characters of the transcript includeTranscriptSnippet returns. Defaults to 500. This is the start of the transcript, not the part that matched — for the matching text, search the transcript target and set includeMatchSnippet to receive matchSnippet, or read transcriptHighlights.",
          "type": "integer",
          "minimum": 1,
          "maximum": 9007199254740991
        },
        "transcriptHighlightLength": {
          "description": "How many characters each highlighted transcript fragment contains when a search term targets the transcript. Defaults to 300.",
          "type": "integer",
          "minimum": 1,
          "maximum": 9007199254740991
        },
        "includeEpisodeIds": {
          "description": "Restrict results to these specific episode IDs",
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "publishedSince": {
          "description": "Only episodes published on or after this date (inclusive)"
        },
        "publishedBefore": {
          "description": "Only episodes published strictly before this date (exclusive). Combine with publishedSince for a bounded window, e.g. publishedSince=90 days ago + publishedBefore=30 days ago."
        },
        "hasTranscript": {
          "description": "Filter to episodes that have a transcript (true) or that do not (false)",
          "type": "boolean"
        },
        "episodeUpdatedSince": {
          "description": "Only episodes whose Pod Engine record changed on or after this date. This tracks our record, not the show's own updates, so use it to pick up everything that has changed since your last sync."
        },
        "episodeCreatedSince": {
          "description": "Only episodes added to Pod Engine on or after this date. An episode is added when we first index it, which can be well after it was published — use publishedSince to filter on the publication date instead."
        },
        "personFilters": {
          "description": "Filter episodes by the people on them. Entries are ANDed together, e.g. [{ \"name\": \"Elon Musk\", \"type\": \"guest\" }, { \"name\": \"Joe Rogan\", \"type\": \"host\" }] returns only episodes where Elon Musk appeared as a guest and Joe Rogan was a host. An entry with matchMode \"not\" instead excludes episodes featuring that person.",
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "name": {
                "type": "string",
                "minLength": 2,
                "description": "The name to match"
              },
              "matchMode": {
                "description": "'must' (default) only returns episodes matching this entry; 'not' excludes episodes matching this entry",
                "type": "string",
                "enum": [
                  "must",
                  "not"
                ]
              },
              "phraseMatch": {
                "description": "Whether to match the name as a phrase (all words, in order). Defaults to true; set false for a loose per-word match.",
                "type": "boolean"
              },
              "slop": {
                "description": "How many words apart tokens can appear in a name to still count as a phrase match",
                "type": "number"
              },
              "fuzzy": {
                "description": "Whether to tolerate close spellings. Uses per-word fuzzy matching with all words required, so it ignores word order and cannot be combined with slop.",
                "type": "boolean"
              },
              "type": {
                "type": "string",
                "enum": [
                  "guest",
                  "host"
                ],
                "description": "Whether the person must appear on the episode as a 'guest' or a 'host'"
              }
            },
            "required": [
              "name",
              "type"
            ],
            "additionalProperties": false
          }
        },
        "sponsorFilters": {
          "description": "Filter episodes by sponsor / advertiser name. Entries are ANDed together; an entry with matchMode \"not\" instead excludes episodes with that sponsor.",
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "name": {
                "type": "string",
                "minLength": 2,
                "description": "The name to match"
              },
              "matchMode": {
                "description": "'must' (default) only returns episodes matching this entry; 'not' excludes episodes matching this entry",
                "type": "string",
                "enum": [
                  "must",
                  "not"
                ]
              },
              "phraseMatch": {
                "description": "Whether to match the name as a phrase (all words, in order). Defaults to true; set false for a loose per-word match.",
                "type": "boolean"
              },
              "slop": {
                "description": "How many words apart tokens can appear in a name to still count as a phrase match",
                "type": "number"
              },
              "fuzzy": {
                "description": "Whether to tolerate close spellings. Uses per-word fuzzy matching with all words required, so it ignores word order and cannot be combined with slop.",
                "type": "boolean"
              }
            },
            "required": [
              "name"
            ],
            "additionalProperties": false
          }
        }
      },
      "additionalProperties": false,
      "description": "Original episode search options, including all positive and excluded terms. Episode pagination is ignored."
    },
    "pageSize": {
      "default": 5,
      "description": "Number of additional passages per page, excluding the best passage. Defaults to 5; maximum 20.",
      "type": "integer",
      "minimum": 1,
      "maximum": 20
    },
    "cursor": {
      "description": "Opaque passage cursor from the previous page. Restart without it after HTTP 409.",
      "type": "string",
      "maxLength": 256
    }
  },
  "required": [
    "searchOptions",
    "pageSize"
  ],
  "additionalProperties": false
}
```

**Response schema:**
```json
{
  "type": "object",
  "properties": {
    "status": {
      "type": "string",
      "enum": [
        "OK"
      ]
    },
    "data": {
      "type": "object",
      "properties": {
        "episodeId": {
          "type": "string",
          "format": "uuid",
          "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
          "description": "Episode identifier from an episode or transcript search result."
        },
        "bestPassage": {
          "nullable": true,
          "description": "The current best passage. Display separately from the additional passages.",
          "type": "object",
          "properties": {
            "id": {
              "type": "string",
              "description": "Stable within this query and transcript revision; not a timestamp."
            },
            "text": {
              "type": "string",
              "description": "A distinct sentence-based passage of approximately 400 characters with <em> highlights."
            }
          },
          "required": [
            "id",
            "text"
          ],
          "additionalProperties": false
        },
        "passages": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "description": "Stable within this query and transcript revision; not a timestamp."
              },
              "text": {
                "type": "string",
                "description": "A distinct sentence-based passage of approximately 400 characters with <em> highlights."
              }
            },
            "required": [
              "id",
              "text"
            ],
            "additionalProperties": false
          },
          "description": "Additional passages in relevance order, excluding bestPassage."
        },
        "total": {
          "type": "object",
          "properties": {
            "value": {
              "type": "integer",
              "minimum": 0,
              "maximum": 9007199254740991
            },
            "relation": {
              "type": "string",
              "enum": [
                "eq",
                "gte"
              ]
            }
          },
          "required": [
            "value",
            "relation"
          ],
          "additionalProperties": false,
          "description": "Distinct matching passages, including bestPassage. eq is exact; gte is a lower bound."
        },
        "additionalCount": {
          "type": "integer",
          "minimum": 0,
          "maximum": 9007199254740991,
          "description": "Total minus the best passage; uses total.relation."
        },
        "hasMore": {
          "type": "boolean",
          "description": "More pages within the browsable set; independent of count truncation."
        },
        "nextCursor": {
          "nullable": true,
          "type": "string"
        },
        "truncated": {
          "type": "boolean",
          "description": "The passage cap was exceeded. Only the best 100 passages can be browsed."
        },
        "passageLimit": {
          "type": "number",
          "enum": [
            100
          ]
        }
      },
      "required": [
        "episodeId",
        "bestPassage",
        "passages",
        "total",
        "additionalCount",
        "hasMore",
        "nextCursor",
        "truncated",
        "passageLimit"
      ],
      "additionalProperties": false
    }
  },
  "required": [
    "status",
    "data"
  ],
  "additionalProperties": false
}
```

---

## See also

- All endpoints in one file: https://www.podengine.ai/api-docs/llms.txt
- Full reference with every schema inlined: https://www.podengine.ai/api-docs/llms-full.txt
- OpenAPI 3.1 spec: https://www.podengine.ai/api-docs/openapi.json
- Get an API key: https://www.podengine.ai/get-started
- Support: support@podengine.ai
