zPodRSS first. Bandwidth off the bill.

zPod API

Scoped Bearer tokens for scripts and agents. Create drafts, upload audio, attach artwork, chapters, and transcripts, set a soundbite, then publish with an explicit scope.

Auth and Scopes

Create a token in Studio under Account → Developer / API Tokens. Creating a token needs a paid plan on a studio you belong to; tokens made earlier keep working. The secret is shown once. Send it as Authorization: Bearer zpod_live_… on /api/v1/* and the hosted MCP at /mcp only. Cookie sessions stay on Studio routes.

Default scopes: shows:read, episodes:read, episodes:write, media:upload. publish, webhooks:write, shows:write, and domains:write are opt-in at token create or edit. Calls that publish, schedule, or unpublish without publish return insufficient_scope. Webhook CRUD without webhooks:write does the same.

shows:write creates shows (POST /api/v1/shows), changes show settings (PATCH /api/v1/shows/{showId}), and sets a cover from a URL. domains:write adds or changes a show's custom domain (PUT /api/v1/shows/{showId}/custom-domain) and runs Check Hostname. Saving a domain needs the studio owner's token, because each domain is billed to the studio's card; Check Hostname needs an owner or admin role on the show. Deleting a show and removing a custom domain are Studio-only.

Optional show filter. Empty filter means every show the token owner can access. Rate limit is 60 requests per minute per token.

A token acts as the account that created it. GET /api/v1/me works with any valid token and returns that account, the studios and show count the token reaches, and its scopes, so an agent can check what it is acting for in one call. When a token reaches no shows, /api/v1/me and /api/v1/shows add a hint saying why.

Scopes never raise the token owner's show role. Writes need edit rights on the show (owner, admin, editor, or producer). An analyst or affiliate token sees released episodes only and gets forbidden on a write.

Canonical Feed URL

Show payloads include feedUrl as the canonical rss.podfora.com URL. A vanity path (for example https://rss.podfora.com/mahler-aac) wins over https://rss.podfora.com/{slug}-{feedId}.

Create → Presign → PUT → Commit → Publish

# 1. Create draft
curl -sS -X POST "https://zpod.cc/api/v1/shows/$SHOW_ID/episodes" \
  -H "Authorization: Bearer $ZPOD_API_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"title":"Episode 42","description":"Notes in **Markdown**."}'

# 2. Presign audio
curl -sS -X POST "https://zpod.cc/api/v1/episodes/$EPISODE_ID/media/presign" \
  -H "Authorization: Bearer $ZPOD_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"filename":"ep42.mp3","contentType":"audio/mpeg","byteSize":12345678}'

# 3. PUT bytes to uploadUrl from the presign response (upload directly; do not post the file through this API)

# 4. Commit (stays draft)
curl -sS -X POST "https://zpod.cc/api/v1/episodes/$EPISODE_ID/media/commit" \
  -H "Authorization: Bearer $ZPOD_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"ticket":"$TICKET","byteSize":12345678}'

# 5. Optional artwork / chapters / transcript (same Studio storage)
# POST …/artwork/presign → PUT → POST …/artwork/commit
# PUT …/chapters  -d '{"chapters":[{"startTime":0,"title":"Open"}]}'
# PATCH …  -d '{"soundbites":[{"startTime":95,"duration":40,"title":"The Hook"}]}'
# POST …/transcript/presign → PUT → POST …/transcript/commit

# 6. Checklist (no side effects)
curl -sS -X POST "https://zpod.cc/api/v1/episodes/$EPISODE_ID/validate" \
  -H "Authorization: Bearer $ZPOD_API_TOKEN"

# 7. Publish now (token needs publish)
curl -sS -X POST "https://zpod.cc/api/v1/episodes/$EPISODE_ID/publish" \
  -H "Authorization: Bearer $ZPOD_API_TOKEN" \
  -H "Idempotency-Key: $(uuidgen)"

# Schedule in the show timezone (ISO, naive local, or dd MMM YYYY HH:mm)
curl -sS -X POST "https://zpod.cc/api/v1/episodes/$EPISODE_ID/schedule" \
  -H "Authorization: Bearer $ZPOD_API_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"publishedAt":"06 Oct 2026 09:00"}'

Error Codes

JSON {"error":"code"} with optional message. Common codes: unauthenticated (no Authorization header), malformed_authorization (a header that is not a Bearer token), invalid_token, token_revoked, token_expired, rate_limited, insufficient_scope, forbidden, not_found, invalid_title, invalid_ticket, episode_not_draft, schedule_time_required, artwork_invalid, chapters_invalid, soundbites_invalid, soundbite_out_of_range, invalid_webhook_url, spend_cap.

Hosted MCP

zPod hosts its MCP server at https://zpod.cc/mcp (Streamable HTTP). Nothing to install. Apps that connect through sign-in (the Claude and ChatGPT connectors) only need the URL: they open zPod, you sign in and approve, and the app gets its own token. Clients that take headers can send an API token in Authorization: Bearer zpod_live_… instead. Every tool call runs through /api/v1 with that token, so its scopes, show filter, and rate limit apply.

claude mcp add --transport http zpod https://zpod.cc/mcp --header "Authorization: Bearer $ZPOD_API_TOKEN"

Any client that takes a remote MCP URL with headers:

{
  "mcpServers": {
    "zpod": {
      "url": "https://zpod.cc/mcp",
      "headers": { "Authorization": "Bearer zpod_live_…" }
    }
  }
}

Sign-in is OAuth 2.1 with PKCE and dynamic client registration; discovery is at /.well-known/oauth-protected-resource and /.well-known/oauth-authorization-server. It needs a paid plan, like any token. A connected app shows under Account → Developer / API Tokens, where Disconnect ends it. Its access token renews hourly, and the connection ends after 90 days without use.

Same tools as the client-run package below, starting with whoami. The client-run @zpod/mcp package still exists for local stdio use.

Cursor MCP (client-run)

Official @zpod/mcp speaks stdio. You run it locally with npx or from this repo. Tools wrap /api/v1 only. Pass small JSON and upload URLs — never base64 audio in the model context. Same Bearer token and scopes as REST.

Tools: whoami, list_shows, get_show, list_directory_links, parse_directory_link, find_directory_listings, confirm_directory_listing, set_directory_link, list_episodes, get_episode, create_episode, update_episode (both take soundbites), get_upload_url, commit_media, get_artwork_upload_url, commit_artwork, set_chapters, set_transcript_generation, get_transcript_upload_url, commit_transcript, validate_episode, publish_episode (now, or schedule via published_at), unpublish_episode.

{
  "mcpServers": {
    "zpod": {
      "command": "npx",
      "args": ["-y", "@zpod/mcp"],
      "env": {
        "ZPOD_API_TOKEN": "zpod_live_…",
        "ZPOD_API_BASE": "https://zpod.cc"
      }
    }
  }
}

In this monorepo: pnpm --filter @zpod/mcp start with the same env. ZPOD_API_BASE is optional and defaults to https://zpod.cc.

Webhooks

Opt-in webhooks:write on a token, or create endpoints in Studio Account. HTTPS only. The signing secret is shown once. Deliveries send X-zPod-Signature: t=<unix>,v1=<hmac-sha256-hex> over timestamp.payload with a 5-minute skew window. v1 events: episode.published, episode.scheduled, episode.unpublished, media.ready. Optional show filter. Async best-effort with capped retries.

curl -sS -X POST "https://zpod.cc/api/v1/webhooks" \
  -H "Authorization: Bearer $ZPOD_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name":"Publish bot","url":"https://example.com/zpod","events":["episode.published","media.ready"]}'

OpenAPI

Download OpenAPI 3.1

MCP is a thin wrapper over this HTTP API.

How zPod Is Built