# Songbrain API reference

> Docs · v1 · schema `songbrain.song/1` · last updated 2026-10-07
> HTML version: https://www.songbrain.ai/docs/api · Changelog: https://www.songbrain.ai/docs/api/changelog · llms.txt: https://www.songbrain.ai/docs/api/llms.txt

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`
- OpenAPI 3.1: https://api.songbrain.ai/v1/openapi.json
- Try it in your browser (interactive docs, click *Authorize* and paste your key): https://api.songbrain.ai/v1/docs
- SDKs: `pip install songbrain` (https://pypi.org/project/songbrain/) · `npm install songbrain` (https://www.npmjs.com/package/songbrain) · source: https://github.com/songbrain-ai/songbrain
- Status: https://www.songbrain.ai/status · Security & sub-processors: https://www.songbrain.ai/security · DPA: https://www.songbrain.ai/dpa

## Contents

1. Overview
2. Authentication
3. Quickstart (curl, Python, Node)
4. SDKs, Postman & cookbook
5. Endpoints
6. POST /v1/songs (incl. Send your lyrics)
7. Idempotency-Key (safe retries)
8. Test mode (free, for CI)
9. The song document (incl. What is measured and what is estimated)
10. The shot plan
11. Webhooks
12. Errors & request IDs
13. Rate limits
14. Pagination
15. Pricing & limits
16. Versioning & status
17. Commercial use & licensing
18. MCP server
19. Migrating from Spotify Audio Features

## 1. Overview

- **Async:** POST a song, get an `id`. The analysis plus shot plan typically takes 60–90 seconds for a 3-minute song (up to ~2 minutes when many songs run at once). Poll `GET /v1/songs/{id}` or pass a `webhook_url`.
- **One document per song**, schema `songbrain.song/1`. New fields can appear and existing fields don't change meaning.
- **Try it without a key:** `GET /v1/examples` (https://api.songbrain.ai/v1/examples) returns real analyses of our own songs in exactly this format.
- Times are in **seconds from the start of the song** unless the field name says otherwise (`start_in_clip`).
- **Live or test:** every song, list item and webhook event carries `livemode`. It is `false` only for test-mode songs.
- **Every response has a request id** in the `Songbrain-Request-Id` header. Quote it when you contact support.

## 2. Authentication

Create a key in the Developer Console (https://app.songbrain.ai/developers, Keys). 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.

## 3. Quickstart

curl:

```bash
# 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:

```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+):

```js
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());
```

## 4. SDKs, Postman & cookbook

The SDKs wrap auth, polling, webhook verification and retries. They send an `Idempotency-Key` automatically, so a retried upload is never charged twice.

```bash
pip install songbrain     # https://pypi.org/project/songbrain/
npm install songbrain     # https://www.npmjs.com/package/songbrain
```

- **Try it in your browser:** https://api.songbrain.ai/v1/docs. Click *Authorize*, paste your key and send real requests.
- **SDK source and examples:** https://github.com/songbrain-ai/songbrain
- **Postman:** https://github.com/songbrain-ai/songbrain/blob/main/postman/Songbrain.postman_collection.json (import, set `api_key`).
- **Cookbook:** https://github.com/songbrain-ai/songbrain/tree/main/cookbook (song to video with your own model, webhook handlers, batch jobs).
- **For LLMs and agents:** this file · https://www.songbrain.ai/docs/api/llms.txt · OpenAPI · MCP server (section 18).

## 5. Endpoints

| Endpoint | Auth | What it does |
|---|---|---|
| `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` & `starting_after` (see Pagination). |
| `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 /v1/webhooks/test` | key | Send a signed ping event to a URL right now. |
| `GET /v1/webhooks/deliveries` | key | Your latest webhook deliveries. `?limit=20`. |
| `GET /v1/status` | no key | Live status of API, pipeline and webhooks. |
| `POST /mcp` | optional | MCP server (streamable HTTP). See section 18. |

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.

## 6. POST /v1/songs

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. |
| `lyrics` | string | Optional. The song's lyrics as plain text, up to 20,000 characters. Your exact words are placed on the transcription's timing (see "Send your lyrics"). |
| `test` | boolean | Optional. `true` = free test song with example data, no audio needed (see Test mode). |
| `Idempotency-Key` | header | Optional. 1–255 printable characters. Makes retries safe (see section 7). |

202 Accepted:

```json
{ "object": "song", "id": "8f0c…", "status": "processing", "eta_sec": 75, "livemode": true,
  "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": 75 }`.

### Send your lyrics

Without lyrics, the words are transcribed from the vocals, and sung words can be misheard. If you have the lyrics (your own text, or the lyrics from Suno), send them as `lyrics`. The timing still comes from the audio; the words come from your text. Words the singer can't be heard on are left out, never guessed. Works the same in the MCP tool `analyze_song`.

```bash
curl -X POST https://api.songbrain.ai/v1/songs \
  -H "Authorization: Bearer $SONGBRAIN_KEY" \
  -H "Content-Type: application/json" \
  -d '{"audio_url": "https://example.com/song.mp3",
       "lyrics": "First line of the verse\nSecond line of the verse\n…"}'
```

The result says what happened in `lyrics.source`: `provided_lyrics`, with `lyrics.alignment` = `{ match_ratio, reference_words, corrected_words }`. If your text doesn't match the recording well enough, the transcription is kept (`source: "transcribed"`) and `alignment` is `{ match_ratio, used: false, reason }`.

## 7. Idempotency-Key: retry without paying twice

If a `POST /v1/songs` times out, you can't know whether we got it. Send an `Idempotency-Key` header (any unique string, e.g. a UUID) and retry with the same key: you get the original song back instead of a second, paid one.

```bash
curl -X POST https://api.songbrain.ai/v1/songs \
  -H "Authorization: Bearer $SONGBRAIN_KEY" \
  -H "Idempotency-Key: 5d2f8c1e-upload-42" \
  -H "Content-Type: application/json" \
  -d '{"audio_url": "https://example.com/song.mp3"}'
# same command again → same id, header Idempotent-Replayed: true, no new charge
```

| Case | Status | What happens |
|---|---|---|
| Same key + same request, within 24 h | 202 | The original body again (same song id, no charge). Header `Idempotent-Replayed: true`. |
| Same key, different request | 409 | `idempotency_key_reused`: other audio_url, other file bytes or other test flag. |
| First request still being accepted | 409 | `idempotency_in_progress`: retry after a second. |
| First request failed (4xx/5xx, no song) | — | Not stored. Retry with the same key. |

Keys are scoped to your account and kept for 24 hours. The Python and TypeScript SDKs send one automatically per call and reuse it on their own retries; pass your own to make retries safe across processes.

## 8. Test mode: free, instant, for CI

Add `"test": true` (JSON) or the form field `test=true`. No audio is needed and anything you send is ignored. You get a `test_…` id back right away; it is `done` immediately and returns a complete example analysis with your `title` and `external_ref`, `"livemode": false` and `billing: { "type": "test", "credits": 0 }`. With a `webhook_url`, a signed `song.done` arrives within about 5 seconds.

```bash
curl -X POST https://api.songbrain.ai/v1/songs \
  -H "Authorization: Bearer $SONGBRAIN_KEY" \
  -H "Content-Type: application/json" \
  -d '{"test": true, "title": "CI check", "external_ref": "build-1234",
       "webhook_url": "https://staging.your.app/songbrain"}'
# → { "id": "test_9a1c…", "status": "processing", "livemode": false,
#     "billing": { "type": "test", "credits": 0 } }
```

**Use it in CI** to test your whole integration, webhook handler included, on every build. Test songs are never charged and don't use your free songs. They are rate-limited like real requests and count toward the daily cap.

## 9. The song document

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

### song_dna

| 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. Written from the final genre, so they don't contradict song_dna.genre. |

### timeline

| 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. |

### best_moments[]

| Field | Type | Meaning |
|---|---|---|
| `rank` | int | 1 = strongest. |
| `start_sec / peak_sec / end_sec` | number | The window and its peak. |
| `score` | int | 0–100. |
| `reason` | string\|null | One sentence: why this moment works. |
| `signals` | string[]\|null | The measured audio signals behind the pick, e.g. "strong sustained energy after impact". |
| `sung` | string\|null | The words sung inside the window. |
| `beat_grid_sec` | number[] | Beats inside the window (omitted in view=summary). |

### lyrics, scores, story

| Field | Type | Meaning |
|---|---|---|
| `lyrics.lines[]` | object[] | { start, end, text, confidence, words: [{ w, start, end }] }. { hidden: true } for recognised commercial recordings. |
| `lyrics.lines[].confidence` | number\|null | 0–1: how sure the transcription is of this line (exp of Whisper's average log-probability). null when the words are not transcribed (your provided lyrics). |
| `lyrics.source` | string | "transcribed" (heard from the vocals) · "provided_lyrics" (your lyrics field) · "suno_lyrics" (lyrics from a Suno link). |
| `lyrics.note / alignment` | string / object | Plain-language note on where the words come from. alignment: { match_ratio, reference_words, corrected_words } when lyrics were used, or { match_ratio, used: false, reason } when they didn't match. |
| `scores.virality` | object | { score 0–100, breakdown{7 parts}, basis }. Song strength for short-form video. It is not a view prediction. |
| `scores.quality / scores.lyrics` | object | Score + breakdown + basis. Plus summary, what_works[], what_to_fix[], audience[]. |
| `scores.*.basis / basis_note` | string | Always "model_estimate": the scores are an AI model's judgement (see provenance). |
| `provenance` | object | Which fields are measured, transcribed, model_estimate or generated, plus confidence_fields. See below. |
| `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. |

### What is measured and what is estimated

HTML: https://www.songbrain.ai/docs/api#provenance

Not every number in the document is a measurement. Every song document carries a `provenance` object that lists, field by field, where each value comes from:

| Kind | Fields | What it means |
|---|---|---|
| Measured | Tempo, key, key changes, loudness (LUFS, true peak, streaming targets), energy, brightness, beats, downbeats, sections, best-moment windows (start, peak, end), moment signals, the beat grid inside moments, shot-plan cut times | Computed from the audio signal with signal processing. No AI judgement involved. |
| Transcribed | Lyrics (unless lyrics.source is provided_lyrics or suno_lyrics), best_moments[].sung | Speech recognition (Whisper large-v3) on the vocals. Sung words can be misheard: check lines[].confidence, or send lyrics. |
| Model estimate | Genre, subgenre, mood, vocals, instruments, vocal timeline, lyric hooks, moment score and reason, all scores | An AI model's listening judgement. Useful for comparing, but a judgement, not a measurement. |
| Generated | Tagline, description, sounds_like, story, shot-plan prompts and visuals | Creative text written by an AI model from the analysis. tagline and sounds_like are written from the final genre. |

Confidence fields (all 0–1): `song_dna.tempo_confidence`, `key_confidence`, `genre_confidence`, `instruments[].confidence` and `lyrics.lines[].confidence`.

```json
"provenance": {
  "measured":       ["song_dna.tempo_bpm", "song_dna.key", "timeline.beats", "timeline.sections", …],
  "transcribed":    ["lyrics (unless lyrics.source is provided_lyrics or suno_lyrics)", "best_moments[].sung"],
  "model_estimate": ["song_dna.genre", "song_dna.mood", "best_moments[].score", "scores", …],
  "generated":      ["song_dna.tagline", "song_dna.sounds_like", "story", "shot_plan.*.prompt", …],
  "confidence_fields": ["song_dna.tempo_confidence", "lyrics.lines[].confidence", …],
  "doc": "https://www.songbrain.ai/docs/api#provenance"
}
```

**About the scores, honestly:** the virality, quality and lyrics scores (and each moment's score) are an AI model's listening judgement, calibrated on our own catalogue. They are good for comparing songs and moments with each other, for example which of your songs or which 15 seconds to lead with. They are not a measurement, and they are not a forecast of views or streams. Every score carries `basis: "model_estimate"`, and `scores.basis_note` says the same in one sentence.

## 10. 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.

| 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.

## 11. Webhooks

If you pass `webhook_url`, we POST when the song is done or failed. The URL has to be public https.

```json
{ "id": "evt_5f1d2c9a8b7e6d5c4b3a2f10", "type": "song.done", "created": 1791200000, "livemode": true,
  "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 | Example | Meaning |
|---|---|---|
| `Songbrain-Signature` | `t=<unix>,v1=<hex>` | HMAC-SHA256 of `"<t>.<raw body>"` with your key's webhook secret (shown once at key creation). |
| `Songbrain-Event-Id` | `evt_…` | Same as the body's id. Stays the same on every retry of this event. |
| `Songbrain-Event-Type` | `song.done` | song.done · song.failed · account.low_balance · ping. |

**Dedupe on the event id.** A retry carries the same `id`, so store it and ignore events you have already handled. Answer with any 2xx quickly and do the work afterwards. Redirects (3xx) are not followed and count as a failure.

**Retries.** Up to 10 attempts over about 3 days, until one gets a 2xx:

| Attempt | After first attempt |
|---|---|
| 1 | immediately |
| 2 · 3 · 4 | +1 min · +5 min · +30 min |
| 5 · 6 · 7 | +2 h · +6 h · +12 h |
| 8 · 9 · 10 | +24 h · +48 h · +72 h (last try; then it shows as failed in the delivery log) |

**Test your endpoint** without sending a song: `POST /v1/webhooks/test` with `{"url": "https://…"}` sends a signed `ping` event right now. `GET /v1/webhooks/deliveries?limit=20` lists your latest deliveries.

```bash
curl -X POST https://api.songbrain.ai/v1/webhooks/test \
  -H "Authorization: Bearer $SONGBRAIN_KEY" -H "Content-Type: application/json" \
  -d '{"url": "https://your.app/songbrain"}'
# → { "delivered": true, "status_code": 200, "latency_ms": 143, "event_id": "evt_…" }

curl "https://api.songbrain.ai/v1/webhooks/deliveries?limit=20" -H "Authorization: Bearer $SONGBRAIN_KEY"
# → { "object": "list", "data": [ { "event_id", "type", "song_id", "url", "attempt",
#      "status_code", "delivered", "latency_ms", "created_at" } ] }
```

**Account events.** When your free songs for the month are used up and the credits cover fewer than 5 songs, the webhook URLs you used in the last 30 days get one `account.low_balance` event (same signature). You also get an email, and the console shows a banner. It fires once per dip and re-arms after you top up.

```json
{ "id": "evt_…", "type": "account.low_balance", "created": 1791200000, "livemode": true,
  "data": { "credits": 75, "songs_left": 3, "free_songs_left": 0, "threshold_songs": 5,
            "price_per_song_credits": 25, "buy_credits_url": "https://app.songbrain.ai/developers/billing" } }
```

Verify (Python):

```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"])
```

## 12. Errors & request IDs

Every response carries a `Songbrain-Request-Id: req_…` header (readable from the browser too). Every error has the same shape and repeats it as `request_id`. Log it; it lets us find your request in seconds.

```json
{ "error": { "code": "insufficient_credits", "message": "…", "request_id": "req_6a0f3e2d1c9b8a7f6e5d4c3b" } }
```

Invalid parameters (a bad query value, a missing field) return `400 invalid_request` with one entry per problem:

```json
{ "error": { "code": "invalid_request", "message": "Invalid request.",
             "request_id": "req_…",
             "errors": [ { "field": "limit", "message": "must be between 1 and 100" } ] } }
```

| Status | Codes | What to do |
|---|---|---|
| 400 | invalid_request · 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 route, or not yours. |
| 409 | still_processing · idempotency_key_reused · idempotency_in_progress | DELETE before the analysis finished, or an Idempotency-Key conflict. |
| 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.

## 13. Rate limits

Every request made with a key returns its current budget:

| Header | Example | Meaning |
|---|---|---|
| `X-RateLimit-Limit` | 120 | Requests per minute for this key. |
| `X-RateLimit-Remaining` | 117 | Requests left in the current window. |
| `X-RateLimit-Reset` | 23 | Seconds until the window has room again. |
| `Retry-After` | 23 | On 429 only: wait this many seconds. |

Separate from the request rate: 3 songs in parallel per account and 200 songs per 24 h (`too_many_in_flight`, `daily_cap`).

## 14. Pagination

`GET /v1/songs` returns newest first. Pass the `next_cursor` of one page as `starting_after` to get the next, until `has_more` is false.

```
GET /v1/songs?limit=20&starting_after=8f0c…
```

```json
{ "object": "list", "has_more": true, "next_cursor": "41be…",
  "data": [ { "id": "…", "status": "done", "livemode": true, … } ] }
```

## 15. Pricing & limits

- **5 free songs** per account per calendar month.
- Then **25 credits per song** (≈ $0.50; 500 credits = $10, 150 = $4.99). Everything is included in that price: analysis, story and shot plan.
- 3 songs in parallel per account, 200 songs per 24 h, 120 requests per minute per key. Need more? https://www.songbrain.ai/api-access#volume
- Legal: Terms §17 (Developer API) https://www.songbrain.ai/terms#api · Privacy §3.6 https://www.songbrain.ai/privacy#api · Data Processing Addendum (applies automatically) https://www.songbrain.ai/dpa · Security & sub-processors https://www.songbrain.ai/security
- Audio is used only for the analysis and never for training. The upload is deleted within 24 h and a compressed preview after 30 days. The analysis stays until you DELETE it.

## 16. Versioning & status

- **/v1 is stable.** We only add fields, endpoints, headers, events and enum values. Ignore fields you don't know.
- Breaking changes only in a new major path (`/v2`), with at least 6 months of overlap and an email to every key owner. Deprecations are announced in the changelog (https://www.songbrain.ai/docs/api/changelog) and in a `Songbrain-Deprecation` response header.
- The response schema id `songbrain.song/1` changes only with a major version.
- **Status:** `GET /v1/status` (no key) returns `operational`, `degraded` or `down` per component, the current queue and the median analysis time. History and incidents: https://www.songbrain.ai/status

```json
{ "status": "operational",
  "components": { "api": "operational", "pipeline": "operational", "webhooks": "operational" },
  "queue": { "songs_processing": 2 }, "median_analysis_sec_24h": 74, "checked_at": "2026-10-07T09:00:00Z" }
```

## 17. Commercial use & licensing

The short version of Terms §17.4 (https://www.songbrain.ai/terms#api):

| Question | Answer | Details |
|---|---|---|
| Use it in a paid SaaS | yes | Including products that generate images or videos for your customers. |
| Pass results to end users | yes | Show, deliver, let them download, changed or unchanged. |
| White-label | yes | No attribution to Songbrain required. |
| Ownership of results | yours | Analysis, story, shot plans and prompts for your songs; we claim no rights in them. |
| Store results | forever | Even after you delete the song or close the account. |
| Train your own models on results | yes | Except a model built to reproduce the Songbrain API for third parties. |
| Resell raw API access / bulk dataset | no | Only with written consent. |
| Volume (1,000+ songs/month) | contract | Volume pricing, invoice, DPA, SLA. |

## 18. 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. Official MCP Registry name: `io.github.songbrain-ai/songbrain`.

```bash
claude mcp add --transport http songbrain https://api.songbrain.ai/mcp \
  --header "Authorization: Bearer sb_live_…"
```

| Tool | Auth | What it does |
|---|---|---|
| `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. Optional `lyrics` for the exact words. |
| `get_song` | key | Status, then the document. |
| `get_account` | key | Free songs left, credits. |

## 19. 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.

| Spotify | | Songbrain |
|---|---|---|
| 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 |

---

Get a free API key (5 free songs every month, no card needed): https://app.songbrain.ai/developers
