Music Library
Search Kolbo's licensed production-music catalog, resolve preview URLs, and acquire clean, unwatermarked masters.
The Music Library is Kolbo's licensed production-music catalog. Use it to score a video, ad, or voiceover with an existing, rights-cleared track.
Every endpoint is authenticated with your X-API-Key.
Discovery is free. Clean final use is included with a subscription, or bought per track. search, catalog, facets, analyze-script, and the three track/:id/* lookups cost no credits. POST /music-library/clean/:trackId and POST /music-library/import require an API key with write permission and resolve payment before any file is fetched:
- Active paid subscription (or any organization / workspace membership) — clean masters, every format, downloads and imports at no charge.
- Everyone else — buys the track once for
cleanTrackCreditsKolbo credits (the price is returned at the top level of every search / catalog response, on each gated track, and on track-audio for a non-subscriber). After that the track is owned: re-downloads, the other format, stems and import of that track are free.
Two-tier URLs. Callers without clean access receive the watermarked 128 kbps preview in every audio slot of search, catalog and track-audio responses (audioUrl320, audioUrlWav and previewDownloadUrl are null), and no URLs at all for stems and alternate versions. Subscribers, and owners of a purchased track, get the clean signed URLs. search, catalog, track/:id/audio and track/:id/related accept an optional projectId (24-hex ObjectId, in the query string, or in the body for search): inside a project you are a member of, a subscriber who owns or pays for that project extends clean access to you. An id you are not a member of grants nothing; a malformed one is ignored.
This is the paid licensed catalog — it is not the default place to look for music. Kolbo also ships its own large AI-generated music library, which is free and consumes no vendor credit: GET /api/v1/stock/search?source=kolbo-ai&mediaType=music in the Stock Library, including natural-language "vibe" search. Reach for the Music Library when you specifically need the licensed catalog, a named commercial cue, or stems.
Library vs generation. These endpoints find an existing track. To compose a brand-new original song, use music generation instead.
Rate limits
| Routes | Limit |
|---|---|
All read routes (search, analyze-script, catalog, facets, track/:id/*) | 60 requests / minute / user |
clean/:trackId and import | 5 requests / minute / user, shared with the in-app music routes |
Requests that fail (4xx / 5xx) do not count against these limits. Separately, clean acquisitions count toward a daily ceiling of 300 music / sound-effect downloads per user (resets at midnight server time); past it the paid routes return 429 STOCK_AUDIO_DAILY_LIMIT.
Search
Relevance search over the catalog with optional filters and sort.
Endpoint
POST /api/v1/music-library/searchRequest Body
| Parameter | Type | Required | Constraints |
|---|---|---|---|
query | string | No | Keyword search, e.g. "uplifting corporate". Max 200 chars (longer → 400); a non-string → 400. Omit for a filter-only browse. |
mood | string | No | Single mood value. Use a value from Facets — free text may match nothing. Matched case-insensitively as a substring of the track's moods when the catalog index serves the request. |
genre | string | No | Single genre value. Use a value from Facets. Same matching rules as mood. |
instrument | string | No | Single instrument value from Facets. Applied only when the catalog index is serving the request — the upstream catalog has no instrument filter. |
bpmMin / bpmMax | number | No | Beats-per-minute range. Must be a real JSON number — a numeric string ("120") is silently ignored, not coerced. |
durationMin / durationMax | number | No | Track duration range, in seconds. Same rule: a numeric string is silently ignored. |
hasStems | boolean | No | Only tracks that have separated stems. Must be a real JSON boolean — the string "true" is silently ignored. true forces the live vendor path; false does not — see the callout below. |
hasLyrics | boolean | No | Only tracks that have lyrics. Same boolean rule, and the same true-only path switch. |
sort | string | No | One of duration-asc, duration-desc, bpm-asc, bpm-desc, title. Any other value is ignored (relevance order). |
shuffleSeed | number | string | No | Stable shuffle seed — the same seed returns the same random order across pages. Applies only to a filter-only browse (no query), only when sort is absent, and only on the indexed path (the live vendor fallback ignores it entirely). |
limit | number | No | Results per page. Default 20, clamped to 1–40. |
offset | number | No | Pagination offset. Default 0. |
projectId | string | No | Project scope for clean-access resolution only (see the two-tier callout above). Does not filter results. |
Over-narrow filters are relaxed, not dead-ended. On the indexed path, when your combination of filters matches nothing, the server drops one filter at a time — in the order hasLyrics, hasStems, instrument, durationMin, durationMax, bpmMin, bpmMax, mood, genre — until something matches. The response does not say which filters were dropped, so re-check returned tracks against any filter that is a hard requirement for you.
Examples
curl -X POST https://api.kolbo.ai/api/v1/music-library/search \
-H "X-API-Key: kolbo_live_..." \
-H "Content-Type: application/json" \
-d '{ "query": "uplifting corporate", "genre": "Corporate", "durationMax": 120, "limit": 5 }'Response
{
"success": true,
"tracks": [
{
"id": "12345",
"title": "Bright Horizons",
"artist": "Studio Collective (ASCAP)",
"artistName": "Studio Collective",
"artistId": "778",
"artists": [{ "id": "778", "name": "Studio Collective", "role": "primary" }],
"primaryArtist": { "id": "778", "name": "Studio Collective" },
"album": "Corporate Vol. 3",
"albumId": "91",
"albumCode": "CORP3",
"durationSeconds": 118,
"bpm": 120,
"musicalKey": "C major",
"genre": "Corporate",
"genres": ["Corporate"],
"moodTags": ["Uplifting", "Hopeful"],
"instruments": ["Piano", "Strings"],
"tags": ["build", "positive"],
"artworkUrl": "https://.../artwork.jpg",
"audioUrl": "https://.../audio_128.mp3",
"audioUrl128": "https://.../audio_128.mp3",
"audioUrl320": "https://.../320.mp3",
"audioUrlWav": "https://.../master.wav",
"previewAudioUrl": "https://.../audio_128.mp3",
"previewDownloadUrl": "https://.../download.mp3",
"hqAvailable": true,
"watermarked": false,
"watermarkedUrl": "https://.../watermarked_128.mp3",
"cleanAccess": true,
"hasStems": true,
"hasLyrics": false,
"versionsCount": 3,
"links": null
}
],
"count": 5,
"total": 87,
"sorted": false,
"hasMore": true,
"cleanAccess": true,
"cleanTrackCredits": 50
}The example above is the subscriber view. Top-level cleanAccess says whether the caller gets clean audio across the board; top-level cleanTrackCredits is the current per-track purchase price in Kolbo credits — returned as the price even to subscribers, who are never charged it. For a caller without clean access each track instead carries watermarked: true, cleanAccess: false, the watermarked URL in audioUrl / audioUrl128 / previewAudioUrl, null in audioUrl320 / audioUrlWav / previewDownloadUrl, and its own cleanTrackCredits. A track that caller has already bought comes back ungated with cleanTrackOwned: true and cleanTrackCredits: 0. hqAvailable is always truthful (a 320 / WAV master exists), even when those URLs are withheld. watermarkedUrl is a permanent public URL, and is null for the minority of masters whose watermarked file has not been rendered — for a non-subscriber such a track has no playable preview at all.
Never persist a track URL. artworkUrl and every clean audio*Url are signed URLs — audio expires roughly 1 hour after it is issued, artwork after 7 days. Store the track id and re-resolve at use time via Track Audio.
count is the number of tracks in this page, total the size of the whole result set, and hasMore whether another page exists (all three as described on the indexed path — see the callout below for the live fallback). sorted is true only when you passed a recognized sort value.
The per-track watermarked flag tells you whether the URLs on that track are a watermarked preview or already clean — read it rather than assuming. Regardless of its value, a licensed final use should go through Acquire Clean Track or Import, which return a file explicitly stamped watermarked: false and record the licence (and, for a non-subscriber, are where the track is bought).
hasMore can be false even when more matches exist upstream. Two paths serve this endpoint. The indexed path (a local catalog mirror) filters, ranks and paginates everything and returns a real total / hasMore. The live vendor path is used while that mirror is still warming, and always when you send hasStems: true or hasLyrics: true (the mirror does not carry those two flags); it fetches exactly one upstream page of limit rows (hard-capped at 40) and always returns hasMore: false. Without sort, offset is ignored entirely, count is the raw upstream row count and total the usable count after rows with no id or audio URL are dropped. With a sort, that single page is sorted, cached, then sliced by offset (itself clamped to 40) — so count becomes the slice length and total the size of the whole page. Use GET /music-library/catalog for stable pagination.
Analyze Script
AI helper that turns a video or voiceover script into a music search you can feed straight into Search.
Endpoint
POST /api/v1/music-library/analyze-scriptRequest Body
| Parameter | Type | Required | Constraints |
|---|---|---|---|
script | string | Yes | The script or scene description. Trimmed; empty → 400. Clipped server-side to the first 8000 characters. |
Examples
curl -X POST https://api.kolbo.ai/api/v1/music-library/analyze-script \
-H "X-API-Key: kolbo_live_..." \
-H "Content-Type: application/json" \
-d '{ "script": "A founder walks through an empty office at dawn, reflecting on the journey before the team arrives." }'Response
{
"success": true,
"query": "inspiring cinematic",
"mood": "Emotional",
"genre": "Soundtrack",
"keywords": ["reflective", "hopeful", "piano", "build", "cinematic"]
}The model is asked for a 1–3 word query and single-word mood / genre; what is enforced is the clipping — query to 80 chars, mood and genre to 40 chars each (either can come back ""), and keywords to at most 5 tags of 40 chars. When the model returns no query, the first keyword is used; if there is neither you get 422 with code MUSIC_LIBRARY_SCRIPT_EMPTY.
Browse Catalog
Stable paginated browse with no query.
Endpoint
GET /api/v1/music-library/catalogQuery Parameters
| Parameter | Type | Required | Constraints |
|---|---|---|---|
limit | number | No | Results per page. Default 50, clamped to 1–100. |
offset | number | No | Pagination offset. Default 0. |
sort | string | No | Same values as Search: duration-asc, duration-desc, bpm-asc, bpm-desc, title. |
projectId | string | No | Clean-access project scope, as in Search. |
This route reads only limit, offset, sort, and projectId. Genre / mood / BPM / duration filters passed here are ignored — send them to POST /music-library/search instead.
curl "https://api.kolbo.ai/api/v1/music-library/catalog?limit=20&sort=bpm-desc" \
-H "X-API-Key: kolbo_live_..."Returns { success, tracks, count, total, sorted, cleanAccess, cleanTrackCredits } with the same track shape and gating as Search. There is no hasMore here — page until offset + count reaches total, or (when total is absent) until count comes back smaller than limit. total is omitted on the unsorted live fallback (when the local catalog mirror is not ready) — treat a missing total as "unknown", not zero.
Facets
Distinct genres, moods, and instruments plus the BPM and duration ranges — use these to build precise filtered searches. Cached server-side for 10 minutes.
Endpoint
GET /api/v1/music-library/facetsResponse
{
"success": true,
"genres": ["Corporate", "Soundtrack", "Hip Hop"],
"moods": ["Uplifting", "Tense", "Emotional"],
"instruments": ["Piano", "Guitar", "Drums"],
"bpmRange": { "min": 60, "max": 180 },
"durationRange": { "min": 30, "max": 360 }
}bpmRange and durationRange are null when the catalog does not report them.
Track Audio
Re-resolve the playable URLs for a single track. Call this instead of caching URLs from a search response.
Endpoint
GET /api/v1/music-library/track/:id/audio| Parameter | Type | Required | Constraints |
|---|---|---|---|
:id | string | Yes | The track id from Search / Catalog. |
projectId (query) | string | No | Clean-access project scope, as in Search. |
curl https://api.kolbo.ai/api/v1/music-library/track/12345/audio \
-H "X-API-Key: kolbo_live_..."Response
{
"success": true,
"id": "12345",
"previewAudioUrl": "https://.../128.mp3",
"previewDownloadUrl": "https://.../download.mp3",
"watermarked": false,
"watermarkedUrl": "https://.../watermarked_128.mp3",
"cleanAccess": true,
"urls": {
"128": "https://.../128.mp3",
"320": "https://.../320.mp3",
"wav": "https://.../master.wav"
}
}urls["320"] and urls.wav are null when the catalog does not expose that variant for the track (hqAvailable on the search result tells you in advance whether a WAV exists). These URLs are signed and short-lived.
For a caller without clean access the same gate as Search applies: previewAudioUrl and urls["128"] carry the watermarked URL, previewDownloadUrl, urls["320"] and urls.wav are null, watermarked: true, cleanAccess: false, and cleanTrackCredits gives the purchase price. A track the caller already bought is returned clean with cleanTrackOwned: true and cleanTrackCredits: 0.
Track Related
Stems and alternate versions (instrumental, cutdown, minus-mix) of a master track.
Endpoint
GET /api/v1/music-library/track/:id/relatedAccepts the same optional projectId query parameter as Track Audio.
Response
{
"success": true,
"stems": [
{
"id": "12345-drums",
"name": "Drums",
"variantLabel": "Drums",
"type": "stem",
"element": "drums",
"stemRef": "stem_ab12cd",
"audioUrl": "https://.../drums.mp3",
"downloadUrl": "https://.../drums-download.mp3",
"watermarked": false,
"audioUrl128": "https://.../drums_128.mp3",
"audioUrl320": "https://.../drums_320.mp3",
"audioUrlWav": null
}
],
"versions": [
{ "id": "12345-30s", "name": "30s Cut", "type": "cutdown", "stemRef": null, "audioUrl": "https://.../30s.mp3" }
],
"cleanAccess": true,
"cleanTrackOwned": false
}Stems and versions have no watermarked variant, so they are subscriber-or-owner content: for a caller without clean access every row keeps its metadata (name, type, element, stemRef) but audioUrl, downloadUrl, audioUrl128, audioUrl320 and audioUrlWav are all null, with watermarked: true and cleanAccess: false. Buying the master track unlocks its stems too — top-level cleanAccess is then true and cleanTrackOwned is true (it is false for subscribers, who never needed to buy).
type is one of stem, cutdown, minus_mix, alternative. Pass a row's stemRef as the stem field of Acquire Clean Track to buy a clean version of that stem instead of the master.
Track Lyrics
Lyrics text, lyrical theme, and explicit flag for a single track.
Endpoint
GET /api/v1/music-library/track/:id/lyricsResponse
{
"success": true,
"hasLyrics": true,
"lyrics": "Verse 1...",
"lyricalTheme": "perseverance",
"explicit": false
}lyrics and lyricalTheme are null when the track has none.
Acquire Clean Track
Resolve a clean, unwatermarked MP3 or WAV master (or a single stem) for final use — download or editing-timeline use. Free for subscribers and for a track you already own; otherwise this call buys the track for cleanTrackCredits Kolbo credits.
Paid and immediate. There is no confirmation step: for a non-subscriber a successful call has charged the credits. The balance is checked before anything is fetched (402 SYNCI_INSUFFICIENT_CREDITS if it is short) and the charge is taken only after the clean file resolves, so a failed acquisition costs nothing. In a shared-credit project (projectId) the project's payer is checked and billed, and a track the payer already bought is not charged again. The API key must have write permission (403 / API_KEY_READ_ONLY otherwise). requestId is a required idempotency key — reuse the same value when retrying the same intended action, and a new value for a genuinely new acquisition.
Endpoint
POST /api/v1/music-library/clean/:trackIdRequest Body
| Parameter | Type | Required | Constraints |
|---|---|---|---|
:trackId (path) | string | Yes | Track id. Max 64 chars. |
requestId | string | Yes | Idempotency key. Must match ^[A-Za-z0-9_-]{8,80}$. Reusing it for the same track/format/purpose/project/licence tier returns the cached result (while its signed URLs are still valid) (reused: true); reusing it for a different action → 409 SYNCI_IDEMPOTENCY_MISMATCH. |
format | string | No | mp3 (default) or wav. Anything else → 400. Request wav only when the track reports hqAvailable: true. |
purpose | string | No | download (default) or timeline. Anything else → 400. |
stem | string | No | A stemRef from Track Related to acquire that stem instead of the master. Max 512 chars. The response kind comes back "stem". |
projectId | string | No | Project to attribute (and, in shared-credit mode, bill) the acquisition to. Must be a valid ObjectId (400 INVALID_PROJECT_ID) and you need edit access (403 PROJECT_ACCESS_DENIED). "all" and omission both mean "no project". Note the camelCase name. |
licenseTier | string | No | personal_creator or monetized_commercial. Any other value (including omission) is recorded as monetized_commercial. Stamped on the compliance record only — it does not gate the request. |
track | object | No | Optional track snapshot from the search response, stored on the audit record for reporting. Unrecognized fields are dropped. |
termsVersion / termsAccepted are not read from the request — the terms version and acceptance timestamp are stamped server-side.
Examples
curl -X POST https://api.kolbo.ai/api/v1/music-library/clean/12345 \
-H "X-API-Key: kolbo_live_..." \
-H "Content-Type: application/json" \
-d '{ "format": "wav", "purpose": "timeline", "requestId": "score-hero-cut-01" }'Response
{
"success": true,
"trackId": "12345",
"format": "wav",
"kind": "master",
"audioUrl": "https://.../clean.wav",
"downloadUrl": "https://.../clean-download.wav",
"watermarked": false,
"creditsRemaining": null,
"purchasedCredits": 50
}kind is master or stem. purchasedCredits is present only when this call bought the track (the Kolbo credits charged); it is absent for subscribers and already-owned tracks. creditsRemaining is the vendor's remaining licence allowance (usually null), not your Kolbo credit balance. reused: true is added only when the call replayed a still-valid cached result for the same requestId; the field is absent on a fresh acquisition. The returned URLs are signed and expire — download promptly. Once the cached result has expired, the same requestId returns 409 SYNCI_CLEAN_REQUEST_USED; re-acquire with a new requestId (free, since the track is now owned or covered by your subscription).
Import Track to Media Library
Acquire one clean file and copy it into your media library as project-scoped stock audio, on a stable Kolbo CDN URL. Same entitlement and purchase rules as Acquire Clean Track, plus the CDN copy. purpose and stem are not accepted here — import always lands the master.
Endpoint
POST /api/v1/music-library/importRequest Body
| Parameter | Type | Required | Constraints |
|---|---|---|---|
trackId | string | Yes | Track id (in the body here, not the path). Max 64 chars. Missing or non-string → 400 with code MUSIC_LIBRARY_ERROR; over 64 chars → 400 SYNCI_BAD_REQUEST. |
format | string | No | mp3 (default) or wav. |
quality | string | No | Legacy alias — "wav" selects WAV; anything else falls back to MP3. Ignored when format is present. |
requestId | string | No | Idempotency key, ^[A-Za-z0-9_-]{8,80}$. When omitted the server generates one, so a retry without it can re-spend a credit. Always send your own. |
projectId | string | No | Project to file the media item under (and, in shared-credit mode, bill). Requires edit access; same INVALID_PROJECT_ID / PROJECT_ACCESS_DENIED rules as Acquire Clean Track. |
licenseTier | string | No | Same values and default as Acquire Clean Track. |
track | object | No | Track snapshot from search — supplies originalName (title), authorName (artist) and durationSeconds on the created media item. Only whitelisted scalar fields are kept; URLs (including artworkUrl) are dropped, so the imported item carries no thumbnail. |
Examples
curl -X POST https://api.kolbo.ai/api/v1/music-library/import \
-H "X-API-Key: kolbo_live_..." \
-H "Content-Type: application/json" \
-d '{ "trackId": "12345", "format": "mp3", "requestId": "import-12345-01" }'Response
{
"success": true,
"libraryItem": {
"_id": "...",
"mediaType": "audio",
"url": "https://media.kolbo.ai/kolboai-media/stock-imports/music/...",
"filename": "...mp3"
},
"alreadyImported": false,
"format": "mp3",
"creditsRemaining": null
}The import response does not carry purchasedCredits; for a non-subscriber the purchase is still charged (once per track) exactly as on the clean route. The charge is taken when the clean file resolves, before the CDN copy — if the copy then fails (SYNCI_LIBRARY_FAILED), the track is already owned and a retry is not charged again.
Dedup is per track, per user. If the same track is already in your library (any project, not deleted) the call returns the existing item with alreadyImported: true and nothing is charged — format and creditsRemaining are omitted in that case.
The created item is sourceType: "stock" with metadata.stockSource: "music", so it shows up alongside other stock imports in the Media Library.
Typical Flow
- From a script →
POST /music-library/analyze-scriptto derivequery/mood/genre. - Search →
POST /music-library/searchwith that query plus filters from Facets. PlaypreviewAudioUrlfor the user. - Inspect →
GET /music-library/track/:id/relatedfor stems and cutdowns,.../lyricsfor vocal tracks. - Commit →
POST /music-library/import(with your ownrequestId) to land a clean file on Kolbo's CDN, orPOST /music-library/clean/:trackIdwhen you only need a short-lived download URL.
Errors
All errors return { "success": false, "error": "...", "code": "..." }. On the two paid routes error is a fixed, generic message per code (unlisted codes read "Music library action failed.") — branch on code and the HTTP status, not on the text.
| Code | Status | Meaning |
|---|---|---|
MUSIC_LIBRARY_UNAVAILABLE | 503 / 502 | Not configured on this environment, or temporarily unreachable (read routes). |
SYNCI_NOT_CONFIGURED | 503 | Same condition on the two paid routes, which report the raw code. |
MUSIC_LIBRARY_TIMEOUT | 504 | Upstream timed out — retry. |
MUSIC_LIBRARY_RATE_LIMITED | 429 | Catalog rate limit reached. |
MUSIC_LIBRARY_NOT_FOUND | 404 | Track not found. |
MUSIC_LIBRARY_ERROR | 502 | Any other catalog failure. |
MUSIC_LIBRARY_SCRIPT_EMPTY | 422 | analyze-script could not derive a search. |
MUSIC_LIBRARY_SCRIPT_ERROR | 502 | analyze-script failed. |
API_KEY_READ_ONLY | 403 | The paid routes need a write-enabled API key. |
SYNCI_BAD_REQUEST | 400 | Invalid clean-music request (bad format, purpose, requestId, or stem). |
INVALID_PROJECT_ID | 400 | projectId is not a valid id. |
PROJECT_ACCESS_DENIED | 403 | You need edit access on that project. |
SYNCI_CLEAN_IN_PROGRESS | 409 | That requestId is already running. |
SYNCI_CLEAN_REQUEST_USED | 409 | That requestId has already been used. |
SYNCI_IDEMPOTENCY_MISMATCH | 409 | That requestId belongs to a different action. |
SYNCI_INSUFFICIENT_CREDITS | 402 | You are not a subscriber and the paying account's Kolbo credit balance is below the track price. Nothing was charged. |
SYNCI_CLEAN_CREDITS_EXHAUSTED | 402 | Clean downloads are temporarily unavailable (vendor licence balance empty). Carries creditsRemaining when the vendor reported one. |
SYNCI_PURCHASE_UNCONFIGURED | 503 | Track purchases are not priced on this environment, so a non-subscriber cannot buy. Subscribers are unaffected. |
STOCK_AUDIO_DAILY_LIMIT | 429 | Daily clean-download ceiling reached (see Rate limits). Resets at midnight. |
SYNCI_RATE_LIMITED | 429 | Vendor rate limit on a paid route — retry shortly. |
SYNCI_NOT_FOUND | 404 | Track (or stem) not found on a paid route. |
MUSIC_LIBRARY_ERROR | 400 | import called without a string trackId. |
SYNCI_LIBRARY_FAILED | 502 | import failed while copying the file to the CDN. |
SYNCI_TIMEOUT | 504 | Vendor timed out on a paid route — retry. |
Any other SYNCI_* code | 502 | The vendor returned an unusable clean-audio response, or the resolved file was on an untrusted host. |