audd is the command-line tool for AudD music recognition. It recognizes music in files, URLs, folders, long recordings, and from the microphone; records and reports on your monitored streams; and manages your AudD account. It prints tables on a terminal and JSON when piped, so people, scripts, and AI agents can all use it.
| Where | Command |
|---|---|
| Node.js | npm install -g @audd/cli |
| Python | pipx install audd-cli or uv tool install audd-cli |
| macOS (Homebrew) | brew install auddmusic/tap/audd |
| macOS, Linux (script) | curl -fsSL https://github.com/AudDMusic/audd-cli/releases/latest/download/install.sh | sh |
| Windows (Scoop) | scoop bucket add auddmusic https://github.com/AudDMusic/scoop-bucket then scoop install auddmusic/audd |
| Windows (installer) | download and run the installer for x64 or ARM64; it adds audd to your PATH |
| Go | go install github.com/AudDMusic/audd-cli/cmd/audd@latest |
| Manually | download an archive from Releases and put audd on your PATH |
Every package contains the same single binary for macOS, Linux, and Windows on x86-64 and ARM64. The install script downloads the release archive for your system, checks it against the release's SHA-256 checksums, and puts audd in ~/.local/bin without sudo (AUDD_INSTALL_DIR and AUDD_VERSION change the folder and version).
--at clips and audd listen use ffmpeg (or sox for listen) when it is installed; other commands need no extra tools. The Docker image has no ffmpeg.
npx @audd/cli recognize song.mp3 # Node.js
uvx audd-cli recognize song.mp3 # Python
docker run --rm -e AUDD_API_TOKEN -v "$PWD:/work" ghcr.io/auddmusic/audd-cli recognize song.mp3audd login # sign in; audd fetches your API token
# or: export AUDD_API_TOKEN=your-api-token (get your token at https://dashboard.audd.io)
audd recognize song.mp3
audd recognize https://audd.tech/example.mp3
audd recognize song.mp3 --format json | jq .result.titleOn a terminal a match prints as a card with the cover art, artist, title, album, label, and links. Piped, it is one JSON document:
{
"schema_version": 1,
"input": "song.mp3",
"cached": false,
"result": {
"artist": "Imagine Dragons",
"title": "Warriors",
"album": "Warriors",
"release_date": "2014-09-18",
"label": "Universal Music",
"timecode": "00:40",
"song_link": "https://lis.tn/Warriors"
}
}When nothing matches, result is null and the exit code is 0.
--return apple_music,spotify adds metadata from those services (also deezer and musicbrainz):
audd recognize song.mp3 --return apple_music,spotify --format json | jq .result.spotify.uriRun audd with no arguments on a terminal, or audd ui, for a full-screen interface to everything audd does:
- Recognize: files, URLs, and folders, with the plan shown before any requests are spent; the Microphone tab identifies the music playing near you.
- Now playing, Streams, and Recognitions: live results, stream setup, recorded plays, past recognitions, and batch jobs.
- Account and Settings: plan, usage, payment links, the API token, profiles, and every
audd configkey. - Help: getting started, the CLI guide, every command, and every key.
The bottom line shows the command each action runs, and y copies it. ctrl+k opens a palette of every command with a form for its arguments and flags. Limits and confirmations work as on the command line: interactive mode asks before spending more than one request and never adds --yes. audd ui --section streams opens a section directly.
Menu on the left, page on the right. Arrows move, Enter goes in, Esc comes back:
- Sidebar: ↑/↓ pick a section, → or Enter opens it (
1-7jump straight to one). - Tab bar (Help, Streams, Recognitions, Account): ←/→ switch tabs, ↓ or Enter goes into the page.
- Page: ↑/↓ move through the list or form, → or Enter opens the selected item; ↑ from the top goes back to the tab bar.
← and Esc always go back one level, and the path at the top of the page shows where you are. Inside typed text ← moves the cursor; on a choice such as Source, ←/→ change it. When a text field is selected you are typing in it: everything you type goes in, ctrl+u clears it, and Esc stops typing but keeps the value and the form. Recognize opens typing in its File, URL, or folder field; other pages open with no text field selected. Enter in a form's last text field runs it, and after an error the cursor is back in the field it is about. Outside text fields each letter means one thing everywhere: o opens in the browser, c copies, r refreshes, d removes after asking. What a page can do is on buttons under it (Tab reaches them, Enter presses one); a letter shown on a button presses it directly. Typing on a saved value replaces it, Enter or Edit edits it, and Esc puts the old value back; settings of audd and a stream's URL save right away, and u (or ctrl+z) undoes the last change until you make another or leave the section. Spending requests and anything that removes or replaces asks in a dialog that opens on Cancel: move to the action and press Enter (or click it); n or Esc cancels. 1-7 jump to a section from the menu, a tab bar, or a list (never in a form), and [ and ] switch tabs outside text fields. A key that does nothing where you press it says so above the hints. q quits from the menu or a tab bar, and asks first when something typed is not saved or a command is running. The mouse works too: click sections, tabs, rows, fields, and buttons, and scroll with the wheel. To select text with the mouse, hold Shift (Option in iTerm2); AUDD_NO_MOUSE=1 turns the mouse off.
Without a terminal, or with CI or AUDD_NO_TUI set, audd prints its help instead, so scripts and agents get plain output.
audd login signs you in to your AudD account. Signing in gives audd your API token and enables the account commands (account, usage, billing, token rotate, token refresh-local). audd picks one of two ways to approve the sign-in:
- Browser: on a desktop (macOS, Windows, or Linux with a display),
auddopens the sign-in page and also prints its URL. If the browser is on another machine, approve there and paste back the address the browser was sent to. - Code: over SSH, in containers, and in scripts and agents,
auddprints a page and a short code. Open the page on any device, check that it shows the same code, and approve. The code expires in 15 minutes.
To choose the method yourself, use audd login --browser or audd login --device. On the approval page you can untick any permission; audd auth status shows what was approved, and a command that needs one you unticked (payment links, rotating the token) asks for it again when you use it.
Without a terminal, audd login prints a login_pending JSON line with verification_uri_complete and user_code for an agent to show you, then waits until you approve.
The API token is the first of these that is set:
--tokenAUDD_API_TOKENaudd config set token your-api-token(kept in the system credential store, or in~/.config/audd/credentials.jsonwith mode 0600 when there is none)- the token
audd loginfetched
audd auth status shows which one is in use. audd logout removes the sign-in and the token it fetched, and stops a background stream recorder that was using that token. CI jobs and agents only need AUDD_API_TOKEN. Profiles (--profile work, AUDD_PROFILE) keep separate sign-ins, tokens, and settings.
The standard endpoint analyzes up to the first 12 seconds of audio. To identify a later moment, send a clip from there (needs ffmpeg):
audd recognize mix.mp3 --at 1:30 # a 12-second clip from 1:30
audd listen # record 10 seconds from the microphoneTo find every song in a long recording, use the enterprise endpoint. It is billed per 12-second chunk, so it needs --limit (chunks per file, or none):
audd recognize mix.mp3 --enterprise --limit 20 --dry-run # see the plan; nothing is sent
audd recognize mix.mp3 --enterprise --limit 20 --tracklist # tracks with start and end timesA folder, a glob, several files, or a list on stdin is a batch. Batches need --max-files (or none), show a plan with the estimated requests and cost, and ask before starting:
audd recognize ./recordings --max-files 500 --dry-run
audd recognize ./recordings --max-files 500 > results.jsonl
find . -name '*.m4a' | audd recognize - --max-files 100 --yesEvery batch is a job saved after each file. Ctrl-C, a crash, or a dropped SSH session loses nothing that finished, and nothing finished is sent again:
audd jobs list
audd jobs resume <id>
audd jobs resume <id> --retry-failedA plain resume sends only the files that are left. Files that failed after their upload started, or that were being sent when audd stopped, may have been counted, so they are sent again only with --retry-failed, which retries the failures a retry can fix (connection, server, rate limit, quota). Audio that cannot be decoded fails the same way again, so it is sent again only with --retry-all-failed.
Running the same batch again while its job is unfinished offers to resume it. Without a terminal it stops with exit 6: pass --resume to continue that job, or --new to recognize every file again.
Results are cached by file contents, so the same audio under another name is free ("cached": true). --no-cache sends it anyway.
--dry-runshows what a command would send and its estimated cost.--limit(enterprise) and--max-files(batches) are required, except with--dry-run, which shows the uncapped plan. There is no unbounded default.--max-requests Nstops before spending more than N requests;audd config set max_requests Nmakes it the default. A stopped batch can be resumed.- Anything that can cost more than one request shows a plan and asks first. Without a terminal it needs
--yes. - Custom catalog uploads (
audd catalog add) are never retried automatically. - Payment commands never charge:
audd billing subscribe,renew, andbuyprint a Stripe link that you open and approve yourself.
audd streams add https://radio.example/stream.mp3 --id 1
audd streams list
audd now-playing
audd streams history --since 24h
audd streams report --by artist --since 30d
audd streams export --since 7d --format csv > plays.csvThe first time you use a streams command, audd starts a recorder in the background. It longpolls all your streams and saves every result to a local database, so history, reports, and now-playing are complete even when no window is open. AudD also keeps the last 30 or so results of each stream, which the recorder reads when it starts to fill short gaps.
audd streams recorder status
audd streams recorder start --install-service # start the recorder when you log in
audd streams recorder stop
audd streams record # the same recorder in the foreground
audd config set streams.background_recorder falseThe recorder runs until you stop it or restart the computer; --install-service adds a systemd user unit, a launchd agent, or a Windows scheduled task.
Receiving results needs a callback URL on the account. When there is none, audd offers to set the placeholder https://audd.tech/empty/; without a terminal to ask in, as in a container, --yes sets it. To use your own, run audd streams callback set <url> first.
For servers, run audd streams record under your own service manager or in Docker:
docker run -d --name audd-recorder --restart unless-stopped \
-e AUDD_API_TOKEN -v audd-data:/home/nonroot ghcr.io/auddmusic/audd-cli streams record --yesaudd streams watch --forward-to http://localhost:8080/callback (and streams record --forward-to) POSTs each result to your URL in the shape of an AudD callback, for testing a callback handler locally.
audd now-playing is the terminal version of the AudD widget: a full-screen card for each stream with the cover art, title, artist, album, label and year of the latest song, and the songs played before it (up to 30). With several streams it shows a grid; the arrow keys pick a stream and Enter zooms in. A stream that is down shows its status instead of a song. --notify sends a desktop notification on each new song.
What the card shows depends on when AudD sends a stream's results:
- By default AudD sends a result when the song ends, with when it started and how long it played. The card shows the song as "Just played" (later "Last played"), how long it played, and when it ended.
- For a stream added with
audd streams add <url> --id N --start, results arrive when songs start, so the card shows "Now playing" with the time since the song started. These results carry no play length, soaudd streams reportcan't count airtime for that stream. - A progress bar needs the track length, which stream results include only with Apple Music, Spotify, or Deezer metadata. Turn it on with
audd streams callback set <url> --return apple_music.
For status bars and scripts:
audd now-playing --once
audd now-playing 1 --format "{{.Artist}} - {{.Title}}" # a line each time the song changes
audd now-playing --timeout 1h > plays.jsonl # JSON lines when pipedJSON output and --format templates include the state of the song (playing, just_played, or last_recognized) with the times that go with it; audd now-playing --help lists the fields.
audd browse opens a full-screen explorer with four tabs:
- Recent: everything you recognized with
audd, with search, full details, links, ISRC and UPC. - Jobs: batch progress, failures, results, and enterprise tracklists; resume from here.
- Streams: what each stream is playing, its health, and recent plays; add or remove streams.
- Usage: requests this billing cycle, per day.
Keys: arrows move, Enter opens, / filters, o opens the song link, c copies it, e exports the view to CSV or JSON, ? lists every key, q quits. audd jobs browse <id> and audd streams watch open the matching tab. Without a terminal, browse prints the same data as JSON, or as flat rows (a column per field, a row per song) with --format csv.
Cover art is drawn as an image on terminals that support Kitty graphics, iTerm2 inline images, or sixel (Kitty, Ghostty, iTerm2, WezTerm, foot, Konsole, and others), and with colored half-block characters everywhere else. --no-art (on recognize, listen, and now-playing) or AUDD_NO_ART=1 turns art off, and AUDD_ART=kitty|iterm2|sixel|blocks|blocks256|none picks the method.
audd account
audd usage
audd usage --check --min-remaining 1000 # exit 8 when fewer requests remain
audd billing plans
audd billing subscribe <plan> --open # opens a Stripe payment link
audd token show # masked; --reveal prints it
audd token rotate- Piped output is JSON. Every document has
"schema_version": 1. Streaming commands (batches,streams watch,streams export, pipednow-playing) print JSON lines with a"type"ofresult,progress,summary,error, orevent. --format table|json|jsonl|csv(orAUDD_FORMAT) picks a format,--fields result.artist,result.titletrims it (CSV columns are flat, such asartist; a field the output can never have exits 2 before anything is sent, and one a result lacks prints as null), and--quiethides notes on stderr and, in table output, prints only the essential line.audd commands --jsondescribes every command, flag, output, and exit code.audd api <method> key=value...calls any AudD API method directly.
audd agent-setup # write a skill or rules file for Claude Code, Cursor, or Codex
claude mcp add audd -- audd mcp # use audd as a local MCP server
audd docs api # the AudD API docs as markdownaudd mcp is a local MCP server over stdio with tools for recognition, enterprise scans (with a required limit), streams, now-playing, custom catalog uploads, usage, account status, and docs. It uses the same token, profile, and limits as the CLI.
The full command reference is docs/cli.md (also audd docs cli).
| Code | Meaning |
|---|---|
| 0 | Success, including no match |
| 1 | Unexpected error, or no match with --fail-on-no-match |
| 2 | Invalid arguments or input, or a missing tool such as ffmpeg |
| 3 | Authentication: missing or rejected token, or sign-in needed |
| 4 | Quota, plan, or feature not available on the account |
| 5 | Network or server error |
| 6 | Safety bound: a missing --limit or --max-files, a confirmation needed (--yes), --max-requests reached, or an unfinished job for the same files (--resume or --new) |
| 7 | Batch finished with some files failed |
| 8 | usage --check found fewer requests remaining than --min-remaining |
| 130 | Stopped with Ctrl-C; audd jobs resume continues a batch |
Errors go to stderr. When output is not a table they are JSON: {"schema_version":1,"error":{"code","api_code","message","hint","retryable"}}, where hint is usually the next command to run.
audd update installs the latest release, or prints the update command for the package manager you installed it with. A notice about new releases appears at most once a day on a terminal, never in CI; AUDD_NO_UPDATE_CHECK=1 turns it off.
There is no telemetry. audd talks only to AudD services, cover art hosts, and GitHub (for the release check). HTTPS_PROXY and NO_PROXY are honored, and --debug logs requests with tokens removed.
Each release has checksums.txt with the SHA-256 of every archive, a Sigstore bundle for it, an SBOM per archive, and GitHub build provenance:
sha256sum --ignore-missing -c checksums.txt
cosign verify-blob checksums.txt --bundle checksums.txt.sigstore.json \
--certificate-identity-regexp '^https://github.com/AudDMusic/audd-cli/' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com
gh attestation verify audd_1.0.0_linux_amd64.tar.gz --repo AudDMusic/audd-cliRun gofmt -l ., go vet ./..., and go test -race ./... before sending changes. The binary must build with CGO_ENABLED=0 for Linux, macOS, and Windows.
Commands live in internal/cli, one file per command group. A file adds its commands from init() with Register, so nobody edits root.go:
func init() {
Register(func(root *cobra.Command, a *app.App) {
root.AddCommand(newStreamsCmd(a))
})
}- The callback runs while the tree is built. The
*app.Appit receives is filled in (config, profile, secrets, printer, flags) just before the command runs, so read its fields insideRunE. - Put each top-level command in a help group with
GroupID(cli.GroupRecognize,GroupStreams,GroupAccount,GroupLocal,GroupAgents,GroupOther). - Mark commands whose piped output is a stream of records with
Annotations: map[string]string{cli.AnnotationStreaming: "true"}; commands that stream only sometimes calla.Out.SetStreaming(). - Do not set
PersistentPreRunEon the root. Subcommands may set their own; the root's still runs first. - Return
*output.Error(viaoutput.Errf) for expected failures so the exit code and hint are right. Any other error fromRunEexits with 1. - Write results through
a.Out(Result,Event,Info,Confirm), never directly toos.Stdout. - Interactive mode runs commands in-process and builds a form for each one from its
Useline (<arg>required,[arg]optional,...repeats) and flags.TestPaletteCoversEveryCommandfails when a command has an argument or flag type the form cannot show.
internal/app holds hooks that let packages call each other without import cycles. Each one has a single owner that assigns it; everyone else only calls it.
| Hook | Owner | Assigned in |
|---|---|---|
app.RunBatch |
internal/jobs |
init() |
app.RunExplorer |
internal/tui |
init() |
app.RunHome |
internal/tui |
init() |
app.RenderResult |
internal/tui (plain text until then) |
init() |
app.EnsureRecorder |
internal/streams |
init() |
App.APIClient |
internal/api |
a cli.Register callback |
App.Account |
internal/account |
a cli.Register callback |
Unassigned hooks return an error with code not_implemented.
testutil.Isolate(t)points the config, cache, and data dirs at temp dirs (AUDD_CONFIG_DIR,AUDD_CACHE_DIR,AUDD_DATA_DIR) and uses the file credential store.testutil.Exec(t, cli.Main, stdin, args...)runs the CLI in-process.testutil.Golden(t, name, got)compares againsttestdata/<name>.golden; setAUDD_UPDATE_GOLDEN=1to rewrite.- Tests never call the AudD API or MCP server. Use
httptestservers, and use0123456789abcdef0123456789abcdefwherever a token is needed.
MIT. Copyright (c) 2026 AudD, LLC (https://audd.io).