Developers
Embed a share
Every public share has an embeddable player at /embed/USER/SHARE. Paste this where you want the player; your dashboard shows the exact snippet for each share.
<iframe src="https://continuo.fm/embed/USER/SHARE"
width="100%" height="360" frameborder="0"
allow="autoplay"></iframe>
Options
theme=light (default), theme=dark, or theme=auto to follow the visitor's system setting.
tracks=0 hides the track list for a compact single-row player. Use height="140" with it.
<iframe src="https://continuo.fm/embed/USER/SHARE?tracks=0&theme=dark"
width="100%" height="140" frameborder="0"
allow="autoplay"></iframe>
Linking to one song
The share button on a track hands out /USER/SHARE/t/SONG — a page for that song alone, titled and previewed as the song, with a link on to the whole record. The slug names the song rather than its position, so a link you have already sent keeps meaning the same song after you rearrange the album or add versions. GET /api/shares/SHARE/tracks returns each track's slug if you are building the links yourself.
When a track was uploaded
Every track carries the date its audio last landed, which a re-upload of the same filename moves and a waveform backfill does not. The player says it once under the album title when a share went up in one session, and on each row when the files arrived at different times, so a collaborator can see what is new without playing anything. GET /USER/SHARE/tracks exposes it as updated per track, plus the share's own updated and a datesVary flag.
oEmbed
Continuo supports oEmbed at /oembed?url=SHARE_URL, and share pages carry the discovery tag, so platforms that unfurl links (WordPress and similar) embed the player automatically when you paste a share URL.
Sending a password-protected share
A share with a password hands out a guest link instead of the plain URL: SHARE_URL?k=TOKEN, which unlocks the share for whoever opens it and then drops the token from the address bar. It is what Copy link and the player's Share button give you while a password is set, and it is what there is — Continuo stores only the password's bcrypt hash, so no page and no endpoint can show the password back, to you or to anybody. Changing or removing the password invalidates every guest link already sent. GET /api/shares/SHARE/links returns url, listen_url (the guest link where there is one), review_url and password_protected.
Review notes
Timestamped notes on a share's tracks, in both directions. They live behind the private review link on the share's dashboard page (SHARE_URL?review=TOKEN) and nowhere else, yours included: open the share through that link and use Add note at current time, and each note lands on the track and second you are hearing. Send the same link to your collaborator and they can read your notes, leave their own, and mark notes done, with no account and no password. The normal share link shows a clean player to everyone who opens it, so a note is never on screen when you play the record for somebody. Rotate the link on the dashboard to revoke one you already sent.
Either side can answer a note where it stands with Reply, so one moment holds one conversation instead of a stack of notes at the same second. Marking done and deleting apply to the whole thread.
The whole loop is scriptable with your API token: GET/POST/DELETE /api/shares/SHARE/comments, PUT /api/shares/SHARE/comments/ID/resolve, and GET or POST /api/shares/SHARE/review-link[/rotate]. A note posts as {"file": "TRACK", "t": 83.2, "body": "..."}; a reply posts as {"parent": NOTE_ID, "body": "..."} and takes the track and timestamp from the note it answers.
A note can name a sequence instead of a track — {"sequence": "OPAQUE_ID", "t": 116, "body": "..."} — for a moment on a timeline inside your own project rather than in an uploaded file. The id is stored and never interpreted here: what it names lives in your project document, and so does any arithmetic relating one timeline’s clock to another’s. A note names a track or a sequence, never both.
"visibility" says who may read a note. "shared" is the default and is the conversation with your reviewer. "owner" is your own working note: returned to your API token and filtered out of everything the review link shows. Move one with PUT /api/shares/SHARE/comments/ID/visibility — the same note keeps its id, its replies and its resolved state. Sharing a note that names a sequence also needs {"file_id": N, "t": SECONDS}, because your reviewer’s player pins every note to a track and cannot open a timeline; only you can say which track that moment lands on.
To poll for changes, watch comments_rev on GET /api/shares: it is a per-share counter that moves on every note change — new notes, replies, resolves, and deletes alike — so one request tells you which shares to refetch. GET /api/shares/SHARE/comments returns the same comments_rev alongside comments, so you can record which revision a list corresponds to. The share list also carries comment_count, unresolved_comment_count, is_public, versions_public, url, and timestamps, so a client can render a share browser from one call.
Uploads and downloads
Upload with PUT /api/shares/SHARE/upload/FILENAME (raw bytes). WAV and AIFF are converted to lossless FLAC; FLAC, MP3, M4A/AAC, Ogg, and Opus are stored as uploaded. To let listeners download the originals from the player, turn it on with PUT /api/shares/SHARE/downloads ({"allow_downloads": true}); each track then exposes GET /USER/SHARE/download/FILE, and the whole share GET /USER/SHARE/download.zip. The archive holds what the caller may hear — one file per track, or every version on a share whose alternate versions are public — named as the player names them.
A listener may also take away a DERIVED format rather than a 96 kHz 24-bit master: mp3 (320 kbps at 44.1 kHz), cd (44.1 kHz 16-bit Apple Lossless) and alac (Apple Lossless at the master's own rate). Both download routes take ?format=; with none they serve the masters, as they always have. There is nothing to turn on: every share that allows downloads offers whatever its audio can produce, which GET /USER/SHARE/tracks lists as downloadFormats.
Nothing is converted until somebody asks. A zip entry's header must declare its checksum and length before its bytes, so a format is still built ahead of the download rather than during it — POST /USER/SHARE/prepare?format=FORMAT starts that and returns {"ready": false, "done": 3, "total": 16}; poll it, then fetch the download. Add &file=BASENAME to prepare a single track instead of the whole archive. A download of a format that is not built yet answers 409, and one the share cannot derive at all — an unknown name, or Apple Lossless where the masters are already 44.1/16 — answers 404, never a quiet fall back to the masters. Rendered files count toward your storage until 30 days pass without anyone downloading them, and are then given back.
Alternate versions
Upload files named SONG T1, SONG T2, and so on, and Continuo folds them into a single album track. The player shows one row per song and plays one version. The others are yours, not the audience's: they sit behind a disclosure for you and for whoever holds the review link, while a plain share link presents the record alone and will not serve an unpublished version even if its name is guessed. Turn that around for a share whose point is the comparison with PUT /api/shares/SHARE/versions-public ({"versions_public": true}), and every listener hears each version; they still cannot publish one. End a filename with * (Eagle Eye T25*.wav) to mark a keeper: the latest keeper is what the album plays. With no keeper marked, the latest take that ran to something like full length wins, which skips the false starts.
A version is listed by its number (T2), or by its name when the filename gives it one after the number: Silvia T2 (Remaster) is listed as Remaster. A file carrying the track's own bare name is the Original. Rename one without re-uploading it with PUT /api/shares/SHARE/files/FILE/name ({"name": "Silvia T2 (Remaster)"}).
Every version says what made it, where the file says so itself: the build of the software that rendered it, and the git commit of the recording session it was rendered from. Continuo reads both off the audio at upload — never off the filename or anything you send — and shows them on the Manage screen under the depth and rate: arc d623dbb · session f84430c — two commits, the software's and the session's, the second linked to it where the project lives on GitHub. The row shows shas because that is what you would paste; the software's full build string (arc 1180 (d623dbb)) is what the file carried and is kept, on the tooltip and over the API. A commit marked -dirty was rendered from a document that had not been committed, so nothing can show you exactly what made it.
GET /api/shares/SHARE/tracks returns them per version as arcBuild, projectCommit and projectRepo, null for a file that does not say. Today arc is what writes them; a file from any other software simply says nothing, and a file uploaded before this reads null, because its tags rode in the WAV and only the converted FLAC is kept.
Read the album with GET /api/shares/SHARE/tracks, set its running order with PUT /api/shares/SHARE/order ({"order": ["Eagle Eye Mantra", "Eagle Eye"]}, by track name; names you leave out keep their place at the end), and choose a version with PUT /api/shares/SHARE/groups/TRACK/chosen ({"file": "Eagle Eye T14.flac"}). A chosen version sticks: a later upload does not override it. DELETE the same path to undo the choice and let the track pick for itself again. The order your own dashboard lists your shares in is PUT /api/shares-sort ({"sort": "updated"}, one of updated, created, title).
Set a track's composer with PUT /api/shares/SHARE/tracks/TRACK/metadata ({"composer": "Gaspar Fernandes"}, null to clear), which applies to every version of that track and needs no re-upload — a credit settled after the master is cut should not cost a re-render. The composer is read from the file's own tags at upload (RIFF IMUS, Vorbis COMPOSER) and comes back on every version from GET /api/shares/SHARE/tracks. A later upload that names a composer replaces what you set; one that names none leaves it alone.
Switching versions while a track is playing resumes at the same musical moment rather than the same second: every version of a track is warped onto one of them (chroma dynamic time warping) when it is uploaded, and the player composes the two maps. Read one track's maps with GET /USER/SHARE/alignment?group=TRACK, which is open to the same people the versions are and returns each version's map as [[seconds in this version], [seconds in the reference]] and the path cost behind it. A cost above 0.13 means the two are not confidently the same music, and the player keeps the same second instead.
When every track carrying versions offers the same version names, the player adds one switch above the list that moves the whole album between them, so a listener compares two masters end to end rather than track by track. Those tracks then lose their own version disclosure, since the switch already does that job; you keep yours, because it is also where you publish a version. Switching is an audition: it changes nothing for anyone else.
To make a comparison blind, do it in the names rather than in the player: call the versions A and B with PUT /api/shares/SHARE/files/FILE/name ({"name": "Silvia (A)"} — a lone letter in parentheses is a version, with no take number in front of it), assign them the same way on every track, and keep the key in PUT /api/shares/SHARE/private-notes ({"private_notes": "A is the remaster"}). Private notes are yours alone: no player payload, share page, or link you send carries them, and GET on the same path reads them back. One caveat — the audio URL is still the stored filename, so a listener who opens their browser's network inspector can tell which file is which.
Whoever holds the review link can do both from the player itself, by dragging tracks and picking versions, without an account. Everyone else listens: arranging travels with the review token and nothing else, so a link copied out of the player grants only playback. Turn it off per share with PUT /api/shares/SHARE/review-arrange ({"review_can_arrange": false}) or the checkbox on the share's dashboard page, which withdraws arranging without rotating the link.
Building a player of your own
A client with no browser signs in with POST /api/auth/token ({"email": "...", "password": "..."}), which returns your account's API token and who it belongs to. Send it as Authorization: Bearer TOKEN and it opens the same routes the web player uses on shares you own, including ones that are unlisted or password-protected: GET /USER/SHARE/tracks for the album, the per-track url it carries for the audio, GET and POST /USER/SHARE/comments for review notes, and POST /USER/SHARE/plays to record a listen. There is no second, parallel API to learn: it is the player's own, with a header instead of a cookie.
/tracks gives each track its duration, its waveform (base64, one byte per bucket — the peak envelope the seek bar draws), and its sampleRate and bitDepth, so you can say what the file actually is. The share carries cover when it has real artwork; with no cover, draw the generated gradient the way /player/theme.js does, seeded on USER/SHARE.
Not everything needs a token. GET /api/featured is open to anyone and returns the albums this homepage offers — path, title, artist, track_count, cover and updated_at — which is enough to open a client on something to play instead of on a sign-in screen. Those shares are public and unprotected by definition, and GET /USER/SHARE/tracks and the audio behind it have always answered an anonymous caller for one.
GET /api/me returns the account a token belongs to. Ask it on launch: a 401 means the token was rotated and the person has to sign in again, and anything else means the network is at fault, which is a different thing to tell them.
The token is long-lived and is the same one your dashboard shows, so signing out of a client means forgetting it there. Rotating it in your account settings ends every session using it.
Notes
Streams are the original files, lossless for FLAC and PCM sources. Password-protected shares cannot be embedded. The player links back to the artist's Continuo profile.