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. Final use is a paid vendor action. search, catalog, facets, analyze-script, and the three track/:id/* lookups cost no credits. POST /music-library/clean/:trackId and POST /music-library/import spend one vendor credit per track and require an API key with write permission.
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 |
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. |
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,
"hasStems": true,
"hasLyrics": false,
"versionsCount": 3
}
],
"count": 5,
"total": 87,
"sorted": false,
"hasMore": true
}Never persist a track URL. artworkUrl and every 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.
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. |
This route reads only limit, offset, and sort. 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 } with the same track shape as Search. 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/audiocurl 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,
"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.
Track Related
Stems and alternate versions (instrumental, cutdown, minus-mix) of a master track.
Endpoint
GET /api/v1/music-library/track/:id/relatedResponse
{
"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" }
]
}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
Spend one vendor credit and resolve a clean, unwatermarked MP3 or WAV master (or a single stem) for final use — download or editing-timeline use.
Paid and immediate. There is no confirmation step: a successful call has spent the credit. 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 returns the cached result (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 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": 412
}kind is master or stem. creditsRemaining is the vendor's remaining allowance (may be 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 or re-acquire with the same requestId.
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 paid vendor action as Acquire Clean Track, plus the CDN copy.
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. |
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. Requires edit access. |
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": 411
}Dedup is per track, per user. If the same track is already in your library the call returns the existing item with alreadyImported: true and no credit is spent — 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": "..." }.
| 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_CLEAN_CREDITS_EXHAUSTED | 402 | Clean downloads are temporarily unavailable (vendor licence balance empty). Carries creditsRemaining when the vendor reported one. |
Any other SYNCI_* code | 502 | The vendor returned an unusable clean-audio response, or the resolved file was on an untrusted host. |