> ## 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.

# Python SDK

> Generate images, video, and audio with every Mage model from Python using the official SDK, mage-space: sync and asyncio clients, typed configs, a run call that waits for the result, safe retries, uploads, and errors.

`mage-space` is the official Python client for the Mage API. It has a sync client and an asyncio client, 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}
pip install mage-space
```

It needs Python 3.10 or later, and depends on `httpx` and `typing-extensions`. The package installs as `mage-space` and imports as `mage_space`.

## Quick start

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

```python theme={null}
from mage_space import Mage

mage = Mage()  # reads MAGE_API_KEY

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

print(request["result"]["url"])
```

`run` submits the generation, polls it with the backoff the API recommends, and returns the completed request. Responses are plain dictionaries shaped like the [request object](/api/requests#the-request-object). The output is at `result.url` and is kept for 30 days, so download what you want to keep.

## Async

`AsyncMage` has the same methods, awaited, and works as an async context manager.

```python theme={null}
import asyncio

from mage_space import AsyncMage


async def main() -> None:
    async with AsyncMage() as mage:
        request = await mage.run("seed_audio", {"prompt": "Rain on a tin roof", "duration": "10"})
        print(request["result"]["url"])


asyncio.run(main())
```

Its `on_update` callback may be a plain function or a coroutine function.

## 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.

```python theme={null}
mage.run("mango", {"prompt": "A paper boat in a gutter stream", "model_id": "mango-v3s"})
```

Each model has a `TypedDict` in `mage_space.types` with its fields, allowed values, and defaults. Annotate a config with it to have your type checker check it:

```python theme={null}
from mage_space.types import CherryConfig

config: CherryConfig = {"prompt": "A paper boat in a gutter stream", "resolution": "1080p"}
mage.run("cherry", config)
```

A model released after your version of the SDK still works by its id. `mage_space.ARCHITECTURES` holds the name, output type, and request JSON Schema of every model this version knows; `mage.architectures.list()` returns the live catalog with prices.

## Submit and poll separately

```python theme={null}
request = mage.generate("cherry", {
    "prompt": "Waves rolling onto a black sand beach at sunset",
    "resolution": "720p",
    "duration": "5",
})
print(request["request_id"], request["status"])  # e.g. in_progress

final = mage.requests.wait(
    request,
    timeout=900,  # seconds
    on_update=lambda r: print(r["status"]),
)
if final["status"] == "completed":
    print(final["result"]["url"])
```

`wait` polls from 2 seconds, multiplies the delay by 1.5 after each read up to 15 seconds, adds jitter, and returns the final request whatever its status. Past `timeout` it raises `MageTimeoutError`, and the request keeps running on Mage. `requests.get(request_id)` reads a request once, and `requests.cancel(request_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.

```python theme={null}
photo = mage.uploads.upload("photo.jpg")  # a path, bytes, or a binary file object
mage.run("kiwi", {"prompt": "The camera slowly pushes in", "first_image": photo["url"]})
```

The content type comes from the file name; pass `content_type=` for raw bytes. Uploads expire after 30 days, at `url_expires_at`. See [Inputs and uploads](/api/inputs) for formats and limits.

## Characters and references

```python theme={null}
ana = mage.characters.create(name="Ana", handle="ana", image="https://example.com/ana.png")
coat = mage.references.create(name="Red coat", handle="red-coat", kind="outfit", image=photo["url"])

mage.run("mango", {"prompt": "@ana walking through a night market, wearing @red-coat"})

page = mage.characters.list(limit=50)
while page["next_cursor"]:
    page = mage.characters.list(cursor=page["next_cursor"])
```

## Errors

| Exception | When |
| - | - |
| `MageAPIError` | The API answered with an [error](/api/errors): `status`, `code` (such as `insufficient_gems`), `message`, `request_id`, and `body`. |
| `MageGenerationError` | `run` ended with a failed or cancelled request: `code` (such as `content_blocked`, or `cancelled`) and the final `request`. |
| `MageTimeoutError` | `wait` or `run` passed its `timeout`. `request` is the last state read, or `None` if none was; the request keeps running. |
| `MageConnectionError` | No response at all, after retries. |
| `MageError` | The base class of all of the above, also raised for a missing API key. |

```python theme={null}
from mage_space import MageAPIError, MageGenerationError

try:
    mage.run("mango", {"prompt": "A lighthouse at night"})
except MageAPIError as error:
    if error.code == "insufficient_gems":
        print("Gems needed:", error.body["error"]["gems_required"])
    else:
        raise
except MageGenerationError as error:
    print("No output:", error.code)
```

New error codes may appear; treat an unknown `code` by its HTTP `status`.

## Retries and idempotency

Reads, cancels, deletes, and uploads are retried up to `max_retries` times (default 2) on connection errors and on `408`, `429`, and `5xx` responses, with exponential backoff.

`generate` and `run` send an `Idempotency-Key` with every submission, a fresh UUID unless you pass `idempotency_key=`, and retry with the same key only when no response arrived or on a `408` or `5xx` that recorded no request. A replayed submission returns the original request and charges nothing. Pass your own key, such as a job id, to make retries across processes safe too. Creating characters and references is never retried.

## Configuration

```python theme={null}
Mage(
    api_key=None,  # default: MAGE_API_KEY
    base_url=None,  # default: MAGE_BASE_URL, else https://api.mage.space
    timeout=60.0,  # seconds per HTTP attempt
    max_retries=2,
    http_client=None,  # your own httpx.Client (an httpx.AsyncClient for AsyncMage)
)
```

Use `with Mage() as mage:` or `mage.close()` to close the connection pool; the SDK closes only an HTTP client it created.

## Reference

| Method | What it does |
| - | - |
| `run(model, config, ...)` | Submit a generation, wait for it, and return the completed request. |
| `generate(model, config, idempotency_key=None)` | Submit a generation and return the request at once. |
| `requests.get(request_id)` | Read a request's current state. |
| `requests.wait(id_or_request, ...)` | Poll until the request is completed, failed, or cancelled. |
| `requests.cancel(request_id)` | Stop a live request. |
| `uploads.upload(data, content_type=None)` | Upload a file and return its ticket; send `ticket["url"]` in a 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-python) and published to [PyPI](https://pypi.org/project/mage-space/) with build attestations. 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.

<CardGroup cols={2}>
  <Card title="TypeScript SDK" icon="js" href="/api/sdks/typescript">
    The same API for Node.js, with typed configs.
  </Card>

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