transistor · diff
git:20260711.a5f7e74 to git:20260830.05b99be
262 added, 52 removed. Audit A to A.
---
name: transistor
- description: 'Manage Transistor.fm podcast hosting from the terminal: view shows,
- list episodes, check analytics, and get subscriber counts. Use when the user mentions
- Transistor, podcast hosting, podcast analytics, show management, or episode tracking.'
+ description: >-
+ Operate Transistor.fm podcast hosting from the terminal: verify API access,
+ browse shows and episodes with JSON:API-aware output, run the episode
+ publish lifecycle (create draft, attach audio, publish or schedule via the
+ dedicated publish endpoint), pull download analytics, and manage private
+ podcast subscribers and webhooks. Use when the user mentions Transistor,
+ Transistor.fm, podcast hosting, episode publishing, private podcast
+ subscribers, or podcast download analytics. Do not use this skill for other
+ podcast hosts (Buzzsprout, Libsyn, Megaphone, Spotify for Creators), for
+ editing or producing audio, or for feed/RSS parsing — the bundled CLI
+ manages a Transistor account through its v1 API and cannot create new
+ shows (dashboard-only).
license: MIT
- compatibility: Requires TRANSISTOR_API_KEY env var (from Settings → API Keys in the
- Transistor.fm dashboard), Python 3.8+, and the `requests` library. Uses the Transistor.fm
- v1 REST API with JSON:API format responses.
+ compatibility: Requires TRANSISTOR_API_KEY env var (Account page -> API Access
+ at https://dashboard.transistor.fm/account), Python 3.8+, and `requests`.
+ Read commands need a working key; `--help` and `--dry-run` never do.
metadata:
- tags: transistor, podcast, podcast-hosting, analytics, api-client
- sources: https://transistor.fm/, https://developers.transistor.fm/
+ tags: transistor, podcast, podcast-hosting, episodes, analytics, api-client
+ sources: https://developers.transistor.fm/, https://support.transistor.fm/
---
- # transistor-cli — Transistor.fm Podcast Hosting
+ # transistor — Transistor.fm podcast hosting from the terminal
- Manage your Transistor.fm podcast shows, episodes, subscribers, and analytics from the terminal. Uses the Transistor.fm v1 REST API.
+ Drive a Transistor.fm account over its v1 JSON:API: shows, episodes, the
+ draft→publish lifecycle, per-day download analytics, private-podcast
+ subscribers, and webhooks. Responses are JSON:API documents; the bundled CLI
+ unwraps them (`--json`) while preserving the raw shapes agents need for jq.
+ Write commands are guarded: episode creation is always a draft, and
+ publishing goes through its own dedicated endpoint.
## Setup
- 1. Get your API key from Transistor.fm: **Settings → API Keys** (bottom of the page)
- 2. Set the environment variable:
+ 1. Find your API key on the Transistor dashboard **Account page → API
+ Access** (https://dashboard.transistor.fm/account) and export it:
```bash
- export TRANSISTOR_API_KEY="your-transistor-api-key"
+ export TRANSISTOR_API_KEY="<API_KEY>"
```
- `--help` and `--dry-run` work without credentials.
+ 2. Verify the key (GET /v1 — the authorization probe; there is no
+ /v1/user route):
+ ```bash
+ transistor user # name and time zone
+ transistor user --json | jq '{id, name, time_zone}'
+ ```
+
+ A key carries the dashboard role of its user (owner / admin / team member)
+ per podcast. `--help` and `--dry-run` work without credentials. Requests are
+ rate-limited to 10 per 10 seconds; the CLI dies with a clear 429 message
+ instead of hammering.
+
## Essential Commands
- ### user — Current user info
+ ### user — authorization probe
```bash
- transistor-cli user # email and timezone
- transistor-cli user --json # machine-readable
+ transistor user # who does this key belong to?
+ transistor user --json
```
- ### shows — List all shows
+ ### shows / show — browse podcasts
```bash
- transistor-cli shows # all shows with episode/subscriber counts
- transistor-cli shows --json # machine-readable with IDs
+ transistor shows # newest-updated first
+ transistor shows --private --json # private podcasts only
+ transistor show --id <SHOW_ID_OR_SLUG> # full attributes incl. feed_url
+ transistor shows --page 1 --per 20 --json
```
- Shows title, episode count, subscriber count, and ID.
+ Show ids and slugs are interchangeable on most show-scoped routes. Show
+ resources carry no counts fields — count via `episodes --show ... --json`,
+ then `meta.totalCount`.
- ### episodes — List episodes
+ ### episodes / episode — browse episodes
```bash
- transistor-cli episodes # last 20 episodes across all shows
- transistor-cli episodes --show 12345 # filter by show ID
- transistor-cli episodes --limit 50 # more results
- transistor-cli episodes --show 12345 --json # machine-readable
+ transistor episodes # newest first, all shows
+ transistor episodes --show <SHOW_ID> --status draft # drafts for one show
+ transistor episodes --show <SHOW_ID> --per 50 --page 1 --json
+ transistor episode --id <EPISODE_ID> --include show # compound doc + parent show
```
- Get show IDs from `transistor-cli shows --json`. Shows season/episode numbers, title, status, duration, and publish date.
+ `--include show` adds `included[]` (the JSON:API compound document); every
+ episode item in `--json` output already carries `show_id` resolved from
+ relationships. `--limit` works as an alias for `--per` for old scripts.
- ### analytics — Download and play analytics
+ ### episode-create / episode-update — drafts and metadata
```bash
- transistor-cli analytics # totals across all shows
- transistor-cli analytics --show 12345 # filter by show ID
- transistor-cli analytics --json # machine-readable with full totals
+ transistor episode-create --show <SHOW_ID> --title "Ep 12: Roasting" \
+ --season 2 --number 4 --audio-url "https://uploads.example.com/ep12.mp3"
+ transistor episode-update --id <EPISODE_ID> --title "New title"
+ transistor episode-update --id <EPISODE_ID> --audio-url "<AUDIO_URL>" # attach audio
```
- Shows total downloads and plays (when available).
+ `episode-create` ALWAYS produces a draft (`status: "draft"`,
+ `published_at: null`) — it never publishes. `episode-update` changes
+ metadata or attaches audio and never touches publishing state.
- ## Global Flags
+ ### episode-publish — the lifecycle switch
- All flags work in any position:
+ ```bash
+ transistor episode-publish --id <EPISODE_ID> # publish now
+ transistor episode-publish --id <EPISODE_ID> --status scheduled \
+ --published-at "2026-09-03 09:00:00" # schedule
+ transistor episode-publish --id <EPISODE_ID> --status draft # unpublish
+ ```
+ Hits `PATCH /v1/episodes/<EPISODE_ID>/publish` with
+ `episode[status]=draft|scheduled|published` — the documented dedicated
+ endpoint. The CLI refuses to publish an episode whose `media_url` is still
+ empty (an unplayable item would hit every subscriber's feed); pass
+ `--force` to override.
+
+ ### authorize-upload — local audio (max 5GB)
+
```bash
- transistor-cli --json shows # flag before subcommand
- transistor-cli shows --json # flag after subcommand
- transistor-cli --dry-run episodes # preview (no API call)
- transistor-cli --force episodes --limit 100 # override safety checks
- transistor-cli --quiet shows # suppress non-essential output
- transistor-cli --verbose episodes # detailed logging
+ transistor authorize-upload --filename ep12.mp3 # plan only
+ transistor authorize-upload --filename ep12.mp3 --file ./ep12.mp3
```
- Extra flag vs other CLIs: `--force` overrides internal safety checks (e.g. large limits).
+ Returns (and, with `--file`, performs) the signed PUT; the printed
+ `audio_url` is what you attach with `episode-create`/`episode-update`. The
+ signed URL expires (~600 s in the docs' example).
+ ### analytics / episode-analytics — downloads per day
+
+ ```bash
+ transistor analytics --show <SHOW_ID> # last 14 days
+ transistor analytics --show <SHOW_ID> \
+ --start-date 01-08-2026 --end-date 28-08-2026 --json
+ transistor episode-analytics --id <EPISODE_ID> --json
+ ```
+
+ Dates are dd-mm-yyyy and must come in pairs. Analytics attributes are
+ per-day `downloads[]` arrays, not totals; the CLI sums them into
+ `downloads_total` and keeps the raw array.
+
+ ### subscribers — private podcast audience
+
+ ```bash
+ transistor subscribers --show <SHOW_ID> --json
+ transistor subscriber-create --show <SHOW_ID> --email "listener@example.com"
+ transistor subscriber-batch --show <SHOW_ID> --email "a@example.com" --email "b@example.com"
+ transistor subscriber-delete --show <SHOW_ID> --email "a@example.com" # or --id
+ ```
+
+ ### webhooks — push instead of poll
+
+ ```bash
+ transistor webhooks --show <SHOW_ID>
+ transistor webhook-create --show <SHOW_ID> --event episode_published \
+ --url "https://example.com/hooks/transistor"
+ transistor webhook-delete --id <WEBHOOK_ID>
+ ```
+
+ Events: `episode_created`, `episode_published`, `subscriber_created`,
+ `subscriber_deleted`. Cap: 50 per account. With a 10 req / 10 s limit,
+ webhooks beat polling for freshness.
+
+ ## Global flags
+
+ ```bash
+ transistor --json shows # flags work in any position
+ transistor --dry-run episodes --show <SHOW_ID> # request plan, zero network
+ transistor --force episode-publish --id <EPISODE_ID> # skip the audio guard
+ transistor --quiet shows # suppress non-essential output
+ transistor --verbose episodes # detailed stderr logging
+ ```
+
+ `--dry-run` emits `{"dry_run": true, "method", "path", "params"}` (write
+ commands add the exact `body` that would be sent — bracket keys and all),
+ so you can verify a plan before touching the API. `--help` and `--dry-run`
+ never require credentials.
+
+ ## Pipeline recipes
+
+ ### Create, attach audio, publish (the core workflow)
+
+ ```bash
+ export TRANSISTOR_API_KEY="<API_KEY>"
+ SHOW=$(transistor shows --json | jq -r '.shows[0].id') # string id
+ AUDIO=$(transistor authorize-upload --filename ep12.mp3 --file ./ep12.mp3 --json | jq -r '.audio_url')
+ EP=$(transistor episode-create --show "$SHOW" --title "Ep 12" \
+ --audio-url "$AUDIO" --json | jq -r '.id') # draft id
+ transistor episode-publish --id "$EP" # dedicated endpoint
+ transistor episode --id "$EP" --json | jq '{status, media_url, published_at}'
+ ```
+
+ Each stage's output feeds the next: `shows` → string `id`,
+ `authorize-upload` → string `audio_url`, `episode-create` → string draft
+ `id`, `episode-publish` → final `status`. Stage 2 is skippable when the
+ audio already has a public URL (pass it straight to `episode-create`).
+
+ ### Draft triage: what is not out yet?
+
+ ```bash
+ transistor episodes --show <SHOW_ID> --status draft --json \
+ | jq -r '.episodes[] | [.id, .title, (if .media_url == "" then "no-audio" else "ready" end)] | @tsv'
+ # publish the ready ones (rate limit: 10 req / 10 s — add sleep 1 between calls)
+ ```
+
+ ### Weekly downloads report
+
+ ```bash
+ transistor shows --json | jq -r '.shows[].id' | while read -r S; do
+ transistor analytics --show "$S" --json \
+ | jq -r --arg id "$S" '[$id, (.downloads_total|tostring)] | @tsv'
+ sleep 1
+ done
+ ```
+
+ ## JSON and jq
+
+ `--json` keys are stable snake_case wrappers around the JSON:API document:
+ `shows`/`episodes`/`subscribers`/`webhooks` (arrays with `meta` attached),
+ flat objects for single resources, `dry_run`/`method`/`path`/`params`/`body`
+ for plans. Attributes keep Transistor's own names — `status`, `season`,
+ `number`, `duration` (seconds), `media_url`, `share_url`, `published_at`,
+ `feed_url` — so jq selectors transfer directly to raw `curl` against
+ api.transistor.fm. Collection pagination surfaces as
+ `meta.currentPage`/`meta.totalPages`/`meta.totalCount`. Example:
+ `transistor episodes --show <SHOW_ID> --json | jq -r '.episodes[] |
+ [.id, .title, .status] | @tsv'`. For compound documents the CLI resolves
+ relationships (`show_id`) and prints `included` show summaries in human
+ mode; with raw curl, match `included[]` by `type` and
+ `relationships.show.data.id`.
+
## Known Gotchas
- - **API key from Settings → API Keys** — Not from the user profile or account page. Navigate to Settings → API Keys at the bottom of the Transistor.fm dashboard.
- - **JSON:API format** — Transistor uses the JSON:API spec. All responses are nested under a `data` key, and attributes are under `data[].attributes`. The CLI unwraps these for display, but raw JSON output shows the full JSON:API structure.
- - **Show IDs are required for filtered queries** — Use `transistor-cli shows --json` to get show IDs first, then pass them to `--show` for episodes and analytics.
- - **Pagination uses cursor-based pagination** — The `--limit` flag controls the page size. Default is 20 for episodes. The API returns `meta` with pagination info.
- - **Analytics are totals only** — The analytics endpoint returns aggregate totals (downloads, plays). Per-episode analytics are not available via this CLI.
- - **Transistor API is append-only via this CLI** — The CLI implements GET endpoints for reading. Creating/updating shows or episodes is not covered here.
- - **Rate limits** — Transistor.fm has rate limits. The CLI does not auto-retry on 429 responses.
+ - **Publishing is a separate endpoint, never a side effect** — POST
+ /episodes and PATCH /episodes/:id cannot change `status`. If an episode
+ stays draft, the missing step is `PATCH /v1/episodes/<ID>/publish` with
+ `episode[status]=published`. (The pre-thickening CLI had no publish path
+ at all.)
+ - **The user probe is `GET /v1`** — `/v1/user` and `/v1/authorization` are
+ 404s, and the user resource has no email attribute (name and time_zone
+ only).
+ - **Pagination is `pagination[page]` + `pagination[per]`** (defaults 0 and
+ 10; docs' examples request page 1). `pagination[limit]` and
+ `page[number]` are silently ignored — loops using them re-read page 1
+ forever. Loop while `meta.currentPage < meta.totalPages`.
+ - **Show resources carry no counts** — derive episode/subscriber counts
+ from filtered listings' `meta.totalCount`.
+ - **Analytics are per-day arrays, not totals** — sum `attributes.downloads`
+ (the CLI provides `downloads_total`); date bounds are dd-mm-yyyy and
+ come in pairs; do not parse the row date format (docs' examples are
+ inconsistent between sections).
+ - **Show creation is dashboard-only** — there is no POST /v1/shows;
+ `show-update` is the only show write.
+ - **Rate limit 10 req / 10 s** — a 429 blocks access for 10 seconds. No
+ retry headers; back off, batch subscriber imports, cache responses, and
+ use webhooks for freshness. Transistor explicitly forbids using the API
+ as a website back end (parse the RSS feed for that).
+ - **`episode[published_at]` uses the show's time zone** (a show attribute),
+ not UTC; scheduling and backdating both ride the publish endpoint.
+ - **Audio processing is asynchronous** — watch `audio_processing` /
+ `processing_failure` after attaching audio; publishing an unprocessed or
+ failed file pushes silence to subscribers.
+ - **Signed upload URLs expire** (~600 s in the docs' example): authorize,
+ PUT with the returned `content_type`, attach promptly.
+ - **Error bodies are not formally specified** — the CLI handles JSON:API
+ `errors[]` arrays and bare `{"message": ...}` objects, flattening either
+ to one stderr line; 401 (bad key), 403 (role), 404 (bad id/route), 429
+ (rate limit) have distinct hints.
- ## References
+ ## When to use
- - [scripts/transistor-cli](scripts/transistor-cli) — The CLI binary. Built following the cli-builder patterns: `--json`, `--dry-run`, `--force`, `--quiet`, `--verbose`, dual-output via `emit()`, lazy auth.
- - [Transistor API Docs](https://developers.transistor.fm/) — Official API reference.
- - [Transistor.fm Dashboard](https://dashboard.transistor.fm/) — Settings → API Keys for your API key.
+ Use this skill for anything that reads or drives a Transistor.fm account
+ through its API: verifying API access, browsing shows/episodes (including
+ drafts and compound documents), running the episode lifecycle
+ (create → attach audio → publish/schedule/unpublish), pulling download
+ analytics windows, importing or revoking private-podcast subscribers, and
+ registering webhooks.
+
+ ## When not to use
+
+ Do not use this skill for other podcast hosts (Buzzsprout, Libsyn,
+ Megaphone, Spotify for Creators — use their own APIs/tooling); for audio
+ production or editing (ffmpeg and DAW territory); for generic RSS feed
+ parsing or website rendering (parse the feed XML directly — Transistor says
+ the API is not a back-end data source); for creating new shows (the API
+ cannot — the dashboard does); or for platform-level distribution questions
+ (Apple/Spotify submission is a dashboard and RSS concern).
+
+ ## Reference Files
+
+ | File | Use it for |
+ | ---- | ---------- |
+ | [references/auth-and-basics.md](references/auth-and-basics.md) | x-api-key auth, key location and role scoping, the JSON:API envelope (data/attributes/relationships/included[]) with jq patterns, pagination params, error surfaces |
+ | [references/endpoint-catalog.md](references/endpoint-catalog.md) | Every route's method, path, and parameters (shows, episodes, publish, uploads, analytics, subscribers, webhooks) plus routes that do not exist |
+ | [references/episode-publish-lifecycle.md](references/episode-publish-lifecycle.md) | The draft/scheduled/published state machine, the exact publish request/response shapes, create→audio→publish recipes in CLI and curl, authorize-upload detour |
+ | [references/gotchas-and-recipes.md](references/gotchas-and-recipes.md) | Symptom → cause → fix field guide (404 user route, silent pagination, 429 storms...) and multi-step workflows (bulk scheduling, analytics reports, subscriber import, webhooks) |
+
+ ## Available Scripts and Prerequisites
+
+ - `scripts/transistor` — the bundled Python CLI (`--json`, `--dry-run`,
+ `--force`, `--quiet`, `--verbose`, `--help` everywhere). Imports only the
+ standard library and `requests`; sends write bodies exactly as documented
+ (bracket-key form fields).
+ - `scripts/test_transistor.py` — offline test suite (pytest + unittest
+ compatible); all HTTP mocked with canned JSON:API documents, zero network
+ egress, no live-call cases (Transistor is a keyed API).
+ - Requires Python 3.8+, `requests`, and `TRANSISTOR_API_KEY` for live
+ commands (Account page → API Access). No service is started by this skill.