# podfeast

> Hosts many podcasts from one deployment. Each podcast has its own RSS feed, show page and media. Base URL: https://podfeast.com

## Shows hosted here

Use the slug in API paths (`/api/v1/podcasts/<slug>/...`).

- **Book Explained** — slug `book-explained`, 66 episodes, feed https://podfeast.com/show/book-explained/feed.xml
- **Clean Food Score** — slug `clean-food-score`, 140 episodes, feed https://podfeast.com/show/clean-food-score/feed.xml
- **Open Exam Hub** — slug `open-exam-hub`, 202 episodes, feed https://podfeast.com/show/open-exam-hub/feed.xml
- **PaycheckZip** — slug `paycheckzip`, 33 episodes, feed https://podfeast.com/show/paycheckzip/feed.xml
- **Trace the Name: Handwriting Tips** — slug `trace-the-name`, 28 episodes, feed https://podfeast.com/show/trace-the-name/feed.xml

## Public URLs (no auth)

- `https://podfeast.com/show/{slug}/feed.xml` — RSS feed to submit to Apple Podcasts, Spotify, etc.
- `https://podfeast.com/show/{slug}/` — show page with players
- `https://podfeast.com/show/{slug}/episodes/{id}/chapters.json` — Podcasting 2.0 JSON chapters (only for episodes with chapters; linked from the feed)
- A podcast with a `custom_domain` is served at the root of that domain instead (`/feed.xml`, `/`).

## Authentication

Send `Authorization: Bearer $PODFEAST_TOKEN`. Never print, log or paste the token.
- **publisher** key: may only manage episodes and upload media for the podcasts it was issued for.
- **admin** key: can also create/edit/delete podcasts and manage keys. Humans mint keys, not agents.
Call `GET /api/v1/me` to see what the current key can do.

## Publish an episode (plain HTTP, nothing to install)

    H="Authorization: Bearer $PODFEAST_TOKEN"
    # 1. upload the audio (up to 95 MB per request; bigger files use the multipart endpoints below)
    curl -X PUT "https://podfeast.com/api/v1/podcasts/<slug>/media?filename=ep1.mp3" -H "$H" --data-binary @ep1.mp3
    #    -> {"path": "<nonce>/ep1.mp3", ...}
    # 2. publish it
    curl -X POST https://podfeast.com/api/v1/podcasts/<slug>/episodes -H "$H" -H 'content-type: application/json' \
      -d '{"title": "Title", "audio_path": "<path>", "client_ref": "<stable id>"}'

Retries are safe: re-sending the same `client_ref` returns the existing episode (HTTP 200, `deduplicated: true`) instead of creating a duplicate.
Optional fields: description (plain text), image_path, pub_date, season, episode_number, episode_type, explicit, duration_seconds, chapters.
Chapters: `"chapters": [{"start": 0, "title": "Intro"}, {"start": 312.5, "title": "Part one"}]` (seconds, sorted by start; optional `url`/`img`). PATCH replaces the list; `null` removes it.
Draft: `"status": "draft"`. Schedule: a future `pub_date`. Both stay out of the feed until live.
Large files: `POST /uploads` -> `PUT /uploads/parts` (one per `part_size` chunk) -> `POST /uploads/complete`, then use the returned `path`.

Optional CLI (separate install, from the owner's repo) wraps all of this, detects duration and handles large files:
`podfeast episode publish --podcast <slug> --audio ep.mp3 --title "Title"` — see `podfeast --help`.

## Endpoints (all under https://podfeast.com/api/v1)

- `GET /api/v1/me` (any key) — Show what the current key can do
- `GET /api/v1/podcasts` (any key) — List podcasts
- `POST /api/v1/podcasts` (admin) — Create a podcast
- `GET /api/v1/podcasts/{slug}` (any key) — Get a podcast
- `PATCH /api/v1/podcasts/{slug}` (admin) — Update podcast details
- `DELETE /api/v1/podcasts/{slug}` (admin) — Delete a podcast and everything in it
- `GET /api/v1/podcasts/{slug}/episodes` (any key) — List episodes (newest first)
- `POST /api/v1/podcasts/{slug}/episodes` (any key) — Publish an episode
- `GET /api/v1/podcasts/{slug}/episodes/{id}` (any key) — Get an episode
- `PATCH /api/v1/podcasts/{slug}/episodes/{id}` (any key) — Update an episode
- `DELETE /api/v1/podcasts/{slug}/episodes/{id}` (any key) — Delete an episode
- `PUT /api/v1/podcasts/{slug}/media` (any key) — Upload an audio or image file (up to 95 MB)
- `POST /api/v1/podcasts/{slug}/uploads` (any key) — Start a multipart upload (large files)
- `PUT /api/v1/podcasts/{slug}/uploads/parts` (any key) — Upload one part of a multipart upload
- `POST /api/v1/podcasts/{slug}/uploads/complete` (any key) — Finish a multipart upload
- `POST /api/v1/podcasts/{slug}/uploads/abort` (any key) — Abort a multipart upload
- `GET /api/v1/keys` (admin) — List API keys
- `POST /api/v1/keys` (admin) — Create an API key
- `DELETE /api/v1/keys/{id}` (admin) — Revoke an API key

Full machine-readable contract: https://podfeast.com/api/v1/openapi.json

## Errors

Non-2xx responses are JSON: `{"error": {"code": "...", "message": "...", "details": ...}}`.
Codes: invalid_request (400), unauthorized (401), forbidden (403), not_found (404), conflict (409), payload_too_large (413).
Unknown JSON fields are rejected, so a typo'd field name is an error rather than silently ignored.
