Skip to navigation

Music

A full voice player per Discord server, driven entirely over HTTP.
View as Markdown

The Music API is the largest part of Aegis and the only one that behaves like a stateful service rather than a set of lookups. Every other feature answers a question; this one remembers.

How it is shaped

Almost every route hangs off a guild:

/v1/music/guilds/{guildId}/<action>

{guildId} is a Discord snowflake. There is no “create a player” call and no player id to hold onto — you name the guild and the player materialises on first use.

Three consequences follow from that:

  • You supply the guild, every time. The API does not track which guild you mean across calls.
  • State persists. play queues behind whatever is already playing; queue reads back everything the player knows.
  • Mutating routes return the new state, so you usually do not need a second read to confirm what happened.

Requirements

The Music API acts as a Discord bot, and it works out which one from your API key rather than from the request. If your key is not linked to a bot, every music endpoint answers:

{
"error": {
"code": "forbidden",
"message": "This API key is not linked to a bot. Link it to one to use the music API."
}
}

Concurrency is bounded by plan — 5 simultaneous players on free, up to 100 on scale. Going over returns 403 with code player_limit. See rate limits.

Playing something

Give it a search query and it plays what it finds:

await aegis.music.play(guildId, {
query: 'lofi hip hop',
channelId: voiceChannelId,
})

channelId is the voice channel to join. It is required the first time, when the bot is not already connected in that guild.

The response tells you what happened:

const { track, position, added } = await aegis.music.play(guildId, { query: 'lofi' })
// position: 0 means it started playing; anything higher means it queued
// added: how many tracks were queued — a playlist adds several

Searching, then playing

search looks through a provider without touching the player, and returns an opaque encoded token per result:

const { results } = await aegis.music.search({ query: 'rick astley', limit: 5 })
await aegis.music.play(guildId, { track: { encoded: results[0].encoded } })

Searching then playing results[0] is not the same as playing the query — search lets someone pick from a list before anything starts. provider accepts youtube (the default) or spotify.

Reading the player

const state = await aegis.music.queue(guildId)
state.current // the track playing now, or null
state.queue // everything behind it
state.paused
state.positionMs
state.volume
state.loop // 'off' | 'track' | 'queue'
state.channelId

Controlling playback

pause(guildId) / resume(guildId)Toggle playback
skip(guildId)Next track; track is null if the queue ran out
restart(guildId)Replay the current track from the start
stop(guildId)Stop and leave; clears the queue
seek(guildId, { position })Jump to a position in milliseconds

The queue

const { queue } = await aegis.music.shuffle(guildId)
await aegis.music.clearQueue(guildId)
await aegis.music.removeFromQueue(guildId, 0) // 0 is the next track up
await aegis.music.moveInQueue(guildId, 3, { to: 0 })

Queue indices are zero-based and capped at 200 entries.

Volume and muting

await aegis.music.setVolume(guildId, { volume: 60 })
await aegis.music.mute(guildId) // or { muted: false }
await aegis.music.unmute(guildId)

Volume runs from 0 to 200, where 100 is normal. Mute is separate from volume, so unmuting restores the level you had rather than resetting it.

Loop mode

await aegis.music.setLoop(guildId, { mode: 'queue' })
const { loop } = await aegis.music.loop(guildId)

track repeats one song, queue cycles the whole rotation — and keeps going even when the queue holds a single track.

Audio filters

filters applies DSP to playback in one call. Every field is optional; send {} to clear them all.

await aegis.music.setFilters(guildId, {
equalizer: [{ band: 0, gain: 0.5 }],
timescale: { speed: 1.5, pitch: 1, rate: 1 },
lowPass: { smoothing: 20 },
})

The fifteen filters are equalizer, karaoke, timescale, tremolo, vibrato, rotation, distortion, channelMix, lowPass, echo, chorus, compressor, highpass, phaser, and spatial. Each takes its own range — equalizer bands run from 0 (25 Hz) to 14 (16 kHz), for instance — so check the API reference for the schema before composing a preset.

Fading

Volume ramps for track starts, ends, seeks, and ducking under other audio. Each section takes a duration in milliseconds and a curve.

await aegis.music.setFading(guildId, {
enabled: true,
trackStart: { duration: 2000, curve: 'linear' },
trackStop: { duration: 3000, curve: 'exponential' },
})

Durations cap at 30 seconds. Curves are linear, exponential, logarithmic, and s-curve.

Mix layers

An extra layer playing alongside the main track — instrumental, backing vocals, anything.

const { layer } = await aegis.music.addMix(guildId, {
track: { encoded },
volume: 0.4,
})
await aegis.music.setMixVolume(guildId, layer.mixId, { volume: 0.6 })
await aegis.music.removeMix(guildId, layer.mixId)

Track metadata

Two endpoints take an encoded token and return what search knows about it:

await aegis.music.meaning(guildId, { encoded }) // encyclopedic context, or null
await aegis.music.lyrics(guildId, { encoded, lang: 'en' })

meaning resolves with meaning: null when nothing is found — that is a successful response, not an error. lyrics reports whether anything was found and whether the timing is synced.

Knowing when things happen

Rather than poll the player, subscribe to the events socket:

const off = aegis.music.on('trackStarted', ({ guildId, track }) => {
console.log(`${track.title} in ${guildId}`)
})

Errors

Music uses its own error mapping, drawn from the same envelope as everything else:

CodeStatusTypical cause
bad_request400An index or track reference that makes no sense
forbidden403The key is not linked to a bot
player_limit403The project is at its concurrent-player ceiling
not_found404No player in that guild yet
conflict409Nothing playing, already connected, or the queue is full
validation_error422A body field failed validation
service_unavailable503The music workers are down or timed out

Music routes take a JSON body, so a request with the wrong Content-Type is rejected with 415 before any of this applies.