Skip to content
ThemeNord

Same themes as the MinusPod app

Default

Catppuccin

Dracula

Nord

Gruvbox

Solarized

Rose Pine

Kanagawa

Ayu

Other

Episodes

Episode data and transcripts

GET /episodes/{slug}/{episode_id}/chapter-images/{filename}

Section titled “GET /episodes/{slug}/{episode_id}/chapter-images/{filename}”

Get a local embedded chapter image

Serves extracted JPEG or PNG bytes without a remote fetch. When feed authentication is enabled, requires a valid global or scoped subscriber key. Served chapters JSON includes the supplied key on local image URLs. HEAD is also supported. Responses prevent content sniffing and allow cross-origin podcast players.

  • slug (path, string, required)
  • episode_id (path, string, required)
  • filename (path, string, required)
  • key (query, string): Required when feed authentication is enabled
Status Description Body
200 Stored chapter image image/jpeg, image/png
401 Missing or invalid feed key
404 Missing, invalid or unsupported chapter image

List episodes for a feed

Returns paginated list of episodes for a podcast. For the recents feed the rows come from the source feeds (see feedSlug and feedTitle); they are all processed, so a status filter other than processed returns an empty page. Title search applies before pagination. With selection=true, pagination is ignored and selection contains every matching episode’s action state, up to 501 rows (truncated is true past that cap); episodes is empty. Bulk actions still accept at most 500 IDs.

  • slug (path, string, required): Podcast feed slug identifier
  • status (query, string, one of all, discovered, pending, processing, processed, failed, permanently_failed, deferred, filtered): Filter by episode status. filtered lists the episodes the feed’s title, description, and duration filters match.
  • limit (query, integer): Maximum number of episodes to return
  • offset (query, integer): Number of episodes to skip
  • sort_by (query, string, one of published_at, created_at, episode_number, title, status): Field to sort episodes by
  • sort_dir (query, string, one of asc, desc): Sort direction
  • search (query, string): Case-insensitive literal title substring, trimmed before matching. Searches the full feed, including Recents; wildcard characters are literal.
  • selection (query, boolean): Return matching action-state rows in selection and an empty episodes array, capped at 501 rows (see truncated). Ignores limit and offset for selecting rows; limit still reports the normal capped request value and offset is 0.
Status Description Body
200 List of episodes object
404 Resource not found Error
  • episodes (array of EpisodeSummary)
  • selection (array of EpisodeSelection): Present only when selection=true; every matching episode without pagination, up to 501 rows.
  • total (integer)
  • limit (integer)
  • offset (integer)
  • truncated (boolean): Present only when selection=true; true when more than 501 episodes matched and selection was capped.

Get episode details

Returns detailed information about an episode including ad markers

  • slug (path, string, required): Podcast feed slug identifier
  • episode_id (path, string, required): Episode identifier: a 12-character MD5-hex string for a subscribed feed’s episode, or an sNNeNN id (e.g. s01e05) for a local feed’s.
Status Description Body
200 Episode details EpisodeDetail
404 Resource not found Error

GET /feeds/{slug}/episodes/{episode_id}/artwork

Section titled “GET /feeds/{slug}/episodes/{episode_id}/artwork”

Get episode artwork

Returns the episode’s cover, fetching and caching it from the publisher on first request. Publishers block images requested with a cross-site Referer, so the web UI asks here instead of loading them from the publisher directly. The URL comes from the episode record, never from the caller. Redirects to the feed cover when the episode has no cover of its own or the publisher refuses the fetch.

  • slug (path, string, required): Podcast feed slug identifier
  • episode_id (path, string, required): Episode identifier: a 12-character MD5-hex string for a subscribed feed’s episode, or an sNNeNN id (e.g. s01e05) for a local feed’s.
Status Description Body
200 Artwork image image/jpeg, image/png
302 No episode cover available; redirects to the feed cover
400 Bad request - invalid input Error

GET /feeds/{slug}/episodes/{episode_id}/transcript

Section titled “GET /feeds/{slug}/episodes/{episode_id}/transcript”

Get episode transcript

Returns the full transcript text for an episode

  • slug (path, string, required): Podcast feed slug identifier
  • episode_id (path, string, required): Episode identifier: a 12-character MD5-hex string for a subscribed feed’s episode, or an sNNeNN id (e.g. s01e05) for a local feed’s.
Status Description Body
200 Episode transcript object
404 Resource not found Error
  • episodeId (string)
  • transcript (string)

GET /feeds/{slug}/episodes/{episode_id}/original-transcript

Section titled “GET /feeds/{slug}/episodes/{episode_id}/original-transcript”

Get original (pre-cut) transcript

Returns the original transcript text captured before ad segments were removed. Only available for episodes processed with v1.0.51+.

  • slug (path, string, required): Podcast feed slug identifier
  • episode_id (path, string, required): Episode identifier: a 12-character MD5-hex string for a subscribed feed’s episode, or an sNNeNN id (e.g. s01e05) for a local feed’s.
Status Description Body
200 Original transcript object
404 Resource not found Error
  • episodeId (string)
  • originalTranscript (string)

GET /feeds/{slug}/episodes/{episode_id}/original-segments

Section titled “GET /feeds/{slug}/episodes/{episode_id}/original-segments”

Get original (pre-cut) transcript segments

Returns the original transcript segments with timestamps, captured before any ads were removed. Only available for episodes processed with v2.0.26+.

  • slug (path, string, required): Podcast feed slug identifier
  • episode_id (path, string, required): Episode identifier: a 12-character MD5-hex string for a subscribed feed’s episode, or an sNNeNN id (e.g. s01e05) for a local feed’s.
Status Description Body
200 Original segments object
404 Resource not found Error
  • episodeId (string)
  • segments (array of object)
  • segments[].start (number)
  • segments[].end (number)
  • segments[].text (string)

GET /feeds/{slug}/episodes/{episode_id}/final-segments

Section titled “GET /feeds/{slug}/episodes/{episode_id}/final-segments”

Get final (post-cut) segments

Returns the post-cut transcript segments with adjusted timestamps after ad removal. Only available for episodes processed with v2.0.26+.

  • slug (path, string, required): Podcast feed slug identifier
  • episode_id (path, string, required): Episode identifier: a 12-character MD5-hex string for a subscribed feed’s episode, or an sNNeNN id (e.g. s01e05) for a local feed’s.
Status Description Body
200 Final segments object
404 Resource not found Error
  • episodeId (string)
  • segments (array of object)
  • segments[].start (number)
  • segments[].end (number)
  • segments[].text (string)

GET /feeds/{slug}/episodes/{episode_id}/runs/{run_number}/log

Section titled “GET /feeds/{slug}/episodes/{episode_id}/runs/{run_number}/log”

Pipeline log for one processing run

Returns the captured pipeline log for a single processing run, either as parsed JSONL lines or as the raw file. Logs are stored when the feed opts in and the retention setting is above zero; the 404 body distinguishes a run that stored no log from one whose log the retention sweep removed.

  • slug (path, string, required): Podcast feed slug identifier
  • episode_id (path, string, required): Episode identifier: a 12-character MD5-hex string for a subscribed feed’s episode, or an sNNeNN id (e.g. s01e05) for a local feed’s.
  • run_number (path, integer, required): Run number, matching processingRuns[].runNumber
  • format (query, string, one of json, raw): json (default) or raw for the JSONL file as a download
  • level (query, string, one of debug, info, warning, error, critical): Minimum level to include; json format only. Levels the server does not know rank above every filter, so they are always returned.
Status Description Body
200 Run log object
400 Bad request - invalid input Error
404 No log for this run object
  • runNumber (integer)
  • truncated (boolean): True when the run hit the size cap
  • bytes (integer)
  • lines (array of object)
  • lines[].ts (string (date-time))
  • lines[].level (string)
  • lines[].logger (string)
  • lines[].msg (string)
  • error (string)
  • status (integer)
  • code (string, one of log_not_stored, log_pruned)

GET /feeds/{slug}/episodes/{episode_id}/peaks

Section titled “GET /feeds/{slug}/episodes/{episode_id}/peaks”

Waveform peaks for an episode window

Returns ffmpeg-derived waveform peaks for the requested window of an episode’s retained original audio. Drives the wavesurfer.js view in the ad editor. The server auto-coarsens the resolution for very long windows so the JSON payload stays bounded; the response echoes the effective resolution actually used.

  • slug (path, string, required): Podcast feed slug identifier
  • episode_id (path, string, required): Episode identifier: a 12-character MD5-hex string for a subscribed feed’s episode, or an sNNeNN id (e.g. s01e05) for a local feed’s.
  • start (query, number): Window start in seconds (default 0)
  • end (query, number): Window end in seconds (default = full duration)
  • resolution_ms (query, integer): Requested peak bucket width in ms (default 50, range 1-1000)
Status Description Body
200 Peaks payload object
400 Bad request - invalid input Error
404 Resource not found Error
  • episodeId (string)
  • start (number)
  • end (number or null)
  • resolutionMs (integer)
  • peaks (array of number)

GET /feeds/{slug}/episodes/{episode_id}/transcript-span

Section titled “GET /feeds/{slug}/episodes/{episode_id}/transcript-span”

Transcript text for a [start, end] window

Returns the transcript text that spans the requested window of an episode. Used by the AdEditor create mode to auto-populate the text_template field when the user marks a new ad.

  • slug (path, string, required): Podcast feed slug identifier
  • episode_id (path, string, required): Episode identifier: a 12-character MD5-hex string for a subscribed feed’s episode, or an sNNeNN id (e.g. s01e05) for a local feed’s.
  • start (query, number, required)
  • end (query, number, required)
Status Description Body
200 Transcript span object
400 Bad request - invalid input Error
404 Resource not found Error
  • episodeId (string)
  • start (number)
  • end (number)
  • text (string)

GET /feeds/{slug}/episodes/{episode_id}/split-candidates

Section titled “GET /feeds/{slug}/episodes/{episode_id}/split-candidates”

Proposed divider points for a multi-sponsor ad marker

Finds ad-transition phrases in the transcript spanning the requested window and maps each match back to the start of the transcript segment it falls in, so the ad editor opens with dividers where a split would actually cut (issue #563).

A window with no transition phrase returns no candidates and a single piece. That is a valid answer, not an error: the block may genuinely be one ad, and the user can still add a divider by hand.

Candidates that would leave a piece shorter than the minimum ad duration are omitted, so the editor never opens in an invalid state.

  • slug (path, string, required): Podcast feed slug identifier
  • episode_id (path, string, required): Episode identifier: a 12-character MD5-hex string for a subscribed feed’s episode, or an sNNeNN id (e.g. s01e05) for a local feed’s.
  • start (query, number, required)
  • end (query, number, required)
Status Description Body
200 Divider candidates and the pieces they would produce object
400 Bad request - invalid input Error
404 Resource not found Error
  • episodeId (string)
  • start (number)
  • end (number)
  • candidates (array of object)
  • candidates[].time (number)
  • candidates[].phrase (string): The transition phrase that produced this candidate
  • pieces (array of object)
  • pieces[].start (number)
  • pieces[].end (number)
  • pieces[].text (string)
  • pieces[].sponsor (string or null)

POST /episodes/{slug}/{episode_id}/reprocess

Section titled “POST /episodes/{slug}/{episode_id}/reprocess”

Force reprocess an episode

Queues an episode for reprocessing. Full and default reprocesses retain the saved transcript until fresh transcription begins. Supports four modes:

  • reprocess (default): Uses learned patterns from the pattern database plus AI analysis
  • full: Skips the pattern database entirely for a fresh AI-only analysis
  • llm: Re-runs ad detection and re-cut using the SAVED transcript, skipping re-transcription. Returns 400 if no transcript exists.

Reprocessed episodes are prioritized in the processing queue.

  • slug (path, string, required): Podcast feed slug identifier
  • episode_id (path, string, required): Episode identifier: a 12-character MD5-hex string for a subscribed feed’s episode, or an sNNeNN id (e.g. s01e05) for a local feed’s.
  • mode (string, one of reprocess, full, llm, recut): Processing mode: - reprocess: Use pattern DB + AI (leverages learned patterns) - full: AI only, skip pattern DB (fresh analysis) - llm: Re-detect + re-cut using the saved transcript (skips re-transcription); requires an existing transcript - recut: Re-cut the retained original from the current ad detections (applying edits) and re-time the saved transcript; no transcription or AI. Requires the retained original audio, saved segments, and existing ad markers Default reprocess.
Status Description Body
202 Reprocessing started, or queued behind the active run object
400 Bad request - invalid input Error
404 Resource not found Error
409 Episode is already processing Error plus object
500 Internal server error Error
  • message (string)
  • episodeId (string)
  • mode (string)
  • status (string, one of processing, queued)
  • reason (string): Present when status is queued; why it could not start immediately.
  • jobState (string, one of queued, processing): Authoritative queue/run state, derived the same way as the episode’s jobState field, so the client reconciles from one field.

Includes every field of Error.

  • jobState (string, one of processing)

POST /feeds/{slug}/episodes/{episode_id}/regenerate-chapters

Section titled “POST /feeds/{slug}/episodes/{episode_id}/regenerate-chapters”

Regenerate chapters from transcript

Regenerates chapters for an episode using the existing VTT transcript. Uses AI topic detection to identify natural chapter boundaries without requiring full reprocessing. The VTT transcript must already exist. The new chapters are also embedded into the processed MP3 as ID3 frames so players that ignore podcast:chapters pick them up. The work runs in the background because the topic pass can outlast a reverse proxy’s request timeout; the episode reports progress. It starts on demand outside the processing queue. Each finished attempt adds a processing-history entry with its own usage and a saved episode log when log storage is enabled, including failed attempts.

  • slug (path, string, required): Podcast feed slug identifier
  • episode_id (path, string, required): Episode identifier: a 12-character MD5-hex string for a subscribed feed’s episode, or an sNNeNN id (e.g. s01e05) for a local feed’s.
Status Description Body
202 Regeneration started in the background. Poll the episode; chaptersRegenerating clears when it finishes and chaptersRegenError carries a failure. object
400 No VTT transcript available (full reprocess required) Error
404 Resource not found Error
409 A regeneration is already running, the episode is being processed, or an LLM provider rate-limit hold is active
500 Internal server error Error
  • message (string)
  • episodeId (string)
  • status (string, one of started)

GET /feeds/{slug}/episodes/{episode_id}/sponsorblock/candidates

Section titled “GET /feeds/{slug}/episodes/{episode_id}/sponsorblock/candidates”

List segments reviewable for SponsorBlock

Lists the cut sponsor and self promotion segments of a YouTube episode that carry evidence beyond the model, with any recorded submission outcome. Segments already submitted are returned so they can be shown as sent. allowed is false, with a reason, when submission is not possible.

  • slug (path, string, required): Podcast feed slug identifier
  • episode_id (path, string, required): Episode identifier: a 12-character MD5-hex string for a subscribed feed’s episode, or an sNNeNN id (e.g. s01e05) for a local feed’s.
Status Description Body
200 Candidate segments object
404 Resource not found Error
  • allowed (boolean, required)
  • reason (string or null, one of not_youtube, not_completed, disabled, no_user_id, community_marks, no_video_id, marks_unverified, service_down, null, required): service_down means SponsorBlock failed its last availability probe.
  • segments (array of object, required)
  • segments[].start (number, required)
  • segments[].end (number, required)
  • segments[].category (string or null, required)
  • segments[].sbCategory (string, one of sponsor, selfpromo, required)
  • segments[].evidence (array of string, required)
  • segments[].submission (one of SponsorBlockSubmission, null, required)

POST /feeds/{slug}/episodes/{episode_id}/sponsorblock/submit

Section titled “POST /feeds/{slug}/episodes/{episode_id}/sponsorblock/submit”

Submit reviewed segments to SponsorBlock

Sends the segments a person selected to SponsorBlock. SponsorBlock forbids automated submissions, so this is the only way MinusPod sends anything. Each segment must match a candidate within 0.05 seconds and must not already be submitted. Outcomes are recorded on the episode. A duplicate or rejected result is a recorded outcome and returns 200. Rate limited to 10 per minute.

  • slug (path, string, required): Podcast feed slug identifier
  • episode_id (path, string, required): Episode identifier: a 12-character MD5-hex string for a subscribed feed’s episode, or an sNNeNN id (e.g. s01e05) for a local feed’s.
  • segments (array of object, required)
  • segments[].start (number, required)
  • segments[].end (number, required)
Status Description Body
200 Recorded outcomes, one per segment SponsorBlockSubmitResult
400 Bad request - invalid input Error
404 Resource not found Error
429 SponsorBlock rate limited the submission, or the endpoint limit was reached. The outcome is recorded. SponsorBlockSubmitResult
502 The request to SponsorBlock failed. The outcome is recorded. SponsorBlockSubmitResult

PUT /feeds/{slug}/episodes/{episode_id}/rating

Section titled “PUT /feeds/{slug}/episodes/{episode_id}/rating”

Rate an episode

Stores a 1 to 5 quality rating. Notes are trimmed, limited to 2000 characters and kept only below 5. A rating of 5 confirms every cut ad segment through the same path as a confirm correction, without changing the audio or queuing a recut. A 5 also dismisses every held segment without recording a correction.

  • slug (path, string, required): Podcast feed slug identifier
  • episode_id (path, string, required): Episode identifier: a 12-character MD5-hex string for a subscribed feed’s episode, or an sNNeNN id (e.g. s01e05) for a local feed’s.
  • rating (integer, required)
  • notes (string or null)
Status Description Body
200 The stored rating, how many cut segments were newly confirmed and how many held segments were dismissed object
400 Bad request - invalid input Error
404 Resource not found Error
  • rating (EpisodeRating, required)
  • confirmedMarkers (integer, required)
  • dismissedMarkers (integer, required)

DELETE /feeds/{slug}/episodes/{episode_id}/rating

Section titled “DELETE /feeds/{slug}/episodes/{episode_id}/rating”

Clear an episode rating

Clears the rating, notes and rating time. Confirmations already recorded stay.

  • slug (path, string, required): Podcast feed slug identifier
  • episode_id (path, string, required): Episode identifier: a 12-character MD5-hex string for a subscribed feed’s episode, or an sNNeNN id (e.g. s01e05) for a local feed’s.
Status Description Body
200 The rating is now null object
404 Resource not found Error
  • rating (null)

POST /feeds/{slug}/episodes/{episode_id}/retry-ad-detection

Section titled “POST /feeds/{slug}/episodes/{episode_id}/retry-ad-detection”

Retry ad detection

Re-runs ad detection using the existing transcript. Faster than full reprocess since it skips transcription. Use when ad detection failed due to API errors or when you want to try with updated prompts.

  • slug (path, string, required): Podcast feed slug identifier
  • episode_id (path, string, required): Episode identifier: a 12-character MD5-hex string for a subscribed feed’s episode, or an sNNeNN id (e.g. s01e05) for a local feed’s.
Status Description Body
200 Ad detection retry completed object
400 Episode has no transcript (must use reprocess instead) Error
404 Resource not found Error
500 Internal server error Error
  • message (string)
  • adsDetected (integer)
  • status (string)

GET /feeds/{slug}/episodes/{episode_id}/original.mp3

Section titled “GET /feeds/{slug}/episodes/{episode_id}/original.mp3”

Stream the pre-cut (original) audio

Returns the untouched audio that was downloaded before ad removal. Used by the Ad Editor’s Review mode so users can hear what was cut. 404 when the episode has no original retained (feature disabled in settings, pre-existing episode processed before the feature, or retention expired). Supports Range requests.

  • slug (path, string, required): Podcast feed slug identifier
  • episode_id (path, string, required): Episode identifier: a 12-character MD5-hex string for a subscribed feed’s episode, or an sNNeNN id (e.g. s01e05) for a local feed’s.
  • download (query, string, one of 1): When 1, the file is sent as an attachment named after the episode title (<Title>-original.mp3 or <Title>-cut.mp3) instead of streamed inline.
Status Description Body
200 Original audio stream audio/mpeg
404 Resource not found Error

GET /feeds/{slug}/episodes/{episode_id}/processed.mp3

Section titled “GET /feeds/{slug}/episodes/{episode_id}/processed.mp3”

Stream the current cut (ad-free) audio

Returns the current processed version of the episode under the session auth the UI already has. The public feed route serves the same file but needs a feed key when authenticated feeds are on. 404 when the episode has not been processed or the file is missing. Supports Range requests.

  • slug (path, string, required): Podcast feed slug identifier
  • episode_id (path, string, required): Episode identifier: a 12-character MD5-hex string for a subscribed feed’s episode, or an sNNeNN id (e.g. s01e05) for a local feed’s.
  • download (query, string, one of 1): When 1, the file is sent as an attachment named after the episode title (<Title>-original.mp3 or <Title>-cut.mp3) instead of streamed inline.
Status Description Body
200 Processed audio stream audio/mpeg
404 Resource not found Error

Episodes with review decisions not yet cut into the audio

Review is bulk work, so a correction that changes the audio stamps its episode instead of re-cutting it. An episode collecting several decisions is listed once and cut once.

  • slug (query, string): Limit the list to one feed’s episodes.
Status Description Body
200 Pending episodes, oldest stamp first object
  • count (integer)
  • episodes (array of object)
  • episodes[].slug (string)
  • episodes[].episodeId (string)
  • episodes[].title (string)
  • episodes[].podcast (string)
  • episodes[].pendingSince (string): When the episode first went stale.
  • episodes[].recutReady (boolean): An apply would queue this row now. False when the retained original audio, saved segments, or markers are missing (the decision then waits for a full reprocess) or while the episode’s own run is queued or underway.
  • episodes[].inFlight (boolean): The episode’s own queued or running pass will apply the decisions itself.

Recut every episode holding unapplied review decisions

Queues one recut per pending episode, using the same recut path and preconditions as the per-feed segment re-render. An episode missing its retained original audio, or whose own run is queued or underway (that run applies the decisions), is skipped and keeps its stamp, so its decisions are not lost.

  • slug (string): Recut only this feed’s pending episodes. Omit to cover every feed.
Status Description Body
200 How many recuts were queued and how many were skipped object
429 Rate limited (5 per minute)
  • queued (integer)
  • skipped (integer)

Episodes waiting for manual approval

Processed episodes held out of the served feed and audio until approved, oldest first, across feeds.

  • slug (query, string): Limit the list to one feed’s episodes.
  • limit (query, integer)
  • offset (query, integer)
Status Description Body
200 Awaiting episodes and the total count object
  • count (integer): Total waiting episodes, ignoring limit and offset.
  • episodes (array of object)
  • episodes[].slug (string)
  • episodes[].episodeId (string)
  • episodes[].title (string)
  • episodes[].podcast (string)
  • episodes[].pendingSince (string): When the episode began waiting for approval. Omitted from the held-detections list.
  • episodes[].adCount (integer)
  • episodes[].pendingReviewCount (integer)

Approve several waiting episodes

Approves the listed episodes, or every waiting episode of one feed when slug is sent instead of episodes. Each feed’s RSS is rebuilt once. An episode that is not waiting is counted as skipped.

  • episodes (array of object)
  • episodes[].slug (string, required)
  • episodes[].episodeId (string, required)
  • slug (string): Approve every waiting episode of this feed. Ignored when episodes is sent.
Status Description Body
200 How many episodes were approved and how many were skipped object
400 Bad request - invalid input Error
404 Resource not found Error
429 Rate limited (10 per minute)
  • approved (integer)
  • skipped (integer)
  • remaining (integer): Waiting episodes of the feed left after the 500 oldest were approved. 0 for an episodes request.

Episodes with detections held for review

Processed episodes with at least one detection held for human review, newest first, across feeds.

  • limit (query, integer)
  • offset (query, integer)
Status Description Body
200 Held episodes and the total count object
  • count (integer): Total episodes with held detections, ignoring limit and offset.
  • episodes (array of object)
  • episodes[].slug (string)
  • episodes[].episodeId (string)
  • episodes[].title (string)
  • episodes[].podcast (string)
  • episodes[].pendingSince (string): When the episode began waiting for approval. Omitted from the held-detections list.
  • episodes[].adCount (integer)
  • episodes[].pendingReviewCount (integer)

POST /feeds/{slug}/episodes/{episode_id}/approve

Section titled “POST /feeds/{slug}/episodes/{episode_id}/approve”

Approve a waiting episode

Clears the approval wait, stamps approvedAt, rebuilds the feed RSS and fires the Episode Processed event.

  • slug (path, string, required)
  • episode_id (path, string, required)
Status Description Body
200 The approved episode object
404 Resource not found Error
409 The episode is not awaiting approval, or does not exist Error

POST /episodes/{slug}/{episode_id}/corrections

Section titled “POST /episodes/{slug}/{episode_id}/corrections”

Submit ad corrections

Submit corrections for detected ads in an episode. This feedback improves the cross-episode pattern database for better future detection.

Actions:

  • confirm: Mark detection as correct (creates/updates pattern)
  • reject: Mark as false positive. Stores transcript text for cross-episode matching - similar text in future episodes of the same podcast will be automatically excluded using TF-IDF similarity matching (threshold: 0.75)
  • adjust: Correct ad boundaries. Also creates patterns from the adjusted boundaries (like confirm), ensuring accurate pattern text is learned from the corrected segment
  • create: Mark a brand-new ad on an episode the detector missed. Top-level start/end/sponsor/text_template required (no original_ad). Creates a new pattern with created_by=‘user’ and a pattern_corrections row of type ‘create’. If the text contains more than one ad transition phrase (a contaminated multi-sponsor read), it is split at those boundaries into one pattern per sponsor segment instead of a single oversized pattern; the response’s patternIds lists every pattern created or reused this way, and pattern_id/sponsor still identify the sponsor-matched primary.
  • split: Replace one marker covering back-to-back ads with N single-sponsor markers (issue #563). Send original_ad plus split_points (a list of times strictly inside the span) and an optional pieces array of {sponsor} overrides in piece order. Every resulting piece must be at least the minimum ad duration or the request is rejected and the marker is left untouched. A request whose original_ad matches no persisted marker returns 404. Pieces inherit the original’s category, detection stage, confidence, and cut state, so a split never silently changes whether the audio was removed. Correction rows use boundary_adjustment for the first piece and create for the rest: correction_type carries a CHECK constraint SQLite cannot alter in place. Response carries markerCount and patternIds.
  • slug (path, string, required): Podcast feed slug identifier
  • episode_id (path, string, required): Episode identifier: a 12-character MD5-hex string for a subscribed feed’s episode, or an sNNeNN id (e.g. s01e05) for a local feed’s.

AdCorrection

Status Description Body
200 Corrections submitted successfully object
400 Bad request - invalid input Error
404 Resource not found Error
409 Segment is kept for this feed; recategorize instead of confirm, reject, adjust, or split Error
  • message (string): Human-readable result, returned by every type
  • pattern_id (integer): Pattern created or updated; returned by confirm, adjust, and create
  • sponsor (string): Canonical sponsor name; returned by create
  • patternIds (array of integer): Patterns created or reused; returned by create and split
  • markerCount (integer): Number of markers that replaced the original; returned by split
  • category (string): New segment category; returned by recategorize
  • previousCategory (string): Segment category before the change; returned by recategorize

Bulk episode actions

Apply an action to multiple episodes at once. Rate limited to 5/min.

  • slug (path, string, required): Podcast feed slug identifier
  • episodeIds (array of string, required)
  • action (string, one of process, reprocess, reprocess_full, reprocess_llm, delete, required)
Status Description Body
200 Bulk action result object
404 Resource not found Error
  • queued (integer)
  • skipped (integer)
  • freedMb (number)
  • errors (array of string)
  • skippedEpisodes (array of object): Episodes skipped because their state changed or processing already owns them.
  • skippedEpisodes[].episodeId (string, required)
  • skippedEpisodes[].reason (string, required)
  • jobState (string, one of idle, queued): Present for process/reprocess/reprocess_full/reprocess_llm actions. Bulk enqueues never start processing synchronously, so this is queued whenever anything was queued, else idle. Not present for delete.

Set per-episode pass-through override

Set or clear the per-episode pass-through override (issue #746) for one or more episodes. An episode flagged this way is relayed with no transcription, ad detection, or cutting, even when the feed’s own processing mode is not passthrough; the audio may still be transcoded to MP3 when the publisher’s enclosure is not already MP3. Enabling enqueues a normal reprocess for each affected episode so the pass-through branch runs now; disabling only clears the flag. Repeated ids are deduplicated in request order, so each id appears once across accepted and rejected. Rate limited to 5/min.

  • slug (path, string, required): Podcast feed slug identifier
  • episodeIds (array of string, required)
  • enabled (boolean, required)
Status Description Body
200 Pass-through override result object
400 Malformed request. Returned when the body is not a JSON object, when episodeIds is missing or is not a non-empty array, when any episodeIds entry is not a non-empty string, when more than 500 ids are sent, or when enabled is missing or not a boolean. Error
404 Resource not found Error
  • updated (integer): Number of episodes whose passthroughEnabled flag was set
  • queued (integer): Episodes that actually hold a queue row after the call, counted from the queue itself rather than from the attempted inserts, so a swallowed enqueue failure is never reported as queued work. Always 0 when disabling.
  • accepted (array of string): Episode ids the override was applied to, in request order.
  • rejected (array of object): Episode ids the override was not applied to, each with its reason.
  • rejected[].episodeId (string, required)
  • rejected[].reason (string, one of not_found, processing, required): not_found means no episode with that id belongs to this feed. processing means a run already owns the episode and has resolved its mode, so enabling would not take effect this run; it is returned only when enabling, since disabling an owned episode is accepted.
  • jobState (string, one of idle, queued): Present only when enabled is true. queued when anything reached the queue, else idle. Disabling enqueues nothing, so the field is omitted entirely.