Unleashed Podcasts CLI
A command-line interface for the UnleashedPodcasts.com
private v1 API, shaped like the AWS CLI: predictable noun verb commands, credentials
from the environment, JSON in and JSON out.
Status: episodes (with transcripts), short form videos, people, ideas, tasks, and leads, plus login, whoami, and configure. See PRD.md for the roadmap.
Install
Once published to PyPI:
uv tool install unleashed-podcasts-cli
Straight from the repository, which works today:
uv tool install git+https://github.com/ScottKeckWarren/UnleashedPodcastsCli
Run it once without installing anything:
uvx --from git+https://github.com/ScottKeckWarren/UnleashedPodcastsCli unleashed --help
pipx works everywhere uv tool does, if you prefer it. Plain pip install only
inside an activated virtualenv — a CLI belongs in its own environment, not alongside
your project's dependencies.
To upgrade or remove:
uv tool upgrade unleashed-podcasts-cli
uv tool uninstall unleashed-podcasts-cli
Working on the CLI itself
git clone git@github.com:ScottKeckWarren/UnleashedPodcastsCli.git
cd UnleashedPodcastsCli
uv tool install --editable . # `unleashed` on PATH, tracking your working tree
uv sync && uv run pytest # run the suite
Log in
unleashed login
That opens your browser on the site's approval page. Sign in if asked, press
Approve, and the CLI saves a personal access token to ~/.unleashed/config.
The token never passes through the browser — the browser carries a one-use code that
only the waiting login process can redeem, and only for two minutes.
unleashed login --profile client-a # write a named profile
unleashed login --local # write ./.unleashed/config
unleashed login --api-url http://localhost/apiv1 # log in to another host
unleashed login --ability episodes:read # ask for less (repeatable)
unleashed login --device-name studio-mac # name shown in the web app
unleashed login --no-browser # print the URL instead
By default the token asks for every ability the site grants. The device name defaults to your hostname; it is what you will see in the web app when you revoke the token.
Check which account and abilities a token carries:
unleashed whoami
A 401 means the token is dead — run unleashed login again. A 403 means the token
is alive but was not granted the ability that command needs; it exits 8.
Configure
To paste a token by hand instead — for CI, or the legacy static token:
unleashed configure
That writes ~/.unleashed/config:
[default]
api_url = https://unleashedpodcasts.com/apiv1
api_token = your-token
Several accounts on one machine
A .unleashed/config in a project directory overrides the home one key by key, so a
project file can carry nothing but a token and inherit the URL from home.
~/.unleashed/config api_url + api_token (your personal account)
~/work/client-a/.unleashed/config api_token (client A's token)
~/work/client-b/.unleashed/config api_token (client B's token)
Run from anywhere inside ~/work/client-a — including a subdirectory — and you are
using client A. The local file is found by walking up from the working directory.
cd ~/work/client-a
unleashed configure --local
Profiles
For several accounts without separate directories, use sections:
[default]
api_url = https://unleashedpodcasts.com/apiv1
api_token = tok-personal
[client-a]
api_url = https://unleashedpodcasts.com/apiv1
api_token = tok-client-a
unleashed --profile client-a episodes create ...
unleashed configure --profile client-a
export UNLEASHED_PROFILE=client-a # or set it once
A profile does not inherit from [default]. Give each one both keys.
Precedence
Highest first:
--profileselects which section is read- the nearest local
.unleashed/config ~/.unleashed/configUNLEASHED_API_URLandUNLEASHED_API_TOKEN
Environment variables sit at the bottom on purpose: a stale export in your shell should
not override a project directory that has already declared which account it belongs to.
They stay the right tool for CI and containers, which set variables and never write files.
Not sure which account you are about to use? --dry-run names the profile and the file
it came from.
$ unleashed episodes create --dry-run ...
# profile default (from /Users/you/work/client-a/.unleashed/config)
POST https://unleashedpodcasts.com/apiv1/episodes
...
Token safety
configure writes the file with mode 0600. If a config file holding a token is
readable by other users, every command warns and tells you the chmod that fixes it.
There is deliberately no --token flag — a token on the command line lands in your shell
history and in the process table of every other user on the machine.
Episodes
Five actions, the same five the API exposes. No ad-hoc verbs.
unleashed episodes list
unleashed episodes create --podcast-uuid ... --name ... --target-published-date ...
unleashed episodes get <uuid>
unleashed episodes update <uuid> --status Scheduled
unleashed episodes delete <uuid>
Create
You need the UUID of the podcast the episode belongs to. Until the podcasts resource ships in v0.3, copy it from the web app.
unleashed episodes create \
--podcast-uuid 7f3a1c2e-... \
--name "Episode 101: Something Useful" \
--target-published-date 2026-09-15 \
--status Draft \
--description "Show notes here."
Pass a payload instead of flags:
unleashed episodes create --cli-input-json '{"podcast_uuid": "...", "name": "...", "target_published_date": "2026-09-15"}'
List
Filters are flags, named for the field they match. Every page is fetched by default, so a result is never a silent truncation.
unleashed episodes list --status Edit --transcript-status none --no-has-cover-art
unleashed episodes list --sort -target_published_date --max-items 20
unleashed episodes list --no-paginate # first page only
--sort takes one allowlisted column; prefix it with - for descending. A column
outside the allowlist is caught before the request is sent:
$ unleashed episodes list --sort nonsense
Error: Cannot sort by 'nonsense'. Allowed columns: target_published_date,
actual_published_date, created_at, name, status (prefix - for desc)
Update
An omitted flag leaves the field alone. --clear-<field> sends an explicit null.
Passing no flags at all is a successful no-op.
unleashed episodes update <uuid> --status Scheduled
unleashed episodes update <uuid> --clear-description # sets description to null
unleashed episodes update <uuid> --is-published # publishes it
unleashed episodes update <uuid> --no-is-published # hides it
Only nullable fields get a --clear- flag, and --description X --clear-description
together is a usage error rather than a coin toss.
--podcast-uuid has no flag here — it is immutable after create, so the manifest keeps
it off this command entirely.
Delete
unleashed episodes delete <uuid>
The API soft-deletes. There is no restore endpoint; recovery is a database task.
Short form videos
The same five actions, plus a video-file attachment.
unleashed short-form-videos create --title "Three mistakes new hosts make" \
--podcast-episode-uuid 7f3a1c2e-... --target-date 2026-10-01
unleashed short-form-videos list --origin generated --standalone --sort -target_date
unleashed short-form-videos get <uuid>
unleashed short-form-videos update <uuid> --clear-podcast-uuid # detach podcast and episode
unleashed short-form-videos delete <uuid>
A short stands alone, sits under a podcast, or sits under an episode. An episode brings
its own podcast, so --podcast-uuid is only needed without one. Moving a short to
another podcast drops its episode.
Video file
unleashed short-form-videos video-file attach <uuid> --file-uuid <file-uuid>
unleashed short-form-videos video-file detach <uuid>
Attaching replaces any video already attached; detaching keeps the file itself. Upload the video in the web app first — file uploads use the browser session, not the API token, so the CLI does not upload.
People
The same five actions, plus a metadata sub-resource.
unleashed people create --name "Ada Lovelace" --email ada@example.test \
--metadata twitter=@ada --metadata source=cli
unleashed people list --no-has-email --last-outreach-before 2026-06-01 --sort last_outreach
unleashed people list --podcast-uuid 7f3a1c2e-... # people who appeared on a show
unleashed people get <uuid>
unleashed people update <uuid> --last-outreach 2026-09-22 --clear-email
unleashed people delete <uuid>
--last-outreach-before includes people never contacted at all — they are the most
overdue. last_outreach cannot be cleared once set, so it has no --clear- flag.
Metadata
Metadata is a flat map of strings, numbers, booleans, and nulls. --metadata KEY=VALUE
on create and update sends strings, and update merges into the existing keys.
To delete keys, send typed values, or replace the map, use the sub-resource:
unleashed people metadata merge <uuid> --set tier=gold --unset twitter
unleashed people metadata merge <uuid> --cli-input-json '{"episodes": 3, "vip": true}'
unleashed people metadata replace <uuid> --set only=this # every other key is removed
unleashed people metadata replace <uuid> --cli-input-json '{}' # clear every key
replace with no --set and no --cli-input-json is refused rather than wiping the map.
Dry run
Every action takes --dry-run, which prints the request with the token redacted and
sends nothing:
$ unleashed episodes create --dry-run --podcast-uuid ... --name ... --target-published-date ...
# profile default (from /Users/you/work/client-a/.unleashed/config)
POST https://unleashedpodcasts.com/apiv1/episodes
Accept: application/json
Authorization: Bearer ***redacted***
Content-Type: application/json
{
"podcast_uuid": "7f3a1c2e-...",
"name": "Episode 101: Something Useful",
"target_published_date": "2026-09-15"
}
Episode transcripts
An episode's most recent transcript is read-only.
unleashed episodes transcript get <uuid> # status plus the full text
unleashed episodes transcript get <uuid> --format none # status only, for polling
unleashed episodes transcript utterances list <uuid> # speaker-attributed segments
unleashed episodes transcript utterances list <uuid> --with-words --max-items 50
An unfinished transcript reads with a null text and an empty utterance list, so the same
command can be polled. An episode with no transcript at all exits 5.
Ideas
The episode-idea backlog. The podcast is optional; an idea without one sits in the general backlog.
unleashed ideas create --title "Interview a sound engineer" --podcast-uuid <uuid>
unleashed ideas list --status open --unassigned --sort position
unleashed ideas get <uuid>
unleashed ideas update <uuid> --status converted --converted-episode-uuid <episode-uuid>
unleashed ideas delete <uuid>
--status and --converted-episode-uuid exist only on update, and the API accepts a
converted episode only alongside --status converted.
Tasks
Your own tasks, optionally tied to an episode. These routes need the tasks:read and
tasks:write abilities; a token issued before they existed exits 8 — run
unleashed login again.
unleashed tasks create --title "Book the guest" --due-date 2026-11-10 --episode-uuid <uuid>
unleashed tasks list --status todo --sort due_date
unleashed tasks get <uuid>
unleashed tasks update <uuid> --status completed --clear-due-date
unleashed tasks delete <uuid>
Leads
Shows worth approaching. The five actions, plus batch create.
unleashed leads create --show-name "The Example Show" --feed-url https://example.test/feed.xml
unleashed leads list --status Identified --min-llm-estimate 60 --sort -llm_estimate
unleashed leads update <uuid> --status Contacted
A single duplicate lead exits 6. Pass a JSON list of up to 25 leads to create them in
one request:
unleashed leads create --cli-input-json "$(cat leads.json)"
The API answers a list with per-item outcomes — created, duplicate, or invalid —
and one bad item never fails the rest. The CLI prints every outcome, then exits 3 if
any item was invalid.
Output
| Flag | Behaviour |
|---|---|
--output table |
Human-readable key/value block. Default on a TTY. |
--output json |
The record as JSON. Default when piped. |
--output text |
Tab-separated values, for awk and cut. |
--query |
A JMESPath expression applied before rendering. |
--dry-run |
Print the request with the token redacted and send nothing. |
unleashed episodes create --query uuid --output text ... # prints just the new UUID
unleashed episodes list --query "[].[uuid,name,status]" --output text | column -t
Exit codes
| Code | Meaning |
|---|---|
| 0 | Success |
| 1 | Unexpected or transport error |
| 2 | Usage error, including missing or unreadable configuration |
| 3 | Validation rejected by the API (422) |
| 4 | Auth failure (401), or login was denied or failed |
| 5 | Not found (404) |
| 6 | Conflict (409) |
| 7 | Rate limited after retries were exhausted (429) |
| 8 | Forbidden: the token lacks the ability (403) |
Scripts can branch on these without parsing output.
A note on the Python package
This package is a CLI. The classes inside it are internal, unstable, and may change in any release — do not import them. A supported library API is planned once the resource abstraction has been tested against a second resource.
Contributing
See CONTRIBUTING.md. Every change is test-first and the build fails below 90% coverage.
License
MIT. See LICENSE.
Metadata
Release files for unleashed-podcasts-cli 0.4.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| unleashed_podcasts_cli-0.4.0.tar.gz | 107.0 kB | With provenance | Details for unleashed_podcasts_cli-0.4.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| unleashed_podcasts_cli-0.4.0-py3-none-any.whl | Python 3 | none | any | Details for unleashed_podcasts_cli-0.4.0-py3-none-any.whl |
Total release size: 144.0 kB
Release files / unleashed_podcasts_cli-0.4.0.tar.gz
| Download URL | unleashed_podcasts_cli-0.4.0.tar.gz |
|---|---|
| Size | 107.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
d9319cd2ef57bee841797aa5fb006778e92533547d8a52147ae5adbfeb7aedcc
|
|
BLAKE2b-256 checksum How to use checksums |
137d8f877910572b6168e86d7bde2012ba775543c412ba867fca3af836363457
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 3, 2026.
Transparency logRelease files / unleashed_podcasts_cli-0.4.0-py3-none-any.whl
| Download URL | unleashed_podcasts_cli-0.4.0-py3-none-any.whl |
|---|---|
| Size | 37.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
0bfae03ef76f882280cdc7284da3fd04a7588b09559b26b9d6936fc338620cd0
|
|
BLAKE2b-256 checksum How to use checksums |
f8f20cb47b1fb21a898e17edd7eb3ec32e297842b9bb0c4a8da516432445e316
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 3, 2026.
Transparency logSecurity for unleashed-podcasts-cli 0.4.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for unleashed_podcasts_cli-0.4.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 3, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.