---
title: Model APIs
description: "Every model behind nolgia.ai through one API: the catalog by modality with credit prices, and the three-line call that runs any of them."
---

# Model APIs

Generate images, video, audio and 3D through the same API used by nolgia.ai. Choose a model from the catalog, send its inputs, and follow the returned job to a downloadable asset.

## Quick example

With a client installed and `NOLGIA_TOKEN` set, these [Quick Start](./getting-started.html) programs generate an image and download it as `first.png`.

```bash tab="curl"
$ curl -sS https://api.nolgia.ai/v1/generate/image \
  -H "Authorization: Bearer $NOLGIA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"model":"flux-pro","prompt":"a paper-cut mountain range at dawn"}'
$ curl -sS "https://api.nolgia.ai/v1/jobs/<id from the response>/wait?timeout_seconds=120" \
  -H "Authorization: Bearer $NOLGIA_TOKEN"
$ curl -sS -o first.png "<asset.signed_url from the finished job>"
```

```bash tab="CLI"
$ nolgia gen image --model flux-pro --prompt "a paper-cut mountain range at dawn" --out first.png
```

```ts tab="TypeScript"
import { writeFile } from "node:fs/promises";
import { createNolgiaClient } from "@nolgia/sdk";

const nolgia = createNolgiaClient(process.env.NOLGIA_TOKEN!);

async function finish(id: string, timeout: number) {
  for (;;) {
    const { data, response } = await nolgia.GET("/jobs/{id}/wait", {
      params: { path: { id }, query: { timeout_seconds: timeout } },
    });
    if (response.status === 408) continue; // the wait window closed; the job is still running
    if (data?.status === "succeeded" && data.asset) return data.asset;
    throw new Error(`job ${id} ended ${data?.status ?? response.status}`);
  }
}

async function download(url: string, file: string) {
  await writeFile(file, Buffer.from(await (await fetch(url)).arrayBuffer()));
}

const { data: image, error } = await nolgia.POST("/generate/image", {
  body: { model: "flux-pro", prompt: "a paper-cut mountain range at dawn", num_images: 1 },
});
if (error) throw new Error(`${error.title}: ${error.detail ?? ""}`);
await download((await finish(image.id, 120)).signed_url, "first.png");
console.log("image job", image.id, "-> first.png");
```

```python tab="Python"
import os
import httpx
from nolgia import AuthenticatedClient
from nolgia.api.generate import generate_image, generate_video
from nolgia.api.jobs import wait_for_job
from nolgia.models import GenerateImageRequest, GenerateVideoRequest, ImageModel, Job, VideoModel

client = AuthenticatedClient(base_url="https://api.nolgia.ai/v1", token=os.environ["NOLGIA_TOKEN"])


def finish(job_id, timeout):
    while True:
        response = wait_for_job.sync_detailed(job_id, client=client, timeout_seconds=timeout)
        if response.status_code == 408:  # the wait window closed while the job was still running
            continue
        done = response.parsed
        if isinstance(done, Job) and done.status == "succeeded":
            return done.asset
        raise SystemExit(f"job {job_id} {getattr(done, 'status', done)}")


def download(url, path):
    with open(path, "wb") as f:
        f.write(httpx.get(url).content)


job = generate_image.sync(client=client, body=GenerateImageRequest(model=ImageModel("flux-pro"), prompt="a paper-cut mountain range at dawn"))
if not isinstance(job, Job):
    raise SystemExit(f"refused: {job}")
download(finish(job.id, 120).signed_url, "first.png")
print("image job", job.id, "-> first.png")
```

```rust tab="Rust" title="src/main.rs"
use std::num::NonZeroU64;

use nolgia_client::{types, ApiError, ClientBuilder};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = ClientBuilder::new("https://api.nolgia.ai/v1")
        .bearer_token(std::env::var("NOLGIA_TOKEN")?)
        .build()?;

    let prompt: types::GenerateImageRequestPrompt = "a paper-cut mountain range at dawn".parse()?;
    let job = client.generate_image()
        .body_map(|b| b.model("flux-pro").prompt(Some(prompt)).num_images(1u64))
        .send().await?.into_inner();
    let asset = finish(&client, &job.id.to_string(), 120).await?;
    std::fs::write("first.png", reqwest::get(&asset.signed_url).await?.bytes().await?)?;
    println!("image job {} -> first.png", job.id);

    Ok(())
}

/// Long-poll until the job settles. A 408 means the wait window closed while
/// the job was still running: wait again.
async fn finish(client: &nolgia_client::Client, id: &str, timeout: u64)
    -> Result<nolgia_client::types::Asset, Box<dyn std::error::Error>> {
    loop {
        match client.wait_for_job().id(id.parse::<uuid::Uuid>()?).timeout_seconds(timeout).send().await {
            Ok(response) => {
                let job = response.into_inner();
                return match (job.status.as_str(), job.asset) {
                    ("succeeded", Some(asset)) => Ok(asset),
                    (status, _) => Err(format!("job {id} ended {status}").into()),
                };
            }
            Err(ApiError::ErrorResponse(response)) if response.status() == 408 => continue,
            Err(error) => return Err(error.into()),
        }
    }
}
```

The submit call answers `202 Accepted`. This production response was captured on 2026-09-21; only the identifying fields are shown.

```json title="202 Accepted"
{
  "id": "4a9b2242-f732-4a83-ba41-17448f5aa15f",
  "status": "queued",
  "model": "flux-pro",
  "modality": "image",
  "created_at": "2026-09-21T03:33:14.622149Z"
}
```

<!-- gen:fields schema=Job only=id,status,model,modality,created_at -->
| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string | Yes |  |
| `modality` | `Modality` | Yes |  |
| `model` | string | Yes |  |
| `status` | `JobStatus` | Yes |  |
| `created_at` | string | Yes |  |
<!-- /gen -->

When that job finishes, reading it returns the asset. This is the matching production response, with download URLs shortened for readability; its lighthouse prompt belongs to the captured run, not the mountain prompt in the example.

```json title="200 OK — succeeded"
{
  "asset": {
    "created_at": "2026-09-21T03:33:21.706691Z",
    "display_name": "Lighthouse on a cliff",
    "expires_at": "2026-09-21T05:00:00Z",
    "favorite": false,
    "has_audio": false,
    "id": "f7bc037c-d7d7-434a-8843-26675574de0d",
    "mime_type": "image/png",
    "modality": "image",
    "model": "flux-pro",
    "prompt": "a lighthouse on a cliff, paper-cut style",
    "signed_url": "https://storage.googleapis.com/nolgia-generations-prod/dad27b53-a85b-4e…",
    "size_bytes": 418584,
    "status": "ready",
    "tags": [

    ],
    "thumbnail_url": "https://storage.googleapis.com/nolgia-generations-prod/dad27b53-a85b-4e…",
    "user_id": "dad27b53-a85b-4e3d-8fd6-b152c803a27c"
  },
  "completed_at": "2026-09-21T03:33:21.805937Z",
  "created_at": "2026-09-21T03:33:14.622149Z",
  "id": "4a9b2242-f732-4a83-ba41-17448f5aa15f",
  "modality": "image",
  "model": "flux-pro",
  "status": "succeeded",
  "updated_at": "2026-09-21T03:33:21.805937Z",
  "user_id": "dad27b53-a85b-4e3d-8fd6-b152c803a27c"
}
```

<!-- gen:fields schema=Job only=status,asset,completed_at,updated_at -->
| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `status` | `JobStatus` | Yes |  |
| `asset` | `Asset` | No |  |
| `updated_at` | string | Yes |  |
| `completed_at` | string, nullable | No |  |
<!-- /gen -->

<!-- gen:fields schema=Asset only=id,signed_url,expires_at,mime_type,width,height,duration_seconds,has_audio,prompt,enhanced_prompt -->
| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string | Yes |  |
| `prompt` | string, nullable | No | The prompt as the customer submitted it.… |
| `enhanced_prompt` | string, nullable | No | Image generations only: the server-composed prompt that was actually rendered, present only when it differs from `prompt` (the Aura layer enhanced the prompt, or a character's or element's canonical description was folded in).… |
| `signed_url` | string | Yes | Time-limited GCS signed URL for download.… |
| `expires_at` | string | Yes | Expiry of `signed_url`. |
| `mime_type` | string | No |  |
| `width` | integer, nullable | No |  |
| `height` | integer, nullable | No |  |
| `duration_seconds` | number, nullable | No | Media duration in seconds for video/audio assets.… |
| `has_audio` | boolean, nullable | No | Whether the asset's media carries an audio stream.… |
<!-- /gen -->

> [!WARNING]
> `signed_url` expires. Download the file promptly and store the asset id; read the asset again when you need a fresh URL.

### Parameters

The example supplies the image model and prompt, with one output requested by the TypeScript and Rust programs. Model-specific optional arguments and capability checks are covered in [Common model arguments](./model-arguments.html).

<!-- gen:fields schema=GenerateImageRequest only=model,prompt,num_images -->
| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `model` | `ImageModel` | Yes |  |
| `prompt` | string | No | What to generate.… |
| `num_images` | integer | No | How many images to render from this one request. Each one is billed, so four images cost four times one. The ceiling is per-model (`image.num_images_max`); most models allow four. |
<!-- /gen -->

### Error responses

| Response | What to do |
| --- | --- |
| `400 validation` | Correct an unsupported model or argument before retrying. |
| `401` | Replace the invalid or expired token. |
| `402 out_of_credits` | Add credits or choose a generation your wallet can pay for. |
| `409` with `job_id` | Follow the earlier job; this duplicate refusal is not billed. |
| `422 confirmation_rejected` | Re-quote if you supplied an expired or mismatched confirmation token. |
| `429 rate_limit` | Wait for capacity or the quota reset before resubmitting. |
| `408 timeout` from wait | Wait again; the generation may still be running. |

Read `failure.code` on an accepted job that later fails; [Errors](./errors.html) describes those outcomes separately from submit refusals.

## How it works

Every model generation returns a job. Your application chooses how to follow it; the job and its credit settlement are the same whichever method you use.

| Method | What happens | Guide |
| --- | --- | --- |
| `subscribe()` (TypeScript and Python 0.1.2; the Rust crate keeps its own) | One helper submits, polls, and returns the completed result. | [Synchronous: subscribe](./subscribe.html) |
| Submit and poll | Submit once, retain the id, and read `GET /jobs/{id}` from any process. | [Asynchronous: submit and poll](./jobs.html) |
| Long-poll | `GET /jobs/{id}/wait` holds a request until completion or the wait window closes. | [Long-poll with wait](./jobs.html#long-poll-with-wait) |
| Status stream | `GET /jobs/{id}/sse` delivers status changes and a final event using a single-use ticket. | [Streaming](./streaming.html) |
| Provider callbacks | A signed provider callback wakes the poller; this is not a customer webhook. | [Callbacks and webhooks](./callbacks.html) |
| Agent | Create a conversation with `POST /agent/sessions`, then ask the agent to generate media. | [Agent Sessions API](./agent-api.html) |

The blocking helper suits a script that needs one finished file before moving on. It ships in 0.1.2; the Quick Start implements the same submit-and-wait behavior with the published clients today.

Submit and poll fits a production worker that must release its HTTP connection immediately. Save the id before doing other work, then resume from that id after a process restart instead of submitting the generation again.

Long-polling reduces repeated status requests when your program can keep a connection open. A `408` closes only that wait window; call it again while the job is still running.

Status streaming is useful for displaying progress as it changes. The stream reports job state rather than progressive image, video or audio output; the result remains a finished file.

Provider callbacks improve how quickly the server notices completion without requiring a listener in your application. The server verifies the callback and reads the provider's authoritative result before settling the job.

The agent is useful when a conversation needs to choose inputs, plan work or make several assets. Its session transcript and generated assets remain available through the Agent Sessions API.

![A generation is queued, runs, and ends with a result or failure](../assets/diagrams/job-lifecycle.svg)

## What you can generate

The catalog below is generated from the model registry and published pricing. Use `POST /jobs/cost` for the exact quote for your request settings; a model's headline price is not a quote for every duration, resolution or output count.

<!-- gen:catalog-summary -->
| Image | Video | Audio | 3D |
| --- | --- | --- | --- |
| 48 | 74 | 17 | 2 |
<!-- /gen -->

### Image

Image models take a prompt, supported reference images and model-specific size or quality settings. They return an image asset, such as the PNG above, with `width` and `height` when known; read each model's capabilities before choosing optional arguments.

<!-- gen:models modality=image price=true -->
| Model id | Modality | Plan | Credits |
| --- | --- | --- | --- |
| `flux-2-flex` | image | starter | 6 per image |
| `flux-2-klein` | image | starter | 2 per image |
| `flux-2-max` | image | starter | 7 per image |
| `flux-2-pro` | image | starter | 2 per image |
| `flux-expand` | image | starter | 5 per image |
| `flux-kontext-max` | image | starter | 8 per image |
| `flux-kontext-pro` | image | starter | 4 per image |
| `flux-pro` | image | starter | 4 per image |
| `flux-schnell` | image | starter | 2 per image |
| `flux-ultra` | image | starter | 6 per image |
| `gpt-image-1` | image | starter | 14 per image |
| `gpt-image-1.5` | image | starter | 12 per image |
| `gpt-image-2` | image | starter | 22 per image |
| `gpt-image-2.5-flare` | image | starter | 8 per image |
| `gpt-image-2.5-sunburst` | image | starter | 8 per image |
| `grok-imagine-image` | image | starter | 2 per image |
| `grok-imagine-image-2.0` | image | starter | 4 per image |
| `grok-imagine-image-quality` | image | starter | 5 per image |
| `ideogram-v3` | image | starter | 6 per image |
| `ideogram-v4` | image | starter | 1 per image |
| `mai-image-2.5` | image | starter | 6 per image |
| `mai-image-2.5-pro` | image | starter | 10 per image |
| `mai-image-2.6` | image | starter | 6 per image |
| `mai-image-2.6-flash` | image | starter | 3 per image |
| `minimax-image-01` | image | starter | 1 per image |
| `nano-banana-2` | image | starter | 4 per image |
| `nano-banana-2-lite` | image | starter | 2 per image |
| `nano-banana-pro` | image | starter | 8 per image |
| `qwen-image-3` | image | starter | 3 per image |
| `recraft-v2` | image | starter | 3 per image |
| `recraft-v3` | image | starter | 4 per image |
| `recraft-v4` | image | starter | 4 per image |
| `recraft-v4-pro` | image | starter | 25 per image |
| `recraft-v4.1` | image | starter | 4 per image |
| `recraft-v4.1-pro` | image | starter | 21 per image |
| `recraft-v4.1-utility` | image | starter | 4 per image |
| `recraft-v4.1-utility-pro` | image | starter | 21 per image |
| `remove-background` | image | starter | 1 per image |
| `riverflow-v2.5-fast` | image | starter | 2 per image |
| `riverflow-v2.5-pro` | image | starter | 14 per image |
| `seedream-4.5` | image | starter | 3 per image |
| `seedream-v5-pro` | image | starter | 5 per image |
| `stable-diffusion-3.5` | image | starter | 4 per image |
| `topaz-image-cgi` | image | starter | 7 per image |
| `topaz-image-high-fidelity` | image | starter | 7 per image |
| `topaz-image-low-resolution` | image | starter | 7 per image |
| `topaz-image-standard` | image | starter | 7 per image |
| `topaz-image-text` | image | starter | 7 per image |
<!-- /gen -->

### Video

Video models take a prompt and, where supported, images, video or audio references plus duration and quality settings. They return an MP4 asset with `duration_seconds` and `has_audio` metadata when known.

<!-- gen:models modality=video price=true -->
| Model id | Modality | Plan | Credits |
| --- | --- | --- | --- |
| `flux-3-video` | video | pro | 48 per clip (5 s) |
| `grok-imagine-video` | video | pro | 20 per clip (5 s) |
| `grok-imagine-video-1.5` | video | pro | 41 per clip (5 s) |
| `happyhorse-1.0` | video | pro | 28 per clip (5 s) |
| `happyhorse-1.0-video-edit` | video | pro | 36 per clip (5 s) |
| `happyhorse-1.1` | video | pro | 28 per clip (5 s) |
| `happyhorse-1.1-r2v` | video | pro | 36 per clip (5 s) |
| `heygen-avatar-iv` | video | pro | 14 per clip (5 s) |
| `kling-avatar` | video | pro | 16 per clip (5 s) |
| `kling-o1` | video | pro | 24 per clip (5 s) |
| `kling-v2-5-turbo` | video | pro | 12 per clip (5 s) |
| `kling-v2-6` | video | pro | 12 per clip (5 s) |
| `kling-v3-i2v` | video | pro | 35 per clip (5 s) |
| `kling-v3-motion-control` | video | pro | 35 per clip (5 s) |
| `kling-v3-omni` | video | pro | 24 per clip (5 s) |
| `kling-v3-omni-audio` | video | pro | 32 per clip (5 s) |
| `kling-v3-pro-i2v` | video | pro | 47 per clip (5 s) |
| `kling-v3-pro-t2v` | video | pro | 47 per clip (5 s) |
| `kling-v3-t2v` | video | pro | 35 per clip (5 s) |
| `kling-v3-turbo` | video | pro | 32 per clip (5 s) |
| `minimax-h3` | video | pro | 65 per clip (5 s) |
| `minimax-h3-max-i2v` | video | pro | 23 per clip (5 s) |
| `minimax-h3-max-t2v` | video | pro | 23 per clip (5 s) |
| `minimax-hailuo-2.3` | video | pro | 28 per clip (5 s) |
| `minimax-hailuo-2.3-fast` | video | pro | 16 per clip (5 s) |
| `omni-1.1-flash` | video | pro | 50 per clip (5 s) |
| `omni-1.1-flash-edit` | video | pro | 50 per clip (5 s) |
| `remove-background-video` | video | starter | 14 per clip (5 s) |
| `runway-aleph-2` | video | pro | 78 per clip (5 s) |
| `runway-gen-4.5` | video | pro | 34 per clip (5 s) |
| `seedance-1-5-pro` | video | pro | 15 per clip (5 s) |
| `seedance-2.0-fast` | video | pro | 34 per clip (5 s) |
| `seedance-2.0-mini` | video | pro | 28 per clip (5 s) |
| `seedance-2.0-pro-i2v` | video | pro | 43 per clip (5 s) |
| `seedance-2.0-pro-r2v` | video | pro | 85 per clip (5 s) |
| `seedance-2.0-pro-t2v` | video | pro | 43 per clip (5 s) |
| `seedance-2.5` | video | pro | 56 per clip (5 s) |
| `seedvr2-restore` | video | starter | 32 per clip (5 s) |
| `topaz-artemis` | video | starter | 18 per clip (5 s) |
| `topaz-artemis-dehalo-low` | video | starter | 18 per clip (5 s) |
| `topaz-artemis-dehalo-medium` | video | starter | 18 per clip (5 s) |
| `topaz-artemis-low` | video | starter | 18 per clip (5 s) |
| `topaz-artemis-medium` | video | starter | 18 per clip (5 s) |
| `topaz-artemis-moire` | video | starter | 18 per clip (5 s) |
| `topaz-dione` | video | starter | 18 per clip (5 s) |
| `topaz-dione-dehalo` | video | starter | 18 per clip (5 s) |
| `topaz-dione-dv` | video | starter | 18 per clip (5 s) |
| `topaz-dione-robust-dehalo` | video | starter | 18 per clip (5 s) |
| `topaz-dione-tv` | video | starter | 18 per clip (5 s) |
| `topaz-gaia` | video | starter | 18 per clip (5 s) |
| `topaz-gaia-cg` | video | starter | 18 per clip (5 s) |
| `topaz-hdr` | video | starter | 18 per clip (5 s) |
| `topaz-hyperion` | video | starter | 80 per clip (5 s) |
| `topaz-iris` | video | starter | 18 per clip (5 s) |
| `topaz-iris-medium` | video | starter | 18 per clip (5 s) |
| `topaz-motion-deblur` | video | starter | 18 per clip (5 s) |
| `topaz-nyx` | video | starter | 18 per clip (5 s) |
| `topaz-nyx-fast` | video | starter | 10 per clip (5 s) |
| `topaz-nyx-xl` | video | starter | 18 per clip (5 s) |
| `topaz-proteus` | video | starter | 18 per clip (5 s) |
| `topaz-proteus-natural` | video | starter | 18 per clip (5 s) |
| `topaz-rhea` | video | starter | 18 per clip (5 s) |
| `topaz-starlight` | video | starter | 40 per clip (5 s) |
| `topaz-starlight-fast` | video | starter | 20 per clip (5 s) |
| `topaz-theia` | video | starter | 18 per clip (5 s) |
| `topaz-theia-fidelity` | video | starter | 18 per clip (5 s) |
| `topaz-wonder` | video | starter | 40 per clip (5 s) |
| `veo-3.1` | video | pro | 112 per clip (5 s) |
| `veo-3.1-fast` | video | pro | 28 per clip (5 s) |
| `veo-3.1-lite` | video | pro | 14 per clip (5 s) |
| `wan-2.6` | video | pro | 28 per clip (5 s) |
| `wan-2.7` | video | pro | 28 per clip (5 s) |
| `wan-3.0` | video | pro | 28 per clip (5 s) |
| `wan-3.0-prime` | video | pro | 39 per clip (5 s) |
<!-- /gen -->

### Audio

Audio models take speech text or a description of music or sound effects, with model-specific voice and speed options. They return an audio asset in the `format` you asked for; the model catalog says which options it accepts.

<!-- gen:models modality=audio price=true -->
| Model id | Modality | Plan | Credits |
| --- | --- | --- | --- |
| `dia-tts` | audio | starter | 3 per 1,000 characters |
| `elevenlabs-music` | audio | starter | 67 per generation |
| `elevenlabs-sound-effects-v2` | audio | starter | 4 per generation |
| `elevenlabs-tts-multilingual-v2` | audio | starter | 6 per 1,000 characters |
| `elevenlabs-tts-turbo-v2.5` | audio | starter | 3 per 1,000 characters |
| `elevenlabs-tts-v3` | audio | starter | 6 per 1,000 characters |
| `elevenlabs-tts-v3-conversational` | audio | starter | 3 per 1,000 characters |
| `inworld-tts` | audio | starter | 1 per 1,000 characters |
| `kokoro-us-english` | audio | starter | 2 per 1,000 characters |
| `lyria-3.5` | audio | starter | 5 per generation |
| `minimax-music-v2.6` | audio | starter | 15 per generation |
| `minimax-speech-2.8-hd` | audio | starter | 6 per 1,000 characters |
| `minimax-speech-2.8-turbo` | audio | starter | 4 per 1,000 characters |
| `mmaudio-v2` | audio | starter | 2 per generation |
| `orpheus-tts` | audio | starter | 3 per 1,000 characters |
| `stable-audio-2.5` | audio | starter | 20 per generation |
| `stable-audio-3-medium` | audio | starter | 3 per generation |
<!-- /gen -->

### 3D

3D models take an HTTPS image or your uploaded image asset ids, with texture and material options where supported. They return a GLB asset that you can download through the same job and asset flow.

<!-- gen:models modality=3d price=true -->
| Model id | Modality | Plan | Credits |
| --- | --- | --- | --- |
| `hunyuan3d-v3` | 3d | starter | 21 per generation |
| `trellis` | 3d | starter | 2 per generation |
<!-- /gen -->

> [!TIP]
> `GET /pricing/models` is public and `nolgia models list` prints the same published prices. Read [Common model arguments](./model-arguments.html) for capabilities and [Pricing and credits](./billing.html) for exact quotes and credit holds.

## Next steps

:::cards
- [Playground](./playground.html): Try a model in the web app before integrating it.
- [Inference methods](./inference.html): Choose how to submit and follow a job.
- [Client setup](./client-setup.html): Configure a client in your language.
- [Pricing and credits](./billing.html): Quote a request and understand settlement.
:::
