Skip to navigation

JavaScript SDK

aegis-api — a typed client for every Aegis endpoint.

View as Markdown

aegis-api wraps the whole API reference with types generated from the same OpenAPI document the reference is built from. It has no runtime dependencies and is ESM-only.

Two things it does for you that raw fetch does not:

  • Successful responses arrive already unwrapped. The API answers { data }; the SDK resolves to the inner value.
  • Anything the API rejects arrives as an AegisError, carrying a status, a code, and the request id.

Install

npm install aegis-api

Needs a runtime with a global fetch (Node 18+). The event socket additionally needs a global WebSocket (Node 22+), unless you supply your own.

Create a client

import { Aegis } from 'aegis-api'
const aegis = new Aegis({ apiKey: process.env.AEGIS_API_KEY })
OptionTypeDefaultNotes
apiKeystring—Sent as Authorization: Bearer <key>
baseUrlstringhttps://api.aegi.runPoint somewhere else for local testing
timeoutMsnumber30000Per request
fetchtypeof fetchglobalInject a custom implementation
userAgentstringaegis.jsSent as user-agent

Passing neither apiKey nor baseUrl throws immediately — it is always a mistake, and failing at construction beats failing on the first request.

A first call

const { translatedText } = await aegis.translation.translate({
text: 'hello',
to: 'es',
})
console.log(translatedText) // hola

The method signature is derived from the operation, so a misspelled field or a value of the wrong type is a compile error rather than a runtime surprise.

Resources

Each namespace maps to a path prefix, and method names follow the path:

NamespaceCoversExample
aegis.music/v1/music/**aegis.music.play(guildId, { query: 'lofi' })
aegis.jobs/v1/jobs/**aegis.jobs.wait(jobId, { timeout: 60 })
aegis.image/v1/image/**aegis.image.filter('ascii', { image })
aegis.tools/v1/tools/**aegis.tools.screenshot({ url })
aegis.minecraft/v1/minecraft/**aegis.minecraft.info('Notch')
aegis.roblox/v1/roblox/**aegis.roblox.user('Builderman')
aegis.translation/v1/translation/**aegis.translation.detect({ text })
aegis.bible/v1/bible/**aegis.bible.chapter('BBE', 'Genesis', 1)
aegis.holidays/v1/holidays/**aegis.holidays.next('US')
aegis.fun/v1/fun/**aegis.fun.roleplay({ type: 'hug' })
aegis.general/v1/statusaegis.general.status()
aegis.binarydeprecated — use aegis.toolsaegis.binary.encode({ text })

The full method-by-method listing is on SDK resources.

Any endpoint, by path

aegis.request reaches any route directly, still typed:

const status = await aegis.request('get', '/v1/status')
await aegis.request('post', '/v1/music/guilds/{guildId}/play', {
params: { guildId },
body: { query: 'lofi' },
})

Passing a query to a route that has none is a type error, so the compiler keeps you honest even on the escape hatch.

Errors

import { AegisError } from 'aegis-api'
try {
await aegis.music.play(guildId, { query: 'lofi' })
} catch (error) {
if (error instanceof AegisError) {
error.status // 403
error.code // 'forbidden'
error.requestId // quote this when asking for help
error.body // the parsed error envelope
}
}

There is one error class, not a hierarchy — branch on status or code. See SDK errors.

Events

aegis.music carries the events socket:

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

Types

The package re-exports the generated schema, so any response shape is reachable by name:

import type { Track, PlayerState, Job, components } from 'aegis-api'
// Not re-exported by name, but still reachable:
type SearchResult = components['schemas']['MusicSearchResult']

Types are regenerated from the live API, so a field the API renames shows up as a type error on your next build rather than as undefined at runtime.