Audio publishing
Publish an album on Sound.fish
Sound.fish gives an album a page of its own at https://sound.fish/@handle/album: a waveform-and-spectrogram player, hearts, comments, and play counts. Every track keeps its revisions in order, listeners hear the current one, and the player embeds on any site. Publishing needs a Hraness account with a username; listening needs no account.
Create and manage an album
- Sign in and choose Upload, or open sound.fish/audio/albums/new. Give the album a title. The slug defaults to the title and becomes the page address
/@handle/slug. - On the manage page, add tracks by uploading MP3, WAV, or FLAC files. Each file is checked after it arrives; the track appears once it is ready.
- Add a cover, a description, and details. The preview player above the form plays the album exactly as listeners will hear it, with plays, hearts, and comments switched off.
- Choose Publish. The album page, your artist page at
/@handle, and the embed go live. Unpublish returns the album to a draft that only you can open.
Albums start as drafts. A draft has no public page, no embed, and collects no plays, hearts, or comments. The manage page shows a usage line (albums 1/1 · tracks 3/10 · storage 120 MiB/2 GiB) from the same account read the CLI's whoami prints.
Tracks and revisions
Upload a file to an existing track to add a revision. Revisions are numbered in order (v1, v2, …) and dated by UTC calendar day, so a comment left on an earlier version reads v1 · Sep 14. The current revision is the one listeners hear; promote any earlier or later revision at any time. A revision may carry a note and details that only you see. A file you already stored is not uploaded again.
Reorder tracks with Up and Down. Deleting a track or an album removes its page, frees its slug, and releases its storage; free accounts keep one album, so deleting makes room for another.
Descriptions and details
Descriptions are plain text; blank lines separate paragraphs. They appear under the album title and inside the active track's lane. Details are up to 32 key–value pairs (keys are lowercase letters, digits, dots, dashes, or underscores; values up to 1,000characters) rendered as a "Details" list on the album page and in the track lane. The embed shows neither.
Formats and limits
Sound.fish is free to use, with these limits per account:
| Limit | Free plan |
|---|---|
| Albums | 1 |
| Tracks per album | 10 |
| Revisions per track | 25 |
| Track file | 200 MiB |
| Track duration | 1 s to 20 min |
| Cover image | 12 MiB, 200–8,000 px per edge |
| Storage | 2 GiB, counting uploads in progress |
| Changes | 600 per rolling hour; 8 uploads in progress at once |
- Audio: MP3 (
audio/mpeg), WAV (audio/wavoraudio/x-wav; 16-, 24-, 32-bit PCM or 32-bit float, 8,000–192,000 Hz), or FLAC (audio/flacoraudio/x-flac), mono or stereo. Duration is subject to the 200 MiB cap (16-bit/44.1 kHz stereo WAV ≈ 19.8 min, 24-bit/48 kHz ≈ 12 min; MP3 and FLAC reach 20 min). Compressed WAV encodings, 8-bit and 64-bit float samples, and RF64 files are refused as unsupported media. - Covers: PNG, JPEG, or WebP. Sound.fish strips metadata and derives a 1,400 px cover and a 400 px thumbnail.
- Text: titles up to 120 characters; slugs of 1–64 lowercase letters, digits, and dashes (
new,manage,embed,edit,media, andcoverare reserved); descriptions up to 4,000 characters; revision notes up to 2,000; comments up to 2,000.
When a limit is reached, the manage page, the CLI, and the API show the same sentence, for example "Free accounts publish one album. Delete the existing album to create another." There is no paid plan and no upgrade link. GET https://sound.fish/api/audio/v1/capabilities publishes the accepted formats and every limit.
Artist profile
Album, artist, and embed pages show the name and avatar from your published Hraness profile, and comments show your handle. Edit the profile at account.hraness.com/account; when it is not published, pages show @handle alone. If another account later claims your handle at Hraness, Sound.fish releases it from your albums once that account signs in at sound.fish: the artist page and the album, track, and embed addresses under that handle then belong to the new holder, and your albums stop answering at them. Sign in at sound.fish to refresh your handle; your albums stay published and return at /@your-current-handle/slug, and an embed copied under the old handle must be copied again. Album and track ids keep working meanwhile.
Listening, hearts, and comments
Anyone can listen without an account. A play counts once a listener has heard enough of a track; Sound.fish sets a first-party listener cookie (__Host-soundfish-listener) or, inside an embed, returns a listener id to the player so repeated plays are not counted twice. Hearts and comments need a Hraness sign-in on the album page. A comment can be pinned to a moment in a track and records which revision was current when it was written.
Embed the player
A published album has an embed at https://sound.fish/embed/@handle/slug; add ?track=<track-slug> to start on one track and t=83 or t=1m23s to start at a moment. Album and track pages accept the same parameters, so a shared link opens on the right track at the right second without autoplaying. The player's share control copies such links. Copy the <iframe> from the manage page or print it with soundfish audio embed <album> --html:
<iframe src="https://sound.fish/embed/@handle/slug" width="100%" height="488" title="Album by @handle" loading="lazy" allow="autoplay; clipboard-write" style="border:0"></iframe>The embed shows the transport, waveform, track list, counts, and a read-only preview of the active track's latest three comments; hearting or opening the comments goes to the album page in a new tab. The height is an estimate from the track count. The page posts { "type": "soundfish:resize", "height": <px> } to its parent window whenever its content height changes, so a host page can listen for that message and resize the frame. Once a listener switches tracks it also posts { "type": "soundfish:location", "track": "<track-slug>" }, so the host can mirror ?track= in its own address bar. Allow clipboard-write on the frame so the share control can copy links.
Markdown pages
Every artist and album page is also served as Markdown for agents and readers: append .md (https://sound.fish/@handle/slug.md) or request the page with Accept: text/markdown. The Markdown carries the title, artist, description, details table, track list with durations and page URLs, and the embed URL.
Publish from the command line
The soundfish audio verbs publish from a terminal or a coding agent. Create an API token at sound.fish/settings/tokens (it is shown once), then run soundfish audio login, which prints that address and reads the token with echo off; in a script, pipe it in with --token-stdin. Tokens look like sfk_ followed by 40 letters or digits and are stored under $XDG_STATE_HOME/hraness/soundfish/credentials.json in a directory only you can read. Revoke a token at the same settings page.
soundfish audio login [--token-stdin]
soundfish audio logout
soundfish audio whoami
soundfish audio albums list|create|show|update|publish|unpublish|delete|stats
soundfish audio tracks list|add|update|remove|reorder
soundfish audio upload <album> <file ...> [--track <track>] [--title <text>] [--slug <slug>] [--note <text>] [--no-promote]
soundfish audio revisions list|promote
soundfish audio art set <album> <image>
soundfish audio embed <album> [--html]
soundfish audio status <assetId>An album is named by alb_<id>, @handle/<slug>, or one of your own slugs; a track by trk_<id> or its slug. upload hashes each file, asks the API for an upload, streams the bytes to storage, waits for inspection, and attaches the result: without --track it creates a track from the file name (or --title) and refuses to replace an existing track silently (exit 4 with the hint to use --track); with --track it adds the next revision and promotes it unless --no-promote is given. --track, --title, --slug, and --note require exactly one file. For a draft album, every successful upload prints the manage page address on standard error so you can listen before publishing.
update verbs accept --description-file <path|->, repeatable --metadata key=value, and --unset-metadata <key>. Every verb accepts --format json, mutating verbs accept --dry-run, and --api-url (or SOUNDFISH_API_URL) selects the API origin. Exit codes: 0 success, 1 network failure or a 5xx response, 2 usage, 3 invalid local input, 4 conflict or plan limit, 5 rejected by the API. Standard output carries only results; progress and hints go to standard error. The CLI audio reference in the Soundfish skill repeats this procedure for agents.
HTTP API
Every route lives under https://sound.fish/api/audio/v1/, accepts and returns JSON (request bodies up to 64 KiB), and answers a failure as { "error": { "code", "message", "retryable" }, "requestId" } with codes invalid_request, sign_in_required, forbidden, not_found, conflict, payload_too_large, unsupported_media, plan_limit, rate_limited, unavailable. A method the route does not implement answers 405 with the same envelope (code invalid_request), an Allow header listing the accepted methods, and cache-control: no-store. Authenticate with Authorization: Bearer sfk_…. Uploads are a three-step contract: POST /uploads with the file's SHA-256 and size returns a presigned PUT, the client sends the bytes there, and POST /uploads/{assetId}/complete inspects the file and reports ready or a failure code. Public reads need no token: GET /capabilities, GET /public/albums, GET /public/albums/@handle/slug, and GET /public/artists/@handle. The OpenAPI description lists every operation and schema.