> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.aegi.run/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.aegi.run/_mcp/server.

# JavaScript SDK

`aegis-api` wraps the whole [API reference](/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

```bash
npm install aegis-api
```

Needs a runtime with a global `fetch` (Node 18+). The [event socket](/sdk-events) additionally needs a global `WebSocket` (Node 22+), unless
you supply your own.

## Create a client

```ts
import { Aegis } from 'aegis-api'

const aegis = new Aegis({ apiKey: process.env.AEGIS_API_KEY })
```

| Option      | Type           | Default                | Notes                                  |
| ----------- | -------------- | ---------------------- | -------------------------------------- |
| `apiKey`    | `string`       | —                      | Sent as `Authorization: Bearer <key>`  |
| `baseUrl`   | `string`       | `https://api.aegi.run` | Point somewhere else for local testing |
| `timeoutMs` | `number`       | `30000`                | Per request                            |
| `fetch`     | `typeof fetch` | global                 | Inject a custom implementation         |
| `userAgent` | `string`       | `aegis.js`             | Sent 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

```ts
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:

| Namespace           | Covers                           | Example                                        |
| ------------------- | -------------------------------- | ---------------------------------------------- |
| `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/status`                     | `aegis.general.status()`                       |
| `aegis.binary`      | *deprecated* — use `aegis.tools` | `aegis.binary.encode({ text })`                |

The full method-by-method listing is on [SDK resources](/sdk-resources).

## Any endpoint, by path

`aegis.request` reaches any route directly, still typed:

```ts
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

```ts
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](/sdk-errors).

## Events

`aegis.music` carries the [events socket](/sdk-events):

```ts
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:

```ts
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.