> ## Documentation Index
> Fetch the complete documentation index at: https://docs.mage.space/llms.txt
> Use this file to discover all available pages before exploring further.

# TypeScript SDK

> Generate images, video, and audio with every Mage model from Node.js using the official TypeScript SDK, @mage-space/sdk: typed configs, a run call that waits for the result, safe retries, uploads, and typed errors.

`@mage-space/sdk` is the official TypeScript client for the Mage API. It wraps every endpoint, types the config of every model, submits a generation and waits for the result in one call, and retries safely without paying twice.

<Note>
  The SDK is in beta at `0.x`, so a minor version may still change it. The API
  itself is versioned separately and stays stable within `v1`.
</Note>

## Install

```bash theme={null}
npm install @mage-space/sdk
```

It needs Node.js 20.19 or later, or another server runtime with `fetch`. The package is ESM-only with no runtime dependencies; Node.js 20.19 and later can also `require()` it.

<Warning>
  Keep your API key on your servers. The API sends no CORS headers, so browsers
  cannot call it, and a key shipped to a browser can be used to spend your Gems.
  Call the SDK from server code, such as a route handler or a server action.
</Warning>

## Quick start

Create a key in [API → API Keys](https://www.mage.space/api?tab=api-keys) and export it as `MAGE_API_KEY`.

```ts theme={null}
import Mage from '@mage-space/sdk';

const mage = new Mage(); // reads MAGE_API_KEY

const request = await mage.run('mango', {
  prompt: 'Editorial portrait in soft daylight, 35mm film look',
  aspect_ratio: '4:5',
});

console.log(request.result.url);
```

`run` submits the generation, polls it with the backoff the API recommends, and resolves to the completed request. The output is at `result.url` and is kept for 30 days, so download what you want to keep.

## Models

The first argument is the model's architecture id, as in its endpoint path: `mango` for images, `cherry` for video, `seed_audio` for audio, and [every other model](/api/models/overview). `model_id` selects a variant.

```ts theme={null}
const video = await mage.run('cherry', {
  prompt: 'Waves rolling onto a black sand beach at sunset',
  model_id: 'cherry-2-pro',
  resolution: '720p',
  duration: '5',
});

const audio = await mage.run('seed_audio', {
  prompt: 'Warm lo-fi beat with vinyl crackle',
  duration: '30',
});
```

Every model's config is typed: your editor completes the fields and flags an option the model does not offer. The types, such as `MangoConfig` and `CherryConfig`, are exported, and `ARCHITECTURES` lists the models this version of the SDK knows.

A model released after your version of the SDK still works: pass its id as a string, and the config is checked only against the fields every model shares.

## Submit and poll separately

Use `generate` when you want the request id before the output is ready, for example to store it and collect the result later.

```ts theme={null}
const submitted = await mage.generate('cherry', { prompt: 'Waves at dusk' });
console.log(submitted.request_id, submitted.status); // e.g. "in_progress"

const final = await mage.requests.wait(submitted.request_id, {
  timeout: 15 * 60_000,
  onUpdate: (request) => console.log(request.status),
});

if (final.status === 'completed') console.log(final.result?.url);
```

`requests.wait` polls from 2 seconds, multiplies the delay by 1.5 after each read up to 15 seconds, adds jitter, and resolves to the final request whatever its status. Its `timeout` also cuts short a status read in progress; the request keeps running on Mage. `requests.get(id)` reads a request once, and `requests.cancel(id)` stops a live one. Gems are not returned for a cancelled request.

## Inputs and uploads

Media fields take an `https` URL or a data URL. For a file larger than about 3 MB, or one that is not at a public URL, upload it first and send the URL the upload returns.

```ts theme={null}
import { readFile } from 'node:fs/promises';

const clip = await mage.uploads.upload(await readFile('clip.mp4'), {
  contentType: 'video/mp4',
});

await mage.run('cherry', {
  prompt: 'Restyle this clip as a watercolor painting',
  videos: [clip.url],
});
```

`upload` creates the ticket, checks the size, and sends the file with the headers storage expects. It takes a `Blob`, an `ArrayBuffer`, or a typed array such as a `Buffer`; a `Blob` supplies its own content type. Uploads expire after 30 days, at `clip.url_expires_at`. See [Inputs and uploads](/api/inputs) for formats and limits.

## Characters and references

Save a character or a reference once, then mention its `@handle` in any prompt.

```ts theme={null}
await mage.characters.create({
  name: 'Ana',
  handle: 'ana',
  image: 'https://example.com/ana.png',
});

await mage.run('mango', { prompt: '@ana walking through a night market' });

const page = await mage.characters.list({ limit: 50 });
// Pass page.next_cursor as cursor to read the next page.
```

`mage.references` works the same way for objects, locations, poses, outfits, and audio clips.

## Errors

| Error | When |
| - | - |
| `MageAPIError` | The API answered with an [error](/api/errors). It has `status`, `code` (such as `insufficient_gems`), `message`, and `requestId`. Treat an unknown `code` by its `status`. |
| `MageGenerationError` | `run` finished with a failed or cancelled request. It has `code` (such as `content_blocked`, or `cancelled`) and `request`. |
| `MageTimeoutError` | `wait` or `run` reached its `timeout`. `request` is the last state read, or `null` if none was; the request keeps running. |
| `MageConnectionError` | No response arrived, after retrying. |
| `MageError` | The base class of all of the above, also thrown for a missing API key. |

```ts theme={null}
import Mage, { MageAPIError, MageGenerationError } from '@mage-space/sdk';

const mage = new Mage();

try {
  await mage.run('mango', { prompt: 'A lighthouse at night' });
} catch (error) {
  if (error instanceof MageAPIError && error.code === 'insufficient_gems') {
    // Top up Gems, then try again.
  } else if (error instanceof MageGenerationError) {
    console.log('No output:', error.code);
  } else {
    throw error;
  }
}
```

## Retries and idempotency

Reads, cancels, deletes, and uploads are retried up to twice on network errors and on `408`, `429`, and `5xx` responses, with exponential backoff.

`generate` and `run` send an `Idempotency-Key` with every submission, so a retried submission is never charged twice. The SDK makes a new key per call and reuses it when it retries that call itself, which it does only when no response arrived or on a `408` or `5xx` that recorded no request. Pass your own key to make retries across processes safe too; see [Retrying a submit safely](/api/requests#retrying-a-submit-safely).

```ts theme={null}
await mage.generate(
  'mango',
  { prompt: 'A lighthouse' },
  { idempotencyKey: `job-${job.id}` }
);
```

Creating a character or a reference is never retried.

## Configuration

```ts theme={null}
const mage = new Mage({
  apiKey: process.env.MAGE_API_KEY, // default: MAGE_API_KEY
  baseURL: 'https://api.mage.space', // default: MAGE_BASE_URL, then this
  timeout: 60_000, // per HTTP attempt, in milliseconds
  maxRetries: 2,
  fetch: customFetch, // default: the global fetch
});
```

Every method also takes a `signal` to abort it.

## Reference

| Method | What it does |
| - | - |
| `run(model, config, options?)` | Submit a generation, wait for it, and return the completed request. |
| `generate(model, config, options?)` | Submit a generation and return the request at once. |
| `requests.get(id)` | Read a request's current state. |
| `requests.wait(idOrRequest, options?)` | Poll until the request is completed, failed, or cancelled. |
| `requests.cancel(id)` | Stop a live request. |
| `uploads.upload(data, options?)` | Upload a file and return its ticket; send `ticket.url` in a media field. |
| `uploads.create({ content_type })` | Create an upload ticket without sending a file. |
| `characters.list / create / delete` | Your saved characters. |
| `references.list / create / delete` | Your saved references. |
| `account.get()` | Your Gems balance. |
| `architectures.list()` | The live catalog: every model with its options, inputs, and price. |

## Source and releases

The SDK is MIT-licensed on [GitHub](https://github.com/mage-space/mage-typescript) and published to [npm](https://www.npmjs.com/package/@mage-space/sdk). Its model types come from this API reference: when the reference changes, a pull request updates them, so new models and options arrive as new releases. Each release is published with npm provenance, and its changes are in the repository's changelog.

<CardGroup cols={2}>
  <Card title="Python SDK" icon="python" href="/api/sdks/python">
    The same API for Python, with sync and asyncio clients.
  </Card>

  <Card title="ComfyUI" icon="diagram-project" href="/api/integrations/comfyui">
    Every Mage model as ComfyUI nodes.
  </Card>
</CardGroup>
