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.
id. The analysis takes about 1–2 minutes. Poll GET /v1/songs/{id} or pass a webhook_url.songbrain.song/1. New fields can appear and existing fields don't change meaning.start_in_clip).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.
# 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"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"])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());| Field | Type | Meaning |
|---|---|---|
| GET /v1/examples | no key | List example analyses (Songbrain's own songs). |
| GET /v1/examples/{id} | no key | Full example document. ?view=summary · ?include=… |
| GET /v1/examples/{id}/shot-plan | no key | Example story + shot plan. |
| POST /v1/songs | key | Analyse a song. 202 → { id, status: "processing" }. |
| GET /v1/songs | key | Your songs, newest first. ?limit=1–100. |
| GET /v1/songs/{id} | key | Status while processing, then the full document. |
| GET /v1/songs/{id}/shot-plan | key | Story + shot plan only. |
| DELETE /v1/songs/{id} | key | Delete audio and analysis now. |
| GET /v1/account | key | Free songs left this month, credit balance, price. |
| GET /v1/pricing | no key | Prices and limits as JSON. |
| POST /mcp | optional | MCP 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.
Send either multipart/form-data with file, or JSON (or form fields) with audio_url.
| Field | Type | Meaning |
|---|---|---|
| file | binary | MP3, WAV, FLAC, M4A, AAC, OGG or AIFF. Up to 100 MB, 30 s to 10 min. |
| audio_url | string | Public http(s) URL of the audio file. Redirects are followed (max 3). |
| title | string | Optional. Defaults to a cleaned file name. |
| artist | string | Optional. |
| webhook_url | string | Optional. Receives song.done / song.failed (see Webhooks). |
| external_ref | string | Optional. Your own id, echoed back in responses and webhooks. |
{ "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 }.
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.
| Field | Type | Meaning |
|---|---|---|
| genre / subgenre | string | Genre and one of 37 subgenres, with genre_confidence (0–1). |
| tempo_bpm | number | Tempo, with tempo_confidence. |
| key | string | e.g. "C minor", with key_confidence. key_changes[]: { at_sec, to }. |
| mood | string[] | e.g. ["epic","powerful","melancholic"]. |
| vocals | string | Vocal style, or "instrumental". |
| instruments | object[] | { name, confidence } for confidence ≥ 0.25. |
| energy / brightness | number | 0–1. |
| loudness_lufs / true_peak_dbfs | number | Integrated loudness (EBU R128) and true peak. |
| streaming_loudness | object | Per platform: { loudness_ok, delta_lufs } against its target. |
| tagline / description / sounds_like | … | One-line character, a sentence and comparable tracks. |
| Field | Type | Meaning |
|---|---|---|
| beats | number[] | Every beat in the song, in seconds. |
| downbeats | number[] | First beat of each bar (4/4 assumed, time_signature). |
| sections | object[] | { 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_timeline | object[] | { at_sec, label }: where the vocal changes character. |
| lyric_hooks | object[] | { at_sec, line, role, strength }: lines that work as hooks. |
| Field | Type | Meaning |
|---|---|---|
| rank | int | 1 = strongest. |
| start_sec / peak_sec / end_sec | number | The window and its peak. |
| score | int | 0–100. |
| why / explanation / judge_note | string | Why this moment works, from the signal analysis and the listening pass. |
| sung | string|null | The words sung inside the window. |
| platform_fit | object | { tiktok, instagram_reels, youtube_shorts }, each 0–1. |
| beat_grid_sec | number[] | Beats inside the window. |
| caption_ideas / hashtags | … | When available. |
| Field | Type | Meaning |
|---|---|---|
| lyrics.lines[] | object[] | { start, end, text, words: [{ w, start, end }] }. { hidden: true } for recognised commercial recordings. |
| scores.virality | object | { score 0–100, breakdown{7 parts} }. Song strength for short-form video. It is not a view prediction. |
| scores.quality / scores.lyrics | object | Score + breakdown. Plus summary, what_works[], what_to_fix[], audience[]. |
| story.meaning / why_these_visuals | string | What the song is about, and why the visuals fit it. |
| story.world / palette | string / string[] | One place and three colours for every shot. |
| story.element | object | { thing, before, event, after }: the one object the video is about, in three states. |
| story.story_beats | object | { setup, turn, payoff }: one sentence each. |
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.
| Field | Type | Meaning |
|---|---|---|
| clip.window_sec | [number, number] | Where the clip sits in the song. |
| clip.beats_sec | number[] | Beats inside the window (grid_bpm is the grid's tempo; it can run at double time). |
| clip.phases_sec | object | tease_end, payoff_start, payoff_end, outro_start. |
| clip.hook_line | string|null | The repeated sung line the payoff lands on. |
| scenes[].start_sec / end_sec | number | Absolute seconds. start_in_clip and duration_sec are relative. |
| scenes[].kind | string | tease · build · cutaway · burst · strobe (≈4-frame flash) · hero · outro. |
| scenes[].act | string | setup · turn · payoff. |
| scenes[].transition_in / motion | string | How to cut into the scene and how the camera moves. |
| scenes[].framing / emotion / symbol | string|null | Shot size, the feeling and the visual symbol the shot carries. |
| scenes[].prompt | string | A ready image/video prompt with world, palette and style. visual is the readable part without the style tail. |
| scenes[].sung | string|null | The 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 }. |
| style | object | { 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.
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.
{ "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).
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"])Every error has the same shape: { "error": { "code": "…", "message": "…" } }.
| Field | Type | Meaning |
|---|---|---|
| 400 | invalid_url · missing_audio · unsupported_format · format_mismatch · audio_too_short · audio_too_long · decode_failed · download_failed | Fix the request. Nothing was charged. |
| 401 | missing_api_key · invalid_api_key | Send a valid key. |
| 402 | insufficient_credits | The free songs are used up and the balance is below 25 credits. |
| 404 | not_found | Unknown id, or not yours. |
| 409 | still_processing | DELETE before the analysis finished. |
| 413 | file_too_large | Over 100 MB. |
| 415 | unsupported_media_type | Use multipart/form-data or application/json. |
| 429 | rate_limited · too_many_in_flight · daily_cap · pipeline_busy | Back 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.
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 mcp add --transport http songbrain https://api.songbrain.ai/mcp \
--header "Authorization: Bearer sb_live_…"| Field | Type | Meaning |
|---|---|---|
| list_example_songs | no key | Example analyses you can open. |
| get_example_analysis | no key | An example document (summary view by default). |
| get_example_shot_plan | no key | An example story + shot plan, with prompts. |
| get_pricing | no key | Prices and limits. |
| analyze_song | key | Start an analysis from a public audio URL. |
| get_song | key | Status, then the document. |
| get_account | key | Free songs left, credits. |
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.
| Field | Type | Meaning |
|---|---|---|
| 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 |