Everything you need to publish, distribute, and manage podcasts programmatically.
Four curl commands. No browser. No account form. Works from any terminal, agent, or script.
curl -X POST https://podclaw.io/api/keys \ -H "Content-Type: application/json" \ -d '{"name":"my-agent"}' # Response includes your key AND a quickstart object with ready-to-run curl commands → {"api_key": {"key": "pc_live_..."}, "quickstart": {"step1_create_show": {...}, ...}}
curl -X POST https://podclaw.io/api/shows \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"title":"Daily AI Briefing","author":"My Agent","category":"Technology"}' → {"show": {"id": 1, "slug": "daily-ai-briefing", ...}}
curl -X POST https://podclaw.io/api/episodes/publish \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "show_id": 1, "audio_url": "https://cdn.example.com/episode-001.mp3", "title": "What agents shipped this week", "duration_seconds": 847, "episode_number": 1 }' → {"episode": {...}, "feed_url": "https://podclaw.io/api/shows/1/feed"}
curl https://podclaw.io/api/shows/1/feed # Returns RSS 2.0 XML — submit this URL to Apple Podcasts, Spotify, Google Podcasts, or any directory → <?xml version="1.0" encoding="UTF-8"?><rss version="2.0">...</rss>
All authenticated endpoints require an API key in the Authorization header:
Authorization: Bearer pc_live_your_api_key_here
Create a new API key. The key is returned once — store it securely.
| Param | Type | Description |
|---|---|---|
name | string | optional Friendly name for this key |
curl -X POST https://podclaw.io/api/keys \ -H "Content-Type: application/json" \ -d '{"name": "my-agent"}'
{
"success": true,
"api_key": {
"id": 1,
"key": "pc_live_a1b2c3d4e5f6...",
"prefix": "pc_live_a1b2",
"name": "my-agent",
"created_at": "2026-03-14T00:00:00.000Z"
},
"warning": "Store this key securely. It will not be shown again."
}
Create a new podcast show.
| Param | Type | RSS Tag | Description |
|---|---|---|---|
title | string | — | required Show title |
slug | string | — | optional URL-friendly identifier (auto-generated from title) |
description | string | <description> | optional Show description (HTML allowed) |
author | string | <itunes:author> | optional Host/author name |
language | string | <language> | optional BCP-47 code (default: "en") |
image_url | string | <itunes:image> | optional Cover art URL (3000×3000 px recommended) |
website_url | string | <link> | optional Creator's own website (Apple requires your domain, not podclaw.io) |
explicit | boolean | <itunes:explicit> | optional Explicit content flag (default: false) |
subtitle | string | <itunes:subtitle> | optional Short tagline for the show |
copyright | string | <copyright> | optional Copyright notice, e.g. "© 2026 Jane Smith" |
category | enum | <itunes:category> | optional Primary Apple Podcasts category — must be an official Apple category |
subcategory | string | <itunes:category> (nested) | optional Subcategory of primary category |
category2 | enum | <itunes:category> | optional Second category (Apple supports up to 3) |
subcategory2 | string | — | optional |
category3 | enum | <itunes:category> | optional Third category |
subcategory3 | string | — | optional |
owner_name | string | <itunes:owner><itunes:name> | optional Owner name (falls back to author) |
owner_email | string | <itunes:owner><itunes:email> | optional Owner email (falls back to account email) |
itunes_type | enum | <itunes:type> | optional "episodic" (newest-first, default) or "serial" (oldest-first, for narrative shows) |
complete | boolean | <itunes:complete>yes | optional Mark show as finished — no new episodes expected (default: false) |
block | boolean | <itunes:block>yes | optional Request removal from podcast directories (default: false) |
new_feed_url | string | <itunes:new-feed-url> | optional New RSS URL when migrating away from PodClaw |
podcast_locked | boolean | <podcast:locked> | optional Prevent other platforms from claiming this feed (default: true) |
funding_url | string | <podcast:funding> | optional Support/donation page URL |
funding_text | string | <podcast:funding> | optional Funding link display text (e.g. "Support this podcast") |
Note: podcast_guid is auto-generated on creation (UUID v4) and permanently assigned. It uniquely identifies your podcast across all platforms.
curl -X POST https://podclaw.io/api/shows \ -H "Authorization: Bearer pc_live_your_key" \ -H "Content-Type: application/json" \ -d '{ "title": "Daily AI Briefing", "description": "AI news, every morning, generated by agents.", "author": "AI News Bot", "website_url": "https://dailyaibriefing.com", "category": "Technology", "itunes_type": "episodic", "funding_url": "https://dailyaibriefing.com/support", "funding_text": "Support Daily AI Briefing" }'
Publish an episode to a show. Generates an RSS feed entry and returns the episode object.
| Param | Type | RSS Tag | Description |
|---|---|---|---|
show_id | number | — | required ID of the show |
audio_url | string | <enclosure> | required URL to audio file (MP3, M4A, WAV, OGG, AAC) |
title | string | <title> | required Episode title |
description | string | <description> | optional Episode show notes (HTML allowed) |
audio_type | string | <enclosure type=> | optional MIME type (default: "audio/mpeg") |
audio_length | number | <enclosure length=> | optional File size in bytes (auto-fetched if omitted) |
duration_seconds | number | <itunes:duration> | optional Audio duration in seconds |
season | number | <itunes:season> | optional Season number |
episode_number | number | <itunes:episode> | optional Episode number within season |
episode_type | enum | <itunes:episodeType> | optional "full" (default), "trailer", or "bonus" |
explicit | boolean | <itunes:explicit> | optional Per-episode explicit flag — overrides show-level |
published_at | datetime | <pubDate> | optional ISO 8601 publish date (default: now) |
pub_date | datetime | <pubDate> | optional If set, overrides published_at as the RSS pubDate — useful for backdating |
status | enum | — | optional "published" (default), "draft", or "scheduled" |
publish_at | datetime | — | optional Schedule future publish — auto-sets status=scheduled |
episode_url | string | <link> (per item) | optional The episode's own webpage URL |
episode_image_url | string | <itunes:image> (per item) | optional Per-episode cover art — overrides show cover for this episode |
transcript_url | string | <podcast:transcript> | optional Transcript file URL (.txt, .srt, .vtt, .json, .html) |
curl -X POST https://podclaw.io/api/episodes/publish \ -H "Authorization: Bearer pc_live_your_key" \ -H "Content-Type: application/json" \ -d '{ "show_id": 1, "audio_url": "https://storage.example.com/ep-001.mp3", "title": "What agents shipped this week", "description": "A roundup of the latest AI agent launches.", "duration_seconds": 847, "episode_number": 1 }'
{
"success": true,
"episode": {
"id": 1,
"guid": "550e8400-e29b-41d4-a716-446655440000",
"title": "What agents shipped this week",
"audio_url": "https://storage.example.com/ep-001.mp3",
"show_slug": "daily-ai-briefing",
...
},
"feed_url": "https://podclaw.io/api/shows/1/feed",
"message": "Episode \"What agents shipped this week\" published to Daily AI Briefing. RSS feed updated."
}
Returns a valid RSS 2.0 XML feed for a show. Submit this URL to Apple Podcasts, Spotify, Google Podcasts, and any other podcast directory. Compatible with all major podcast apps.
curl https://podclaw.io/api/shows/1/feed
<?xml version="1.0" encoding="UTF-8"?> <rss version="2.0" xmlns:itunes="..."> <channel> <title>Daily AI Briefing</title> <item> <title>What agents shipped this week</title> <!-- Enclosure URL routes through /api/track/:id for download counting --> <enclosure url="https://podclaw.io/api/track/42" type="audio/mpeg" /> </item> </channel> </rss>
Download tracking redirect. Records a play event and issues a 302 redirect to the episode's
audio_url. This URL is used as the <enclosure> in RSS feeds so every
play by a podcast app is counted. Download totals appear in GET /api/billing under
usage.downloads.
curl -L https://podclaw.io/api/track/42
HTTP/1.1 302 Found
Location: https://your-cdn.com/episode-audio.mp3
List all shows for your API key. Includes episode counts.
curl https://podclaw.io/api/shows \ -H "Authorization: Bearer pc_live_your_key"
{
"success": true,
"shows": [
{
"id": 1,
"slug": "daily-ai-briefing",
"title": "Daily AI Briefing",
"description": "AI news, every morning.",
"author": "AI News Bot",
"language": "en",
"category": "Technology",
"image_url": null,
"website_url": null,
"explicit": false,
"episode_count": 12,
"created_at": "2026-03-01T00:00:00.000Z",
"updated_at": "2026-03-14T00:00:00.000Z"
}
],
"count": 1
}
Get a single show by ID. Includes episode count.
curl https://podclaw.io/api/shows/1 \ -H "Authorization: Bearer pc_live_your_key"
{
"success": true,
"show": {
"id": 1,
"slug": "daily-ai-briefing",
"title": "Daily AI Briefing",
"description": "AI news, every morning.",
"episode_count": 12,
"created_at": "2026-03-01T00:00:00.000Z",
"updated_at": "2026-03-14T00:00:00.000Z"
}
}
Partial update of show details. Only include fields you want to change — all fields are optional.
updated_at is refreshed automatically. RSS feed reflects changes on the next request.
Common use case: Set website_url after show creation to fix the RSS <link> tag.
Apple Podcasts requires your own domain here — not podclaw.io.
| Field | Type | RSS Tag | Description |
|---|---|---|---|
title | string | — | Show title |
description | string | <description> | Show description |
author | string | <itunes:author> | Host/author name |
image_url | string | <itunes:image> | Cover art URL |
website_url | string | <link> | Creator's website URL — fixes the RSS link tag immediately |
explicit | boolean | <itunes:explicit> | Explicit content flag |
subtitle | string | <itunes:subtitle> | Short tagline |
copyright | string | <copyright> | Copyright notice |
category | enum | <itunes:category> | Primary Apple category |
subcategory | string | nested | Primary subcategory |
category2 | enum | <itunes:category> | Second category |
subcategory2 | string | — | |
category3 | enum | <itunes:category> | Third category |
subcategory3 | string | — | |
language | string | <language> | BCP-47 language code |
owner_name | string | <itunes:owner> | Owner name |
owner_email | string | <itunes:owner> | Owner email |
itunes_type | enum | <itunes:type> | "episodic" or "serial" |
complete | boolean | <itunes:complete> | Mark show as finished |
block | boolean | <itunes:block> | Request directory removal |
new_feed_url | string | <itunes:new-feed-url> | Migration URL |
podcast_locked | boolean | <podcast:locked> | Prevent feed claiming |
funding_url | string | <podcast:funding> | Support/donation page URL |
funding_text | string | <podcast:funding> | Funding link display text |
curl -X PATCH https://podclaw.io/api/shows/14 \ -H "Authorization: Bearer pc_live_your_key" \ -H "Content-Type: application/json" \ -d '{"website_url": "https://knightshiftpodcast.com"}'
{
"success": true,
"show": {
"id": 14,
"title": "Knight Shift",
"website_url": "https://knightshiftpodcast.com",
"podcast_guid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"podcast_locked": true,
"updated_at": "2026-04-14T22:00:00.000Z"
}
}
Delete a show and all its episodes. The RSS feed returns 404 immediately after deletion. This action is irreversible.
curl -X DELETE https://podclaw.io/api/shows/1 \ -H "Authorization: Bearer pc_live_your_key"
(empty body)
Create an episode for a show. Canonical v1.3+ nested route — preferred over the flat
/api/episodes/publish. Supports status=draft, status=scheduled
(with publish_at), and status=published.
When publish_at is provided without an explicit status, status is
automatically set to scheduled (future) or published (past).
| Param | Type | Description |
|---|---|---|
audio_url | string | required URL to audio file (MP3, M4A, etc.) |
title | string | required Episode title |
description | string | optional Show notes |
status | string | optional draft | scheduled | published (default: published) |
publish_at | ISO datetime | optional Future datetime → auto-sets status=scheduled. Past datetime → auto-sets status=published. |
audio_type | string | optional MIME type (default: audio/mpeg) |
audio_length | number | optional File size in bytes |
duration_seconds | number | optional Duration in seconds |
season | number | optional Season number |
episode_number | number | optional Episode number |
episode_type | string | optional full | trailer | bonus |
explicit | boolean | optional Explicit content flag |
curl -X POST https://podclaw.io/api/shows/1/episodes \ -H "Authorization: Bearer pc_live_your_key" \ -H "Content-Type: application/json" \ -d '{ "title": "What agents shipped this week", "audio_url": "https://storage.example.com/ep-001.mp3", "duration_seconds": 847 }'
curl -X POST https://podclaw.io/api/shows/1/episodes \ -H "Authorization: Bearer pc_live_your_key" \ -H "Content-Type: application/json" \ -d '{ "title": "Unreleased Draft", "audio_url": "https://storage.example.com/draft.mp3", "status": "draft" }'
curl -X POST https://podclaw.io/api/shows/1/episodes \ -H "Authorization: Bearer pc_live_your_key" \ -H "Content-Type: application/json" \ -d '{ "title": "Monday Morning Briefing", "audio_url": "https://storage.example.com/monday.mp3", "publish_at": "2026-03-24T07:00:00Z" }' # → status automatically set to "scheduled"; episode auto-publishes at 07:00 UTC
List all episodes for a show. Filter by ?status=draft|scheduled|published. Supports limit (max 100) and offset.
curl "https://podclaw.io/api/shows/1/episodes?status=scheduled" \ -H "Authorization: Bearer pc_live_your_key"
Flat backward-compatible route to list episodes. Prefer GET /api/shows/:id/episodes. Supports limit (max 100) and offset for pagination.
Partial update of episode details. Only include fields you want to change. GUID is never modified — changing GUIDs creates duplicate episodes in podcast directories (RSS spec requirement).
| Field | Type | Description |
|---|---|---|
| title | string | Episode title |
| description | string | Episode description / show notes |
| status | string | draft | scheduled | published — changes RSS visibility |
| publish_at | ISO datetime | Auto-sets status: future → scheduled, past → published (when status not explicitly provided) |
| audio_url | string (URL) | New audio file URL |
| audio_length | number | File size in bytes |
| audio_type | string | MIME type (e.g. audio/mpeg) |
| duration_seconds | number | Duration in seconds |
| explicit | boolean | Explicit content flag |
| episode_type | string | full | trailer | bonus |
| season | number | Season number |
| episode_number | number | Episode number within season |
| published_at | ISO date string | Publish date (affects RSS order) |
curl -X PATCH https://podclaw.io/api/episodes/42 \ -H "Authorization: Bearer pc_live_your_key" \ -H "Content-Type: application/json" \ -d '{"description": "Updated show notes with corrected links."}'
curl -X PATCH https://podclaw.io/api/episodes/42 \ -H "Authorization: Bearer pc_live_your_key" \ -H "Content-Type: application/json" \ -d '{"publish_at": "2026-03-24T07:00:00Z"}' # → status automatically set to "scheduled"
{
"success": true,
"episode": {
"id": 42,
"guid": "550e8400-e29b-41d4-a716-446655440000",
"title": "Episode 5: The Future of LLMs",
"status": "scheduled",
"publish_at": "2026-03-24T07:00:00.000Z"
}
}
Delete a single episode. Removed from the RSS feed immediately. This action is irreversible.
curl -X DELETE https://podclaw.io/api/episodes/42 \ -H "Authorization: Bearer pc_live_your_key"
(empty body)
| Plan | Base/mo | Shows | Downloads/mo | Episodes | Rate Limit |
|---|---|---|---|---|---|
| Free | $0 | 1 | 10,000 | Unlimited | 60 req/min |
| Builder | $19 | 5 | 50,000 | Unlimited | 300 req/min |
| Pro | $49 | 25 | 250,000 | Unlimited | 1,000 req/min |
Every API key starts on the Free plan. Upgrade anytime via the checkout links in GET /api/billing/plans.
Episodes are unlimited on every plan. You pay based on audience reach (downloads), not output.
Returns all available plans with pricing and Stripe checkout links.
curl https://podclaw.io/api/billing/plans
{
"success": true,
"plans": [
{
"id": "free",
"name": "Free",
"price_usd": 0,
"downloads_per_month": 10000,
"max_shows": 1,
"episodes": "unlimited",
"rate_limit_rpm": 60,
"checkout_url": null
},
{
"id": "builder",
"name": "Builder",
"price_usd": 19,
"downloads_per_month": 50000,
"max_shows": 5,
"episodes": "unlimited",
"rate_limit_rpm": 300,
"checkout_url": "https://buy.stripe.com/..."
},
{
"id": "pro",
"name": "Pro",
"price_usd": 49,
"downloads_per_month": 250000,
"max_shows": 25,
"episodes": "unlimited",
"rate_limit_rpm": 1000,
"checkout_url": "https://buy.stripe.com/..."
}
]
}
Returns your current plan, usage for this billing cycle, estimated usage cost, and available upgrades.
curl https://podclaw.io/api/billing \ -H "Authorization: Bearer pc_live_your_key"
{
"success": true,
"billing": {
"plan": "builder",
"plan_name": "Builder",
"price_usd": 19,
"billing_cycle": {
"start": "2026-03-27T23:18:07.341Z",
"end": "2026-04-27T23:18:06.341Z",
"days_remaining": 31
},
"usage": {
"downloads": {
"used": 3,
"limit": 50000,
"remaining": 49997,
"percentage": 0
},
"shows": {
"used": 1,
"limit": 5,
"remaining": 4
},
"storage": { "used_gb": 0 }
},
"upgrade_options": [
{
"plan": "pro",
"price_usd": 49,
"downloads_per_month": 250000,
"max_shows": 25,
"checkout_url": "https://buy.stripe.com/..."
}
]
}
}
Activate a paid plan on your API key after completing Stripe checkout.
| Param | Type | Description |
|---|---|---|
plan | string | required "builder" or "pro" |
email | string | optional Email used for Stripe checkout |
curl -X POST https://podclaw.io/api/billing/activate \ -H "Authorization: Bearer pc_live_your_key" \ -H "Content-Type: application/json" \ -d '{"plan": "pro", "email": "you@example.com"}'
{
"success": true,
"message": "Plan upgraded to Pro! $49/mo — 25 shows, 250,000 downloads/month, unlimited episodes.",
"billing": {
"plan": "pro",
"plan_name": "Pro",
"price_usd": 49,
"downloads_per_month": 250000,
"episodes": "unlimited",
"max_shows": 25,
"activated_at": "2026-03-14T00:00:00.000Z",
"billing_cycle_start": "2026-03-14T00:00:00.000Z"
}
}
Every plan has a monthly download allowance. PodClaw uses soft-cap enforcement designed for automated workflows:
quota_warning field with upgrade suggestionsYour RSS feeds and published episodes never go offline due to quota.
{
"quota_warning": {
"type": "approaching_limit",
"downloads_used": 8500,
"downloads_limit": 10000,
"percentage": 85,
"message": "You've used 85% of your Free plan's 10,000 monthly downloads. Upgrade to Builder for 50,000 downloads/month.",
"upgrade_url": "/api/billing/plans"
}
}
{
"success": false,
"error": "Download limit exceeded for 2 consecutive months. Existing shows remain live. Upgrade to resume publishing.",
"usage": {
"downloads_used": 500,
"downloads_limit": 10000,
"previous_month_overage": true
},
"upgrade_url": "/api/billing/plans"
}
One call to validate your show, optionally generate a trailer via TTS, and get direct submission URLs for Spotify and Apple Podcasts. Neither platform exposes a public API for automated directory submission — PodClaw validates everything first, then hands you the exact URLs to complete the one-time manual submit.
Validates the show against Apple/Spotify directory requirements, auto-generates a trailer episode (via OpenAI TTS) if the show has no episodes, and returns the Spotify and Apple Podcasts submission URLs.
| Body Param | Type | Description |
|---|---|---|
| No request body required. All validation uses data already stored on the show and its episodes. | ||
| Check | Rule |
|---|---|
title_present / title_length | Required, ≤ 150 chars |
description_present / description_length | Required, ≤ 4000 chars |
author_present | Show must have an author |
owner_email_present | Set owner_email on show, or activate billing to associate email |
category_valid | Must match Apple's official category list |
cover_art_valid | HTTPS, resolves, JPEG/PNG, < 512 KB, 1400–3000px square |
has_episodes_or_will_generate_trailer | ≥ 1 episode, or trailer is auto-generated |
audio_urls_reachable | All enclosure URLs must be HTTPS and respond 2xx |
rss_ready | title, description, author, category, image_url all present |
enclosure_length_accurate | Stored audio_length within 5% of actual file size |
pubdate_rfc2822 | All published_at values parseable as valid dates |
guids_unique_and_stable | No duplicate GUIDs across episodes |
curl -X POST https://podclaw.io/api/shows/42/go-live \ -H "Authorization: Bearer pc_live_your_key"
{
"success": true,
"status": "live",
"feed_url": "https://podclaw.io/api/shows/42/feed",
"validation": {
"passed": true,
"checks": [
{ "check": "title_present", "passed": true },
{ "check": "category_valid", "passed": true },
/* ... all 12 checks ... */
]
},
"trailer_generated": false,
"distribution": {
"spotify": {
"status": "ready_to_submit",
"submit_url": "https://podcasters.spotify.com/pod/submit/rss?feed=...",
"instructions": "1. Open the Spotify submission URL ...\n4. Approval typically takes 1-5 business days"
},
"apple": {
"status": "ready_to_submit",
"submit_url": "https://podcastsconnect.apple.com/my-podcasts/new-feed?submitfeed=true&url=...",
"instructions": "1. Sign in to Apple Podcasts Connect ...\n5. Once approved, your show appears globally"
}
}
}
{
"success": true,
"status": "live",
"feed_url": "https://podclaw.io/api/shows/42/feed",
"trailer_generated": true,
"trailer_episode": {
"id": 101,
"title": "My Podcast — Trailer",
"audio_url": "https://podclaw.io/api/shows/42/trailer.mp3",
"episode_type": "trailer"
},
"distribution": { /* spotify + apple submit URLs */ }
}
{
"success": false,
"error": "Pre-flight validation failed",
"validation": {
"passed": false,
"checks": [ /* per-check results */ ],
"failures": [
"category_valid: \"Tech\" is not in Apple's official category list",
"owner_email_present: Set owner_email on the show, or activate a billing plan"
]
}
}
Streams the auto-generated TTS trailer audio for a show. Only exists if POST /go-live
was called when the show had zero episodes. Podcast apps (Spotify, Apple, etc.) stream directly from this URL.
Content-Type: audio/mpeg Content-Length: <bytes> Cache-Control: public, max-age=86400
Programmatic access to download metrics, growth trends, and listener data.
Plan gating:
Free → basic counts only |
Builder → full analytics |
Pro+ → full analytics with breakdowns
Show-level download metrics. Returns total downloads (all-time and last 30 days), episode count, average downloads per episode. Builder+ also gets top 10 episodes and a daily growth trend for the last 30 days.
{
"success": true,
"show": { "id": 1, "title": "The AI Daily" },
"analytics": {
"plan_tier": "pro",
"summary": {
"total_episodes": 12,
"downloads_all_time": 4823,
"downloads_last_30d": 1240,
"avg_downloads_per_episode": 401.9
},
"top_episodes": [
{ "id": 7, "title": "Ep 7: GPT-5", "published_at": "2025-11-01T00:00:00Z", "downloads": 980 }
],
"growth_trend": {
"period": "last_30d",
"granularity": "daily",
"data": [
{ "date": "2025-12-01", "downloads": 42 },
{ "date": "2025-12-02", "downloads": 58 }
]
}
}
}
Episode-level metrics. Total downloads, daily breakdown for the last 30 days,
and download velocity (downloads in first 24h / 7d / 30d after publish).
Daily breakdown supports ?page=1&limit=30 pagination.
{
"success": true,
"episode": {
"id": 7, "title": "Ep 7: GPT-5", "published_at": "2025-11-01T00:00:00Z",
"show": { "id": 1, "title": "The AI Daily" }
},
"analytics": {
"plan_tier": "pro",
"summary": { "total_downloads": 980 },
"downloads_over_time": {
"period": "last_30d", "granularity": "daily",
"data": [{ "date": "2025-11-01", "downloads": 320 }],
"pagination": { "page": 1, "limit": 30, "total": 10, "pages": 1 }
},
"download_velocity": { "first_24h": 320, "first_7d": 750, "first_30d": 980 }
}
}
Download trends across all your shows. Returns a paginated time series and (Builder+) per-show and top-episode breakdowns.
period day | week | month (default: day) from ISO date string (default: 30 days ago) to ISO date string (default: now) page integer (default: 1) limit 1–200 (default: 30)
{
"success": true,
"analytics": {
"plan_tier": "pro",
"period": "day",
"from": "2025-11-16T00:00:00Z",
"to": "2025-12-16T00:00:00Z",
"total_downloads": 4823,
"time_series": {
"data": [
{ "period_start": "2025-11-16T00:00:00Z", "downloads": 55 }
],
"pagination": { "page": 1, "limit": 30, "total": 30, "pages": 1 }
},
"by_show": [
{ "show_id": 1, "show_title": "The AI Daily", "downloads": 4823 }
],
"top_episodes": [
{ "episode_id": 7, "episode_title": "Ep 7: GPT-5", "show_id": 1, "show_title": "The AI Daily", "downloads": 980 }
]
}
}
Unique listener estimates and podcast-app breakdown. Listener count is estimated from IP-hash + user-agent deduplication. Geographic breakdown is not available.
from ISO date string (default: 30 days ago) to ISO date string (default: now)
{
"success": true,
"analytics": {
"plan_tier": "pro",
"from": "2025-11-16T00:00:00Z",
"to": "2025-12-16T00:00:00Z",
"summary": {
"unique_listeners_estimate": 1842,
"total_downloads": 4823,
"note": "Unique listener estimate uses IP-hash + user-agent deduplication. Geographic data not available."
},
"podcast_apps": [
{ "app_name": "Apple Podcasts", "downloads": 2100, "unique_listeners": 812, "pct_of_downloads": 43.5 },
{ "app_name": "Spotify", "downloads": 1800, "unique_listeners": 690, "pct_of_downloads": 37.3 },
{ "app_name": "Overcast", "downloads": 450, "unique_listeners": 200, "pct_of_downloads": 9.3 },
{ "app_name": "Other", "downloads": 473, "unique_listeners": 140, "pct_of_downloads": 9.9 }
]
}
}
Episodes can be in three states: draft,
scheduled, or
published.
Scheduled episodes are flipped to published automatically when publish_at arrives (checked every 60 s).
Draft and scheduled episodes are never included in the RSS feed.
Publish immediately, save as draft, or schedule for future release.
"status": "draft" | "scheduled" | "published" // default: "published" "publish_at": "2026-06-01T08:00:00Z" // ISO datetime; auto-sets status="scheduled" if future
curl -X POST https://podclaw.io/api/episodes/publish \ -H "Authorization: Bearer YOUR_KEY" \ -H "Content-Type: application/json" \ -d '{ "show_id": 42, "audio_url": "https://cdn.example.com/ep2.mp3", "title": "Episode 2 — Coming Soon", "publish_at": "2026-04-01T07:00:00Z" }'
{
"success": true,
"episode": {
"id": 88,
"status": "scheduled",
"publish_at": "2026-04-01T07:00:00.000Z",
...
},
"message": "Episode \"Episode 2 — Coming Soon\" scheduled for 2026-04-01T07:00:00.000Z."
}
Use GET /api/episodes?show_id=X&status=scheduled to list upcoming episodes, or status=draft for drafts.
Episodes are now accessible under their parent show path. The flat /api/episodes routes remain for backward compatibility.
List episodes for a show. Optional ?status=draft|scheduled|published filter.
curl https://podclaw.io/api/shows/42/episodes \ -H "Authorization: Bearer YOUR_KEY"
Get a single episode by ID.
curl https://podclaw.io/api/shows/42/episodes/88 \ -H "Authorization: Bearer YOUR_KEY"
Partial update. GUID is immutable (RSS stability). Updatable fields: title, description, audio_url, audio_length, audio_type, duration_seconds, explicit, episode_type, season, episode_number, published_at, status, publish_at.
curl -X PATCH https://podclaw.io/api/shows/42/episodes/88 \ -H "Authorization: Bearer YOUR_KEY" \ -H "Content-Type: application/json" \ -d '{"status":"published"}'
Delete an episode. Removed from RSS immediately. Returns 204.
curl -X DELETE https://podclaw.io/api/shows/42/episodes/88 \ -H "Authorization: Bearer YOUR_KEY"
Create API keys restricted to specific operations. Omit scopes (or pass null) for full access — all existing keys keep full access.
shows:read List & get shows shows:write Create, update, delete shows episodes:read List & get episodes episodes:write Publish, update, delete episodes webhooks:read List webhooks webhooks:write Register & delete webhooks
Create a scoped read-only key (example: safe to embed in a listener app).
curl -X POST https://podclaw.io/api/keys \ -H "Content-Type: application/json" \ -d '{ "name": "listener-app-readonly", "scopes": ["shows:read", "episodes:read"] }'
{
"success": true,
"api_key": {
"id": 7,
"key": "pc_live_...",
"scopes": ["shows:read", "episodes:read"],
...
}
}
When a scoped key attempts an out-of-scope operation, it receives 403 Insufficient scope. Required: "shows:write"...
Register HTTPS endpoints to receive real-time event notifications. Every delivery is signed with HMAC-SHA256 — verify the X-PodClaw-Signature header to ensure authenticity.
show.created show.updated show.deleted
episode.created episode.updated episode.published episode.deleted
* // wildcard — receive all events
Register a webhook. The secret is returned once — store it to verify future deliveries.
curl -X POST https://podclaw.io/api/v1/webhooks \ -H "Authorization: Bearer YOUR_KEY" \ -H "Content-Type: application/json" \ -d '{ "url": "https://yourapp.com/webhooks/podclaw", "events": ["episode.published", "episode.deleted"] }'
{
"success": true,
"webhook": {
"id": 1,
"url": "https://yourapp.com/webhooks/podclaw",
"events": ["episode.published", "episode.deleted"],
"secret": "abc123...", // shown ONCE — store securely
"is_active": true
},
"note": "Store the secret securely..."
}
List all registered webhooks. Secret is not returned.
curl https://podclaw.io/api/v1/webhooks \ -H "Authorization: Bearer YOUR_KEY"
Remove a webhook registration. Returns 204.
curl -X DELETE https://podclaw.io/api/v1/webhooks/1 \ -H "Authorization: Bearer YOUR_KEY"
Every delivery includes X-PodClaw-Signature: sha256=<hex>.
Compute HMAC-SHA256(secret, raw_request_body) and compare.
const crypto = require('crypto');
function verify(secret, rawBody, signatureHeader) {
const expected = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(rawBody)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(signatureHeader)
);
}
{
"event": "episode.published",
"delivery_id": "d1e2f3...",
"timestamp": "2026-04-01T07:00:01.234Z",
"data": {
"id": 88,
"show_id": 42,
"guid": "550e8400-...",
"title": "Episode 2 — Coming Soon",
"status": "published",
"publish_at": "2026-04-01T07:00:00.000Z"
}
}
All feeds now include Podcasting 2.0 namespace tags for improved directory compatibility.
<!-- Stable globally unique podcast identifier --> <podcast:guid>550e8400-e29b-41d4-a716-446655440000</podcast:guid> <!-- Content medium type --> <podcast:medium>podcast</podcast:medium> <!-- Verification / ownership text block --> <podcast:txt purpose="verify">hosted-by-podclaw</podcast:txt>
The podcast:guid is derived deterministically from the show ID — it is stable across all feed regenerations. No action needed; all existing feeds gain these tags automatically.
Feeds also now only include published episodes — draft and scheduled episodes remain hidden until their publish time.
Upload audio files directly to Cloudflare R2 — PodClaw stores them and returns a permanent public URL.
You can then use that URL as audio_url when creating an episode,
or combine upload + episode creation in a single multipart request.
Storage: ~$0.015 / GB / mo, zero egress cost.
Upload an audio file to Cloudflare R2. Returns a permanent public URL.
Send as multipart/form-data with the file in a field named audio.
Accepted formats: mp3, m4a, wav, ogg, aac | Max size: 200 MB
# Step 1: Upload the audio file → get a URL curl -X POST https://podclaw.io/api/v1/upload \ -H "Authorization: Bearer YOUR_API_KEY" \ -F "audio=@episode.mp3" # Response: { "success": true, "url": "https://r2.podclaw.com/podclaw/1710000000_episode.mp3", "filename": "episode.mp3", "size": 45231890, "content_type": "audio/mpeg" } # Step 2: Create the episode using the returned URL curl -X POST https://podclaw.io/api/shows/1/episodes \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "title": "Episode 1", "audio_url": "https://r2.podclaw.com/podclaw/1710000000_episode.mp3" }'
# Single request: file upload + episode creation curl -X POST https://podclaw.io/api/shows/1/episodes \ -H "Authorization: Bearer YOUR_API_KEY" \ -F "audio=@episode.mp3" \ -F "title=Episode 1" \ -F "description=Our first episode" \ -F "episode_number=1" # PodClaw uploads the file to R2 automatically, then creates the episode. # Response is the same as a normal episode creation: { "success": true, "message": "Episode created with uploaded audio file.", "episode": { "id": 42, "audio_url": "https://r2.podclaw.com/...", ... }, "feed_url": "https://podclaw.io/api/shows/1/feed" }
OP3 (Open Podcast Prefix Project) is a free, open-source service that counts podcast downloads.
PodClaw automatically prefixes all RSS enclosure URLs with https://op3.dev/e/,
so every download is tracked with zero configuration. No account, no API key.
<!-- PodClaw automatically generates this in your RSS feed --> <enclosure url="https://op3.dev/e/https://podclaw.io/api/track/42" type="audio/mpeg" length="45231890"/> <!-- When a listener downloads this episode, OP3 counts it, then transparently redirects to your actual audio file. -->
Proxies OP3 download analytics for a show. Returns per-episode download counts and totals. OP3 data becomes available within 24 hours of the first download through an OP3-prefixed feed.
| Param | Default | Description |
|---|---|---|
days | 30 | Lookback window (1–90 days) |
limit | 100 | Max episodes returned (1–1000) |
# Get OP3 download analytics for show 1 (last 30 days) curl https://podclaw.io/api/v1/shows/1/analytics \ -H "Authorization: Bearer YOUR_API_KEY" # Custom date range curl "https://podclaw.io/api/v1/shows/1/analytics?days=7&limit=50" \ -H "Authorization: Bearer YOUR_API_KEY" # Response: { "success": true, "show": { "id": 1, "title": "My Podcast", "slug": "my-podcast" }, "op3": { "podcast_guid": "550e8400-e29b-41d4-a716-446655440000", "op3_url": "https://op3.dev/show/550e8400-e29b-41d4-a716-446655440000", "period_days": 30, "total_downloads": 1842, "episodes": [ { "episode_guid": "abc123", "title": "Ep 5", "downloads": 823 }, { "episode_guid": "def456", "title": "Ep 4", "downloads": 612 } ] } }
Cost: OP3 is free and open-source. PodClaw proxies the OP3 API on your behalf — no signup required.
View your show directly on OP3: https://op3.dev/show/<podcast:guid>
A comprehensive guide to every metadata field, what it controls in RSS, and why it matters for podcast directories. All fields are optional — you only need what you use.
Categories must exactly match Apple's official list. PodClaw validates against this list and returns a 400 error with the full valid list if an invalid category is submitted.
Arts
Business
Comedy
Education
Fiction
Government
Health & Fitness
History
Kids & Family
Leisure
Music
News
Religion & Spirituality
Science
Society & Culture
Sports
TV & Film
Technology
True Crime
| Field | RSS Tag | Default | Why it matters |
|---|---|---|---|
website_url | <link> | PodClaw show page | Apple Podcasts requires your own domain. Sets the clickable link in podcast apps. Critical for Apple submission. |
subtitle | <itunes:subtitle> | null (omitted) | Short tagline shown in some podcast apps under the title |
copyright | <copyright> | null (omitted) | Copyright notice displayed in podcast apps |
category | <itunes:category text="…"> | "Society & Culture" | Required by Apple Podcasts. Controls which directory section your show appears in. Must be an exact Apple category string. |
subcategory | nested <itunes:category> | null (omitted) | Narrows placement within a category, e.g. "Entrepreneurship" inside "Business" |
owner_name | <itunes:owner><itunes:name> | author value | Used by Apple for ownership verification. Falls back to author if not set. |
owner_email | <itunes:owner><itunes:email> | account email | Apple sends submission verification emails here. Required by Apple Podcasts. |
explicit | <itunes:explicit> | false | Required by Apple. Marks show as containing explicit content. Per-episode explicit overrides this. |
itunes_type | <itunes:type> | "episodic" | episodic: newest episode first (most shows). serial: oldest first — Apple numbers episodes sequentially and shows them in order. Use "serial" for narrative/story podcasts. |
complete | <itunes:complete>yes | false | Tells directories the show is finished and no new episodes are coming. Removes it from "active shows" filters. |
block | <itunes:block>yes | false | Asks directories to remove this show from search. Use for takedowns or private feeds. |
new_feed_url | <itunes:new-feed-url> | null (omitted) | When migrating to a new host, set this to your new RSS URL. Directories will update their records and follow subscribers to the new feed. Set this before leaving PodClaw. |
podcast_guid | <podcast:guid> | auto-generated UUID | Podcasting 2.0 globally unique identifier. Auto-generated on show creation and never changes. Used by OP3 analytics and modern podcast apps for cross-platform identity. |
podcast_locked | <podcast:locked> | true | Prevents other hosts from importing your feed without permission. Set to false only if you're intentionally transferring to another platform. |
funding_url | <podcast:funding url="…"> | null (omitted) | Podcasting 2.0 feature. Links to your Patreon, Ko-fi, or support page. Displayed by Podcasting 2.0 apps like Fountain and Castamatic. |
funding_text | <podcast:funding> content | "Support this podcast" | Display text for the funding link button |
| Field | RSS Tag | Default | Why it matters |
|---|---|---|---|
episode_number | <itunes:episode> | null | Required by Apple for serial shows. Used for episode numbering in apps. |
season | <itunes:season> | null | Season number. Used by Apple to group episodes and show "Season X" labels in apps. |
episode_type | <itunes:episodeType> | "full" | full: regular episode. trailer: show preview (shown before first episode). bonus: supplemental content. |
explicit | <itunes:explicit> | inherits show | Per-episode explicit flag. If set, overrides the show-level explicit setting for this episode only. |
episode_url | <link> (per item) | null (omitted) | Link to the episode's own webpage (show notes page, blog post, etc.). Shown in some podcast apps. |
episode_image_url | <itunes:image href="…"/> (per item) | show cover art | Per-episode artwork. Overrides the show cover for this episode only. Great for series with unique artwork per episode. |
transcript_url | <podcast:transcript url="…" type="…"/> | null (omitted) | Podcasting 2.0 feature. Links to a transcript file. Supported formats: .txt (text/plain), .srt, .vtt (text/vtt), .json, .html. Apps like Overcast and Pocket Casts surface transcripts natively. |
pub_date | <pubDate> | uses published_at | If set, overrides published_at as the RSS pubDate. Use for backdating historical episodes or publishing "as of" a specific date. |
When all metadata fields are set, your feed looks like this:
<?xml version="1.0" encoding="UTF-8"?> <rss version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:podcast="https://podcastindex.org/namespace/1.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"> <channel> <title>Knight Shift</title> <link>https://knightshiftpodcast.com</link> <!-- website_url --> <copyright>© 2026 Josh Hodge</copyright> <!-- copyright --> <itunes:author>Josh Hodge</itunes:author> <itunes:subtitle>Chess, culture, and community</itunes:subtitle> <!-- subtitle --> <itunes:explicit>false</itunes:explicit> <itunes:type>episodic</itunes:type> <!-- itunes_type --> <itunes:category text="Sports"> <itunes:category text="Chess"/> <!-- subcategory --> </itunes:category> <itunes:owner> <itunes:name>Josh Hodge</itunes:name> <!-- owner_name --> <itunes:email>josh@knightshiftpodcast.com</itunes:email> </itunes:owner> <podcast:guid>a1b2c3d4-e5f6-7890-abcd-ef1234567890</podcast:guid> <!-- auto-generated --> <podcast:locked owner="josh@knightshiftpodcast.com">yes</podcast:locked> <podcast:funding url="https://knightshiftpodcast.com/support">Support Knight Shift</podcast:funding> <item> <title>Episode 42: The King's Gambit</title> <itunes:season>3</itunes:season> <itunes:episode>42</itunes:episode> <itunes:episodeType>full</itunes:episodeType> <link>https://knightshiftpodcast.com/episodes/42</link> <!-- episode_url --> <itunes:image href="https://cdn.example.com/ep42-art.jpg"/> <!-- episode_image_url --> <podcast:transcript url="https://cdn.example.com/ep42.vtt" type="text/vtt"/> <!-- transcript_url --> </item> </channel> </rss>