VYBE Library API v1
VYBE Library for developers
Bring real music from independent artists into your app, royalty-free. Every song comes with what you need to use it well: BPM, key, Camelot, energy, VYBEs, lyrics, credits, artwork and a waveform, all packed inside its .vybe file.
Only songs whose artists switched on "Share with VYBE partner apps". Nothing else is ever returned.
One .vybe file holds the audio in several codecs, a preview, the cover, the waveform and all metadata.
Filter by VYBE, BPM range, key, Camelot and energy to build sets, game soundtracks or party queues.
Getting access
The VYBE Library is open to approved partner apps only. To apply, email [email protected] with your company, the app you want to use the music in, where it is available, and whether the use is commercial (for example in a paid product, a venue, or around ads). We review every request.
When you are approved we create your partner account and send you an API key. Using the API means you accept the VYBE Library API and Partner Terms. Commercial partners only receive songs whose artists also allowed commercial use.
Authentication
All requests go to the base URL below over HTTPS. Send your key in the Authorization header as a bearer token, or in an x-vybe-key header. Keys look like vybe_pk_ followed by 40 letters and numbers.
https://vybemusic.com/api/library/v1curl https://vybemusic.com/api/library/v1/me \
-H "Authorization: Bearer $VYBE_KEY"
const VYBE = 'https://vybemusic.com/api/library/v1';
const vybe = (path, init = {}) => fetch(VYBE + path, {
...init,
headers: { Authorization: 'Bearer ' + process.env.VYBE_KEY, 'Content-Type': 'application/json', ...init.headers },
}).then(async r => { const body = await r.json(); if (!r.ok) throw Object.assign(new Error(body.error.message), body.error); return body; });
const me = await vybe('/me');Errors
Errors use normal HTTP status codes and always have the same JSON shape:
{ "error": { "code": "invalid_request", "message": "bpm_min must be a number between 20 and 400" } }| Status | Code | Meaning |
|---|---|---|
| 400 | invalid_request | A parameter or the body is not valid. The message says which. |
| 401 | missing_key, invalid_key | No key, a malformed key, or a revoked key. |
| 403 | partner_paused, partner_revoked | Your access is paused or has ended. |
| 404 | not_found | The track or artist does not exist or is not shared with you (any more). |
| 413 | too_large | The body is over 512 KB. |
| 429 | rate_limited | Too many requests. Wait for the number of seconds in Retry-After. |
| 5xx | unavailable, server_error | Something went wrong on our side. Retry with backoff. |
Rate limits
Each key has a per-minute request limit, set when you are approved (600 a minute unless agreed otherwise; /v1/me shows yours). Every response carries X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset (Unix seconds). Over the limit you get a 429 with a Retry-After header. Cache search results on your side instead of repeating the same query, and batch usage events.
/v1/meYour partner account, its status and your rate limit.
{
"name": "Your App",
"slug": "your-app",
"status": "active",
"commercial": false,
"rate_limit": { "per_minute": 600 },
"terms_version": "2026-10-21",
"key": { "prefix": "vybe_pk_Ab3x", "label": "production" }
}/v1/vybesThe VYBEs a song can belong to. Use the id in the vybe filter of /v1/tracks.
| id | Name |
|---|---|
| euphoric | Happy |
| chill | Chill |
| latenight | Late Night |
| sunset | Road Trip |
| focus | Focus |
| dreamy | Sleep |
| hype | Party |
| deep | Sad |
| fuel | Workout |
| crush | Love |
{ "data": [ { "id": "hype", "name": "Party", "line": "Your living room is a club now. Act accordingly." }, ... ] }/v1/tracksSearch and filter the songs shared with you, newest releases first. All parameters are optional and can be combined.
| Parameter | Description |
|---|---|
| q | Text search in song titles and artist names. |
| vybe | One VYBE id, for example hype. |
| bpm_min, bpm_max | Tempo range, 20 to 400. |
| key | Musical key as stored, for example A minor (case does not matter). |
| camelot | Camelot code, 1A to 12B. Neighbouring codes mix well. |
| energy_min, energy_max | Energy from 0 (calm) to 1 (intense). |
| genre | Genre name, exact match, case does not matter. |
| explicit | false for clean songs only, true for explicit only. |
| artist | An artist slug. |
| limit | 1 to 100, default 50. |
| cursor | The next_cursor from the previous page. |
curl "https://vybemusic.com/api/library/v1/tracks?vybe=hype&bpm_min=120&bpm_max=128&camelot=8A&explicit=false&limit=20" \
-H "Authorization: Bearer $VYBE_KEY"
const qs = new URLSearchParams({ vybe: 'hype', bpm_min: '120', bpm_max: '128', camelot: '8A', explicit: 'false', limit: '20' });
let page = await vybe('/tracks?' + qs);
const all = [...page.data];
while (page.next_cursor) {
page = await vybe('/tracks?' + new URLSearchParams({ ...Object.fromEntries(qs), cursor: page.next_cursor }));
all.push(...page.data);
}{
"data": [
{
"id": "6c1f2a4e-8d3b-4f7a-9b21-0e5d7c9a1f30",
"slug": "song-title",
"title": "Song Title",
"artist": { "name": "Artist Name", "slug": "artist-name", "avatar": "https://vybemusic.com/m/pub/a/.../image/avatar.webp", "verified": true, "url": "https://vybemusic.com/app/#/artist/artist-name" },
"release": { "title": "Release Title", "kind": "ep" },
"duration": 201.4,
"bpm": 124,
"key": "A minor",
"camelot": "8A",
"energy": 0.82,
"loudness": -8.6,
"vybes": ["hype", "fuel"],
"genre": "House",
"language": "en",
"explicit": false,
"isrc": "AEA0Q2600001",
"cover": "https://vybemusic.com/m/pub/a/.../image/cover.webp",
"waveform": [0.12, 0.34, 0.58, ...],
"has_lyrics": true,
"codecs": ["opus", "aac"],
"released_at": "2026-10-01T00:00:00Z",
"credits": [ { "name": "Artist Name", "role": "Main artist" }, { "name": "Producer Name", "role": "Producer" } ],
"links": { "vybe": "https://vybemusic.com/app/#/track/song-title" },
"attribution": "Music from VYBE: Song Title by Artist Name"
}
],
"next_cursor": "eyJvIjoyMH0"
}When next_cursor is null there are no more results. A page can hold fewer items than limit; keep following next_cursor until it is null.
/v1/tracks/:idOne song by id (or slug), with everything from the list plus lyrics and the analysis made when the artist uploaded it.
curl https://vybemusic.com/api/library/v1/tracks/6c1f2a4e-8d3b-4f7a-9b21-0e5d7c9a1f30 \
-H "Authorization: Bearer $VYBE_KEY"
const track = await vybe('/tracks/' + id);{
...,
"lyrics": {
"plain": "First line\nSecond line",
"synced": [ { "t": 12.4, "line": "First line" }, { "t": 15.9, "line": "Second line" } ]
},
"analysis": {
"beats": { "first": 0.42, "period": 0.48387, "downbeat": 0.42 },
"sections": [ { "start": 0, "end": 30.9, "label": "intro" }, ... ],
"drop": 61.8,
"danceability": 0.78,
"valence": 0.64,
"energy_curve": [0.21, 0.25, ...]
}
}lyrics and analysis are null when the song has none. Beat times are in seconds: beat n is at first + n * period.
/v1/tracks/:id/streamGet a media link for one song. Links work for 6 hours, support HTTP Range requests (seeking, iPhone players) and need no headers, so you can hand them straight to a player.
| Field | Description |
|---|---|
| format | audio (default): the full song as one playable audio file. vybe: the whole .vybe file, for the reader SDK. |
| user | Optional. Your own opaque id for the listener (never an email, name or phone number). We store only a one-way hash. |
| country | Optional. The listener's country as a 2 letter ISO code, for example AE. |
curl -X POST https://vybemusic.com/api/library/v1/tracks/6c1f2a4e-8d3b-4f7a-9b21-0e5d7c9a1f30/stream \
-H "Authorization: Bearer $VYBE_KEY" \
-H "Content-Type: application/json" \
-d '{"format":"audio","user":"u_83f1c2","country":"AE"}'
const media = await vybe('/tracks/' + id + '/stream', {
method: 'POST',
body: JSON.stringify({ format: 'audio', user: 'u_83f1c2', country: 'AE' }),
});
audioElement.src = media.stream_urls.aac || media.url; // in the browser or app{
"format": "audio",
"url": "https://vybemusic.com/m/4f9c...e21a",
"meta_url": "https://vybemusic.com/m/4f9c...e21a?meta=1",
"codecs": ["opus", "aac"],
"stream_urls": { "opus": "https://vybemusic.com/m/4f9c...e21a?c=opus", "aac": "https://vybemusic.com/m/4f9c...e21a?c=aac" },
"expires_at": "2026-10-21T18:00:00.000Z",
"attribution": "Music from VYBE: Song Title by Artist Name"
}For audio, the plain url picks a codec for the player (AAC for Apple devices, Opus elsewhere). Add ?c=opus, ?c=aac or ?c=mp3 to choose, and ?meta=1 to read the song's metadata (the META chunk) as JSON. For vybe, url is the whole file and meta_url is null; read the metadata with the SDK. Each call counts as one media link in your usage.
/v1/usageReport what your listeners did. Up to 500 events per call.
| Field | Description |
|---|---|
| track_id | The song id. |
| event | stream, complete, skip or like. |
| seconds | Seconds listened in this play (0 to 86400). |
| user | Optional opaque listener id, hashed on arrival. |
| country | Optional 2 letter ISO country code. |
curl -X POST https://vybemusic.com/api/library/v1/usage \
-H "Authorization: Bearer $VYBE_KEY" \
-H "Content-Type: application/json" \
-d '{"events":[{"track_id":"6c1f2a4e-8d3b-4f7a-9b21-0e5d7c9a1f30","event":"stream","seconds":187,"user":"u_83f1c2","country":"AE"}]}'
// collect events as they happen, then send them in batches
await vybe('/usage', { method: 'POST', body: JSON.stringify({ events: queue.splice(0, 500) }) });{ "accepted": 1, "rejected": 0 }Events for songs that are not shared with you are not counted and show up in rejected. Invalid events make the whole call fail with a 400 that names the event.
/v1/artists/:slugAn artist's public profile and the songs they share with you (newest first).
curl https://vybemusic.com/api/library/v1/artists/artist-name \
-H "Authorization: Bearer $VYBE_KEY"
const artist = await vybe('/artists/artist-name');{
"name": "Artist Name",
"slug": "artist-name",
"avatar": "https://vybemusic.com/m/pub/a/.../image/avatar.webp",
"cover": "https://vybemusic.com/m/pub/a/.../image/cover.webp",
"city": "Dubai",
"verified": true,
"genres": ["House", "Afro House"],
"url": "https://vybemusic.com/app/#/artist/artist-name",
"tracks": [ { ...track } ]
}The .vybe format
A .vybe file is one song in one file: metadata, cover art, one or more full length audio streams, preview clips and a waveform, packed as typed chunks behind a small index. All integers are little endian.
| Bytes | Field |
|---|---|
| 0 to 3 | Magic, ASCII VYBE |
| 4 to 5 | u16 version (1) |
| 6 to 7 | u16 flags (bit 0: signed by VYBE) |
| 8 to 11 | u32 number of chunks, N |
| 12, 28 each | Index entry per chunk: type (4 ASCII), u32 flags, u64 offset, u64 length, u32 CRC32 |
The index is always in the first bytes of the file, so a player can read it with one small Range request and then fetch only the chunks it needs.
| Chunk | Contents |
|---|---|
| META | UTF-8 JSON metadata: title, artist, BPM, key, Camelot, energy, VYBEs, lyrics, credits, sections. Always first. |
| ARTW | Cover image (WebP or JPEG). |
| AOPS, AAAC, AMP3 | Full song as Ogg Opus, AAC (ADTS) or MP3. |
| POPS, PAAC, PMP3 | Preview clip in the same codecs. |
| PEAK | Waveform, 2000 points from 0 to 255. |
| SIGN | Signature written by VYBE. Opaque to clients. |
Readers must ignore chunk types and META fields they do not know, so new ones can be added without breaking your app.
Reader SDK
vybe.js reads (and writes) .vybe files with no dependencies. It works in browsers, Web Workers, Cloudflare Workers and Node.
<script src="https://vybemusic.com/sdk/vybe.js"></script>
<script>
// media.url from POST /v1/tracks/:id/stream with {"format":"vybe"}
const song = await Vybe.open(media.url); // reads only the index and META (Range requests)
console.log(song.meta.title, song.meta.bpm);
const audio = await song.audio({ prefer: ['opus', 'aac', 'mp3'] }); // best stream this device can play
player.src = URL.createObjectURL(audio.blob);
const peaks = await song.peaks(); // Uint8Array waveform
const lyrics = song.lyrics(); // { plain, synced: [{ t, line }] } or null
</script>
const Vybe = require('./vybe.js'); // or: import Vybe from './vybe.js'
const buf = await fetch(media.url).then(r => r.arrayBuffer());
const file = Vybe.parse(buf); // the whole file in memory
file.meta; // META JSON
file.verify(); // true when every chunk matches its CRC32
file.audio({ prefer: ['aac'] }); // { blob, mime, codec, type, bytes }
file.audio({ preview: true }); // the preview clip instead of the full song
file.cover(); // Blob of the artwork
file.get('PEAK'); // raw bytes of any chunk| Call | Returns |
|---|---|
| Vybe.open(url, {fetch, headers}) | A remote file read with Range requests. Its get, audio, cover and peaks return promises. |
| Vybe.parse(arrayBuffer) | A file held in memory: meta, chunks, has(type), get(type), cover(), audio({prefer, preview}), peaks(), lyrics(), verify(). |
| Vybe.readIndex(bytes) | Only the index, from the first bytes of a file. |
| Vybe.canPlay(codec) | Whether this device can play opus, aac or mp3. |
| Vybe.pack({meta, chunks}) | Builds a .vybe file (for tools and tests). |
Usage reporting rules
- Send a
streamevent for every play that starts, with the seconds listened when it ends. Addcompletewhen a song plays to the end,skipwhen a listener skips it, andlikewhen they save or like it. - Send events in batches of up to 500, at least once a day. Sending them as they happen is fine too, within your rate limit.
- Never send personal data.
usermust be an opaque id that means nothing outside your app; we store only a one-way hash of it. Leave it out if you do not need per-listener insights. - Artists see how their songs do in partner apps, so accurate reports help the artists who share music with you.
Attribution
- Whenever a song plays or is listed, show "Music from VYBE" with the artist name and the song title. The
attributionfield gives you the exact text, for exampleMusic from VYBE: Song Title by Artist Name. - Where your interface allows links, link to the artist or the song on VYBE (
artist.url,links.vybe). - Keep credits, artwork, ISRC and other metadata intact. Do not suggest that an artist endorses your app.
Caching and removals
- Stream only. Media links last 6 hours; ask for a new one when a link expires (it then answers
403). Do not store audio beyond the short buffering a player needs, and never offer downloads. - You may cache song metadata, but refresh it at least once a day. When a song or artist answers
404, it is no longer shared with you: remove it from your app, caches and saved lists within 30 days, or sooner if we ask. - Do not use any audio, lyrics, artwork or metadata to train or evaluate AI models.
The full rules are in the VYBE Library API and Partner Terms. Questions: [email protected].