save-to-spotify
Create polished audio episodes with TTS narration, rich timelines, cover images, and save them to Spotify via the save-to-spotify CLI.
Open source Open in the app JSON README (API)
About
Create polished audio episodes with TTS narration, rich timelines, cover images, and save them to Spotify via the save-to-spotify CLI.
Details
- Kind
- Plugins
- Topic
- Media, design & games
- Publisher
- spotify
- Origin
- marketplace
- Category
- ferramentas
- Stars
- 396
- Forks
- 25
- Open pull requests
- 3
- Last push
- 2026-07-22T08:41:59Z
- Repository state
- ativo
- Language
- Go
- License
- Apache-2.0
- Added
- 2026-08-30 01:48:58
- Updated
- 2026-08-30 01:48:58
- Origin id
spotify/save-to-spotify/save-to-spotify
README
# Save to Spotify
A command-line tool for saving audio content to Spotify. Built for agents and automation, generate a daily briefing, language lesson, or meeting recap, then push it to Spotify where it's available alongside your other listening.
## What you can make
Point your agent at content you have, and get a finished episode on Spotify:
- **Morning briefing** — your calendar, tasks, and overnight repo activity as a 3-minute daily brief
- **Deep dive** — turn a topic, article, or PDF into a personalized audio explainer
- **Meeting recap** — drop in a transcript, get a 2-minute summary with action items
- **Travel guide** — a private audio itinerary for your next trip
- **News roundup** — a daily roundup from the newsletters, feeds, and sites you follow
- **Bedtime story** — a custom story using your kid's name and favorite setting
Describe what you want in plain English; the agent sources, scripts, narrates, and saves it.
## Making the audio
This CLI *saves* audio to Spotify — it does not generate speech itself. You bring a voice engine:
- **Kokoro** (recommended) — free, local, no API key, no limits (~340 MB)
- **OpenAI TTS** — high quality, needs `OPENAI_API_KEY`
- **ElevenLabs** — most natural, needs `ELEVENLABS_API_KEY`
No key? Kokoro is the default. Already have an OpenAI or ElevenLabs key? The agent detects it and offers to use it — no extra setup.
Run `save-to-spotify tts status` to see what's available, or `save-to-spotify tts add` to register any other engine.
## Quick Start
Prompt your agent to install:
```text
> Install Save to Spotify by running https://saveto.spotify.com/install.sh
```
Installs the CLI and exposes the skill to Claude Code, Cursor, Codex, and any agent that reads `.agents/skills/`.
Once installed, invoke with `/save-to-spotify` (Claude Code) or `$save-to-spotify` (Codex), or just describe what you want in plain English.
## Install
### Curl-bash
Same script the Quick Start prompt runs:
```bash
curl -fsSL https://saveto.spotify.com/install.sh | bash
```
Common options:
```bash
# Pin a specific version
curl -fsSL https://saveto.spotify.com/install.sh | bash -s -- --version 0.2.0
# Custom install directory
curl -fsSL https://saveto.spotify.com/install.sh | bash -s -- --dir ~/.local/bin
# Binary only, skip the agent skill
curl -fsSL https://saveto.spotify.com/install.sh | bash -s -- --no-skills
```
### Skills.sh
Install the agent skill to Claude Code, Cursor, Codex, and 50+ other agents:
```bash
npx skills add spotify/save-to-spotify
```
### ClawHub / OpenClaw
Save to Spotify is listed on ClawHub as [`@spotify/save-to-spotify`](https://clawhub.ai/spotify/save-to-spotify). Install it into your active OpenClaw workspace:
```bash
openclaw skills install @spotify/save-to-spotify
```
The ClawHub listing installs the agent skill. The skill uses the `save-to-spotify` CLI, so if the binary is not already on your `PATH`, run the curl-bash installer above.
### Claude Code plugin marketplace
```
/plugin marketplace add spotify/save-to-spotify
/plugin install save-to-spotify@save-to-spotify
```
### Manual
If you can't run the install script:
1. Download `save-to-spotify-{os}-{arch}-v{version}.zip` and its matching `.sha256` from the [releases page](https://github.com/spotify/save-to-spotify/releases).
2. Verify integrity:
```bash
shasum -c save-to-spotify-darwin-arm64-v0.2.0.zip.sha256
```
3. Unzip, you get the binary plus a `skills/save-to-spotify/` tree.
4. Move the binary to a directory on your `PATH` and `chmod +x` it (not needed on Windows).
5. (Optional) Copy `skills/save-to-spotify/` into your agent's skill directory, e.g. `~/.claude/skills/save-to-spotify/` or `~/.cursor/skills/save-to-spotify/`.
### Windows notes
- Run the install script in **Git Bash** (ships with Git for Windows); it installs `save-to-spotify.exe` to `~/.local/bin` and offers a PATH fix.
- The Windows binaries are not code-signed yet: SmartScreen may block the first run. Unblock with right-click → Properties → Unblock, or `Unblock-File save-to-spotify.exe` in PowerShell.
### Build from source
Requires [Go](https://go.dev/dl/) 1.21+.
```bash
go build -ldflags "-X github.com/spotify/save-to-spotify/cmd.commit=$(git rev-parse --short HEAD)" \
-o save-to-spotify .
sudo mv save-to-spotify /usr/local/bin/
```
## Authentication
Log in once, the CLI stores your token and refreshes it automatically.
```bash
# Opens your browser to authorize (default)
save-to-spotify auth login
# Headless mode, prints a URL to open on any device, then you paste back the redirect URL
# Useful for SSH sessions and remote servers
save-to-spotify auth login --no-browser
# Check if you're logged in
save-to-spotify auth status
# Log out (removes stored credentials)
save-to-spotify auth logout
```
Tokens are stored in `~/.config/save-to-spotify/token.json` (using `$XDG_CONFIG_HOME` if set). If you use DPoP-bound tokens (the default), the sibling `dpop_key.json` is also required. For CI and headless environments, see the [CI / Automation guide](docs/ci-automation.md).
## Commands
### upload
The primary command. Uploads a media file and creates an episode in one step. This is the main entrypoint for agents, a single command handles show creation, file upload, and episode metadata.
```bash
save-to-spotify upload <file> --title <title> [flags]
```
| Flag | Description |
|---|---|
| `--title` | Episode title (required) |
| `--show-id` | Target show ID or URI |
| `--new-show` | Create a new show with this title and save the file to it |
| `--summary` | Episode description |
| `--image` | Cover image file (`.jpg`/`.png`, max 1 MB) |
| `--language` | Language code (default: `en`) |
If `--show-id` or `--new-show` is not specified, the CLI will use your most recently created show, or create a new one if none exists.
```bash
# Save an audio file to Spotify, uses your most recent show (or creates one automatically)
save-to-spotify upload ./recap.mp3 --title "Monday Standup Recap"
# Save to a specific show
save-to-spotify upload ./lesson.mp3 --title "Spanish Lesson 12" --show-id spotify:show:abc123
# Organize saved items into separate shows
save-to-spotify upload ./lecture.mp3 --title "CS 101 — Lecture Notes Week 6" --new-show "Lecture Notes"
# With cover image and description
save-to-spotify upload ./briefing.mp3 \
--title "Morning Briefing" \
--summary "Weather and calendar for today" \
--image ./cover.jpg
```
**Supported formats:** `.mp3`, `.m4a`, `.wav`, `.ogg`.
### shows
Shows are containers for your saved content. You can use a single show for everything or create separate shows to organize by topic (e.g. "Daily Briefings", "Language Practice", "Meeting Recaps").
```bash
# List your shows
save-to-spotify shows
# Create a show
save-to-spotify shows create --title "Lecture Notes" --summary "Audio summaries of CS 101 lectures"
# Get show details
save-to-spotify shows get <id>
# Delete a show and all its episodes
save-to-spotify shows delete <id>
```
`shows create` flags:
| Flag | Description |
|---|---|
| `--title` | Show title (required) |
| `--summary` | Show description (default: "(no description)") |
| `--image` | Cover image file (`.jpg`/`.png`, max 1 MB) |
| `--language` | Language code (default: `en`) |
### episodes
Each saved file becomes an episode within a show.
```bash
# List episodes (for your most recent show)
save-to-spotify episodes
# List episodes for a specific show
save-to-spotify episodes --show-id spotify:show:abc123
# Create an episode (more control than `upload`, same underlying operation)
save-to-spotify episodes create --title "Sprint Review Notes" --file ./review.mp3
# Check if an episode is ready for playback
save-to-spotify episodes status <episode-id>
# Wait until an episode is ready (default timeout: 5m)
save-to-spotify episodes status <episode-id> --wait
# Wait with a custom readiness timeout
save-to-spotify episodes status <episode-id> --wait 2m
# Delete an episode
save-to-spotify episodes delete <episode-id>
```
`episodes create` flags:
| Flag | Description |
|---|---|
| `--title` | Episode title (required) |
| `--file` | Media file path (required) |
| `--show-id` | Target show ID or URI (default: most recent show) |
| `--summary` | Episode description |
| `--image` | Cover image file (`.jpg`/`.png`, max 1 MB) |
| `--language` | Language code |
### timeline
Add chapter markers and in-player companion content to saved episodes. A timeline can include chapters, images, external links, and Spotify-native references via `spotify_entity`. Prefer `spotify_entity` whenever the thing you want listeners to open already exists on Spotify, prefer full `spotify:...` URIs over bare IDs or `open.spotify.com` URLs, and note that a `link` and a `spotify_entity` can both appear in the same timeline when both destinations are useful.
```bash
# Get timeline items for an episode
save-to-spotify timeline get <episode-id>
# Set (replace all) timeline items from a JSON file
save-to-spotify timeline set --episode-id <episode-id> --from-file timeline.json
# Delete all timeline items
save-to-spotify timeline delete <episode-id>
```
Example `timeline.json`:
```json
{
"items": [
{ "chapter": { "title": "Introduction", "start_time_ms": 0 } },
{ "chapter": { "title": "Key Concepts", "start_time_ms": 45000 } },
{ "spotify_entity": { "start_time_ms": 60000, "duration_ms": 30000, "uri": "spotify:track:abc123" } },
{ "link": { "start_time_ms": 95000, "duration_ms": 20000, "url": "https://example.com/slides" } },
{ "chapter": { "title": "Summary", "start_time_ms": 120000 } }
]
}
```
Timeline rules:
- `chapter`: optional, but if present there must be at least 2; the first must start at `0`.
- `image`: requires a positive `duration_ms` and a local `.jpg`/`.png` file path or an `image_token`.
- `link`: requires a positive `duration_ms` and an HTTP(S) URL.
- `spotify_entity`: requires a full `spotify:...` URI; `duration_ms` is optional but must be positive when set.
- Companion items (`image`, `link`, `spotify_entity`) must not overlap in time, but a `link` and a `spotify_entity` may share the same segment/chapter as long as their windows do not overlap.
### list
Alternative command for listing your content.
```bash
# List episodes for the most recent show
save-to-spotify list
# List episodes for a specific show
save-to-spotify list episodes --show-id spotify:show:abc123
# List all shows
save-to-spotify list shows
```
### Other commands
| Command | Description |
|---|---|
| `update` | Check for and install CLI updates |
| `token` | Print access token to stdout (for piping to `curl`, etc.) |
| `version` | Print version and commit hash |
## Global flags
| Flag | Description |
|---|---|
| `--json` | Output results as JSON (for scripting and automation) |
| `--timeout <duration>` | API request timeout (default: `30s`, e.g. `1m`, `90s`) |
## Documentation
- [Agent integration](docs/agent-integration.md) — JSON mode, typical workflow, error handling
- [CI / Automation](docs/ci-automation.md) — GitHub Actions setup, headless auth, credential persistence
## Environment variables
| Variable | Purpose | Default |
|---|---|---|
| `SAVE_TO_SPOTIFY_AUTH_TOKEN` | Bearer token override (expires in ~1 hour, no auto-refresh — see [CI guide](docs/ci-automation.md)) | — |
| `SAVE_TO_SPOTIFY_BACKEND_URL` | Override the backend API URL | `https://saveto.spotify.com` |
| `SAVE_TO_SPOTIFY_TIMEOUT` | API request timeout (e.g. `30s`, `2m`) | `30s` |
| `SAVE_TO_SPOTIFY_CLIENT_ID` | OAuth client ID override | built-in |
| `SAVE_TO_SPOTIFY_NO_UPDATE_CHECK` | Disable passive update checks | off |
| `SAVE_TO_SPOTIFY_HEADERS` | Additional headers for backend requests (JSON object of header name/value pairs; only `X-STS-*` headers are accepted) | — |