Skip to content
ThemeNord

Same themes as the MinusPod app

Default

Catppuccin

Dracula

Nord

Gruvbox

Solarized

Rose Pine

Kanagawa

Ayu

Other

Patterns

Cross-episode ad pattern management

List ad patterns

Returns all ad patterns from the cross-episode pattern database. Patterns are organized by scope (global, network, podcast) and can be filtered to show only active patterns.

  • scope (query, string, one of all, global, network, podcast): Filter by pattern scope
  • active_only (query, boolean): Only return active patterns. Accepts the legacy active query param as an alias.
  • podcast_id (query, string): Filter to patterns scoped to this podcast slug
  • network_id (query, string): Filter to patterns scoped to this network id
  • source (query, string, one of local, community, imported): Filter by pattern source
Status Description Body
200 List of patterns object

List unassigned legacy corrections

Candidates are current feed episodes or durable processing-history entries for an active feed. History candidates include their entry count and latest processing timestamp. They do not imply a current episode, transcript, or media file exists.

Status Description Body
200 Unassigned corrections and safe feed candidates object
  • count (integer): Total unassigned corrections, which may exceed the page returned
  • corrections (array of object)
  • corrections[].id (integer)
  • corrections[].episode_id (string)
  • corrections[].podcast_title (string or null)
  • corrections[].episode_title (string or null)
  • corrections[].correction_type (string)
  • corrections[].original_bounds (any): Parsed JSON when valid, otherwise the stored string
  • corrections[].corrected_bounds (any): Parsed JSON when valid, otherwise the stored string
  • corrections[].created_at (string or null)
  • corrections[].candidates (array of object)
  • corrections[].candidates[].slug (string)
  • corrections[].candidates[].podcast_title (string or null)
  • corrections[].candidates[].episode_title (string or null)
  • corrections[].candidates[].episode_available (boolean)
  • corrections[].candidates[].source (string, one of current, history)
  • corrections[].candidates[].history_run_count (integer or null)
  • corrections[].candidates[].history_latest_processed_at (string or null)

POST /patterns/corrections/unresolved/bulk

Section titled “POST /patterns/corrections/unresolved/bulk”

Assign or delete selected unassigned corrections

Updates up to 500 unique correction IDs in one transaction. Assignment requires one feed with current episode or processing-history evidence for every selected correction. Any stale or invalid row rejects the whole request without changes.

  • action (string, one of assign, delete, required)
  • correctionIds (array of integer, required)
  • slug (string): Required when action is assign.
  • confirm (boolean, one of true, required)
Status Description Body
200 All selected corrections updated object
400 Invalid action, confirmation, feed, or correction IDs Error
409 A selected correction changed or lacks feed evidence Error
  • action (string, one of assign, delete, required)
  • correctionIds (array of integer, required)

POST /patterns/corrections/{correction_id}/assign

Section titled “POST /patterns/corrections/{correction_id}/assign”

Assign an unassigned correction to a feed

  • correction_id (path, integer, required)
  • slug (string, required)
  • confirm (constant true, required)
Status Description Body
200 Correction assigned after explicit confirmation object
400 Bad request - invalid input Error
404 Resource not found Error
409 Correction was already assigned
  • assigned (constant true, required)
  • correctionId (integer, required)
  • slug (string, required)

DELETE /patterns/corrections/{correction_id}

Section titled “DELETE /patterns/corrections/{correction_id}”

Delete one unassigned correction

Deletes only a correction whose feed is still unassigned. It never changes patterns, processing history, media, or feed records.

  • correction_id (path, integer, required)
Status Description Body
200 Unassigned correction deleted object
404 Correction not found
409 Correction is already assigned
  • deleted (constant true, required)
  • correctionId (integer, required)

Get a single pattern

Returns details for a specific ad pattern

  • pattern_id (path, integer, required): Pattern identifier (integer)
Status Description Body
200 Pattern details Pattern
404 Resource not found Error

Update a pattern

Update pattern properties like active status, sponsor, category, or disabled reason

  • pattern_id (path, integer, required): Pattern identifier (integer)
  • is_active (boolean): Whether the pattern is active. False stamps disabled_at (an already disabled pattern keeps its original time); true clears disabled_at, and disabled_reason too unless the request sends one.
  • sponsor (string): Sponsor name for this pattern
  • disabled_reason (string): Reason for disabling the pattern
  • category (string or null, one of sponsor, cross_promo, self_promo, interaction, intro, outro, recap, null): Segment category for this pattern’s matches; each feed’s segment actions map the category to remove, beep, keep, or mark. Null clears the category, and an uncategorized pattern resolves as sponsor. An unrecognized value is rejected with 400.
Status Description Body
200 Pattern updated object
400 Bad request - invalid input Error
404 Resource not found Error
  • message (string)
  • pattern (Pattern)

Delete a pattern

Permanently delete an ad pattern from the database

  • pattern_id (path, integer, required): Pattern identifier (integer)
Status Description Body
200 Pattern deleted successfully object
404 Resource not found Error
  • message (string)

Backfill false positive texts

Backfill transcript text for existing false positive corrections that were created before cross-episode matching was enabled. This extracts the transcript text from each correction’s original boundaries and stores it for future cross-episode matching.

Status Description Body
200 Backfill completed object
500 Internal server error Error
  • message (string)
  • updated (integer): Number of corrections updated with transcript text
  • skipped (integer): Number of corrections skipped (missing transcript, too short)

Deduplicate patterns

Manually trigger deduplication of similar patterns in the database

Status Description Body
200 Deduplication completed object
500 Internal server error Error
  • message (string)
  • removed_count (integer): Number of duplicate patterns removed

Export patterns

Export all patterns as JSON for backup or transfer

  • include_corrections (query, boolean): Attach each pattern’s pattern_corrections rows as corrections. Rows are exported whole, including origin (user or auto_pass2).
Status Description Body
200 Exported patterns object
  • exportedAt (string (date-time))
  • version (string)
  • patterns (array of Pattern)

Import patterns

Import patterns from a JSON export file

  • patterns (array of Pattern, required)
  • merge (boolean): Merge with existing patterns (true) or replace all (false) Default true.
Status Description Body
200 Patterns imported object
  • message (string)
  • imported (integer)
  • skipped (integer)

Get pattern statistics

Returns aggregate statistics about the pattern database including counts by scope, stale patterns, patterns without sponsors, and patterns with high false positive rates.

Status Description Body
200 Pattern statistics object
  • total (integer)
  • active (integer)
  • inactive (integer)
  • by_scope (object)
  • by_scope.global (integer)
  • by_scope.network (integer)
  • by_scope.podcast (integer)
  • no_sponsor (integer): Patterns without a sponsor name
  • never_matched (integer): Patterns that have never been confirmed
  • stale_count (integer): Patterns not matched in 30+ days
  • high_false_positive_count (integer): Patterns with more false positives than confirmations
  • stale_patterns (array of object)
  • stale_patterns[].id (integer)
  • stale_patterns[].sponsor (string)
  • stale_patterns[].last_matched_at (string)
  • stale_patterns[].confirmation_count (integer)
  • no_sponsor_patterns (array of object)
  • no_sponsor_patterns[].id (integer)
  • no_sponsor_patterns[].scope (string)
  • no_sponsor_patterns[].podcast_name (string or null)
  • no_sponsor_patterns[].created_at (string)
  • no_sponsor_patterns[].text_preview (string)
  • high_false_positive_patterns (array of object)
  • high_false_positive_patterns[].id (integer)
  • high_false_positive_patterns[].sponsor (string)
  • high_false_positive_patterns[].confirmation_count (integer)
  • high_false_positive_patterns[].false_positive_count (integer)

Check pattern health

Identify oversized patterns that are too large to match effectively. Returns patterns exceeding character thresholds, likely containing multiple merged ads.

Status Description Body
200 Pattern health report object
  • total_patterns (integer)
  • healthy (integer)
  • issues_count (integer)
  • critical_count (integer): Patterns over 3500 chars
  • warning_count (integer): Patterns over 2500 chars
  • issues (array of object)
  • issues[].id (integer)
  • issues[].sponsor (string)
  • issues[].podcast_id (string or null)
  • issues[].podcast_name (string or null)
  • issues[].template_len (integer)
  • issues[].confirmation_count (integer)
  • issues[].severity (string, one of warning, critical)
  • issues[].issue (string)
  • issues[].recommendation (string, one of review, delete)

Find contaminated patterns

Scan all active patterns for those containing multiple ad transition phrases, indicating they may contain merged multi-sponsor ads that should be split.

Status Description Body
200 List of contaminated patterns object
  • count (integer): Number of contaminated patterns found
  • patterns (array of object)
  • patterns[].id (integer)
  • patterns[].sponsor (string)
  • patterns[].podcast_id (string)
  • patterns[].text_length (integer)
  • patterns[].transition_count (integer)
  • patterns[].scope (string)

Split a contaminated pattern

Split a pattern containing multiple ad transition phrases into separate single-sponsor patterns. The original pattern is disabled and new patterns are created for each detected sponsor segment.

  • pattern_id (path, integer, required): ID of the pattern to split
Status Description Body
200 Pattern successfully split object
400 Pattern does not need splitting or not found Error
  • success (boolean)
  • original_pattern_id (integer)
  • new_pattern_ids (array of integer)
  • message (string)

Suggested pattern merges

Same-sponsor near-duplicate pattern clusters that could fold into one row. Clusters are precomputed and cached server-side (keyed on each sponsor group’s text signature). Patterns without a sponsor are never clustered.

Status Description Body
200 Cluster suggestions object
500 Internal server error Error
  • suggestions (array of object)
  • suggestions[].sponsor_id (integer or null)
  • suggestions[].sponsor (string or null)
  • suggestions[].suggested_keep_id (integer): Keep-target chosen by the tiebreaker
  • suggestions[].pattern_ids (array of integer)
  • suggestions[].count (integer)
  • suggestions[].result_intro_variant_count (integer)
  • suggestions[].result_outro_variant_count (integer)

Merge patterns

Fold patterns into a single kept pattern. The kept pattern keeps its own text_template (so its audio fingerprint stays valid) and receives the combined confirmation and false-positive counts plus the unioned intro/outro variants (deduped, capped at five per side). Corrections are reassigned, and the merged rows and their audio fingerprints are deleted. All patterns must share the kept pattern’s sponsor (cross-sponsor merges are rejected). Merging reads below 75% similarity is allowed but returns an advisory warning.

  • keep_id (integer, required): ID of the pattern to keep
  • merge_ids (array of integer, required): IDs of patterns to merge into the kept pattern
Status Description Body
200 Patterns merged successfully object
400 Bad request - invalid input Error
404 Resource not found Error
500 Internal server error Error
  • message (string)
  • kept_pattern_id (integer)
  • merged_count (integer)
  • total_confirmations (integer)
  • total_false_positives (integer)
  • intro_variant_count (integer)
  • outro_variant_count (integer)
  • warning (string): Present when a merged pair is below 75% similar

Get pattern cleanup status and settings

Scheduled LLM review of learned ad patterns (Experiments). Combines the six configurable settings with live state: whether a run currently holds the lock, the last run’s timestamp/error/summary, and the pending suggestion counts by kind.

Status Description Body
200 Pattern cleanup status PatternCleanupStatus

Run pattern cleanup now

Starts a cleanup run on a background thread and returns immediately with the new run’s id. Each run processes a configured batch. force resets review history used for eligibility and replaces pending suggestions for every active learned pattern; approved and rejected decision records remain. Later normal runs continue the remaining batches. Calling force again restarts this process. A missing body means force=false. A supplied body must be a JSON object and force must be a JSON boolean. Rate limited to 6/hour.

  • force (boolean): Default false.
Status Description Body
202 Run started object
400 Malformed JSON, non-object request body, or non-boolean force Error
409 A cleanup run is already in progress (error: cleanup_in_progress) Error
429 Rate limit exceeded Error
  • runId (integer)

List recent pattern cleanup runs

  • limit (query, integer)
Status Description Body
200 Recent runs, newest first object

List pattern cleanup suggestions

Each suggestion carries the current pattern summary (sponsor, scope, podcast title, counts), the proposed payload, and an immutable before snapshot from review time. Prefer before_id (the smallest id already seen) over offset to page to older decisions, including approved suggestions available for undo: a keyset cursor is not disturbed by rows that leave the result set between pages, where offset can skip or repeat rows. before_id takes precedence over offset when both are given.

  • status (query, string, one of pending, approved, rejected, undone)
  • kind (query, string, one of trim, split, rename, retire, flag, category)
  • limit (query, integer)
  • offset (query, integer)
  • before_id (query, integer): Keyset cursor; only suggestions with id below this are returned.
Status Description Body
200 Suggestions, newest first object
400 Unknown status or kind filter Error

POST /patterns/cleanup/suggestions/{suggestion_id}/approve

Section titled “POST /patterns/cleanup/suggestions/{suggestion_id}/approve”

Approve a pattern cleanup suggestion

Applies the change in place; the pattern keeps a before snapshot so it can be undone.

  • suggestion_id (path, integer, required)
Status Description Body
200 Updated suggestion PatternCleanupSuggestion
404 Suggestion not found Error
409 The suggestion or its pattern does not allow this action (error: invalid_transition) Error

POST /patterns/cleanup/suggestions/{suggestion_id}/reject

Section titled “POST /patterns/cleanup/suggestions/{suggestion_id}/reject”

Reject a pattern cleanup suggestion

Leaves the pattern alone and acknowledges the reviewed version and statistical evidence. Returns a conflict when the pattern changed after the suggestion was made; unseen newer text is not marked reviewed.

  • suggestion_id (path, integer, required)
Status Description Body
200 Updated suggestion PatternCleanupSuggestion
404 Suggestion not found Error
409 The suggestion does not allow this action (error: invalid_transition) Error

POST /patterns/cleanup/suggestions/{suggestion_id}/undo

Section titled “POST /patterns/cleanup/suggestions/{suggestion_id}/undo”

Undo an approved pattern cleanup suggestion

Restores only fields owned by the approval, using its before and post-approval snapshots. Split undo restores the original pattern and disables its unchanged children.

  • suggestion_id (path, integer, required)
Status Description Body
200 Updated suggestion PatternCleanupSuggestion
404 Suggestion not found Error
409 Refused when the suggestion is not approved, a later approval on the same pattern has not been undone, owned fields or split children changed afterward, or an older approval lacks enough saved state for safe undo (error: invalid_transition) Error

Approve or reject several pattern cleanup suggestions

  • ids (array of integer, required)
  • action (string, one of approve, reject, required)
Status Description Body
200 Per-id results; a failed id carries an error instead of status object
400 Malformed ids or action Error
  • results (array of object)
  • results[].id (integer)
  • results[].status (string, one of approved, rejected)
  • results[].error (string, one of not_found, invalid_transition)

List ad detections across all feeds

Flattens every episode’s ad markers into one list for the Ad Review tab, with a computed status and resolution per detection. Detections are resolved by POSTing to the existing per-episode corrections endpoint.

  • page (query, integer)
  • limit (query, integer)
  • status (query, string, one of needs_review, pending, rejected, accepted, all): needs_review returns pending-review and rejected detections that have no matching correction yet.
  • feed (query, string): Filter to a single feed by slug
  • q (query, string): Case-insensitive match against sponsor and reason
  • sort (query, string, one of date, confidence, podcast)
  • order (query, string, one of asc, desc)
  • category (query, string, one of sponsor, cross_promo, self_promo, interaction, intro, outro, recap, none): Filter by segment category. Use none for markers no detection stage classified. Omit for every category.
  • reviewer (query, string, one of adjusted, unadjusted): Filter by whether the span was moved (see ReviewDetection.reviewerMoved). adjusted keeps moved rows; unadjusted keeps everything else. Omit for no filter. Applies to the cutSummary totals as well.
  • holdReason (query, string, one of max_duration, no_cue_evidence, uncorroborated_tail, reviewer_contradiction, reviewer_boundary_conflict, reviewer_inconclusive_bounds, estimated_pattern_bounds, reviewer_reject_conflict, reviewer_failed, no_splice_evidence, verification_miss, verification_kept_conflict, differential_uncorroborated, large_vad_gap_extension, cue_template_unproven, cue_low_confidence, no_transcript_evidence, transcript_differential_unreviewed): Keep only pending-review detections held for this reason. Omit for no filter.
Status Description Body
200 Paginated detection list object
400 Bad request - invalid input Error
  • detections (array of ReviewDetection)
  • total (integer)
  • page (integer)
  • totalPages (integer)
  • limit (integer)
  • counts (object): Pre-filter overview counts across all detections
  • counts.total (integer)
  • counts.needsReview (integer)
  • counts.pending (integer)
  • counts.rejected (integer)
  • counts.accepted (integer)
  • counts.confirmed (integer)
  • counts.dismissed (integer)
  • counts.pendingByHoldReason (map of integer): Pending-review detections per hold reason. Counts follow only the feed and status filters; the q, reviewer, holdReason and category filters do not narrow them. Holds with no recorded reason are not counted.
  • cutSummary (object): Totals over detections that were cut. Reflects the status, feed, and q filters but not the category filter, so the byCategory breakdown stays complete while the podcast selector still narrows it.
  • cutSummary.count (integer)
  • cutSummary.durationSeconds (number)
  • cutSummary.byCategory (map of integer): Count per segment category, plus a none key for markers with no category set.
  • cutSummary.distinctSponsors (integer)
  • cutSummary.distinctPodcasts (integer)

Bulk-delete patterns

Hard-delete patterns by id or by source filter. Both confirm: true and expected_count: N are required; the call is rejected if the actual matched count does not equal expected_count.

  • ids (array of integer)
  • source (string, one of local, community, imported)
  • confirm (boolean, required)
  • expected_count (integer, required)
Status Description Body
200 Deletion summary object
400 Validation failure
  • deleted (integer)
  • ids (array of integer)

Bulk-disable patterns

Same shape as bulk-delete; sets is_active=0 instead.

BulkPatternOp

Status Description Body
200 Disable summary
400 Validation failure

Dry-run the community export pipeline for a list of pattern ids

Returns which patterns would pass the quality gates and which would be rejected, with per-id reasons. Used by the UI to show a preview before the user builds the actual submission bundle.

  • ids (array of integer, required)
Status Description Body
200 Preview result object
400 ids list missing or empty
  • ready (array of integer)
  • rejected (array of object)
  • rejected[].id (integer)
  • rejected[].sponsor (string or null)
  • rejected[].reasons (array of string)
  • ready_count (integer)
  • rejected_count (integer)
  • pattern_count (integer)

Build a downloadable community-submission bundle

Runs the export pipeline on each id and returns a single JSON file (Content-Disposition: attachment) containing every pattern that passed quality gates. The file is the artifact the contributor commits into patterns/community/ to open one PR for all the selected patterns. The PR-side validator and the manifest builder both handle the bundle format natively, so the maintainer does not need to split it after merge.

  • ids (array of integer, required)
Status Description Body
200 Bundle JSON file (downloaded as attachment) object
400 ids missing, or every pattern failed quality gates
  • format (string)
  • bundle_version (integer)
  • submitted_at (string (date-time))
  • submitted_app_version (string)
  • pattern_count (integer)
  • patterns (array of object)

POST /patterns/{pattern_id}/submit-to-community

Section titled “POST /patterns/{pattern_id}/submit-to-community”

Submit a local pattern to the community repository

Runs the community export pipeline: quality gates, tag validation, PII strip, metadata strip, sponsor classification, and returns a prefilled GitHub PR URL plus the JSON payload. When too_large is true the URL exceeds GitHub’s limit and the client should offer the JSON file as a download instead.

  • pattern_id (path, integer, required)
Status Description Body
200 Export result object
400 Pattern failed export gates
  • payload (object)
  • filename (string)
  • pr_url (string)
  • too_large (boolean)
  • sponsor_match (string, one of exact, alias, fuzzy, unknown)

Mark a community pattern as protected from sync

  • pattern_id (path, integer, required)
Status Description Body
200 Updated protection state
400 Pattern is not source=community

Clear the protected_from_sync flag on a community pattern

  • pattern_id (path, integer, required)
Status Description Body
200 Updated protection state

Get the canonical community-pattern tag vocabulary

Returns the fixed tag vocabulary used by the pattern and sponsor tag pickers: the full tag list plus the podcast-genre, sponsor-industry, and special-tag groupings. The vocabulary ships with the app image and only changes when vocabulary_version is bumped.

Status Description Body
200 Tag vocabulary object
  • vocabulary_version (integer)
  • all_tags (array of string)
  • podcast_genres (array of object)
  • podcast_genres[].tag (string)
  • podcast_genres[].description (string)
  • sponsor_industries (array of object)
  • sponsor_industries[].tag (string)
  • sponsor_industries[].description (string)
  • special_tags (array of object)
  • special_tags[].tag (string)
  • special_tags[].description (string)