Skip to content

RSS API

Manages RSS feed cache invalidation for the RSS Feed Worker.

Base Path: /api/rss

Authentication: Requires Bearer token.

Endpoints

MethodPathAuthDescription
POST/invalidateYesInvalidate RSS cache for podcast

Invalidate RSS Cache

Triggers cache invalidation for a podcast's RSS feed.

POST /api/rss/invalidate

Request Body:

json
{
	"slug": "the-tech-show",
	"podcast_id": "uuid",
	"episode_id": "uuid",
	"reason": "episode.published",
	"immediate": true,
	"delay_seconds": 0
}

Only slug is required. delay_seconds (0-43200) defers the invalidation via Cloudflare Queues — used to schedule cache rebuilds at a known future moment (e.g. when an email-visibility window expires). When set, HTTP is bypassed; the queue is authoritative.

Response:

json
{
	"success": true,
	"method": "queue"
}

Invalidation Methods

MethodDescriptionWhen Used
queueMessage sent to invalidation queueProduction (Workers available)
httpDirect HTTP request to RSS WorkerDevelopment/fallback
skippedNo action takenQueue/worker unavailable

Automatic Invalidation

RSS cache is automatically invalidated when content that affects the feed changes. The reason field in the queue message identifies the trigger and is emitted by the helper in src/lib/utils/rss-invalidate.ts:

typescript
type InvalidationReason =
	| 'podcast.info.updated'
	| 'podcast.classification.updated'
	| 'podcast.status.updated'
	| 'podcast.image.updated'
	| 'podcast.ownership.updated'
	| 'podcast.advanced.updated'
	| 'podcast.funding.updated'
	| 'podcast.email_visibility.updated'
	| 'podcast.email_visibility.expired'
	| 'podcast.disclosure.updated'
	| 'episode.published'
	| 'episode.updated'
	| 'episode.deleted'
	| 'episode.credits.updated'
	| 'episode.soundbites.updated'
	| 'episode.disclosure.updated'
	| 'manual.invalidation';

The Podcast Import Executor worker additionally sends a single import.completed message from its finalisation path when a back-catalogue import closes its last item.

Queue Message Format

typescript
interface RssInvalidationMessage {
	type: string; // reason string, e.g. 'episode.published' or 'import.completed'
	podcast_id: string;
	podcast_slug: string;
	episode_id?: string;
	timestamp: string;
}

Cache Strategy

TypeScript Client Usage

typescript
import { createApiClient } from '$api/client';

const client = createApiClient(fetch);

// Invalidate RSS cache
const res = await client.api.rss.invalidate.$post(
	{
		json: {
			podcast_id: 'uuid',
			podcast_slug: 'the-tech-show'
		}
	},
	{
		headers: { Authorization: `Bearer ${token}` }
	}
);

Utility Functions

Helpers live in src/lib/utils/rss-invalidate.ts. They authenticate via the RSS_INVALIDATION_SECRET environment secret, so they can be called from +page.server.ts actions without a user token. All helpers are no-ops when hosting_type === 'external'.

typescript
// src/lib/utils/rss-invalidate.ts
export async function invalidateRssFeed(
	fetch: typeof globalThis.fetch,
	options: {
		slug: string;
		podcastId?: string;
		episodeId?: string;
		reason?: InvalidationReason;
		immediate?: boolean; // default true
		delaySeconds?: number; // max 43200 (12h)
	}
): Promise<{ success: boolean; method: 'queue' | 'http' | 'skipped'; error?: string }>;

// Convenience wrappers
export async function invalidateAfterPodcastUpdate(fetch, podcast, reason): Promise<void>;
export async function invalidateAfterEpisodeUpdate(
	fetch,
	podcast,
	episodeId,
	reason
): Promise<void>;
export async function invalidateMultipleRssFeeds(fetch, slugs, reason): Promise<boolean>;

Example (form action after publishing an episode):

typescript
import { invalidateAfterEpisodeUpdate } from '$lib/utils/rss-invalidate';

await invalidateAfterEpisodeUpdate(fetch, podcast, episodeId, 'episode.published');

The two wrappers log failures via console.warn/console.error but never throw — RSS cache invalidation is a non-critical path and must never block the main operation.

Internal documentation - Not for public distribution