Docs · v1 · schema songbrain.song/1

Songbrain API reference

Send a song, get back everything a video needs: Song DNA, a beat grid, sections, the best moments, timed lyrics, the story, and a shot plan that cuts on the beat. Base URL https://api.songbrain.ai/v1. Machine-readable: OpenAPI 3.1 · interactive docs · llms.txt.

Overview

Authentication

Create a key in Settings → API. It is shown once. Send it with every request in either header:

Authorization: Bearer sb_live_…
X-API-Key: sb_live_…

Keys can be revoked at any time. Each account can have up to 5 active keys. Keep keys on your server and never ship them in a browser or app bundle.

Quickstart

curl
# 1. send a file (or JSON {"audio_url": "https://…"})
curl -X POST https://api.songbrain.ai/v1/songs \
  -H "Authorization: Bearer $SONGBRAIN_KEY" \
  -F "file=@song.mp3" -F "title=My Song"

# 2. poll until status is "done"
curl https://api.songbrain.ai/v1/songs/$ID -H "Authorization: Bearer $SONGBRAIN_KEY"
Python
import os, time, requests

API = "https://api.songbrain.ai/v1"
H = {"Authorization": f"Bearer {os.environ['SONGBRAIN_KEY']}"}

with open("song.mp3", "rb") as f:
    song = requests.post(f"{API}/songs", headers=H, files={"file": f}).json()

while True:
    doc = requests.get(f"{API}/songs/{song['id']}", headers=H).json()
    if doc["status"] in ("done", "failed"):
        break
    time.sleep(10)

for scene in doc["shot_plan"]["clip"]["scenes"]:
    print(scene["start_sec"], scene["end_sec"], scene["act"], scene["prompt"])
Node (18+)
const API = "https://api.songbrain.ai/v1";
const H = { Authorization: `Bearer ${process.env.SONGBRAIN_KEY}` };

const created = await fetch(`${API}/songs`, {
  method: "POST",
  headers: { ...H, "Content-Type": "application/json" },
  body: JSON.stringify({ audio_url: "https://example.com/song.mp3", webhook_url: "https://your.app/songbrain" }),
}).then(r => r.json());

// later (or in your webhook handler):
const plan = await fetch(`${API}/songs/${created.id}/shot-plan`, { headers: H }).then(r => r.json());

Endpoints

FieldTypeMeaning
GET /v1/examplesno keyList example analyses (Songbrain's own songs).
GET /v1/examples/{id}no keyFull example document. ?view=summary · ?include=…
GET /v1/examples/{id}/shot-planno keyExample story + shot plan.
POST /v1/songskeyAnalyse a song. 202 → { id, status: "processing" }.
GET /v1/songskeyYour songs, newest first. ?limit=1–100.
GET /v1/songs/{id}keyStatus while processing, then the full document.
GET /v1/songs/{id}/shot-plankeyStory + shot plan only.
DELETE /v1/songs/{id}keyDelete audio and analysis now.
GET /v1/accountkeyFree songs left this month, credit balance, price.
GET /v1/pricingno keyPrices and limits as JSON.
POST /mcpoptionalMCP server (streamable HTTP). See below.

Query options on document endpoints: view=summary drops word timings and beat arrays (≈ 3× smaller). include=song_dna,shot_plan returns only the named sections. The sections are song_dna, timeline, best_moments, lyrics, scores, story and shot_plan.

POST /v1/songs

Send either multipart/form-data with file, or JSON (or form fields) with audio_url.

FieldTypeMeaning
filebinaryMP3, WAV, FLAC, M4A, AAC, OGG or AIFF. Up to 100 MB, 30 s to 10 min.
audio_urlstringPublic http(s) URL of the audio file. Redirects are followed (max 3).
titlestringOptional. Defaults to a cleaned file name.
artiststringOptional.
webhook_urlstringOptional. Receives song.done / song.failed (see Webhooks).
external_refstringOptional. Your own id, echoed back in responses and webhooks.
202 Accepted
{ "object": "song", "id": "8f0c…", "status": "processing", "eta_sec": 90,
  "billing": { "type": "free", "credits": 0 }, "url": "https://api.songbrain.ai/v1/songs/8f0c…" }

While processing, GET /v1/songs/{id} returns { "status": "processing", "progress": 0.6, "eta_sec": 90 }.

The song document

Top level: object, schema, id, status, song, song_dna, timeline, best_moments, lyrics, scores, story, shot_plan, external_ref, billing. See a full one at /v1/examples/old-truck-home.

song_dna

FieldTypeMeaning
genre / subgenrestringGenre and one of 37 subgenres, with genre_confidence (0–1).
tempo_bpmnumberTempo, with tempo_confidence.
keystringe.g. "C minor", with key_confidence. key_changes[]: { at_sec, to }.
moodstring[]e.g. ["epic","powerful","melancholic"].
vocalsstringVocal style, or "instrumental".
instrumentsobject[]{ name, confidence } for confidence ≥ 0.25.
energy / brightnessnumber0–1.
loudness_lufs / true_peak_dbfsnumberIntegrated loudness (EBU R128) and true peak.
streaming_loudnessobjectPer platform: { loudness_ok, delta_lufs } against its target.
tagline / description / sounds_like…One-line character, a sentence and comparable tracks.

timeline

FieldTypeMeaning
beatsnumber[]Every beat in the song, in seconds.
downbeatsnumber[]First beat of each bar (4/4 assumed, time_signature).
sectionsobject[]{ start_sec, end_sec, label, energy }. The same letter means the same music, e.g. A, B, A. energy 0–1 is relative to the song's loudest part.
vocal_timelineobject[]{ at_sec, label }: where the vocal changes character.
lyric_hooksobject[]{ at_sec, line, role, strength }: lines that work as hooks.

best_moments[]

FieldTypeMeaning
rankint1 = strongest.
start_sec / peak_sec / end_secnumberThe window and its peak.
scoreint0–100.
why / explanation / judge_notestringWhy this moment works, from the signal analysis and the listening pass.
sungstring|nullThe words sung inside the window.
platform_fitobject{ tiktok, instagram_reels, youtube_shorts }, each 0–1.
beat_grid_secnumber[]Beats inside the window.
caption_ideas / hashtags…When available.

lyrics, scores, story

FieldTypeMeaning
lyrics.lines[]object[]{ start, end, text, words: [{ w, start, end }] }. { hidden: true } for recognised commercial recordings.
scores.viralityobject{ score 0–100, breakdown{7 parts} }. Song strength for short-form video. It is not a view prediction.
scores.quality / scores.lyricsobjectScore + breakdown. Plus summary, what_works[], what_to_fix[], audience[].
story.meaning / why_these_visualsstringWhat the song is about, and why the visuals fit it.
story.world / palettestring / string[]One place and three colours for every shot.
story.elementobject{ thing, before, event, after }: the one object the video is about, in three states.
story.story_beatsobject{ setup, turn, payoff }: one sentence each.

The shot plan

shot_plan.clip is a complete, beat-synced edit of the best moment (about 15 s, 9:16). It uses the same edit our own storyboard videos use: a setup that builds, a turn of hard cuts on the beat from the payoff on, and a payoff image held at the end. shot_plan.full_song extends it to every section of the song.

FieldTypeMeaning
clip.window_sec[number, number]Where the clip sits in the song.
clip.beats_secnumber[]Beats inside the window (grid_bpm is the grid's tempo; it can run at double time).
clip.phases_secobjecttease_end, payoff_start, payoff_end, outro_start.
clip.hook_linestring|nullThe repeated sung line the payoff lands on.
scenes[].start_sec / end_secnumberAbsolute seconds. start_in_clip and duration_sec are relative.
scenes[].kindstringtease · build · cutaway · burst · strobe (≈4-frame flash) · hero · outro.
scenes[].actstringsetup · turn · payoff.
scenes[].transition_in / motionstringHow to cut into the scene and how the camera moves.
scenes[].framing / emotion / symbolstring|nullShot size, the feeling and the visual symbol the shot carries.
scenes[].promptstringA ready image/video prompt with world, palette and style. visual is the readable part without the style tail.
scenes[].sungstring|nullThe words sung during the scene.
full_song.sections[]object[]{ start_sec, end_sec, section, energy_rank, act, cut_every_beats, cuts_sec[], role, prompt, sung }.
styleobject{ palette, prompt_suffix, world }: apply the same suffix to your own prompts to stay in the look.

Use it directly: render one image or clip per scene from prompt and cut at start_sec. The cuts already sit on beats. Or use it as the brief for your own model: the story.element and story_beats keep a longer video coherent.

Webhooks

If you pass webhook_url, we POST when the song is done or failed. The URL has to be public. We try three times: right away, after 1 minute and after 5 minutes.

Body
{ "type": "song.done", "created": 1791200000,
  "data": { "id": "8f0c…", "status": "done", "external_ref": "your-id",
            "url": "https://api.songbrain.ai/v1/songs/8f0c…",
            "shot_plan_url": "https://api.songbrain.ai/v1/songs/8f0c…/shot-plan" } }

Header Songbrain-Signature: t=<unix>,v1=<hex>, where v1 is the HMAC-SHA256 of "<t>.<raw body>"with your key's webhook secret (shown once at key creation).

Verify (Python)
import hmac, hashlib, time

def verify(raw_body: bytes, header: str, secret: str, tolerance=300) -> bool:
    parts = dict(p.split("=", 1) for p in header.split(","))
    if abs(time.time() - int(parts["t"])) > tolerance:
        return False
    mac = hmac.new(secret.encode(), f"{parts['t']}.".encode() + raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(mac, parts["v1"])

Errors

Every error has the same shape: { "error": { "code": "…", "message": "…" } }.

FieldTypeMeaning
400invalid_url · missing_audio · unsupported_format · format_mismatch · audio_too_short · audio_too_long · decode_failed · download_failedFix the request. Nothing was charged.
401missing_api_key · invalid_api_keySend a valid key.
402insufficient_creditsThe free songs are used up and the balance is below 25 credits.
404not_foundUnknown id, or not yours.
409still_processingDELETE before the analysis finished.
413file_too_largeOver 100 MB.
415unsupported_media_typeUse multipart/form-data or application/json.
429rate_limited · too_many_in_flight · daily_cap · pipeline_busyBack off. A Retry-After header says how long.

If an analysis fails after it was accepted, its status becomes failed. Paid credits are refunded automatically, and a free song doesn't count against the month.

Pricing & limits

MCP server

https://api.songbrain.ai/mcp speaks the Model Context Protocol over streamable HTTP (stateless JSON-RPC). It works in Claude Code, Claude Desktop, Cursor and any MCP client. Without a key you get the example tools; add your key as a header to analyse your own songs.

Claude Code
claude mcp add --transport http songbrain https://api.songbrain.ai/mcp \
  --header "Authorization: Bearer sb_live_…"
FieldTypeMeaning
list_example_songsno keyExample analyses you can open.
get_example_analysisno keyAn example document (summary view by default).
get_example_shot_planno keyAn example story + shot plan, with prompts.
get_pricingno keyPrices and limits.
analyze_songkeyStart an analysis from a public audio URL.
get_songkeyStatus, then the document.
get_accountkeyFree songs left, credits.

Migrating from Spotify Audio Features

Spotify closed /audio-features and /audio-analysis to new apps in November 2024. Here is where the fields you used live in Songbrain. The difference: you send the audio file, so it also works for unreleased songs.

FieldTypeMeaning
tempo→song_dna.tempo_bpm
key + mode→song_dna.key ("C minor"), key_changes[]
energy→song_dna.energy (0–1)
loudness→song_dna.loudness_lufs (EBU R128, not dB average)
valence / danceability→song_dna.mood[] (words, not numbers)
instrumentalness→song_dna.vocals ("instrumental") + song.has_vocals
audio-analysis beats / bars→timeline.beats / timeline.downbeats
audio-analysis sections→timeline.sections (with repeat letters)
(not in Spotify)+best_moments, lyrics with word timing, story, shot_plan

Ready to try it on your song?

5 free songs every month, no card needed.

Get a free API key →