Kolbo.AIKolbo.AI Docs
Developer API

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 cleanTrackCredits Kolbo 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

RoutesLimit
All read routes (search, analyze-script, catalog, facets, track/:id/*)60 requests / minute / user
clean/:trackId and import5 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.

Relevance search over the catalog with optional filters and sort.

Endpoint

POST /api/v1/music-library/search

Request Body

ParameterTypeRequiredConstraints
querystringNoKeyword search, e.g. "uplifting corporate". Max 200 chars (longer → 400); a non-string → 400. Omit for a filter-only browse.
moodstringNoSingle 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.
genrestringNoSingle genre value. Use a value from Facets. Same matching rules as mood.
instrumentstringNoSingle instrument value from Facets. Applied only when the catalog index is serving the request — the upstream catalog has no instrument filter.
bpmMin / bpmMaxnumberNoBeats-per-minute range. Must be a real JSON number — a numeric string ("120") is silently ignored, not coerced.
durationMin / durationMaxnumberNoTrack duration range, in seconds. Same rule: a numeric string is silently ignored.
hasStemsbooleanNoOnly 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.
hasLyricsbooleanNoOnly tracks that have lyrics. Same boolean rule, and the same true-only path switch.
sortstringNoOne of duration-asc, duration-desc, bpm-asc, bpm-desc, title. Any other value is ignored (relevance order).
shuffleSeednumber | stringNoStable 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).
limitnumberNoResults per page. Default 20, clamped to 1–40.
offsetnumberNoPagination offset. Default 0.
projectIdstringNoProject 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-script

Request Body

ParameterTypeRequiredConstraints
scriptstringYesThe 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/catalog

Query Parameters

ParameterTypeRequiredConstraints
limitnumberNoResults per page. Default 50, clamped to 1–100.
offsetnumberNoPagination offset. Default 0.
sortstringNoSame values as Search: duration-asc, duration-desc, bpm-asc, bpm-desc, title.
projectIdstringNoClean-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/facets

Response

{
  "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
ParameterTypeRequiredConstraints
:idstringYesThe track id from Search / Catalog.
projectId (query)stringNoClean-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.

Stems and alternate versions (instrumental, cutdown, minus-mix) of a master track.

Endpoint

GET /api/v1/music-library/track/:id/related

Accepts 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/lyrics

Response

{
  "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/:trackId

Request Body

ParameterTypeRequiredConstraints
:trackId (path)stringYesTrack id. Max 64 chars.
requestIdstringYesIdempotency 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.
formatstringNomp3 (default) or wav. Anything else → 400. Request wav only when the track reports hqAvailable: true.
purposestringNodownload (default) or timeline. Anything else → 400.
stemstringNoA stemRef from Track Related to acquire that stem instead of the master. Max 512 chars. The response kind comes back "stem".
projectIdstringNoProject 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.
licenseTierstringNopersonal_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.
trackobjectNoOptional 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/import

Request Body

ParameterTypeRequiredConstraints
trackIdstringYesTrack 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.
formatstringNomp3 (default) or wav.
qualitystringNoLegacy alias — "wav" selects WAV; anything else falls back to MP3. Ignored when format is present.
requestIdstringNoIdempotency 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.
projectIdstringNoProject 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.
licenseTierstringNoSame values and default as Acquire Clean Track.
trackobjectNoTrack 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

  1. From a script → POST /music-library/analyze-script to derive query / mood / genre.
  2. Search → POST /music-library/search with that query plus filters from Facets. Play previewAudioUrl for the user.
  3. Inspect → GET /music-library/track/:id/related for stems and cutdowns, .../lyrics for vocal tracks.
  4. Commit → POST /music-library/import (with your own requestId) to land a clean file on Kolbo's CDN, or POST /music-library/clean/:trackId when 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.

CodeStatusMeaning
MUSIC_LIBRARY_UNAVAILABLE503 / 502Not configured on this environment, or temporarily unreachable (read routes).
SYNCI_NOT_CONFIGURED503Same condition on the two paid routes, which report the raw code.
MUSIC_LIBRARY_TIMEOUT504Upstream timed out — retry.
MUSIC_LIBRARY_RATE_LIMITED429Catalog rate limit reached.
MUSIC_LIBRARY_NOT_FOUND404Track not found.
MUSIC_LIBRARY_ERROR502Any other catalog failure.
MUSIC_LIBRARY_SCRIPT_EMPTY422analyze-script could not derive a search.
MUSIC_LIBRARY_SCRIPT_ERROR502analyze-script failed.
API_KEY_READ_ONLY403The paid routes need a write-enabled API key.
SYNCI_BAD_REQUEST400Invalid clean-music request (bad format, purpose, requestId, or stem).
INVALID_PROJECT_ID400projectId is not a valid id.
PROJECT_ACCESS_DENIED403You need edit access on that project.
SYNCI_CLEAN_IN_PROGRESS409That requestId is already running.
SYNCI_CLEAN_REQUEST_USED409That requestId has already been used.
SYNCI_IDEMPOTENCY_MISMATCH409That requestId belongs to a different action.
SYNCI_INSUFFICIENT_CREDITS402You are not a subscriber and the paying account's Kolbo credit balance is below the track price. Nothing was charged.
SYNCI_CLEAN_CREDITS_EXHAUSTED402Clean downloads are temporarily unavailable (vendor licence balance empty). Carries creditsRemaining when the vendor reported one.
SYNCI_PURCHASE_UNCONFIGURED503Track purchases are not priced on this environment, so a non-subscriber cannot buy. Subscribers are unaffected.
STOCK_AUDIO_DAILY_LIMIT429Daily clean-download ceiling reached (see Rate limits). Resets at midnight.
SYNCI_RATE_LIMITED429Vendor rate limit on a paid route — retry shortly.
SYNCI_NOT_FOUND404Track (or stem) not found on a paid route.
MUSIC_LIBRARY_ERROR400import called without a string trackId.
SYNCI_LIBRARY_FAILED502import failed while copying the file to the CDN.
SYNCI_TIMEOUT504Vendor timed out on a paid route — retry.
Any other SYNCI_* code502The vendor returned an unusable clean-audio response, or the resolved file was on an untrusted host.