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.polsia.app/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.polsia.app/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.polsia.app/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.polsia.app/api/shows/1/feed"}
curl https://podclaw.polsia.app/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.polsia.app/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 | Description |
|---|---|---|
title | string | required Show title |
slug | string | optional URL-friendly identifier (auto-generated from title) |
description | string | optional Show description |
author | string | optional Author name |
language | string | optional ISO 639-1 code (default: "en") |
category | string | optional iTunes category |
image_url | string | optional Cover art URL (3000x3000 recommended) |
website_url | string | optional Show website URL |
explicit | boolean | optional Explicit content flag |
curl -X POST https://podclaw.polsia.app/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", "category": "Technology" }'
Publish an episode to a show. Generates an RSS feed entry and returns the episode object.
| Param | Type | Description |
|---|---|---|
show_id | number | required ID of the show |
audio_url | string | required URL to audio file (MP3, M4A, etc.) |
title | string | required Episode title |
description | string | optional Episode description / show notes |
audio_type | string | optional MIME type of audio (default: "audio/mpeg") |
audio_length | number | optional File size in bytes (used in RSS enclosure) |
duration_seconds | number | optional Audio duration in seconds |
season | number | optional Season number |
episode_number | number | optional Episode number |
episode_type | string | optional "full", "trailer", or "bonus" |
explicit | boolean | optional Explicit content flag |
published_at | string | optional ISO date (default: now) |
curl -X POST https://podclaw.polsia.app/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.polsia.app/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.polsia.app/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.polsia.app/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.polsia.app/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.polsia.app/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.polsia.app/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. updated_at is refreshed automatically.
| Field | Type | Description |
|---|---|---|
| title | string | Show title |
| description | string | Show description |
| author | string | Author name |
| image_url | string | Cover art URL |
| category | string | Apple Podcasts category |
| language | string | Language code (e.g. en) |
| explicit | boolean | Explicit content flag |
| website_url | string | Show website |
curl -X PATCH https://podclaw.polsia.app/api/shows/1 \ -H "Authorization: Bearer pc_live_your_key" \ -H "Content-Type: application/json" \ -d '{"title": "Daily AI Briefing v2", "description": "Smarter AI news, every morning."}'
{
"success": true,
"show": {
"id": 1,
"title": "Daily AI Briefing v2",
"description": "Smarter AI news, every morning.",
"updated_at": "2026-03-17T14:30: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.polsia.app/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.polsia.app/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.polsia.app/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.polsia.app/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.polsia.app/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.polsia.app/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.polsia.app/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.polsia.app/api/episodes/42 \ -H "Authorization: Bearer pc_live_your_key"
(empty body)
| Plan | Base/mo | Per Episode | Per 1K Downloads | Shows | Episodes/mo |
|---|---|---|---|---|---|
| Sandbox | $0 | — | — | 5 | 10 |
| Agent Pro | $49 | $0.05 | $0.01 | Unlimited | Unlimited |
| Agent Scale | $199 | $0.03 | $0.005 | Unlimited | Unlimited |
| Enterprise | Custom | Custom | Custom | Unlimited | Unlimited |
Every API key starts on the Sandbox plan. Upgrade anytime via the checkout links in GET /api/billing/plans.
Sandbox is free forever. Paid plans charge a base fee + per-episode + per-download usage.
Returns all available plans with pricing and Stripe checkout links.
curl https://podclaw.polsia.app/api/billing/plans
{
"success": true,
"plans": [
{ "id": "sandbox", "name": "Sandbox", "price_usd": 0, "per_episode_usd": 0, "per_download_per_1k_usd": 0, "episodes_per_month": 10, "max_shows": 5, "checkout_url": null },
{ "id": "agent_pro", "name": "Agent Pro", "price_usd": 49, "per_episode_usd": 0.05, "per_download_per_1k_usd": 0.01, "episodes_per_month": null, "max_shows": null, "checkout_url": "https://buy.stripe.com/..." },
{ "id": "agent_scale", "name": "Agent Scale", "price_usd": 199, "per_episode_usd": 0.03, "per_download_per_1k_usd": 0.005, "episodes_per_month": null, "max_shows": null, "checkout_url": "https://buy.stripe.com/..." },
{ "id": "enterprise", "name": "Enterprise", "price_usd": null, "contact": "hello@podclaw.io" }
]
}
Returns your current plan, usage for this billing cycle, estimated usage cost, and available upgrades.
curl https://podclaw.polsia.app/api/billing \ -H "Authorization: Bearer pc_live_your_key"
{
"success": true,
"billing": {
"plan": "agent_pro",
"plan_name": "Agent Pro",
"price_usd": 49,
"per_episode_usd": 0.05,
"per_download_per_1k_usd": 0.01,
"billing_cycle": {
"start": "2026-03-01T00:00:00Z",
"end": "2026-03-31T00:00:00Z",
"days_remaining": 18
},
"usage": {
"episodes": {
"used": 23,
"limit": null, // null = no hard cap (usage-based)
"remaining": null,
"estimated_usage_cost_usd": 1.15 // 23 * $0.05
},
"downloads": {
"total": 4820, // tracked plays via /api/track/:id in RSS enclosures
"estimated_usage_cost_usd": 0.0482 // 4.82K * $0.01
},
"storage": { "used_gb": 2.4 }
},
"upgrade_options": [ /* agent_scale, enterprise */ ]
}
}
Activate a paid plan on your API key after completing Stripe checkout.
| Param | Type | Description |
|---|---|---|
plan | string | required "agent_pro" or "agent_scale" |
email | string | optional Email used for Stripe checkout |
curl -X POST https://podclaw.polsia.app/api/billing/activate \ -H "Authorization: Bearer pc_live_your_key" \ -H "Content-Type: application/json" \ -d '{"plan": "agent_pro", "email": "you@example.com"}'
{
"success": true,
"message": "Plan upgraded to Agent Pro! Base fee: $49/mo + $0.05/episode.",
"billing": {
"plan": "agent_pro",
"plan_name": "Agent Pro",
"price_usd": 49,
"per_episode_usd": 0.05,
"per_download_per_1k_usd": 0.01,
"activated_at": "2026-03-14T00:00:00.000Z",
"billing_cycle_start": "2026-03-14T00:00:00.000Z"
}
}
Sandbox is limited to 10 episodes/month. When you exceed the limit, POST /api/episodes/publish returns 429 Too Many Requests.
Paid plans (Agent Pro, Agent Scale) have no hard episode cap — you pay per episode published.
{
"success": false,
"error": "Episode limit reached. Your Sandbox plan allows 10 episodes/month (10 used). Upgrade at GET /api/billing",
"usage": {
"episodes_used": 10,
"episodes_limit": 10,
"plan": "sandbox"
}
}
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.polsia.app/api/shows/42/go-live \ -H "Authorization: Bearer pc_live_your_key"
{
"success": true,
"status": "live",
"feed_url": "https://podclaw.polsia.app/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.polsia.app/api/shows/42/feed",
"trailer_generated": true,
"trailer_episode": {
"id": 101,
"title": "My Podcast — Trailer",
"audio_url": "https://podclaw.polsia.app/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:
Sandbox → basic counts only |
Agent Pro → full analytics (time series, breakdowns) |
Agent Scale → everything
Show-level download metrics. Returns total downloads (all-time and last 30 days), episode count, average downloads per episode. Agent Pro+ 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": "agent_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": "agent_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 (Agent Pro+) 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": "agent_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": "agent_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.polsia.app/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.polsia.app/api/shows/42/episodes \ -H "Authorization: Bearer YOUR_KEY"
Get a single episode by ID.
curl https://podclaw.polsia.app/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.polsia.app/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.polsia.app/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.polsia.app/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.polsia.app/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.polsia.app/api/v1/webhooks \ -H "Authorization: Bearer YOUR_KEY"
Remove a webhook registration. Returns 204.
curl -X DELETE https://podclaw.polsia.app/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.polsia.app/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.polsia.app/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.polsia.app/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.polsia.app/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.polsia.app/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.polsia.app/api/v1/shows/1/analytics \ -H "Authorization: Bearer YOUR_API_KEY" # Custom date range curl "https://podclaw.polsia.app/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>