# Build with Nolgia Source: https://docs.nolgia.ai/index.md One API for image, video, audio and 3D generation. Authenticate with a Personal Access Token, then submit a generation as a job you can poll, long-poll or stream. Quote the price before you spend. The base URL is `https://api.nolgia.ai/v1`. :::cards - [Model APIs](./models.html) icon=spot-library: Find a model, quote its price and run it through the API. - [Agent Sessions API](./agent-api.html) icon=spot-agent: Build persistent conversations that generate media. - [The CLI](./cli.html) icon=spot-terminal: Generate and manage media from your terminal. - [API reference](../api/) icon=spot-keys: Every endpoint and schema, rendered from the OpenAPI spec. ::: ## Start with a model API call Set `NOLGIA_TOKEN` to your Personal Access Token, submit an image, wait for the job, and download the file. Start with [Model APIs](./models.html) for the catalog and calling methods. These are the programs that ran against production with the published packages on 2026-09-20; the [Quickstart](./getting-started.html) adds the video step. ```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//wait?timeout_seconds=120" \ -H "Authorization: Bearer $NOLGIA_TOKEN" $ curl -sS -o 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 from nolgia.api.jobs import wait_for_job from nolgia.models import GenerateImageRequest, ImageModel, Job 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") ``` ## How a generation runs ![Submit a generation, wait while it runs, then read the result](../assets/diagrams/job-lifecycle.svg) A generation returns a job that moves from `queued` to `running`, then reaches `succeeded`, `failed` or `canceled`. Read [Jobs](./jobs.html) to follow its status and retrieve the finished asset. ## Clients | Client | Package | Version | | --- | --- | --- | | TypeScript | `@nolgia/sdk` | 0.1.4 | | Python | `nolgia` on PyPI | 0.1.4 | | Rust | `nolgia-client` | published with the CLI release; see crates.io | ## Where next :::cards - [Quick Start](./getting-started.html) icon=spot-library: Get a token and generate your first image and video. - [Why Nolgia](./why-nolgia.html) icon=spot-jobs: Read the API's job, pricing and refund model. - [Run MCP](./mcp.html) icon=spot-terminal: Generate from inside your coding agent's conversation. - [Agent Sessions API](./agent-api.html) icon=spot-agent: Create persistent conversations and collect their assets. - [API reference](../api/) icon=spot-keys: Read every endpoint and schema. ::: --- # Why Nolgia Source: https://docs.nolgia.ai/guides/why-nolgia.md You can generate image, video, audio and 3D media through the same API, quote a request before spending credits, and reuse your subjects and looks across generations. ## One API, one job model `POST /generate/image`, `POST /generate/video`, `POST /generate/audio` and `POST /generate/3d` return `202` with a `Job`; `GET /jobs/{id}/wait` waits for its result. Start with the [Quick Start](./getting-started.html). | Image | Video | Audio | 3D | | --- | --- | --- | --- | | 48 | 74 | 17 | 2 | ## The price before the spend > [!TIP] > `POST /jobs/cost` returns the exact credits your submit will hold and a `confirmation_token`; a quote reserves and charges nothing. `GET /pricing/models` is public. See [Billing](./billing.html). ## Check the refund outcome A failed job carries `failure.code` and reports the ledger outcome in `failure.credits_refunded`: `true` means refunded; `false` means charged, including a content-filter refusal the provider billed for. An absent value does not confirm a refund; see [Errors](./errors.html). | Value | Meaning | | --- | --- | | `out_of_credits` | the wallet cannot pay for the job. Nothing was submitted and nothing was charged. Top up, or submit a cheaper model or fewer seconds. | | `rate_limit` | refused for now, not refused outright - the caller's own concurrency ceiling, or the provider's. Retry later; the same request will be accepted. | | `prompt_nsfw` | a content filter refused the request or the result it produced, on safety grounds. Editing the prompt or the reference media is the fix. Whether the credits were refunded is `failure.credits_refunded`, not this code. | | `ip_detected` | a content filter refused it for a real person's likeness or a protected work, rather than for safety. The fix is different from `prompt_nsfw` - change the reference image or the named subject, not the tone of the prompt - which is why it is its own code. | | `job_failed` | the job ran and did not produce an asset for any other reason, including a provider error. The default for an unclassified failure. | | `timeout` | the job ran past its time budget, or a `GET /jobs/{id}/wait` returned before the job reached a terminal status. On a failed job the work is over; on a `wait` the job may still be running. | | `validation` | the request itself is not acceptable - a missing or malformed field, a model that does not support the capability asked of it, a duration the model will not render. Nothing was submitted and nothing was charged. Retrying unchanged will fail identically. | | `confirmation_rejected` | the cost confirmation gate did not pass. Either a client showed the customer a quote and the customer declined, or a supplied confirmation token was refused. A submit that carries no confirmation token is never refused for this reason. | | `canceled` | the job was canceled by its owner (`POST /jobs/{id}/cancel`), not broken. It is the code the SDK wait helpers raise for a `canceled` job; `cancellation` on the job says what the provider did and what was refunded. A canceled job carries no `failure`. | | `job_not_cancellable` | `POST /jobs/{id}/cancel` refused (`409`) because the job already finished, or its finished result is already being delivered. The job will reach `succeeded` or `failed` on its own; nothing was changed. | | `approval_required` | refused with `402` (title `Approval Needed`) because the generation would take an agent run past the price its customer approved (the `credit_ceiling` a guided preset's review card showed, sent with the brief). Nothing was submitted and nothing was charged. The detail names the generation's cost and the new run total; the agent must ask the customer to approve that total before it continues, never retry, split the job or switch models to fit. | | `run_ended` | refused with `409` (title `Run Ended`) because the agent run this generation was started for (the turn its turn-scoped credential names) has already ended: the platform failed, swept or stopped the turn, or it finished and delivered its reply. Nothing was submitted and nothing was charged. The agent must stop working on that run, never retry or switch models; the customer's chat already shows how it ended. | ## Reusable subjects and looks Reuse these in your generations or through the [Agent Sessions API](./agent-api.html). | Resource | What you reuse | | --- | --- | | Characters | `character_ids` supplies an ordered cast of up to four characters. | | Locations | Save a place's description and reference images. | | Products | Reuse products imported from a store link or built from your images. | | Brand kits | Keep palettes, fonts, logos and never-rules together. | | Saved styles | Apply a prompt fragment and reference images with `style_id`. | | Presets | Choose an outcome-named workflow that opens a creation tool or hands off to the agent. | ## From wherever you work Use [nolgia.ai](https://nolgia.ai), the `nolgia` CLI, the MCP server for Claude Code and Cursor, skills for coding agents, or the NOLGIA Agent. :::cards - [Quick Start](./getting-started.html) icon=spot-library: Install a client and generate your first image and video. - [Get your API key](./authentication.html) icon=spot-keys: Create and test a Personal Access Token. - [Run MCP](./mcp.html) icon=spot-terminal: Generate inside your coding agent's conversation. - [Agent Sessions API](./agent-api.html) icon=spot-agent: Create persistent conversations and collect their assets. ::: --- # Quick Start Source: https://docs.nolgia.ai/guides/getting-started.md Install a client, create your API key, and generate an image and a video. Each example waits for the job and downloads the finished file; choose the language you use. ![An image taking shape from a request](../assets/art/quickstart.jpg) ## Install a client Choose one install; every tab is one command. The examples below use the published packages, and each tab needs only the tool it names. ```bash tab="npm" $ npm install @nolgia/sdk ``` ```bash tab="yarn" $ yarn add @nolgia/sdk ``` ```bash tab="pnpm" $ pnpm add @nolgia/sdk ``` ```bash tab="bun" $ bun add @nolgia/sdk ``` ```bash tab="pip" $ pip install nolgia ``` ```bash tab="uv" $ uv add nolgia ``` ```bash tab="cargo" $ cargo add nolgia-client ``` ```bash tab="Homebrew" $ brew install nolgiainc/nolgia/nolgia ``` ```bash tab="curl installer" $ curl -fsSL https://raw.githubusercontent.com/nolgiainc/nolgia-cli/main/install.sh | bash ``` ```bash tab="curl" $ curl https://api.nolgia.ai/v1/me -H "Authorization: Bearer $NOLGIA_TOKEN" ``` ## Get your key Create a Personal Access Token at [nolgia.ai/settings/api-tokens](https://nolgia.ai/settings/api-tokens), copy it once, and set it in your environment; it starts with `nol_`. The CLI can instead open the browser device flow with `nolgia auth login`; see [Get your API key](./authentication.html). ```bash $ export NOLGIA_TOKEN=nol_... $ nolgia auth login ``` ## Your first image Generate a paper-cut mountain range with `flux-pro` and save it as `first.png`. The curl tab shows each call in turn: submit, wait on the returned `id`, then download `asset.signed_url` from the finished job. Every SDK tab downloads the file itself — in Rust, `client.download(url, path)` — so nothing here needs a package its install tab did not name. ```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//wait?timeout_seconds=120" \ -H "Authorization: Bearer $NOLGIA_TOKEN" $ curl -sS -o first.png "" ``` ```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::tokio; use nolgia_client::{types, ApiError, ClientBuilder, ClientExt}; #[tokio::main] async fn main() -> Result<(), Box> { 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, 120).await?; client.download(&asset.signed_url, "first.png").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, job: &types::Job, timeout: u64) -> Result> { loop { match client.wait_for_job().id(job.id).timeout_seconds(timeout).send().await { Ok(response) => { let done = response.into_inner(); return match (done.status.as_str(), done.asset) { ("succeeded", Some(asset)) => Ok(asset), (status, _) => Err(format!("job {} ended {status}", job.id).into()), }; } Err(ApiError::ErrorResponse(response)) if response.status() == 408 => continue, Err(error) => return Err(error.into()), } } } ``` > [!NOTE] > Submitting the exact same request twice within five minutes answers `409 Conflict` naming the earlier job in `job_id`, so a retry cannot bill twice. The curl examples follow that existing job. Send a fresh `Idempotency-Key` header to run the same prompt again on purpose; see [Jobs](./jobs.html). > > These examples use the endpoint interfaces of the published clients. TypeScript and Python 0.1.2 also include the `submit` and `subscribe` convenience helpers and `NolgiaGenerationError`; Python's `nolgia.api.jobs.wait_for_job` above remains the endpoint module. The Rust examples use the published builder client. ## Your first video This step requires a Pro plan or higher: `veo-3.1-lite` is not available on Starter. Generate four seconds at 720p with sound using `veo-3.1-lite`; append the TypeScript and Python code to the program above, or insert the Rust block before `Ok(())` in `main`. ```bash tab="curl" $ curl -sS https://api.nolgia.ai/v1/generate/video \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H "Content-Type: application/json" \ -d '{"model":"veo-3.1-lite","prompt":"a paper-cut mountain range at dawn, slow push-in as the sun rises","duration_seconds":4}' $ curl -sS "https://api.nolgia.ai/v1/jobs//wait?timeout_seconds=300" \ -H "Authorization: Bearer $NOLGIA_TOKEN" $ curl -sS -o first.mp4 "" ``` ```bash tab="CLI" $ nolgia gen video --model veo-3.1-lite --prompt "a paper-cut mountain range at dawn, slow push-in as the sun rises" --duration-seconds 4 --out first.mp4 ``` ```ts tab="TypeScript" const { data: video, error: videoError } = await nolgia.POST("/generate/video", { body: { model: "veo-3.1-lite", prompt: "a paper-cut mountain range at dawn, slow push-in as the sun rises", duration_seconds: 4 }, }); if (videoError) throw new Error(`${videoError.title}: ${videoError.detail ?? ""}`); await download((await finish(video.id, 300)).signed_url, "first.mp4"); console.log("video job", video.id, "-> first.mp4"); ``` ```python tab="Python" job = generate_video.sync(client=client, body=GenerateVideoRequest(model=VideoModel("veo-3.1-lite"), prompt="a paper-cut mountain range at dawn, slow push-in as the sun rises", duration_seconds=4)) if not isinstance(job, Job): raise SystemExit(f"refused: {job}") download(finish(job.id, 300).signed_url, "first.mp4") print("video job", job.id, "-> first.mp4") ``` ```rust tab="Rust" let job = client.generate_video() .body_map(|b| b.model("veo-3.1-lite") .prompt("a paper-cut mountain range at dawn, slow push-in as the sun rises") .duration_seconds(NonZeroU64::new(4))) .send().await?.into_inner(); let asset = finish(&client, &job, 300).await?; client.download(&asset.signed_url, "first.mp4").await?; println!("video job {} -> first.mp4", job.id); ``` ## Wait, then download A job moves from `queued` to `running`, then reaches `succeeded`, `failed` or `canceled`. `GET /jobs/{id}/wait` holds the connection up to `timeout_seconds` (maximum 900) and answers `408` when the window closes while the job is still running. Download from `asset.signed_url`, a time-limited URL. | Parameter | In | Required | Description | | --- | --- | --- | --- | | `id` | path | Yes | Job UUID. | | `timeout_seconds` | query | No | | ![Submit a generation, wait while it runs, then read the result](../assets/diagrams/job-lifecycle.svg) ## What it costs | Model id | Modality | Plan | Credits | | --- | --- | --- | --- | | `flux-pro` | image | starter | 4 per image | | `veo-3.1-lite` | video | pro | 14 per clip (5 s) | `POST /jobs/cost` quotes the exact credits for your settings before you submit (a four-second `veo-3.1-lite` clip quoted 12 credits on 2026-09-20; the table shows the per-clip baseline); see [Billing](./billing.html). ## Where next :::cards - [Get your API key](./authentication.html) icon=spot-keys: Create and manage the credential your client uses. - [Jobs](./jobs.html) icon=spot-jobs: Follow a generation to completion. - [Errors](./errors.html) icon=spot-terminal: Read problem responses and retry safely. - [Libraries, APIs and community](./client-libraries.html) icon=spot-library: Choose a client and find help. ::: --- # Accounts and identity Source: https://docs.nolgia.ai/guides/accounts.md Your account owns your personal space and can belong to organizations. Choose where you work before you generate: your active context determines which resources and credits your requests use. ![Your identity connects your clients to the API](../assets/art/authentication.jpg) ## Sign in At [nolgia.ai/signin](https://nolgia.ai/signin), you can sign in with Google, Apple, email and password, or SSO for enterprise organizations. ## Personal space or one active organization You work in your personal space or inside one active organization. `PUT /me/active-organization` switches that context; it is server state resolved on each request. | Method | Path | What it does | | --- | --- | --- | | [GET](../api/#tag/auth/get/me) | `/me` | Get the currently authenticated user. | | [GET](../api/#tag/auth/get/me/settings) | `/me/settings` | Get the current user's notification settings. | | [PUT](../api/#tag/auth/put/me/settings) | `/me/settings` | Update the current user's notification settings. | | [PUT](../api/#tag/organizations/put/me/active-organization) | `/me/active-organization` | Switch the current user between their personal space and one of their organizations. | | Field | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | Yes | | | `email` | string | Yes | | | `name` | string, nullable | No | | | `image_url` | string, nullable | No | | | `created_at` | string | Yes | | | `organizations` | array of `UserOrganization` | No | Every organization the user belongs to, with their role. | | `active_organization` | `UserOrganization`, nullable | No | The organization the user is currently working in, or `null` in the personal space. | | `generation_limits` | `GenerationLimits`, nullable | No | Current shared generation concurrency across image, audio, and video. | | `connector` | `ConnectorCredential` | No | Present only when this request authenticated with a token an OAuth connector obtained through NOLGIA's authorization server (an AI assistant connecting to mcp.nolgia.ai).… | | `insider` | `InsiderBadge` | No | Present only when the user is an active NOLGIA Insider: the badge the app shows under the logo. Absent for everyone else. | ## What a token is bound to | Credential | Context | | --- | --- | | Personal Access Token | Belongs to the user who created it and follows that user's active context; see [Get your API key](./authentication.html). | | Organization API key | Belongs to the organization and stays bound to it, regardless of its creator's active context; see [Teams and organizations](./organizations.html). | :::cards - [Get your API key](./authentication.html) icon=spot-keys: Create a token and choose how your client signs in. - [Teams and organizations](./organizations.html) icon=spot-organizations: Switch context and manage members, keys and shared credits. ::: --- # Get your API key Source: https://docs.nolgia.ai/guides/authentication.md Use a Personal Access Token for your own scripts, the device flow for CLI sign-in, or OAuth 2.1 for a connector that asks you to approve access in the browser. ![Authentication](../assets/art/authentication.jpg) ## Create your key 1. Open [API tokens](https://nolgia.ai/settings/api-tokens) while signed in. 2. Create a Personal Access Token. 3. Copy the token once; you will not see its plaintext again. You can also call `POST /pat` with your signed-in session JWT, and revoke a token with `DELETE /pat/{id}`. ### Token expiry Every new token expires. Set `expires_in_days` from 1 to 365; leave it out and the token lasts 365 days. `GET /pat` (and `nolgia pat list`) shows each token's `expires_at`. From that moment the token is refused with `401`, exactly like a revoked one, so create its replacement first, switch your scripts over, then revoke the old one. An expired token stays in the list until you revoke it, and it no longer counts toward the limit of 10 active tokens. Tokens created before expiry was introduced (September 2026) show `expires_at: null`: they have no expiry and keep working. Rotating them is recommended. Send the token in the `Authorization: Bearer nol_…` header. A PAT you create spends the credits shown in `available_for_api`; the token an OAuth connection issues (ChatGPT, Claude or another MCP client) spends like the app, `available_for_app`. See [Billing](./billing.html). ![Settings, API tokens on nolgia.ai: create a token with an optional expiry (1), then see and revoke the active ones (2)](../assets/screens/api-tokens.jpg) | Field | Type | Required | Description | | --- | --- | --- | --- | | `name` | string | Yes | | | `expires_in_days` | integer, nullable | No | Lifetime of the token in days from now, 1 to 365. Omitted or null: 365 days. New tokens always expire. | ## Set your key ```bash tab="macOS/Linux" $ export NOLGIA_TOKEN=nol_... ``` ```powershell tab="Windows (PowerShell)" $env:NOLGIA_TOKEN="nol_..." ``` ```dotenv tab=".env file" NOLGIA_TOKEN=nol_... ``` ## Test your key The response is your account; its `email` field is the address you signed up with. ```bash $ curl -s https://api.nolgia.ai/v1/me -H "Authorization: Bearer $NOLGIA_TOKEN" ``` ## Device flow ![Authentication paths: personal token, device approval, and connector consent](../assets/diagrams/auth-paths.svg) Start with `POST /auth/device`. You receive `device_code`, `user_code`, `verification_uri`, `expires_in`, and `interval`. Open the verification URI in your browser and approve the code while signed in. Poll `POST /auth/device/token` with the device code and the same `client_id`, waiting `interval` seconds between attempts. The CLI command `nolgia auth login` does this for you. | Field | Type | Required | Description | | --- | --- | --- | --- | | `device_code` | string | Yes | | | `user_code` | string | Yes | | | `verification_uri` | string | Yes | | | `verification_uri_complete` | string, nullable | No | | | `expires_in` | integer | Yes | | | `interval` | integer | Yes | | ## OAuth 2.1 for connectors ChatGPT and Claude connectors use dynamic client registration and an authorization-code flow with PKCE. The connector registers a public client, sends you to the browser for consent with an S256 code challenge, then exchanges the code with its matching verifier. Discover the supported endpoints through the authorization server metadata. A connector's access token appears in your token list and counts toward your active token limit. When you are at the limit, connecting again revokes the least recently used connector token to make room (the same connector's first) instead of failing. Tokens you created yourself are never revoked this way, so if they alone fill the limit, revoke one in Settings first. A connector's token bills like the app, not like a token you created: it spends your subscription credits first and then your top-up credits. A connector is you using NOLGIA through another assistant, so it reaches the same credits the app does. | Method | Path | What it does | | --- | --- | --- | | [GET](../api/#tag/auth/get/.well-known/oauth-authorization-server) | `/.well-known/oauth-authorization-server` | OAuth 2.0 authorization server metadata (RFC 8414). | | [POST](../api/#tag/auth/post/oauth/register) | `/oauth/register` | Dynamic client registration for MCP connectors (RFC 7591). | | [GET](../api/#tag/auth/get/oauth/authorize) | `/oauth/authorize` | Begin an OAuth 2.1 authorization-code flow (browser redirect). | | [POST](../api/#tag/auth/post/oauth/token) | `/oauth/token` | Exchange an authorization code or refresh token for an access token. | | [POST](../api/#tag/auth/post/oauth/revoke) | `/oauth/revoke` | Revoke an access or refresh token issued to a connector (RFC 7009). | ## Organization API keys Create a key with `POST /organizations/{id}/api-keys` as an owner or admin. You receive a `nol_` bearer forced into that organization, regardless of your active organization. It carries your current role, stops working when your membership ends, and is never listed on `/pat`. See [Teams and organizations](./organizations.html) for the active context and membership rules. ## Staging Use separate accounts and tokens for production and staging. A token from one environment does not sign you in to the other. | Environment | Base URL | | --- | --- | | Production | `https://api.nolgia.ai/v1` | | Staging | `https://api.stg.nolgia.ai/v1` | | Local development | `http://localhost:8080/v1` | ## What an agent credential cannot do > [!NOTE] > Agent PATs and turn tokens receive 403 with `agent_cannot_create_personal_access_token` when creating a personal token, or `agent_cannot_change_organization` when changing organization state. Create credentials and change organizations yourself in the app. :::cards - [Quick Start](./getting-started.html) icon=spot-library: Create a token and generate your first image and video. - [Teams and organizations](./organizations.html) icon=spot-organizations: Choose a workspace and manage its members and keys. - [Billing](./billing.html) icon=spot-credits: Read the balance your credential can spend. ::: --- # Run MCP Source: https://docs.nolgia.ai/guides/mcp.md Connect to `https://mcp.nolgia.ai/mcp` over Streamable HTTP and authenticate with `Authorization: Bearer nol_...`, using your Personal Access Token. ## Connect Replace `nol_your_token_here` with your token. ```bash tab="Claude Code" $ claude mcp add --transport http nolgia https://mcp.nolgia.ai/mcp --header "Authorization: Bearer nol_your_token_here" ``` ```json tab="Cursor" title="~/.cursor/mcp.json" { "mcpServers": { "nolgia": { "url": "https://mcp.nolgia.ai/mcp", "headers": { "Authorization": "Bearer nol_your_token_here" } } } } ``` ```bash tab="Codex" $ codex mcp add nolgia --url https://mcp.nolgia.ai/mcp ``` ```text tab="Any client" https://mcp.nolgia.ai/mcp Authorization: Bearer nol_your_token_here ``` For Codex, set the bearer header under that server in `~/.codex/config.toml`; `codex mcp add --help` lists the available options. ## The tools The server registry has 72 tools in eight groups. #### Generate | Tool | What it does | | --- | --- | | `nolgia_text_to_image` | Generate image from text | | `nolgia_image_to_image` | Generate image from an image | | `nolgia_text_to_video` | Generate video from text | | `nolgia_image_to_video` | Animate an image into video | | `nolgia_text_to_audio` | Generate audio from text | | `nolgia_run_preset` | Run a preset | | `nolgia_render_blocks` | Assemble clips and narration into one video | | `nolgia_multicam_plan` | Plan a Multicam job | | `nolgia_multicam_start` | Start a Multicam job | | `nolgia_multicam_run` | Check a Multicam job | | `nolgia_multicam_download` | Download every Multicam angle | | `nolgia_multicam_cancel` | Stop a Multicam job | | `nolgia_generate_3d` | Generate a 3D model | | `nolgia_generate_set` | Generate a set of images | | `nolgia_miroge_options` | Price a Miroge remake | | `nolgia_miroge` | Remake a clip with Miroge | #### Your library | Tool | What it does | | --- | --- | | `nolgia_list_assets` | List generated assets | | `nolgia_get_asset` | Open an asset | | `nolgia_delete_asset` | Trash an asset | | `nolgia_update_asset_tags` | Set an asset's tags | | `nolgia_upload_asset` | Upload an image | | `nolgia_list_characters` | List saved characters | | `nolgia_get_character` | Get one character | | `nolgia_create_character` | Save a character | | `nolgia_update_character` | Update a character | | `nolgia_delete_character` | Delete a character | | `nolgia_list_styles` | List saved styles | | `nolgia_create_style` | Save a style | | `nolgia_delete_style` | Delete a saved style | | `nolgia_transcribe_asset` | Transcribe an asset | | `nolgia_get_transcript` | Get an asset transcript | | `nolgia_suggest_clips` | Find short video clips | | `nolgia_create_clips` | Create short video clips | | `nolgia_get_set` | Get a set of images | | `nolgia_get_miroge` | Check a Miroge remake | #### Projects | Tool | What it does | | --- | --- | | `nolgia_list_projects` | List projects | | `nolgia_get_project` | Get one project | | `nolgia_create_project` | Create a project | | `nolgia_update_project` | Update a project | | `nolgia_delete_project` | Delete a project | | `nolgia_add_project_assets` | Add assets to a project | | `nolgia_remove_project_asset` | Remove an asset from a project | | `nolgia_list_canvases` | List canvases | | `nolgia_get_canvas` | Get a canvas | | `nolgia_create_canvas` | Create a canvas | | `nolgia_update_canvas` | Update a canvas | #### Edit sessions and tweaks | Tool | What it does | | --- | --- | | `nolgia_list_tweak_scopes` | List image tweak scopes | | `nolgia_create_edit_session` | Start an edit session | | `nolgia_get_edit_session` | Read an edit session | | `nolgia_edit_session_step` | Apply the next image edit | | `nolgia_revert_edit_session` | Revert an edit session | | `nolgia_estimate_edit_session` | Estimate the next edit | #### Presets | Tool | What it does | | --- | --- | | `nolgia_list_presets` | List presets | | `nolgia_get_preset` | Get one preset | #### Timelines | Tool | What it does | | --- | --- | | `nolgia_validate_mask` | Validate a timeline mask | | `nolgia_validate_motion` | Validate timeline motion | | `nolgia_list_color_presets` | List color-grade presets | | `nolgia_get_render` | Check a render | #### Desktop apps | Tool | What it does | | --- | --- | | `nolgia_app_status` | List connected desktop apps | | `nolgia_app_info` | Read a desktop app's state | | `nolgia_app_run` | Run code in a desktop app | | `nolgia_app_command` | Check a desktop app command | | `nolgia_app_preview` | Preview a desktop app's document | | `nolgia_app_import` | Import an asset into a desktop app | | `nolgia_app_export` | Export from a desktop app | | `nolgia_app_save` | Save the document in a desktop app | | `nolgia_app_open` | Open a document in a desktop app | #### Models, account and jobs | Tool | What it does | | --- | --- | | `nolgia_list_models` | List models | | `nolgia_get_account` | Get the signed-in account | | `nolgia_get_usage` | Get generation counts | | `nolgia_cancel_job` | Cancel a generation job | | `nolgia_list_jobs` | List generation jobs | ## Prompts The server also offers prompts. Every public Film assistant preset, a preset that runs in a desktop app on your computer such as Blender, is a prompt named by its slug with one optional argument, `brief`. Getting one returns a single message: the preset's starter prompt, your brief, then the workflow your agent follows in the app. Running it is free; anything it makes with NOLGIA is priced as usual. In Claude Code they appear as `/mcp__nolgia__`, with the preset's slug, such as `blender-destruction`, at the end. ## What to expect > [!NOTE] > ChatGPT and Claude desktop/web need OAuth sign-in rather than a bearer header; sign-in is live, but their setup instructions are [not published yet](https://nolgia.ai/mcp). :::cards - [Get your API key](./authentication.html) icon=spot-keys: Create the token your MCP client sends. - [Nolgia for Claude Code, Cursor and Codex](./coding-agents.html) icon=spot-agent: Install skills and the agent runbook. - [The CLI](./cli.html) icon=spot-terminal: Generate and manage media from your terminal. ::: --- # Nolgia for Claude Code, Cursor and Codex Source: https://docs.nolgia.ai/guides/coding-agents.md Give your coding agent the install runbook, then use skills or the Cursor plugin to generate and manage media from your conversation. ## Paste this into your agent ```text Read https://raw.githubusercontent.com/nolgiainc/nolgia-cli/main/INSTALL_FOR_AGENTS.md and follow it end to end to install and set up the Nolgia CLI for me. ``` The runbook installs the CLI without sudo, waits while you authenticate, installs the skills and runs one verification generation. ## Skills The public [nolgiainc/nolgia-skills repository](https://github.com/nolgiainc/nolgia-skills) has four skills: `nolgia-platform`, `nolgia-video-prompting`, `nolgia-ugc-ads` and `nolgia-after-effects`; the CLI bundles the first three. ```bash tab="npx skills" $ npx skills add nolgiainc/nolgia-skills ``` ```bash tab="gh skill" $ gh skill install nolgiainc/nolgia-skills --all ``` ```text tab="Claude Code plugin" /plugin marketplace add nolgiainc/nolgia-skills ``` ```bash tab="nolgia CLI" $ nolgia skills install ``` Inside Claude Code, the marketplace line above is followed by one more command: `/plugin install nolgia@nolgia`. ## Cursor plugin The [Cursor plugin](https://github.com/nolgiainc/cursor-plugin) adds a /nolgia command over the NOLGIA MCP server. ## The runbook Read [INSTALL_FOR_AGENTS.md](https://github.com/nolgiainc/nolgia-cli/blob/main/INSTALL_FOR_AGENTS.md) for installation, authentication, skills and verification steps. :::cards - [Run MCP](./mcp.html) icon=spot-agent: Connect your editor to the hosted tools. - [The CLI](./cli.html) icon=spot-terminal: Install, authenticate and script generations. - [Agent-readable surfaces](./agent-surfaces.html) icon=spot-library: Fetch Markdown, the spec and the model catalog. ::: --- # Agent-readable surfaces Source: https://docs.nolgia.ai/guides/agent-surfaces.md Give your agent the docs index, API contract or model catalog so it can look up the request it needs. The documentation and pricing surfaces are public; model capabilities require your token. ## What to fetch | Surface | What it gives your agent | | --- | --- | | [llms.txt](https://docs.nolgia.ai/llms.txt) | A short index of the guides and reference. | | [llms-full.txt](https://docs.nolgia.ai/llms-full.txt) | All manifest guides as one text file. | | [Landing Markdown](https://docs.nolgia.ai/index.md) and [guide Markdown](https://docs.nolgia.ai/guides/getting-started.md) | Every guide has a /guides/\.md mirror; the landing is /index.md. Use each page's Markdown link. | | [OpenAPI spec](https://docs.nolgia.ai/api/openapi.yaml) | Paths, request and response schemas, and authentication. | | [MCP tools](https://docs.nolgia.ai/mcp/tools.json) | The server's tool catalog and input schemas. | | [Model pricing](https://api.nolgia.ai/v1/pricing/models) | Public prices without a token. | | `GET /models` | Model capabilities, with a bearer token. | | CLI `--json` | Machine-readable command output. | | [Agent installation runbook](https://raw.githubusercontent.com/nolgiainc/nolgia-cli/main/INSTALL_FOR_AGENTS.md) | CLI installation, authentication, skills and verification. | ## Fetch the index ```bash $ curl -fsSL https://docs.nolgia.ai/llms.txt ``` ## Read public prices ```bash $ curl -fsSL https://api.nolgia.ai/v1/pricing/models ``` ## Read model capabilities ```bash $ curl -fsSL https://api.nolgia.ai/v1/models \ -H "Authorization: Bearer $NOLGIA_TOKEN" ``` :::cards - [Run MCP](./mcp.html) icon=spot-agent: Connect your agent to the tools. - [Nolgia for Claude Code, Cursor and Codex](./coding-agents.html) icon=spot-terminal: Install the CLI and skills from your conversation. - [Libraries, APIs and community](./client-libraries.html) icon=spot-library: Find a client, the API contract and support. ::: --- # The CLI Source: https://docs.nolgia.ai/guides/cli.md Use `nolgia` to generate media, follow jobs and manage your library from a terminal; add `--json` when another program will read the output. ## Install Choose one installation method. ```bash tab="Homebrew" $ brew install nolgiainc/nolgia/nolgia ``` ```bash tab="npm" $ npm install -g @nolgia/cli ``` ```bash tab="Cargo" $ cargo install nolgia-cli ``` ```bash tab="curl installer" $ curl -fsSL https://raw.githubusercontent.com/nolgiainc/nolgia-cli/main/install.sh | bash ``` ### Platforms The installer and the released binaries cover every target below. The Linux musl builds are statically linked, so the CLI runs in a slim container — Alpine, `debian:bookworm-slim` — with no libc to install first. | Platform | Architectures | Install | | --- | --- | --- | | macOS | Apple silicon and Intel | Homebrew, npm, Cargo or the installer script | | Linux (glibc) | x86_64 and aarch64 | Ubuntu, Debian, Fedora and the like | | Linux (musl) | x86_64 and aarch64 | Alpine and slim containers; statically linked, no libc to install | | Windows | x86_64 and aarch64 | npm or the released binary | ## Log in Sign in through the browser device flow, or supply a Personal Access Token with `NOLGIA_TOKEN` or `--token`. ```bash $ nolgia auth login $ nolgia auth whoami ``` ## Generate Save an image, a video or an audio file with `--out`; `--json` makes the command output machine-readable. ```bash $ nolgia gen image --model flux-pro --prompt "a paper-cut mountain range at dawn" --out first.png $ nolgia gen video --model veo-3.1-lite --prompt "a paper-cut mountain range at dawn, slow push-in as the sun rises" --duration-seconds 4 --out first.mp4 $ nolgia gen audio --model stable-audio-2.5 --prompt "quiet wind through mountain pines" --out first.mp3 ``` ## Commands From `nolgia --help` in release 0.2.30, published 2026-09-21: | Command | What it does | | --- | --- | | `auth` | Authenticate this machine | | `gen` | Generate images, video, or audio | | `restore` | Restore and upscale existing footage (de-noise, de-haze, up-res) | | `status` | Show current job status | | `jobs` | List your generation jobs | | `wait` | Wait for a job to finish | | `assets` | List and manage generated assets | | `characters` | Manage reusable characters for generation | | `projects` | Group assets into projects | | `products` | Products imported from a store link, reusable in every ad | | `compositions` | Assemble clips into a Studio timeline and render one finished video | | `render` | Assemble ordered clip and narration pairs into one finished video | | `account` | Inspect account details and usage | | `billing` | Inspect billing state and portal links | | `pat` | Manage personal access tokens | | `org` | Organization workspaces: list, status, switch, create, members, invite, credits [aliases: workspace] | | `skills` | Bundled AI-agent skills (list, show, install) | | `ability` | Marketplace abilities for your NOLGIA Agent (list, install, sync, init, pack, publish) | | `models` | Live model catalog with capabilities and pricing | | `voices` | Voice catalog for text-to-speech models (list) | | `motions` | Camera-move library for `gen video --motion` (list) | | `color-presets` | Built-in color-grade preset looks for Studio compositions (list, cube) | | `masks` | Timeline masks for Studio compositions (validate, example) | | `completion` | Generate shell completions (bash, zsh, fish, powershell) | | `help` | Print this message or the help of the given subcommand(s) | ## Idempotency and scripting Reuse `--idempotency-key` or `NOLGIA_IDEMPOTENCY_KEY` to collapse generation retries into one job; use a fresh key for an intentional second take. List existing work with `nolgia jobs` and `nolgia assets`, follow a job with `nolgia wait`, and read the model catalog before submitting. ```bash $ nolgia --json jobs list $ nolgia --json wait "$JOB_ID" $ nolgia --json assets list $ nolgia models list --modality image ``` :::cards - [Quick Start](./getting-started.html) icon=spot-library: Generate your first image and video. - [Run MCP](./mcp.html) icon=spot-agent: Connect the tools to your coding agent. - [Nolgia for Claude Code, Cursor and Codex](./coding-agents.html) icon=spot-terminal: Install the skills and follow the runbook. ::: --- # Libraries, APIs and community Source: https://docs.nolgia.ai/guides/client-libraries.md Choose a client for your language, use the CLI or MCP server, or generate your own client from the [OpenAPI spec](../api/openapi.yaml). The [Quick Start](./getting-started.html) shows complete image and video programs using the published packages. ![Clients connected to one API](../assets/art/clients.jpg) ## Packages | Client | Package | Version | | --- | --- | --- | | TypeScript | `@nolgia/sdk` | 0.1.4 | | Python | `nolgia` on PyPI | 0.1.4 | | Rust | `nolgia-client` | published with the CLI release; see crates.io | ## Published clients | Language | Published package | Interface | | --- | --- | --- | | TypeScript | `@nolgia/sdk` on npm | `createNolgiaClient` returns an `openapi-fetch` client typed from the spec; call `GET` and `POST`. | | Python | `nolgia` on PyPI | Import endpoint modules from `nolgia.api.*` and request and response types from `nolgia.models`. | | Rust | `nolgia-client` on crates.io, released with the CLI | A progenitor-generated builder client: `client.generate_image().body_map(…).send()`. | ## Install ```bash tab="npm" $ npm install @nolgia/sdk ``` ```bash tab="yarn" $ yarn add @nolgia/sdk ``` ```bash tab="pnpm" $ pnpm add @nolgia/sdk ``` ```bash tab="bun" $ bun add @nolgia/sdk ``` ```bash tab="pip" $ pip install nolgia ``` ```bash tab="uv" $ uv add nolgia ``` ```bash tab="cargo" $ cargo add nolgia-client ``` ```bash tab="Homebrew" $ brew install nolgiainc/nolgia/nolgia ``` ```bash tab="curl installer" $ curl -fsSL https://raw.githubusercontent.com/nolgiainc/nolgia-cli/main/install.sh | bash ``` ```bash tab="curl" $ curl https://api.nolgia.ai/v1/me -H "Authorization: Bearer $NOLGIA_TOKEN" ``` ## Helpers and error types > [!NOTE] > The `submit` and `subscribe` helpers and `NolgiaGenerationError` ship in `@nolgia/sdk` and `nolgia` 0.1.4, whose job handle `cancel()` cancels the job on the server. They submit a generation, wait for its result and report failures in one call. The examples on this site deliberately call the endpoint interfaces directly instead, because every published release carries those, so a sample runs whichever version you already have installed. ## Other ways to connect | Surface | Start here | | --- | --- | | Terminal and scripts | [The CLI](./cli.html) | | Claude Code, Cursor and Codex | [Run MCP](./mcp.html) | | Coding-agent skills and installation runbook | [Nolgia for Claude Code, Cursor and Codex](./coding-agents.html) | | REST endpoints and schemas | [API reference](../api/) and [OpenAPI spec](../api/openapi.yaml) | | Plain-text and JSON discovery | [Agent-readable surfaces](./agent-surfaces.html) | ## MCP tools The server registry has 72 tools in eight groups. #### Generate | Tool | What it does | | --- | --- | | `nolgia_text_to_image` | Generate image from text | | `nolgia_image_to_image` | Generate image from an image | | `nolgia_text_to_video` | Generate video from text | | `nolgia_image_to_video` | Animate an image into video | | `nolgia_text_to_audio` | Generate audio from text | | `nolgia_run_preset` | Run a preset | | `nolgia_render_blocks` | Assemble clips and narration into one video | | `nolgia_multicam_plan` | Plan a Multicam job | | `nolgia_multicam_start` | Start a Multicam job | | `nolgia_multicam_run` | Check a Multicam job | | `nolgia_multicam_download` | Download every Multicam angle | | `nolgia_multicam_cancel` | Stop a Multicam job | | `nolgia_generate_3d` | Generate a 3D model | | `nolgia_generate_set` | Generate a set of images | | `nolgia_miroge_options` | Price a Miroge remake | | `nolgia_miroge` | Remake a clip with Miroge | #### Your library | Tool | What it does | | --- | --- | | `nolgia_list_assets` | List generated assets | | `nolgia_get_asset` | Open an asset | | `nolgia_delete_asset` | Trash an asset | | `nolgia_update_asset_tags` | Set an asset's tags | | `nolgia_upload_asset` | Upload an image | | `nolgia_list_characters` | List saved characters | | `nolgia_get_character` | Get one character | | `nolgia_create_character` | Save a character | | `nolgia_update_character` | Update a character | | `nolgia_delete_character` | Delete a character | | `nolgia_list_styles` | List saved styles | | `nolgia_create_style` | Save a style | | `nolgia_delete_style` | Delete a saved style | | `nolgia_transcribe_asset` | Transcribe an asset | | `nolgia_get_transcript` | Get an asset transcript | | `nolgia_suggest_clips` | Find short video clips | | `nolgia_create_clips` | Create short video clips | | `nolgia_get_set` | Get a set of images | | `nolgia_get_miroge` | Check a Miroge remake | #### Projects | Tool | What it does | | --- | --- | | `nolgia_list_projects` | List projects | | `nolgia_get_project` | Get one project | | `nolgia_create_project` | Create a project | | `nolgia_update_project` | Update a project | | `nolgia_delete_project` | Delete a project | | `nolgia_add_project_assets` | Add assets to a project | | `nolgia_remove_project_asset` | Remove an asset from a project | | `nolgia_list_canvases` | List canvases | | `nolgia_get_canvas` | Get a canvas | | `nolgia_create_canvas` | Create a canvas | | `nolgia_update_canvas` | Update a canvas | #### Edit sessions and tweaks | Tool | What it does | | --- | --- | | `nolgia_list_tweak_scopes` | List image tweak scopes | | `nolgia_create_edit_session` | Start an edit session | | `nolgia_get_edit_session` | Read an edit session | | `nolgia_edit_session_step` | Apply the next image edit | | `nolgia_revert_edit_session` | Revert an edit session | | `nolgia_estimate_edit_session` | Estimate the next edit | #### Presets | Tool | What it does | | --- | --- | | `nolgia_list_presets` | List presets | | `nolgia_get_preset` | Get one preset | #### Timelines | Tool | What it does | | --- | --- | | `nolgia_validate_mask` | Validate a timeline mask | | `nolgia_validate_motion` | Validate timeline motion | | `nolgia_list_color_presets` | List color-grade presets | | `nolgia_get_render` | Check a render | #### Desktop apps | Tool | What it does | | --- | --- | | `nolgia_app_status` | List connected desktop apps | | `nolgia_app_info` | Read a desktop app's state | | `nolgia_app_run` | Run code in a desktop app | | `nolgia_app_command` | Check a desktop app command | | `nolgia_app_preview` | Preview a desktop app's document | | `nolgia_app_import` | Import an asset into a desktop app | | `nolgia_app_export` | Export from a desktop app | | `nolgia_app_save` | Save the document in a desktop app | | `nolgia_app_open` | Open a document in a desktop app | #### Models, account and jobs | Tool | What it does | | --- | --- | | `nolgia_list_models` | List models | | `nolgia_get_account` | Get the signed-in account | | `nolgia_get_usage` | Get generation counts | | `nolgia_cancel_job` | Cancel a generation job | | `nolgia_list_jobs` | List generation jobs | ## Community | Where | What for | | --- | --- | | [GitHub](https://github.com/nolgiainc) | Public repositories and issues. | | [Contact](https://nolgia.ai/contact) | Reach the team. | | [Changelog](https://nolgia.ai/changelog) | Read release updates. | :::cards - [Quick Start](./getting-started.html) icon=spot-library: Generate an image and a video with a published client. - [Run MCP](./mcp.html) icon=spot-agent: Connect your coding agent to the server. - [The CLI](./cli.html) icon=spot-terminal: Generate and script from your terminal. ::: --- # Model APIs Source: https://docs.nolgia.ai/guides/models.md 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//wait?timeout_seconds=120" \ -H "Authorization: Bearer $NOLGIA_TOKEN" $ curl -sS -o first.png "" ``` ```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> { 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> { loop { match client.wait_for_job().id(id.parse::()?).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" } ``` | Field | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | Yes | | | `modality` | `Modality` | Yes | | | `model` | string | Yes | | | `status` | `JobStatus` | Yes | | | `created_at` | string | Yes | | 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" } ``` | Field | Type | Required | Description | | --- | --- | --- | --- | | `status` | `JobStatus` | Yes | | | `asset` | `Asset` | No | | | `updated_at` | string | Yes | | | `completed_at` | string, nullable | No | | | 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.… | > [!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). | 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. | ### 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. | Image | Video | Audio | 3D | | --- | --- | --- | --- | | 48 | 74 | 17 | 2 | ### 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. | 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 | ### 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. | 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) | ### 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. | 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 | ### 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. | Model id | Modality | Plan | Credits | | --- | --- | --- | --- | | `hunyuan3d-v3` | 3d | starter | 21 per generation | | `trellis` | 3d | starter | 2 per generation | > [!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. ::: --- # Playground Source: https://docs.nolgia.ai/guides/playground.md Use the web app to choose inputs, inspect a result and decide which model belongs in your integration. The same model ids, request fields, jobs and assets are available through the API. ## Try it on nolgia.ai Open a creation page, choose a model and enter your prompt. The creation pages and Library ask anonymous visitors to sign in. | Start here | What to try | | --- | --- | | [Image](https://nolgia.ai/ai/image) | Generate an image from a prompt or supported references. | | [Video](https://nolgia.ai/ai/video) | Choose a video model and its supported duration, quality and inputs. | | [Audio](https://nolgia.ai/ai/audio) | Generate speech, music or sound effects with an audio model. | | [Presets](https://nolgia.ai/presets) | Start from a prepared generation recipe. | | [Models](https://nolgia.ai/models) | Browse the model catalog. | | [Veo 3.1 Lite](https://nolgia.ai/models/veo-3.1-lite) | Inspect the video model used in the Quick Start. | | [Library](https://nolgia.ai/assets) | Find completed assets and inspect their details. | For an API integration, start with the same model and prompt, then consult [Common model arguments](./model-arguments.html) for the supported settings. A model name alone does not guarantee support for every size, duration or reference type. ## What the web app shows The app shows the price before the run, the job's status while it runs, and the asset when it finishes. The price comes from the same `POST /jobs/cost` quote and confirmation token you can use from the API. | In the web app | API equivalent | What to keep | | --- | --- | --- | | Price before generation | `JobCostQuote.credits`, `settings`, `confirmation_token`, `expires_at` | The quoted request and its token until you submit it. | | Queued or running generation | `Job.status`, `progress`, `status_detail`, `status_message` | The job id so you can follow it later. | | Finished asset | `Job.asset` | Its asset id; download from its time-limited `signed_url`. | | The prompt that actually ran | `Asset.enhanced_prompt` | The composed image prompt, when it differs from your original. | `enhanced_prompt` is an image field. It is absent or null when your words ran unchanged, on video or audio, and on older assets that predate the field; its absence does not mean the job failed. | 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`. | ![The same quote and confirmation gate used by the web app and API](../assets/diagrams/confirmation-gate.svg) ## The same job underneath Model generations made in the app are jobs and assets the API can return in the same account or organization context. Read `GET /jobs/{id}` for the generation and `GET /assets/{id}` for the file; there is no separate web-only result to export before an integration can use it. | Attribution | What it records | | --- | --- | | `preset_slug` | The preset that launched the generation, when supplied. | | `agent_session_id` | The conversation responsible for the job or asset, when present. | | `X-Nolgia-Surface` | The calling surface's attribution header; the CLI sets its own value. | These fields explain where a generation came from; they do not change how you wait for its result. See [Platform headers](./headers.html) for surface attribution and [Agent Sessions API](./agent-api.html) for collecting a conversation's media. ## Comparing and sharing Quote the same inputs on several models before choosing. Each call to `POST /jobs/cost` validates the requested settings and returns their exact credit cost without creating a job or reserving credits. ```bash tab="curl" $ curl --fail-with-body -sS https://api.nolgia.ai/v1/jobs/cost \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H "Content-Type: application/json" \ -d '{"kind":"image","image":{"model":"flux-pro","prompt":"a paper-cut mountain range at dawn"}}' $ curl --fail-with-body -sS https://api.nolgia.ai/v1/jobs/cost \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H "Content-Type: application/json" \ -d '{"kind":"image","image":{"model":"gpt-image-2","prompt":"a paper-cut mountain range at dawn"}}' ``` ```ts tab="TypeScript" import { createNolgiaClient } from "@nolgia/sdk"; const nolgia = createNolgiaClient(process.env.NOLGIA_TOKEN!); for (const model of ["flux-pro", "gpt-image-2"] as const) { const { data: quote, error } = await nolgia.POST("/jobs/cost", { body: { kind: "image", image: { model, prompt: "a paper-cut mountain range at dawn" } }, }); if (error) throw new Error(`${error.title}: ${error.detail ?? ""}`); console.log(quote); } ``` ```python tab="Python" import os from nolgia import AuthenticatedClient from nolgia.api.jobs import quote_job_cost from nolgia.models import GenerateImageRequest, ImageModel, JobCostKind, JobCostQuote, JobCostRequest client = AuthenticatedClient(base_url="https://api.nolgia.ai/v1", token=os.environ["NOLGIA_TOKEN"]) for model in ("flux-pro", "gpt-image-2"): quote = quote_job_cost.sync(client=client, body=JobCostRequest( kind=JobCostKind("image"), image=GenerateImageRequest(model=ImageModel(model), prompt="a paper-cut mountain range at dawn"), )) if not isinstance(quote, JobCostQuote): raise SystemExit(f"refused: {quote}") print(quote.to_dict()) ``` This captured `flux-pro` quote shows the response shape; the confirmation token is redacted. Run the calls above for current prices and fresh tokens rather than reusing the captured quote. ```json title="200 OK — captured image quote" { "balance_credits": 2573, "basis": "one_generation", "confirmation_token": "…", "credits": 4, "expires_at": "2026-09-21T03:43:14.171728407Z", "kind": "image", "model": "flux-pro", "settings": [ { "label": "Model", "value": "flux-pro" } ], "sufficient_credits": true } ``` | Field | Type | Required | Description | | --- | --- | --- | --- | | `kind` | `JobCostKind` | Yes | | | `model` | string | Yes | The model id the quote is for, after defaulting — not necessarily the one you sent. | | `credits` | integer | Yes | Display credits. This is the number the submit will hold, computed by the same pricer the submit path uses, for exactly these settings. It is what to show the customer. | | `basis` | one of `one_generation`, `set_run` | Yes | Whether `credits` covers one generation or a whole set run. | | `members` | integer, nullable | No | Number of set members `credits` covers. Present only when `basis` is `set_run`. | | `duration_seconds` | integer, nullable | No | The billed duration the price is for, on a video quote. | | `quality` | string, nullable | No | The resolved quality tier the price is for, when the model has one. | | `settings` | array of `JobCostSetting` | Yes | The settings that made this price, in the order a dialog should show them. Human-readable; do not parse it. | | `balance_credits` | integer, nullable | No | The wallet balance this quote was compared against, when one could be read. | | `sufficient_credits` | boolean | Yes | Whether the balance covers `credits` right now. Advisory — the submit re-checks. | | `confirmation_token` | string | Yes | Short-lived, single-request proof that this price was quoted. Send it back as `confirmation_token` on the matching generate request. Opaque: do not parse or construct it. | | `expires_at` | string | Yes | After this the token is refused and a submit carrying it fails with `confirmation_rejected`. Re-quote. | ### Parameters Set `kind` and the matching request object. Keep that object unchanged when you send its confirmation token to the generation endpoint; changing the model or settings requires another quote. | Field | Type | Required | Description | | --- | --- | --- | --- | | `kind` | `JobCostKind` | Yes | | | `image` | `GenerateImageRequest` | No | | | `video` | `GenerateVideoRequest` | No | | | `audio` | `GenerateAudioRequest` | No | | | `three_d` | `Generate3DRequest` | No | | | `set` | `GenerateSetRequest` | No | | > [!NOTE] > A quote does not reserve your balance. Submission re-checks it, and a token that is expired or does not match the request is refused with `422 confirmation_rejected`. ### Error responses | Status | Code | What to do | | --- | --- | --- | | `400` | `validation` | Correct the model or unsupported settings before quoting again. | | `401` | No generation code is required | Replace the missing, expired or invalid token. | | `422` on submission with a quote token | `confirmation_rejected` | Quote the final request again and use the fresh token. | Once you have a result worth sharing, create a share link instead of sending its expiring download URL. [File access controls](../api/#tag/sharing) covers share-link creation and revocation in the API reference. There is no side-by-side compare view and no "copy this form as code" button; the [Quick Start tabs](./getting-started.html) are the code. ## Next steps :::cards - [Model APIs](./models.html): Browse models and published credit prices by modality. - [Quick Start](./getting-started.html): Run the same workflow in your own language. ::: --- # Inference methods Source: https://docs.nolgia.ai/guides/inference.md Submit a generation once, then choose how your application follows the job. A blocking script, background worker and live progress display can all use the same job id and finished asset. | Method | How it works | When to use it | Page | | --- | --- | --- | --- | | `subscribe()` (TypeScript and Python 0.1.2; the Rust crate keeps its own) | The helper submits and polls until it has a result. | A script that needs a finished asset before continuing. | [Synchronous: subscribe](./subscribe.html) | | Submit and poll | Submit returns immediately; read `GET /jobs/{id}` later. | Durable background workers and parallel jobs. | [Asynchronous: submit and poll](./jobs.html) | | Long-poll | `GET /jobs/{id}/wait` waits within one HTTP request. | A worker that can keep a connection open. | [Long-poll with wait](./jobs.html#long-poll-with-wait) | | Status stream | A single-use ticket authorizes an SSE connection. | A UI that should react as job state changes. | [Streaming](./streaming.html) | | Provider callbacks | The provider wakes the server's poller; you still read the job. | Automatic server-side completion detection. | [Callbacks and webhooks](./callbacks.html) | | Agent | A persistent conversation requests generations and retains its assets. | Multi-step work directed through conversation. | [Agent Sessions API](./agent-api.html) | The helpers ship in TypeScript and Python 0.1.2. The published clients already expose the raw endpoints below. | Method | Path | What it does | | --- | --- | --- | | [POST](../api/#tag/jobs/post/jobs/cost) | `/jobs/cost` | Price a generation before submitting it. | | [GET](../api/#tag/jobs/get/jobs) | `/jobs` | List jobs for the current user. | | [GET](../api/#tag/jobs/get/jobs/{id}) | `/jobs/{id}` | Get a single job. | | [GET](../api/#tag/jobs/get/jobs/{id}/wait) | `/jobs/{id}/wait` | Block until the job reaches a terminal state (or timeout). | | [POST](../api/#tag/jobs/post/jobs/{id}/cancel) | `/jobs/{id}/cancel` | Cancel a job, and stop it at the model provider where the provider allows it. | | [POST](../api/#tag/jobs/post/jobs/{id}/sse-ticket) | `/jobs/{id}/sse-ticket` | Mint a single-use ticket for the job status stream. | | [GET](../api/#tag/jobs/get/jobs/{id}/sse) | `/jobs/{id}/sse` | Stream job status updates over Server-Sent Events. | | [POST](../api/#tag/jobs/post/callbacks/{provider}) | `/callbacks/{provider}` | Receive a render provider's completion callback (a wake-up signal). | ![A job progresses from submission to a terminal result](../assets/diagrams/job-lifecycle.svg) ## Synchronous helper Use this pattern when the next step in a script depends on the finished file. Submission is still asynchronous on the server; the client supplies the blocking behavior by waiting for the job. Today, make the same calls with a published client or with curl: ```bash $ 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//wait?timeout_seconds=120" \ -H "Authorization: Bearer $NOLGIA_TOKEN" ``` ### Parameters Pass the same model-specific body you would send to the generation endpoint. The wait uses the returned job id and a `timeout_seconds` window; the helper's polling interval and client timeout are documented on its page. > [!NOTE] > A `408` means this wait window ended with the job still running. Wait again; stopping your client does not cancel the generation. :::cards - [Synchronous: subscribe](./subscribe.html): Use the complete five-language examples and helper parameter reference. ::: ## Submit and poll Use this method when a request handler or worker needs to return immediately and continue elsewhere. Submit to the appropriate generation endpoint, save the response's `id`, and read the job whenever you need its latest state. ```bash $ curl --fail-with-body -sS "https://api.nolgia.ai/v1/jobs/$JOB_ID" \ -H "Authorization: Bearer $NOLGIA_TOKEN" ``` ### Parameters | Parameter | In | Required | Description | | --- | --- | --- | --- | | `id` | path | Yes | Job UUID. | `queued` and `running` are non-terminal. Stop polling on `succeeded`, `failed` or `canceled`; read `asset` for success and `failure` for failure. A `404` means this job could not be found for your request, not that the model is still running. :::cards - [Asynchronous: submit and poll](./jobs.html): Submit, read every state and retrieve the final asset. ::: ## Long-poll with wait Use long-polling when you want the server to hold a connection until the job is terminal, avoiding repeated status reads during that window. A terminal job returns immediately; a running job can outlast the window without failing. ```bash $ curl --fail-with-body -sS "https://api.nolgia.ai/v1/jobs/$JOB_ID/wait?timeout_seconds=120" \ -H "Authorization: Bearer $NOLGIA_TOKEN" ``` ### Parameters | Parameter | In | Required | Description | | --- | --- | --- | --- | | `id` | path | Yes | Job UUID. | | `timeout_seconds` | query | No | | `timeout_seconds` is a query parameter with a maximum of 900 seconds. A window that expires returns `408` with code `timeout`; call the endpoint again. The job's execution deadline is a separate server policy described in [Reliability](./reliability.html). :::cards - [Long-poll with wait](./jobs.html): Copy the wait loop and handle a closed window safely. ::: ## Status stream Use SSE when a progress display should update as soon as state changes arrive. Mint a ticket with your bearer token, then open the stream using only that single-use ticket; the stream reports status changes and a final `complete` event, not unfinished media. ```bash $ curl -sS -X POST "https://api.nolgia.ai/v1/jobs/$JOB_ID/sse-ticket" -H "Authorization: Bearer $NOLGIA_TOKEN" $ curl -N "https://api.nolgia.ai/v1/jobs/$JOB_ID/sse?ticket=" ``` ### Parameters | Parameter | In | Required | Description | | --- | --- | --- | --- | | `id` | path | Yes | Job UUID. | | `ticket` | query | Yes | Single-use ticket from `POST /jobs/{id}/sse-ticket`. | Request a fresh ticket for a new connection: reuse or expiry does not authorize another stream. Ticket issuance can return `401`, `404` or `503`; opening the stream can return `401`, `403` or `503`. :::cards - [Streaming](./streaming.html): Read the real event frames and stream them in your language. ::: ## Provider callbacks Supported providers send a signed callback to Nolgia when there is something to read, waking the poller immediately. This is automatic and requires no endpoint in your application. It is useful regardless of which client method you chose; continue reading the same job to observe completion. ```bash $ curl --fail-with-body -sS "https://api.nolgia.ai/v1/jobs/$JOB_ID" \ -H "Authorization: Bearer $NOLGIA_TOKEN" ``` ### Parameters There are no customer callback parameters to set. `POST /callbacks/{provider}` accepts signed provider traffic, not a customer webhook registration; its receipt is a wake-up signal, and the normal result read determines settlement. :::cards - [Callbacks and webhooks](./callbacks.html): Understand callback behavior and how to be notified today. ::: ## Agent Use an agent session for a conversation that plans work, chooses tools and creates assets over multiple turns. Start by creating the session; creating it does not itself generate media. Send a message through the Agent Sessions API to ask for the work. ```bash $ curl --fail-with-body -sS https://api.nolgia.ai/v1/agent/sessions \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H "Content-Type: application/json" \ -d '{"name":"Docs quickstart"}' ``` ### Parameters | Field | Type | Required | Description | | --- | --- | --- | --- | | `name` | string | Yes | | | `project_id` | string | No | Project (owned by the caller) to scope the session to. | | `preset_slug` | string | No | Preset to run the session under. Must name an existing public preset (400 otherwise); the agent receives the preset's guardrails and output context on every turn. | | `campaign` | `SessionCampaign` | No | Marketing attribution for this launch, when the session was started from a campaign link (`nolgia.ai/p/?utm_source=...`).… | The session response is `201` with an `id`. Follow subsequent turns through the session's events stream and list its assets when the agent creates media; a session id is not a generation job id. :::cards - [Agent Sessions API](./agent-api.html): Create a session, send messages, follow replies and collect assets. ::: ## Error responses | Operation | Status and exact code | Next action | | --- | --- | --- | | Generate | `400 validation` | Correct the request's inputs. | | Generate | `402 out_of_credits` | Add credits or choose a request the wallet can pay for. | | Generate | `409`, with the earlier `job_id` | Follow the previously accepted job instead of resubmitting. | | Generate with a confirmation token | `422 confirmation_rejected` | Re-quote the final request. | | Generate at a concurrency or quota ceiling | `429 rate_limit` | Wait for capacity or the supplied quota reset time. | | Authenticated call | `401`, no generation code required | Replace the bad or expired token. | | Read a job | `404`, no generation code required | Check the job id and account context. | | Long-poll | `408 timeout` | Continue waiting; this response is not a terminal job failure. | An accepted job can fail after submission; inspect its `failure.code` rather than the HTTP status of a successful status read. See [Errors](./errors.html) for the complete code reference. ## Getting started :::cards - [Client setup](./client-setup.html): Install a client and configure the token and base URL. - [Proxy setup](./proxy-setup.html): Keep your token on the server when a browser calls the API. ::: --- # Client setup Source: https://docs.nolgia.ai/guides/client-setup.md Choose a published client, set `NOLGIA_TOKEN` in your server environment, and check the connection with `GET /me`. The clients use the same endpoint paths and request fields as the [API reference](../api/) and [OpenAPI spec](../api/openapi.yaml). ## Installation Choose one client. These are the installation commands used by the [Quick Start](./getting-started.html). ```bash tab="npm" $ npm install @nolgia/sdk ``` ```bash tab="yarn" $ yarn add @nolgia/sdk ``` ```bash tab="pnpm" $ pnpm add @nolgia/sdk ``` ```bash tab="bun" $ bun add @nolgia/sdk ``` ```bash tab="pip" $ pip install nolgia ``` ```bash tab="uv" $ uv add nolgia ``` ```bash tab="cargo" $ cargo add nolgia-client ``` ```bash tab="Homebrew" $ brew install nolgiainc/nolgia/nolgia ``` ```bash tab="curl installer" $ curl -fsSL https://raw.githubusercontent.com/nolgiainc/nolgia-cli/main/install.sh | bash ``` ```bash tab="curl" $ curl https://api.nolgia.ai/v1/me -H "Authorization: Bearer $NOLGIA_TOKEN" ``` ## Configuration Create a Personal Access Token using [Get your API key](./authentication.html), then read it from the environment rather than writing it into your source. ```bash $ export NOLGIA_TOKEN=nol_... ``` | Environment | Base URL | | --- | --- | | Production | `https://api.nolgia.ai/v1` | | Staging | `https://api.stg.nolgia.ai/v1` | | Local development | `http://localhost:8080/v1` | Use the full base URL including the version prefix shown in the table. TypeScript defaults to production; Python and Rust take the base URL explicitly. Pass the staging URL from the table when configuring a staging client. ```ts tab="TypeScript" import { createNolgiaClient } from "@nolgia/sdk"; const nolgia = createNolgiaClient(process.env.NOLGIA_TOKEN!, undefined, { headers: { "X-Nolgia-Surface": "pipeline" }, }); ``` ```python tab="Python" import os from nolgia import AuthenticatedClient client = AuthenticatedClient( base_url="https://api.nolgia.ai/v1", token=os.environ["NOLGIA_TOKEN"], headers={"X-Nolgia-Surface": "pipeline"}, ) ``` ```rust tab="Rust" use nolgia_client::ClientBuilder; let client = ClientBuilder::new("https://api.nolgia.ai/v1") .bearer_token(std::env::var("NOLGIA_TOKEN")?) .surface("pipeline") .idempotency_key("my-generation-1") .build()?; ``` | Client | Constructor or option | Meaning | | --- | --- | --- | | TypeScript | `createNolgiaClient(token, baseUrl?, options?)` | The third argument accepts `openapi-fetch` options, including `headers` and a custom `fetch`. | | Python | `AuthenticatedClient(base_url=…, token=…, headers=…)` | Sets the base URL, bearer token and additional headers; generated endpoint modules accept this client. | | Rust | `ClientBuilder::new(base_url)` | Set the bearer token, optional calling surface and optional idempotency key, then call `build()`. | An idempotency key on the client is sent on every request. Reuse it for retries of one generation; choose a new key for a deliberate repeat of identical input. See [Platform headers](./headers.html). ## Making your first call `GET /me` identifies the authenticated account and reports its current generation limits. It does not submit a generation or spend credits. ```bash tab="curl" $ curl --fail-with-body -sS https://api.nolgia.ai/v1/me \ -H "Authorization: Bearer $NOLGIA_TOKEN" ``` ```bash tab="CLI" $ nolgia account me --json ``` ```ts tab="TypeScript" import { createNolgiaClient } from "@nolgia/sdk"; const nolgia = createNolgiaClient(process.env.NOLGIA_TOKEN!); const { data, error } = await nolgia.GET("/me"); if (error) throw new Error(`${error.title}: ${error.detail ?? ""}`); console.log(data); ``` ```python tab="Python" import os from nolgia import AuthenticatedClient from nolgia.api.auth import get_current_user from nolgia.models import User client = AuthenticatedClient(base_url="https://api.nolgia.ai/v1", token=os.environ["NOLGIA_TOKEN"]) user = get_current_user.sync(client=client) if not isinstance(user, User): raise SystemExit(f"refused: {user}") print(user.to_dict()) ``` ```rust tab="Rust" title="src/main.rs" use nolgia_client::ClientBuilder; #[tokio::main] async fn main() -> Result<(), Box> { let client = ClientBuilder::new("https://api.nolgia.ai/v1") .bearer_token(std::env::var("NOLGIA_TOKEN")?) .build()?; let user = client.get_current_user().send().await?.into_inner(); println!("{user:?}"); Ok(()) } ``` The captured production response below preserves the account id; only the email is replaced. ```json title="200: current account" { "id": "dad27b53-a85b-4e3d-8fd6-b152c803a27c", "email": "you@example.com", "active_organization": null, "generation_limits": { "concurrent_active": 0, "concurrent_max": 8 } } ``` | Field | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | Yes | | | `email` | string | Yes | | | `active_organization` | `UserOrganization`, nullable | No | The organization the user is currently working in, or `null` in the personal space. | | `generation_limits` | `GenerationLimits`, nullable | No | Current shared generation concurrency across image, audio, and video. | | Field | Type | Required | Description | | --- | --- | --- | --- | | `concurrent_max` | integer | Yes | Maximum generations this account may run at once on its effective plan. | | `concurrent_active` | integer | Yes | Generations currently running across image, audio, and video. | ### Parameters reference `GET /me` has no path or query parameters. Send your bearer token with the request. | Parameter | In | Required | Description | | --- | --- | --- | --- | ## Client methods | Client | Published interface today | Submit and wait | | --- | --- | --- | | TypeScript | Typed `GET`, `POST` and other HTTP methods from `openapi-fetch`. | The Quick Start's `finish()` loop calls the typed wait endpoint. | | Python | Typed request and response models, with synchronous and asynchronous functions in endpoint modules. | Call `nolgia.api.jobs.wait_for_job` in the Quick Start's `finish()` loop. | | Rust | Typed request builders ending in `send().await`. | Call `wait_for_job()` in the Quick Start's `finish()` loop. | | Go | Generated endpoint methods and request/response types in the private repository. | Use the job and wait endpoints directly. | | CLI | Commands for generation, account, models and assets. | `nolgia gen image … --out first.png` waits and downloads; `nolgia wait ` follows an existing job. | | Convenience layer (0.1.2) | `subscribe` / `submit`, shipping in TypeScript and Python 0.1.2; the Rust crate keeps its own helper. | The [synchronous guide](./subscribe.html) documents the upcoming helpers and today's published-client equivalent. | ## Server-side or browser? Use these token-bearing clients in a server, worker, or a script you control. A browser or mobile application sends requests to your own authenticated backend, which adds the PAT and forwards only the routes your product needs. > [!WARNING] > Never ship a PAT to a browser or mobile app. Client source, bundled configuration and network requests are visible to the person running the app. :::cards - [Proxy setup](./proxy-setup.html): Keep the PAT on your server with a small allow-list of API routes. ::: ## Error responses `GET /me` returns `401 Unauthorized` when authentication is invalid. Replace a missing, expired or revoked token; retrying it unchanged will not help. A `402` / `out_of_credits` response from a generation call instead concerns the wallet, not your token. ```json title="401: invalid authentication" {"type":"about:blank","title":"Unauthorized","status":401,"detail":"valid authentication is required"} ``` | Field | Type | Required | Description | | --- | --- | --- | --- | | `code` | string | No | Machine-readable error code.… | | `type` | string | Yes | A URI reference identifying the problem type. | | `title` | string | Yes | | | `status` | integer | Yes | | | `detail` | string | No | | | `instance` | string | No | | | `job_id` | string | No | The job this refusal points at, so a client can follow it without reading `detail`.… | | `request_id` | string | No | | ## Client versions | Client | Package | Version | | --- | --- | --- | | TypeScript | `@nolgia/sdk` | 0.1.4 | | Python | `nolgia` on PyPI | 0.1.4 | | Rust | `nolgia-client` | published with the CLI release; see crates.io | ## Next steps :::cards - [Quick Start](./getting-started.html): Generate an image and a video with the published clients. - [Proxy setup](./proxy-setup.html): Call Nolgia from your browser through your own server. - [Synchronous: subscribe](./subscribe.html): Submit and wait with the endpoint interfaces, and meet the 0.1.2 helpers. - [Libraries, APIs and community](./client-libraries.html): Find packages, interfaces and support. ::: --- # Proxy setup Source: https://docs.nolgia.ai/guides/proxy-setup.md Browser source is public, so your PAT must stay on your server. A small proxy adds the bearer token and forwards an allow-list of routes, letting the browser quote a generation, submit it and follow its job without receiving the token. ## How it works Your browser calls a route on your own origin under **/api/nolgia**. Your server checks that the upstream path is allowed, attaches `NOLGIA_TOKEN` from its environment, and sends the request to the Nolgia API. The server passes the upstream status and response body back to the browser. ![The browser calls your route; your server adds the PAT and calls the Nolgia API, then returns the response](../assets/diagrams/proxy-request.svg) ## Plain Node Save this program as `proxy.mjs`. It is the fixture exercised against `GET /me` and `POST /jobs/cost` on 2026-09-21. ```js title="proxy.mjs" // A key-keeping proxy: the browser calls this server, this server calls Nolgia with the PAT. import { createServer } from "node:http"; const UPSTREAM = "https://api.nolgia.ai/v1"; const ALLOWED = [/^\/generate\/(image|video|audio|3d)$/, /^\/jobs\/[0-9a-f-]+(\/wait)?$/, /^\/jobs\/cost$/, /^\/me$/]; createServer(async (req, res) => { const url = new URL(req.url, "http://localhost"); const path = url.pathname.replace(/^\/api\/nolgia/, ""); if (!ALLOWED.some((rule) => rule.test(path))) { res.writeHead(404).end(); return; } const upstream = await fetch(UPSTREAM + path + url.search, { method: req.method, headers: { Authorization: `Bearer ${process.env.NOLGIA_TOKEN}`, "Content-Type": req.headers["content-type"] ?? "application/json" }, body: req.method === "GET" || req.method === "HEAD" ? undefined : req, duplex: "half", }); res.writeHead(upstream.status, { "Content-Type": upstream.headers.get("content-type") ?? "application/json" }); res.end(Buffer.from(await upstream.arrayBuffer())); }).listen(3115, () => console.log("proxy on http://localhost:3115/api/nolgia")); ``` ```bash $ export NOLGIA_TOKEN=nol_... $ node proxy.mjs ``` ## Express Use this equivalent route in an Express server. Save it as `express-proxy.mjs`; this fixture was exercised against the same two endpoints on 2026-09-21. ```bash $ npm install express ``` ```js title="express-proxy.mjs" import express from "express"; const app = express(); app.use(express.json()); const UPSTREAM = "https://api.nolgia.ai/v1"; app.all(/^\/api\/nolgia\/(generate\/(image|video|audio|3d)|jobs\/cost|jobs\/[0-9a-f-]+(\/wait)?|me)$/, async (req, res) => { const path = req.path.replace(/^\/api\/nolgia/, ""); const query = new URL(req.originalUrl, "http://localhost").search; const upstream = await fetch(UPSTREAM + path + query, { method: req.method, headers: { Authorization: `Bearer ${process.env.NOLGIA_TOKEN}`, "Content-Type": "application/json" }, body: req.method === "GET" ? undefined : JSON.stringify(req.body), }); res.status(upstream.status).type("application/json").send(Buffer.from(await upstream.arrayBuffer())); }); app.listen(3115, () => console.log("proxy on http://localhost:3115/api/nolgia")); ``` ```bash $ export NOLGIA_TOKEN=nol_... $ node express-proxy.mjs ``` > [!NOTE] > Add your own user authentication in front of this route. These examples only keep the PAT off the client; they do not decide which of your users may spend its credits. ## Call it from the browser Serve this browser code from the same origin as your proxy, or route **/api/nolgia** to that server in your application's development server. It quotes the exact image request, asks for confirmation, passes the returned token to the submit call, and repeats the wait when a `408` closes the wait window. The type-only import adds API types to your build and sends no PAT to the browser. ```ts title="browser.ts" import type { components } from "@nolgia/sdk"; type Job = components["schemas"]["Job"]; type Problem = components["schemas"]["Error"]; type Quote = components["schemas"]["JobCostQuote"]; async function finish(id: string, timeout: number) { for (;;) { const response = await fetch(`/api/nolgia/jobs/${id}/wait?timeout_seconds=${timeout}`); if (response.status === 408) continue; // the wait window closed; the job is still running if (!response.ok) { const error: Problem = await response.json(); throw new Error(`${error.title}: ${error.detail ?? ""}`); } const data: Job = await response.json(); if (data?.status === "succeeded" && data.asset) return data.asset; throw new Error(`job ${id} ended ${data?.status ?? response.status}`); } } const image = { model: "flux-pro", prompt: "a paper-cut mountain range at dawn", num_images: 1, }; const quoteResponse = await fetch("/api/nolgia/jobs/cost", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ kind: "image", image }), }); if (!quoteResponse.ok) { const error: Problem = await quoteResponse.json(); throw new Error(`${error.title}: ${error.detail ?? ""}`); } const quote: Quote = await quoteResponse.json(); if (!window.confirm(`Generate this image for ${quote.credits} credits?`)) { throw new Error("Generation declined"); } const submitResponse = await fetch("/api/nolgia/generate/image", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ ...image, confirmation_token: quote.confirmation_token }), }); if (!submitResponse.ok) { const error: Problem = await submitResponse.json(); throw new Error(`${error.title}: ${error.detail ?? ""}`); } const job: Job = await submitResponse.json(); const asset = await finish(job.id, 120); console.log(asset.signed_url); ``` The quote response is captured production JSON; the confirmation token is redacted. Use the actual token from your own quote when you submit. ```json title="200: quote" { "balance_credits": 2573, "basis": "one_generation", "confirmation_token": "…", "credits": 4, "expires_at": "2026-09-21T03:43:14.171728407Z", "kind": "image", "model": "flux-pro", "settings": [ { "label": "Model", "value": "flux-pro" } ], "sufficient_credits": true } ``` | Field | Type | Required | Description | | --- | --- | --- | --- | | `kind` | `JobCostKind` | Yes | | | `model` | string | Yes | The model id the quote is for, after defaulting — not necessarily the one you sent. | | `credits` | integer | Yes | Display credits. This is the number the submit will hold, computed by the same pricer the submit path uses, for exactly these settings. It is what to show the customer. | | `basis` | one of `one_generation`, `set_run` | Yes | Whether `credits` covers one generation or a whole set run. | | `members` | integer, nullable | No | Number of set members `credits` covers. Present only when `basis` is `set_run`. | | `duration_seconds` | integer, nullable | No | The billed duration the price is for, on a video quote. | | `quality` | string, nullable | No | The resolved quality tier the price is for, when the model has one. | | `settings` | array of `JobCostSetting` | Yes | The settings that made this price, in the order a dialog should show them. Human-readable; do not parse it. | | `balance_credits` | integer, nullable | No | The wallet balance this quote was compared against, when one could be read. | | `sufficient_credits` | boolean | Yes | Whether the balance covers `credits` right now. Advisory — the submit re-checks. | | `confirmation_token` | string | Yes | Short-lived, single-request proof that this price was quoted. Send it back as `confirmation_token` on the matching generate request. Opaque: do not parse or construct it. | | `expires_at` | string | Yes | After this the token is refused and a submit carrying it fails with `confirmation_rejected`. Re-quote. | The submit call returns a job immediately. These captured job responses come from the fixture's lighthouse prompt rather than the browser example's mountain prompt. ```json title="202: queued" { "created_at": "2026-09-21T03:33:14.622149Z", "id": "4a9b2242-f732-4a83-ba41-17448f5aa15f", "modality": "image", "model": "flux-pro", "status": "queued", "updated_at": "2026-09-21T03:33:14.622149Z", "user_id": "dad27b53-a85b-4e3d-8fd6-b152c803a27c" } ``` The wait returns a terminal job. A successful response contains the finished asset; the signed URLs here are shortened for display. ```json title="200: 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" } ``` | Field | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | Yes | | | `modality` | `Modality` | Yes | | | `model` | string | Yes | | | `status` | `JobStatus` | Yes | | | `asset` | `Asset` | No | | | `failure` | `JobFailure` | No | | | `progress` | number, nullable | No | | | `created_at` | string | Yes | | | `completed_at` | string, nullable | No | | ### Parameters reference The browser sends the same request fields as a direct client. The proxy removes **/api/nolgia** from its local path and preserves query parameters, including the wait window. | Field | Type | Required | Description | | --- | --- | --- | --- | | `kind` | `JobCostKind` | Yes | | | `image` | `GenerateImageRequest` | No | | | `video` | `GenerateVideoRequest` | No | | | `audio` | `GenerateAudioRequest` | No | | | `three_d` | `Generate3DRequest` | No | | | `set` | `GenerateSetRequest` | No | | | Field | Type | Required | Description | | --- | --- | --- | --- | | `confirmation_token` | string | No | Optional proof that this exact request and price were shown to the customer, from `POST /jobs/cost`.… | | `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. | | Parameter | In | Required | Description | | --- | --- | --- | --- | | `id` | path | Yes | Job UUID. | | `timeout_seconds` | query | No | | ## What to allow Keep the allow-list limited to the operations your application needs. The two programs above forward these route shapes; they do not proxy the whole API. | Route | Why the examples forward it | | --- | --- | | `POST /jobs/cost` | Show the exact credit quote before generation. | | `POST /generate/image`, `POST /generate/video`, `POST /generate/audio`, `POST /generate/3d` | Submit one of the supported generation modalities. | | `GET /jobs/{id}` | Read the accepted job's current state. | | `GET /jobs/{id}/wait` | Wait for a terminal state, within the requested wait window. | | `GET /me` | Inspect the upstream account and generation limits during integration. | | Keep outside the browser proxy | Why | | --- | --- | | `/pat` | Token management belongs in a trusted account flow. | | `/billing/*` | Billing operations should not inherit a shared server PAT through a generic route. | | `/organizations/*` | Workspace administration requires its own authorization decisions. | The fixtures forward the content type and server-side bearer token. They do not forward browser-supplied `Authorization`, `Idempotency-Key`, `X-Nolgia-Surface`, or request-id headers; add an explicit server policy if your product needs those headers. They buffer the upstream response, so use the wait endpoint here; these programs are not SSE stream proxies. ## Error responses An unlisted route receives `404` from the proxy with an empty body. An allowed route preserves the upstream HTTP status and body: `400` / `validation` means the generation arguments need correction, `401` means the server's token is invalid, and `408` from the wait endpoint means the job is still running. See [Errors](./errors.html) for other submit refusals and terminal job failures. > [!WARNING] > Download an asset's `signed_url` promptly. It is time-limited; store the asset id and read the asset again when you need a fresh URL. ## Next steps :::cards - [Client setup](./client-setup.html): Configure the authenticated client on your server. - [Platform headers](./headers.html): Understand idempotency, attribution and the wait and stream query parameters. ::: --- # Synchronous: subscribe Source: https://docs.nolgia.ai/guides/subscribe.md The `subscribe` / `submit` helpers, shipping in TypeScript and Python 0.1.2, wrap a generation request and status polling. Today, the published clients provide the same submit-and-wait behaviour through the endpoint calls below. ## Today: submit, then wait The generation endpoint returns a job immediately. The `finish()` loop holds `GET /jobs/{id}/wait` open, repeats the wait after `408`, and returns the asset when the job succeeds; it reports any other terminal outcome. These are the image programs from the Quick Start, including their client construction, wait loop and error handling. ```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//wait?timeout_seconds=120" \ -H "Authorization: Bearer $NOLGIA_TOKEN" $ curl -sS -o first.png "" ``` ```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> { 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> { loop { match client.wait_for_job().id(id.parse::()?).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` with this captured job. The blocking program continues waiting after this response. ```json title="202: queued" { "created_at": "2026-09-21T03:33:14.622149Z", "id": "4a9b2242-f732-4a83-ba41-17448f5aa15f", "modality": "image", "model": "flux-pro", "status": "queued", "updated_at": "2026-09-21T03:33:14.622149Z", "user_id": "dad27b53-a85b-4e3d-8fd6-b152c803a27c" } ``` The successful wait returns the finished job and its asset. This is the captured production response with the signed URLs shortened; the fixture's prompt is from its lighthouse run. ```json title="200: 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" } ``` | Field | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | Yes | | | `modality` | `Modality` | Yes | | | `model` | string | Yes | | | `status` | `JobStatus` | Yes | | | `asset` | `Asset` | No | | | `failure` | `JobFailure` | No | | | `progress` | number, nullable | No | | | `created_at` | string | Yes | | | `completed_at` | string, nullable | No | | | Field | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | Yes | | | `signed_url` | string | Yes | Time-limited GCS signed URL for download.… | | `expires_at` | string | Yes | Expiry of `signed_url`. | | `mime_type` | string | No | | ### Parameters reference The image request below is the source of truth for `model`, `prompt`, and the optional image count. Choose supported values from [Common model arguments](./model-arguments.html). | 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. | | Parameter | In | Required | Description | | --- | --- | --- | --- | | `id` | path | Yes | Job UUID. | | `timeout_seconds` | query | No | | > [!NOTE] > `408` means the wait window closed while the job was still running. Wait again using the same job id; do not submit a replacement generation. ## The helper (next release) The following `subscribe` signatures (TypeScript and Python 0.1.2; the Rust crate keeps its own) document the implementation that is already checked in. They poll `GET /jobs/{id}`, then read the job's assets; they do not use the long-poll endpoint shown above. These are signatures and call forms, not imports supported by today's published packages. ```ts tab="TypeScript" title="repository signature" subscribe(client, endpoint, args, { pollInterval, maxPollTime, onStatus, signal, headers, }); ``` ```python tab="Python" title="repository signature" def subscribe(client: AuthenticatedClient, endpoint: str, arguments: dict, *, poll_interval: float = 0.5, max_poll_time: float = 1800.0, on_status: Optional[Callable[[StatusUpdate], None]] = None, headers: Optional[dict[str, str]] = None) -> GenerationResult: ... ``` ```rust tab="Rust" title="repository call form" subscribe(&client, endpoint, arguments, SubscribeOptions::default()).await? ``` A successful result contains the terminal job, its job id, a `media` collection, and `url` for the first returned media item. Each media entry identifies an asset and its signed URL. See [Uploads](./uploads.html) for asset access, and download a signed URL before it expires. | Result field | TypeScript | Python / Rust | Meaning | | --- | --- | --- | --- | | Job id | `jobId` | `job_id` | The accepted job's id, reusable outside this process. | | Terminal job | `job` | `job` | The job returned by the API. | | Media | `media` | `media` | Assets listed for the job, falling back to the inline asset if the asset list is unavailable. | | First URL | `url` | `url` | The first media URL, or no value when there are no media entries. | ## Parameters ### TypeScript The repository helper takes `client`, `endpoint`, `args`, and an optional options object. Durations are in **milliseconds**. | Name | Type | Default | Meaning | | --- | --- | --- | --- | | `client` | `NolgiaClient` | Required | The authenticated typed client. | | `endpoint` | `GenerateEndpoint` | Required | `/generate/image`, `/generate/audio`, `/generate/video`, `/generate/3d`, or `/restore/video`. | | `args` | `unknown` | Required | JSON arguments passed unchanged to the endpoint for validation. | | `pollInterval` | `number` | `500` | Delay between status reads; finite and greater than zero. | | `maxPollTime` | `number` | `1800000` | Client wait budget; finite and nonnegative. | | `onStatus` | `(update: StatusUpdate) => void` | Unset | Receives the initial state and changes; callback exceptions propagate. | | `signal` | `AbortSignal` | Unset | Stops the client's wait when aborted. | | `headers` | `Record` | Unset | Additional headers on the submission request. | ### Python The repository helper takes `client`, `endpoint`, `arguments`, and keyword-only options. Durations are in **seconds**. | Name | Type | Default | Meaning | | --- | --- | --- | --- | | `client` | `AuthenticatedClient` | Required | The authenticated client. | | `endpoint` | `str` | Required | The generation endpoint; it must return a job. | | `arguments` | `dict` | Required | JSON request body. | | `poll_interval` | `float` | `0.5` | Delay between status reads; finite and greater than zero. | | `max_poll_time` | `float` | `1800.0` | Client wait budget; finite and nonnegative. | | `on_status` | `Callable[[StatusUpdate], None]` or `None` | `None` | Receives the initial state and changes; callback exceptions propagate. | | `headers` | `dict[str, str]` or `None` | `None` | Additional headers on the submission request. | ### Rust The repository helper takes `&Client`, an endpoint string, a `serde_json::Value` request body and `SubscribeOptions`. | Name | Type | Default | Meaning | | --- | --- | --- | --- | | `client` | `&Client` | Required | The authenticated client. | | `endpoint` | `&str` | Required | `/generate/image`, `/generate/audio`, `/generate/video`, `/generate/3d`, or `/restore/video`. | | `arguments` | `serde_json::Value` | Required | JSON request body. | | `poll_interval` | `Duration` | `Duration::from_millis(500)` | Delay between status reads. | | `max_poll_time` | `Duration` | `Duration::from_secs(1800)` | Client wait budget. | | `on_status` | Optional boxed callback | `None` | Receives `&StatusUpdate`; the callback is `Send + Sync`. | | `headers` | `Vec<(String, String)>` | Empty | Additional headers on the submission request. | `/generate/set` returns an output set rather than a job and does not use this helper flow. See [Asynchronous: submit and poll](./jobs.html#sets). > [!NOTE] > A helper timeout stops waiting, not the generation. The job continues on the server and credits are still spent. To stop the job itself, call the handle's `cancel()` (TypeScript and Python), which calls `POST /jobs/{id}/cancel` and returns the canceled job with its `cancellation`; see [Cancel a request](./jobs.html#cancel-a-request). ## Progress updates The repository callbacks `onStatus` and `on_status` receive `status` and `progress`, together with the job id, status detail, status message and full job. They run once for the initial submit response and again when one of those status fields changes. Progress may be absent; do not invent a percentage when it is missing. Today, read the same status information from `GET /jobs/{id}`. A job can remain queued with `status_detail: provider_down` or `upstream_at_capacity`; display `status_message` while it waits. [Streaming](./streaming.html) describes receiving changes without client polling. | Field | Type | Required | Description | | --- | --- | --- | --- | | `status` | `JobStatus` | Yes | | | `status_detail` | string, nullable | No | Machine-readable refinement of a non-terminal status.… | | `status_message` | string, nullable | No | Human-readable companion to status_detail, safe to render to the customer as-is.… | | `progress` | number, nullable | No | | ## When to use Use a blocking call for a command-line script or a worker that handles one job at a time and needs the finished file before continuing. For a web request or several jobs in parallel, store the accepted job id, return promptly, and follow it from a separate process using [Asynchronous: submit and poll](./jobs.html). ## Error responses The TypeScript and Python repository helpers raise `NolgiaGenerationError`; Rust returns `GenerationError`. Their `code` preserves the server's problem or failure code, including unknown future codes. The published endpoint calls expose the problem response on submission and `failure.code` on a failed job; keep those two stages separate. | Outcome | Where to read it | What to do | | --- | --- | --- | | Submit refused | HTTP problem `code` and `detail` | Correct the request or follow the code's retry guidance. A refused submit has no new accepted job. | | Job failed | `failure.code`, `failure.message`, `failure.credits_refunded` | Report the terminal reason and the recorded credit settlement. | | Wait window closed | HTTP `408` from the wait endpoint | Keep waiting for the existing job. | | Client wait budget expired | Helper error `timeout` | Read the existing job later; the generation continues. | | Field | Type | Required | Description | | --- | --- | --- | --- | | `code` | `GenerationErrorCode` | No | Stable refinement of `kind`, present on every job that failed after this field shipped and derived from the recorded reason for older ones.… | | `kind` | string | Yes | What stopped the job.… | | `message` | string | Yes | Human-readable reason, safe to show the customer as-is. The same text as `error.detail`. | | `credits_refunded` | boolean, nullable | No | What the credit ledger actually did with this job's credit hold, recorded when the hold settled.… | | Value | Meaning | | --- | --- | | `out_of_credits` | the wallet cannot pay for the job. Nothing was submitted and nothing was charged. Top up, or submit a cheaper model or fewer seconds. | | `rate_limit` | refused for now, not refused outright - the caller's own concurrency ceiling, or the provider's. Retry later; the same request will be accepted. | | `prompt_nsfw` | a content filter refused the request or the result it produced, on safety grounds. Editing the prompt or the reference media is the fix. Whether the credits were refunded is `failure.credits_refunded`, not this code. | | `ip_detected` | a content filter refused it for a real person's likeness or a protected work, rather than for safety. The fix is different from `prompt_nsfw` - change the reference image or the named subject, not the tone of the prompt - which is why it is its own code. | | `job_failed` | the job ran and did not produce an asset for any other reason, including a provider error. The default for an unclassified failure. | | `timeout` | the job ran past its time budget, or a `GET /jobs/{id}/wait` returned before the job reached a terminal status. On a failed job the work is over; on a `wait` the job may still be running. | | `validation` | the request itself is not acceptable - a missing or malformed field, a model that does not support the capability asked of it, a duration the model will not render. Nothing was submitted and nothing was charged. Retrying unchanged will fail identically. | | `confirmation_rejected` | the cost confirmation gate did not pass. Either a client showed the customer a quote and the customer declined, or a supplied confirmation token was refused. A submit that carries no confirmation token is never refused for this reason. | | `canceled` | the job was canceled by its owner (`POST /jobs/{id}/cancel`), not broken. It is the code the SDK wait helpers raise for a `canceled` job; `cancellation` on the job says what the provider did and what was refunded. A canceled job carries no `failure`. | | `job_not_cancellable` | `POST /jobs/{id}/cancel` refused (`409`) because the job already finished, or its finished result is already being delivered. The job will reach `succeeded` or `failed` on its own; nothing was changed. | | `approval_required` | refused with `402` (title `Approval Needed`) because the generation would take an agent run past the price its customer approved (the `credit_ceiling` a guided preset's review card showed, sent with the brief). Nothing was submitted and nothing was charged. The detail names the generation's cost and the new run total; the agent must ask the customer to approve that total before it continues, never retry, split the job or switch models to fit. | | `run_ended` | refused with `409` (title `Run Ended`) because the agent run this generation was started for (the turn its turn-scoped credential names) has already ended: the platform failed, swept or stopped the turn, or it finished and delivered its reply. Nothing was submitted and nothing was charged. The agent must stop working on that run, never retry or switch models; the customer's chat already shows how it ended. | > [!WARNING] > A `401` is a missing, bad or expired token. A generation's `402` / `out_of_credits` means the wallet cannot pay for it. Do not retry a `401` with the same token. ## Next steps :::cards - [Asynchronous: submit and poll](./jobs.html): Return the job id immediately and follow it from any process. - [Reliability](./reliability.html): Understand retries, provider outages, deadlines and refunds. ::: --- # Asynchronous: submit and poll Source: https://docs.nolgia.ai/guides/jobs.md Submit a generation, store its job id, and let your application carry on while the model runs. Submit and poll is the recommended production pattern: a worker can resume following the same durable job after your original request or process ends. ![Jobs](../assets/art/jobs.jpg) ## How jobs work ![Submit a generation, wait while it runs, then read the result](../assets/diagrams/job-lifecycle.svg) | Status | What is happening | SDK value | | --- | --- | --- | | `queued` | Accepted and waiting to run; a provider outage or capacity wait can keep it here. | `queued` | | `running` | The generation or delivery of its result is in progress. | `running` | | `succeeded` | The finished asset is available. | `succeeded` | | `failed` | Work ended without a delivered asset; inspect `failure` and its recorded refund outcome. | `failed` | | `canceled` | Its owner canceled it with `POST /jobs/{id}/cancel`. It is never delivered; `cancellation` says what the provider did and what happened to the credits. | `canceled` | | `status_detail` | Meaning | What to show | | --- | --- | --- | | `provider_down` | The provider is unavailable; the queued job waits for recovery. | `status_message` | | `upstream_at_capacity` | The provider is healthy but has no free generation slot; the job waits for capacity. | `status_message` | Both details are non-terminal. Keep the existing job and its credit hold; do not submit replacements. Treat an unfamiliar detail as a plain queued job. ### Key guarantees - A job is durable once you have its id. Follow it from another request or process. - Credits are held at submit and settled or refunded at the end; [Pricing and credits](./billing.html) explains the ledger and the provider-billed content-policy exception. - Duplicate submission protection prevents the same request and key from being billed twice inside its five-minute window. - A job that cannot run ends with an explicit failure and refund; it is never silently dropped. ## Submit a request Use this when you want the job id immediately and will follow it later. Image, video, audio and 3D generation return `202 Accepted` with a `Job`; set generation and prompt enhancement have their own response shapes. | Method | Path | What it does | | --- | --- | --- | | [POST](../api/#tag/generate/post/generate/image) | `/generate/image` | Submit an image generation job (asynchronous). | | [POST](../api/#tag/generate/post/generate/audio) | `/generate/audio` | Submit an audio generation job (asynchronous). | | [POST](../api/#tag/generate/post/generate/video) | `/generate/video` | Submit a video generation job (asynchronous). | | [POST](../api/#tag/generate/post/generate/video/enhance-prompt) | `/generate/video/enhance-prompt` | Rewrite a video prompt for the model it will run on (signed in; 1 credit). | | [POST](../api/#tag/generate/post/generate/set) | `/generate/set` | Generate a coordinated set of image Outputs. | | [POST](../api/#tag/generate/post/generate/3d) | `/generate/3d` | Turn product or object photos into a 3D model. | | [POST](../api/#tag/generate/post/remove-background/video) | `/remove-background/video` | Remove a video's background (asynchronous). | | [POST](../api/#tag/generate/post/restore/video) | `/restore/video` | Submit a video restoration/upscale job (asynchronous). | ```bash tab="curl" $ curl --fail-with-body -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"}' ``` ```bash tab="CLI" $ nolgia gen image --model flux-pro --prompt "a paper-cut mountain range at dawn" --no-wait ``` ```ts tab="TypeScript" import { createNolgiaClient } from "@nolgia/sdk"; const nolgia = createNolgiaClient(process.env.NOLGIA_TOKEN!); 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 ?? ""}`); console.log(image.id); ``` ```python tab="Python" import os from nolgia import AuthenticatedClient from nolgia.api.generate import generate_image from nolgia.models import GenerateImageRequest, ImageModel, Job client = AuthenticatedClient(base_url="https://api.nolgia.ai/v1", token=os.environ["NOLGIA_TOKEN"]) 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}") print(job.id) ``` ```rust tab="Rust" use nolgia_client::{types, ClientBuilder}; #[tokio::main] async fn main() -> Result<(), Box> { 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(); println!("{}", job.id); Ok(()) } ``` The production capture below is the actual `202` response for a `flux-pro` image request; its ids and timestamps are intact. The captured prompt differs from the reusable Quick Start prompt above. ```json { "created_at": "2026-09-21T03:33:14.622149Z", "id": "4a9b2242-f732-4a83-ba41-17448f5aa15f", "modality": "image", "model": "flux-pro", "status": "queued", "updated_at": "2026-09-21T03:33:14.622149Z", "user_id": "dad27b53-a85b-4e3d-8fd6-b152c803a27c" } ``` | Field | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | Yes | | | `modality` | `Modality` | Yes | | | `model` | string | Yes | | | `status` | `JobStatus` | Yes | | | `status_detail` | string, nullable | No | Machine-readable refinement of a non-terminal status.… | | `status_message` | string, nullable | No | Human-readable companion to status_detail, safe to render to the customer as-is.… | | `progress` | number, nullable | No | | | `created_at` | string | Yes | | | `updated_at` | string | Yes | | | `completed_at` | string, nullable | No | | > [!TIP] > Store the `id`. You can read it from another process; disconnecting does not cancel the generation. ### Parameters Every submission selects a `model` and the fields supported by that model. The [Common model arguments](./model-arguments.html) reference covers the request schemas and catalog capabilities. | Field | Type | Required | Description | | --- | --- | --- | --- | | `confirmation_token` | string | No | Optional proof that this exact request and price were shown to the customer, from `POST /jobs/cost`.… | | `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. | ## Price it first Call `POST /jobs/cost` with `kind` and the matching request body, such as `image`. The quote uses the same price calculation as submission. It submits nothing, reserves nothing, and charges nothing. ![Confirmation gate: quote the request, show the price, and submit with its token](../assets/diagrams/confirmation-gate.svg) | Field | Type | Required | Description | | --- | --- | --- | --- | | `kind` | `JobCostKind` | Yes | | | `image` | `GenerateImageRequest` | No | | | `video` | `GenerateVideoRequest` | No | | | `audio` | `GenerateAudioRequest` | No | | | `three_d` | `Generate3DRequest` | No | | | `set` | `GenerateSetRequest` | No | | | Field | Type | Required | Description | | --- | --- | --- | --- | | `kind` | `JobCostKind` | Yes | | | `model` | string | Yes | The model id the quote is for, after defaulting — not necessarily the one you sent. | | `credits` | integer | Yes | Display credits. This is the number the submit will hold, computed by the same pricer the submit path uses, for exactly these settings. It is what to show the customer. | | `basis` | one of `one_generation`, `set_run` | Yes | Whether `credits` covers one generation or a whole set run. | | `members` | integer, nullable | No | Number of set members `credits` covers. Present only when `basis` is `set_run`. | | `duration_seconds` | integer, nullable | No | The billed duration the price is for, on a video quote. | | `quality` | string, nullable | No | The resolved quality tier the price is for, when the model has one. | | `settings` | array of `JobCostSetting` | Yes | The settings that made this price, in the order a dialog should show them. Human-readable; do not parse it. | | `balance_credits` | integer, nullable | No | The wallet balance this quote was compared against, when one could be read. | | `sufficient_credits` | boolean | Yes | Whether the balance covers `credits` right now. Advisory — the submit re-checks. | | `confirmation_token` | string | Yes | Short-lived, single-request proof that this price was quoted. Send it back as `confirmation_token` on the matching generate request. Opaque: do not parse or construct it. | | `expires_at` | string | Yes | After this the token is refused and a submit carrying it fails with `confirmation_rejected`. Re-quote. | The `confirmation_token` is optional. If you include one, an expired, malformed, foreign, or mismatched token is refused with 422 and `confirmation_rejected`. Quote again if you change the request or pass `expires_at`. With `NOLGIA_TOKEN` set, quote the request first, then submit the same body with the `confirmation_token` the quote returned. ```bash tab="curl" $ curl -sS https://api.nolgia.ai/v1/jobs/cost \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H "Content-Type: application/json" \ -d '{"kind":"image","image":{"model":"gpt-image-2","prompt":"a paper-cut mountain range at dawn"}}' $ curl -sS https://api.nolgia.ai/v1/generate/image \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H "Content-Type: application/json" \ -d '{"model":"gpt-image-2","prompt":"a paper-cut mountain range at dawn","confirmation_token":""}' ``` The matching production quote below held no credits. Its opaque confirmation token is redacted. ```json { "balance_credits": 2573, "basis": "one_generation", "confirmation_token": "…", "credits": 4, "expires_at": "2026-09-21T03:43:14.171728407Z", "kind": "image", "model": "flux-pro", "settings": [ { "label": "Model", "value": "flux-pro" } ], "sufficient_credits": true } ``` ## Check status Use `GET /jobs/{id}` when a worker or application wants a snapshot without keeping a connection open. Read `status`, keep polling while it is `queued` or `running`, then handle the asset or typed failure. ```bash tab="curl" $ curl --fail-with-body -sS "https://api.nolgia.ai/v1/jobs/$JOB_ID" \ -H "Authorization: Bearer $NOLGIA_TOKEN" ``` ```bash tab="CLI" $ nolgia status "$JOB_ID" ``` ```ts tab="TypeScript" import { createNolgiaClient } from "@nolgia/sdk"; const nolgia = createNolgiaClient(process.env.NOLGIA_TOKEN!); const { data, error } = await nolgia.GET("/jobs/{id}", { params: { path: { id: process.env.JOB_ID! } }, }); if (error) throw new Error(`${error.title}: ${error.detail ?? ""}`); console.log(data); ``` ```python tab="Python" import os from nolgia import AuthenticatedClient from nolgia.api.jobs import get_job from nolgia.models import Job client = AuthenticatedClient(base_url="https://api.nolgia.ai/v1", token=os.environ["NOLGIA_TOKEN"]) job = get_job.sync(os.environ["JOB_ID"], client=client) if not isinstance(job, Job): raise SystemExit(f"refused: {job}") print(job.to_dict()) ``` ```rust tab="Rust" use nolgia_client::ClientBuilder; #[tokio::main] async fn main() -> Result<(), Box> { let client = ClientBuilder::new("https://api.nolgia.ai/v1") .bearer_token(std::env::var("NOLGIA_TOKEN")?) .build()?; let id = std::env::var("JOB_ID")?.parse::()?; let job = client.get_job().id(id).send().await?.into_inner(); println!("{job:?}"); Ok(()) } ``` ### Queued ```json { "created_at": "2026-09-21T03:33:14.622149Z", "id": "4a9b2242-f732-4a83-ba41-17448f5aa15f", "modality": "image", "model": "flux-pro", "status": "queued", "updated_at": "2026-09-21T03:33:14.642113Z", "user_id": "dad27b53-a85b-4e3d-8fd6-b152c803a27c" } ``` ### Running These are the fields that change, taken from the production stream's running frame; this is an excerpt, not a complete `Job`. ```json {"status":"running","progress":0} ``` ### Succeeded ```json { "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-4…", "size_bytes": 418584, "status": "ready", "tags": [ ], "thumbnail_url": "https://storage.googleapis.com/nolgia-generations-prod/dad27b53-a85b-4…", "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" } ``` ### Failed A Gemini video failure was observed on 2026-09-20 with the message beginning “Video generation failed due to an internal server issue…”. There is no saved failed-job response fixture: this excerpt is built from `JobFailure`, using that observed message and refund, rather than presented as a captured full response. ```json { "status": "failed", "failure": { "kind": "error", "code": "job_failed", "message": "Video generation failed due to an internal server issue…", "credits_refunded": true } } ``` | Field | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | Yes | | | `modality` | `Modality` | Yes | | | `model` | string | Yes | | | `status` | `JobStatus` | Yes | | | `status_detail` | string, nullable | No | Machine-readable refinement of a non-terminal status.… | | `status_message` | string, nullable | No | Human-readable companion to status_detail, safe to render to the customer as-is.… | | `asset` | `Asset` | No | | | `error` | `Error` | No | | | `failure` | `JobFailure` | No | | | `progress` | number, nullable | No | | | `created_at` | string | Yes | | | `completed_at` | string, nullable | No | | | Field | Type | Required | Description | | --- | --- | --- | --- | | `code` | `GenerationErrorCode` | No | Stable refinement of `kind`, present on every job that failed after this field shipped and derived from the recorded reason for older ones.… | | `kind` | string | Yes | What stopped the job.… | | `message` | string | Yes | Human-readable reason, safe to show the customer as-is. The same text as `error.detail`. | | `credits_refunded` | boolean, nullable | No | What the credit ledger actually did with this job's credit hold, recorded when the hold settled.… | ### Parameters | Parameter | In | Required | Description | | --- | --- | --- | --- | | `id` | path | Yes | Job UUID. | ## Long-poll with wait Use long-poll when you want one HTTP request to return the finished job. `GET /jobs/{id}/wait` holds the connection until the job reaches a terminal state or the requested window closes; the SDK examples reuse the Quick Start's `finish()` loop. Set `JOB_ID` to the accepted job id. ```bash tab="curl" $ curl --fail-with-body -sS "https://api.nolgia.ai/v1/jobs/$JOB_ID/wait?timeout_seconds=120" \ -H "Authorization: Bearer $NOLGIA_TOKEN" ``` ```bash tab="CLI" $ nolgia wait "$JOB_ID" ``` ```ts tab="TypeScript" 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}`); } } console.log(await finish(process.env.JOB_ID!, 120)); ``` ```python tab="Python" import os from nolgia import AuthenticatedClient from nolgia.api.jobs import wait_for_job from nolgia.models import Job 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)}") print(finish(os.environ["JOB_ID"], 120)) ``` ```rust tab="Rust" use nolgia_client::{ApiError, ClientBuilder}; #[tokio::main] async fn main() -> Result<(), Box> { let client = ClientBuilder::new("https://api.nolgia.ai/v1") .bearer_token(std::env::var("NOLGIA_TOKEN")?) .build()?; let asset = finish(&client, &std::env::var("JOB_ID")?, 120).await?; println!("{}", asset.signed_url); 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> { loop { match client.wait_for_job().id(id.parse::()?).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()), } } } ``` A successful wait returns the same succeeded `Job` shown above, including `asset`; a failed terminal job is also returned as a `Job`, with `failure`. The response fields are the `Job` and `JobFailure` tables above. ### Parameters | Parameter | In | Required | Description | | --- | --- | --- | --- | | `id` | path | Yes | Job UUID. | | `timeout_seconds` | query | No | | The server accepts a wait window up to 900 seconds. > [!NOTE] > `408` on `wait` is not a generation failure. The window closed while the job was still running; wait again with the same id. ## Stream status updates Use SSE for status changes without client polling. Mint a ticket using your bearer token, then open the stream with only that ticket. The ticket is single-use and expires after 60 seconds; reconnect by minting another ticket. These TypeScript and Python stream examples print the frames and have not been proven against production. ```bash tab="curl" $ curl -sS -X POST "https://api.nolgia.ai/v1/jobs/$JOB_ID/sse-ticket" -H "Authorization: Bearer $NOLGIA_TOKEN" $ curl -N "https://api.nolgia.ai/v1/jobs/$JOB_ID/sse?ticket=" ``` ```ts tab="TypeScript" title="example" import { createNolgiaClient } from "@nolgia/sdk"; const nolgia = createNolgiaClient(process.env.NOLGIA_TOKEN!); const id = process.env.JOB_ID!; const { data, error } = await nolgia.POST("/jobs/{id}/sse-ticket", { params: { path: { id } }, }); if (error) throw new Error(`${error.title}: ${error.detail ?? ""}`); const response = await fetch(`https://api.nolgia.ai/v1/jobs/${id}/sse?ticket=${encodeURIComponent(data.ticket)}`); if (!response.ok || !response.body) throw new Error(`stream HTTP ${response.status}`); const reader = response.body.pipeThrough(new TextDecoderStream()).getReader(); for (;;) { const { done, value } = await reader.read(); if (done) break; process.stdout.write(value); } ``` ```python tab="Python" title="example" import os import httpx from nolgia import AuthenticatedClient from nolgia.api.jobs import create_job_sse_ticket from nolgia.models import SSETicket client = AuthenticatedClient(base_url="https://api.nolgia.ai/v1", token=os.environ["NOLGIA_TOKEN"]) job_id = os.environ["JOB_ID"] ticket = create_job_sse_ticket.sync(job_id, client=client) if not isinstance(ticket, SSETicket): raise SystemExit(f"refused: {ticket}") with httpx.stream("GET", f"https://api.nolgia.ai/v1/jobs/{job_id}/sse", params={"ticket": ticket.ticket}, timeout=None) as response: response.raise_for_status() for line in response.iter_lines(): print(line) ``` The ticket response is `201 Created`: ```json { "ticket": "…", "expires_at": "2026-09-21T03:34:15.423881465Z" } ``` | Field | Type | Required | Description | | --- | --- | --- | --- | | `ticket` | string | Yes | Opaque single-use secret to pass as the `ticket` query parameter. | | `expires_at` | string | Yes | Instant after which the ticket can no longer be redeemed. | The stream answers `200` with `Content-Type: text/event-stream`. These are the three captured production frames, with the signed URL shortened: ```text event: status data: {"status":"queued"} event: status data: {"status":"running","progress":0} event: complete data: {"status":"succeeded","progress":0,"asset_url":"https://storage.googleapis.com/nolgia-generations-prod/dad27b53-a85b-4…"} ``` | Event | Fields | Meaning | | --- | --- | --- | | `status` | `status`, optional `progress` | Current state immediately, then updates while `queued` or `running`; `progress` is a number from 0 to 1 when known. | | `complete` on success | `status: succeeded`, optional `progress`, `asset_url` when available | Terminal success; the stream closes. `asset_url` may be a `gs://` URI or an expired signed URL. Fetch `GET /jobs/{id}` or `GET /assets/{id}` with bearer authentication for a fresh asset `signed_url` before downloading. | | `complete` on failure | `status: failed`, optional `error`, `failure_kind`, `failure_code`, `credits_refunded` | Terminal failure; read the typed failure and recorded refund result. | | `complete` on cancellation | `status: canceled`, optional `error` (the plain statement of what the cancel did), `credits_refunded` once the hold settles | The job was canceled with `POST /jobs/{id}/cancel`; read `GET /jobs/{id}` for the full `cancellation`. | ### Parameters | Parameter | In | Required | Description | | --- | --- | --- | --- | | `id` | path | Yes | Job UUID. | | Parameter | In | Required | Description | | --- | --- | --- | --- | | `id` | path | Yes | Job UUID. | | `ticket` | query | Yes | Single-use ticket from `POST /jobs/{id}/sse-ticket`. | Read [Streaming](./streaming.html) for the full stream examples and agent session events. ## Get the result A succeeded job embeds its `asset`, shown in the succeeded response above. Download `asset.signed_url`; metadata describes the returned media, and `enhanced_prompt` records the composed prompt when available. | Field | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | Yes | | | `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.… | | `thumbnail_url` | string, nullable | No | Time-limited signed URL for a server-generated thumbnail (image downscale or video poster frame).… | > [!WARNING] > A `signed_url` is time-limited: download it promptly and store the asset id, never the URL. The captured asset has `expires_at: 2026-09-21T05:00:00Z`; read the asset again for a fresh URL. See [Uploads and file access](./uploads.html). ## Cancel a request `POST /jobs/{id}/cancel` cancels a queued or running job. The job moves to `canceled` at once and is never delivered: no asset is added to your library, even if the model provider finishes the render afterwards. The response is the job, and its `cancellation` object says plainly what happened: | Where the job was | What Nolgia does | Credits | | --- | --- | --- | | Not yet at the provider (`stage: before_submit`): queued, or waiting for a provider slot | Nothing is sent | Refunded in full now (`settlement: refunded`) | | At the provider, and the provider stops it before it starts (`provider_cancel: cancelled`) | Asks the provider to stop it | Refunded in full now | | At the provider, and the provider stops it part way and bills only what it rendered (`stopped_partway`) | Asks the provider to stop it | The rendered share is charged, the rest refunded (`partially_refunded`) | | At the provider, and it cannot be stopped (`requested`, `refused`, `unsupported`, `failed`) | Asks the provider to stop it where it can, then waits for its final answer | `settlement: pending` until then: refunded if the provider stops or fails the render without billing, charged if it finishes and bills | `cancellation.message` (also served as `status_message`) is the customer-facing sentence; show it as written. It never claims a refund before the ledger has made it, and a pending settlement is rewritten when the provider answers. `credits_refunded` and `credits_charged` carry the amounts once settled. Canceling twice returns the same record (`200`). A job that already finished, or whose finished result is already being delivered, answers `409` with `code: job_not_cancellable`. Another account's job is a `404`. In an organization, members may cancel their own jobs and owners and admins any job; viewers and billing contacts get `403`. An agent credential may cancel what its owner may. | Parameter | In | Required | Description | | --- | --- | --- | --- | | `id` | path | Yes | Job UUID. | To stop an agent turn instead, use `POST /agent/sessions/{id}/interrupt`; see [Agent Sessions API](./agent-api.html). ## List your jobs Use `GET /jobs` to recover accepted work or group generations by their agent session. The response is a `JobPage` with `items`, `total`, and an optional `next_cursor`. | Parameter | In | Required | Description | | --- | --- | --- | --- | | `limit` | query | No | Maximum items to return. | | `cursor` | query | No | Opaque pagination cursor returned by a prior response. | | `status` | query | No | | | `modality` | query | No | | | `agent_session_id` | query | No | Return only jobs whose agent turn ran in the given chat session (`jobs.agent_session_id`, stamped at submit). Scoped to the caller's own jobs. Jobs created before this attribution shipped, and generations launched outside an agent turn, are not matched. | ```bash tab="curl" $ curl --fail-with-body -sS "https://api.nolgia.ai/v1/jobs?status=running&modality=video" \ -H "Authorization: Bearer $NOLGIA_TOKEN" ``` | Field | Type | Required | Description | | --- | --- | --- | --- | | `items` | array of `Job` | Yes | | | `next_cursor` | string, nullable | No | | | `total` | integer | Yes | Total number of jobs matching the status/modality filter, ignoring pagination. | ## Duplicate submissions and Idempotency-Key The same body with the same `Idempotency-Key`, or with no key both times, inside five minutes returns `409 Conflict` and names the earlier `job_id`. A different key deliberately starts another generation and returns `202`. A `409` is never billed. ```bash tab="curl" $ curl --fail-with-body -sS https://api.nolgia.ai/v1/generate/image \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: image-take-2" \ -d '{"model":"flux-pro","prompt":"a paper-cut mountain range at dawn"}' ``` ```bash tab="CLI" $ nolgia --idempotency-key image-take-2 gen image --model flux-pro --prompt "a paper-cut mountain range at dawn" --no-wait ``` ```ts tab="TypeScript" import { createNolgiaClient } from "@nolgia/sdk"; const nolgia = createNolgiaClient(process.env.NOLGIA_TOKEN!, undefined, { headers: { "Idempotency-Key": "image-take-2" } }); 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 ?? ""}`); console.log(image.id); ``` ```python tab="Python" import os from nolgia import AuthenticatedClient from nolgia.api.generate import generate_image from nolgia.models import GenerateImageRequest, ImageModel, Job client = AuthenticatedClient(base_url="https://api.nolgia.ai/v1", token=os.environ["NOLGIA_TOKEN"], headers={"Idempotency-Key": "image-take-2"}) 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}") print(job.id) ``` ```rust tab="Rust" use nolgia_client::{types, ClientBuilder}; #[tokio::main] async fn main() -> Result<(), Box> { let client = ClientBuilder::new("https://api.nolgia.ai/v1") .bearer_token(std::env::var("NOLGIA_TOKEN")?) .idempotency_key("image-take-2") .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(); println!("{}", job.id); Ok(()) } ``` The duplicate response from production: ```json { "detail": "this exact request was already submitted as job 35b6ff8d-0c78-440b-bdb3-bfe476b56d76 less than 5m0s ago and has not been billed twice — check it with GET /jobs/35b6ff8d-0c78-440b-bdb3-bfe476b56d76. To run it again anyway, resubmit with a different Idempotency-Key header.", "job_id": "35b6ff8d-0c78-440b-bdb3-bfe476b56d76", "request_id": "localhost/7bnUUXx7b2-007291", "status": 409, "title": "Conflict", "type": "about:blank" } ``` | Field | Type | Required | Description | | --- | --- | --- | --- | | `code` | string | No | Machine-readable error code.… | | `type` | string | Yes | A URI reference identifying the problem type. | | `title` | string | Yes | | | `status` | integer | Yes | | | `detail` | string | No | | | `instance` | string | No | | | `job_id` | string | No | The job this refusal points at, so a client can follow it without reading `detail`.… | | `request_id` | string | No | | The supplied comparison capture records an accepted new submission; only the job identity and state fields are shown: ```json { "created_at": "2026-09-21T03:33:52.474923Z", "id": "ecf04b6a-d5a1-4ef6-9afd-22b3c10f8e9e", "modality": "image", "model": "flux-pro", "status": "queued" } ``` | Field | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | Yes | | | `modality` | `Modality` | Yes | | | `model` | string | Yes | | | `status` | `JobStatus` | Yes | | | `created_at` | string | Yes | | > [!NOTE] > Follow the `job_id` on a `409`; do not automatically change the key. A new key means you intend to pay for another generation. The CLI also reads `NOLGIA_IDEMPOTENCY_KEY`. ## Provider callbacks Jobs without a provider callback use a 5-second polling cadence. Callback-enabled video jobs use a 20-second backstop: a verified provider callback wakes an immediate status read, and the poller reconciles if a callback is lost. Kling is the first callback provider. Measured on production, working callbacks reduced reads per render from about 15 to about 3; a broken callback host still settled within about 26 seconds. These measurements are observations, not response-time guarantees. ![The provider callback wakes one status read; the poller remains a backstop](../assets/diagrams/callback-wakeup.svg) Read [Callbacks and webhooks](./callbacks.html) for the boundary between provider callbacks and customer notifications. ## Sets `POST /generate/set` answers 202 with an `OutputSet`, not a job. Each member runs as its own image job with its own credit hold. Poll `GET /sets/{id}` to follow the set. | Status | Meaning | | --- | --- | | `generating` | Member jobs are still running. | | `ready` | All Outputs are ready. | | `partial` | Some member jobs failed. | | `failed` | All member jobs failed. | If a later member is refused, accepted members keep running and further members are not submitted. Read `problems` for the refused labels. An `Idempotency-Key` on the set endpoint is ignored per member. ## Error responses A refused request has an HTTP problem body; an accepted job that later fails has `failure.code` on the job. Do not mistake the HTTP `200` that reads a failed job for a successful generation. | Where | HTTP status | Code | Action | | --- | --- | --- | --- | | Invalid generation request | `400` | `validation` | Correct the field, model or unsupported capability. | | Empty wallet or exceeded credit budget | `402` | `out_of_credits` | Top up or reduce the request's cost. | | Duplicate body and key | `409` | No code; `job_id` identifies the existing job | Follow that job. | | Rejected quote confirmation | `422` | `confirmation_rejected` | Get a fresh quote for the exact request. | | Submit-time content-policy refusal | `422` | `prompt_nsfw` or `ip_detected` | Change the prompt or references. These codes can also occur later on a failed job. | | Submit-time upstream refusal or failure | `422`, `502` or `500` | `job_failed` | Read the problem and retry safely when appropriate. | | Concurrency or account quota | `429` | `rate_limit` | Wait before resubmitting; use `Retry-After-Reset` on quota responses. | | Wait window elapsed | `408` | `timeout` | Wait again; this does not fail the job. | | Job reaches its deadline or fails during execution | Job read, not a submit refusal | `timeout`, `job_failed`, `prompt_nsfw`, `ip_detected` in `failure.code` | Inspect `failure.message` and `failure.credits_refunded`. | | Value | Meaning | | --- | --- | | `out_of_credits` | the wallet cannot pay for the job. Nothing was submitted and nothing was charged. Top up, or submit a cheaper model or fewer seconds. | | `rate_limit` | refused for now, not refused outright - the caller's own concurrency ceiling, or the provider's. Retry later; the same request will be accepted. | | `prompt_nsfw` | a content filter refused the request or the result it produced, on safety grounds. Editing the prompt or the reference media is the fix. Whether the credits were refunded is `failure.credits_refunded`, not this code. | | `ip_detected` | a content filter refused it for a real person's likeness or a protected work, rather than for safety. The fix is different from `prompt_nsfw` - change the reference image or the named subject, not the tone of the prompt - which is why it is its own code. | | `job_failed` | the job ran and did not produce an asset for any other reason, including a provider error. The default for an unclassified failure. | | `timeout` | the job ran past its time budget, or a `GET /jobs/{id}/wait` returned before the job reached a terminal status. On a failed job the work is over; on a `wait` the job may still be running. | | `validation` | the request itself is not acceptable - a missing or malformed field, a model that does not support the capability asked of it, a duration the model will not render. Nothing was submitted and nothing was charged. Retrying unchanged will fail identically. | | `confirmation_rejected` | the cost confirmation gate did not pass. Either a client showed the customer a quote and the customer declined, or a supplied confirmation token was refused. A submit that carries no confirmation token is never refused for this reason. | | `canceled` | the job was canceled by its owner (`POST /jobs/{id}/cancel`), not broken. It is the code the SDK wait helpers raise for a `canceled` job; `cancellation` on the job says what the provider did and what was refunded. A canceled job carries no `failure`. | | `job_not_cancellable` | `POST /jobs/{id}/cancel` refused (`409`) because the job already finished, or its finished result is already being delivered. The job will reach `succeeded` or `failed` on its own; nothing was changed. | | `approval_required` | refused with `402` (title `Approval Needed`) because the generation would take an agent run past the price its customer approved (the `credit_ceiling` a guided preset's review card showed, sent with the brief). Nothing was submitted and nothing was charged. The detail names the generation's cost and the new run total; the agent must ask the customer to approve that total before it continues, never retry, split the job or switch models to fit. | | `run_ended` | refused with `409` (title `Run Ended`) because the agent run this generation was started for (the turn its turn-scoped credential names) has already ended: the platform failed, swept or stopped the turn, or it finished and delivered its reply. Nothing was submitted and nothing was charged. The agent must stop working on that run, never retry or switch models; the customer's chat already shows how it ended. | > [!WARNING] > A `401` means the token is bad or expired; do not retry it unchanged. A `402` with `out_of_credits` means the wallet cannot pay, not that your token is wrong. ## Next steps :::cards - [Streaming](./streaming.html): Follow status changes without polling. - [Callbacks and webhooks](./callbacks.html): Understand callback wake-ups and notifications. - [Reliability](./reliability.html): Read the retry, outage and deadline behavior. - [Errors](./errors.html): Handle exact failure codes. ::: --- # Streaming Source: https://docs.nolgia.ai/guides/streaming.md There is no progressive media output; models return finished files. Server-sent events stream job status and agent session events so your application can react while work is in progress. ## Job status over SSE ### How it works Call `POST /jobs/{id}/sse-ticket` with your bearer token, then open `GET /jobs/{id}/sse?ticket=…`. A ticket expires after 60 seconds and authorizes exactly one stream connection; the stream uses only the ticket, with no bearer header. It sends the current state immediately, follows changes, then emits `complete` and closes for a terminal job. No client polling is needed. ![The client mints a ticket, opens the stream, and receives status and completion events](../assets/diagrams/job-sse.svg) Set `JOB_ID` to an accepted job id. These TypeScript, Python and Rust blocks are examples, not live-proven programs. They print the wire frames without assuming that a network chunk is a complete event. The Rust example additionally requires `futures-util` and the `stream` feature on `reqwest`. ```bash tab="curl" $ curl -sS -X POST "https://api.nolgia.ai/v1/jobs/$JOB_ID/sse-ticket" -H "Authorization: Bearer $NOLGIA_TOKEN" $ curl -N "https://api.nolgia.ai/v1/jobs/$JOB_ID/sse?ticket=" ``` ```bash tab="CLI" # A job SSE command is not available in the CLI; use long-poll instead. $ nolgia wait "$JOB_ID" ``` ```ts tab="TypeScript" title="example" import { createNolgiaClient } from "@nolgia/sdk"; const nolgia = createNolgiaClient(process.env.NOLGIA_TOKEN!); const id = process.env.JOB_ID!; const { data, error } = await nolgia.POST("/jobs/{id}/sse-ticket", { params: { path: { id } }, }); if (error) throw new Error(`${error.title}: ${error.detail ?? ""}`); const response = await fetch(`https://api.nolgia.ai/v1/jobs/${id}/sse?ticket=${encodeURIComponent(data.ticket)}`); if (!response.ok || !response.body) throw new Error(`stream HTTP ${response.status}`); const reader = response.body.pipeThrough(new TextDecoderStream()).getReader(); for (;;) { const { done, value } = await reader.read(); if (done) break; process.stdout.write(value); } ``` ```python tab="Python" title="example" import os import httpx from nolgia import AuthenticatedClient from nolgia.api.jobs import create_job_sse_ticket from nolgia.models import SSETicket client = AuthenticatedClient(base_url="https://api.nolgia.ai/v1", token=os.environ["NOLGIA_TOKEN"]) job_id = os.environ["JOB_ID"] ticket = create_job_sse_ticket.sync(job_id, client=client) if not isinstance(ticket, SSETicket): raise SystemExit(f"refused: {ticket}") with httpx.stream("GET", f"https://api.nolgia.ai/v1/jobs/{job_id}/sse", params={"ticket": ticket.ticket}, timeout=None) as response: response.raise_for_status() for line in response.iter_lines(): print(line) ``` ```rust tab="Rust" title="example" use std::io::Write; use futures_util::StreamExt; use nolgia_client::ClientBuilder; #[tokio::main] async fn main() -> Result<(), Box> { let client = ClientBuilder::new("https://api.nolgia.ai/v1") .bearer_token(std::env::var("NOLGIA_TOKEN")?) .build()?; let id = std::env::var("JOB_ID")?.parse::()?; let ticket = client.create_job_sse_ticket().id(id).send().await?.into_inner(); let response = reqwest::Client::new() .get(format!("https://api.nolgia.ai/v1/jobs/{id}/sse")) .query(&[("ticket", ticket.ticket)]) .send().await?.error_for_status()?; let mut stream = response.bytes_stream(); while let Some(chunk) = stream.next().await { std::io::stdout().write_all(&chunk?)?; std::io::stdout().flush()?; } Ok(()) } ``` The ticket response is `201 Created`; this is the captured response with the single-use secret redacted: ```json { "ticket": "…", "expires_at": "2026-09-21T03:34:15.423881465Z" } ``` | Field | Type | Required | Description | | --- | --- | --- | --- | | `ticket` | string | Yes | Opaque single-use secret to pass as the `ticket` query parameter. | | `expires_at` | string | Yes | Instant after which the ticket can no longer be redeemed. | The following three frames are from the production stream. The stream's signed `asset_url` is shortened for display. ```text event: status data: {"status":"queued"} event: status data: {"status":"running","progress":0} event: complete data: {"status":"succeeded","progress":0,"asset_url":"https://storage.googleapis.com/nolgia-generations-prod/dad27b53-a85b-4…"} ``` ### Parameters | Parameter | In | Required | Description | | --- | --- | --- | --- | | `id` | path | Yes | Job UUID. | | Parameter | In | Required | Description | | --- | --- | --- | --- | | `id` | path | Yes | Job UUID. | | `ticket` | query | Yes | Single-use ticket from `POST /jobs/{id}/sse-ticket`. | > [!NOTE] > A ticket cannot be replayed. If the connection drops, mint a new ticket and reconnect; the new stream gives you the current state. Job status SSE has no `Last-Event-ID` replay contract. ## Event reference | Event | Fields | Meaning | | --- | --- | --- | | `status` | `status`, optional `progress` | Current state immediately, then updates while `queued` or `running`; `progress` is a number from 0 to 1 when known. | | `complete` on success | `status: succeeded`, optional `progress`, `asset_url` when available | Terminal success; the stream closes. `asset_url` may be a `gs://` URI or an expired signed URL. Fetch `GET /jobs/{id}` or `GET /assets/{id}` with bearer authentication for a fresh asset `signed_url` before downloading. | | `complete` on failure | `status: failed`, optional `error`, `failure_kind`, `failure_code`, `credits_refunded` | Terminal failure; read the typed failure and recorded refund result. | | `complete` on cancellation | `status: canceled`, optional `error` (the plain statement of what the cancel did), `credits_refunded` once the hold settles | The job was canceled with `POST /jobs/{id}/cancel`; read `GET /jobs/{id}` for the full `cancellation`. | Fields are omitted when there is no value to report. Heartbeats are SSE comment lines, not state changes. The failure frame below is a schema-shaped example of a terminal state, not a production capture: ```text event: complete data: {"status":"failed","error":"generation failed","failure_kind":"error","failure_code":"job_failed","credits_refunded":true} ``` Read `GET /jobs/{id}` for the full terminal `Job` and embedded asset. Store its id and download the file; do not retain signed URLs as permanent asset identifiers. ## Agent session events `GET /agent/sessions/{id}/events` is the only live transport for agent turns. It begins with a `retry:` reconnection hint, puts a monotonic `id:` on each `message` frame, and replays persisted messages from the database when you reconnect with `Last-Event-ID`. It uses bearer authentication, unlike the ticket-based job stream. ```bash tab="curl" $ curl --fail-with-body -N "https://api.nolgia.ai/v1/agent/sessions/$SESSION_ID/events" \ -H "Authorization: Bearer $NOLGIA_TOKEN" # On reconnect, LAST_EVENT_ID is the last id received from a message frame. $ curl --fail-with-body -N "https://api.nolgia.ai/v1/agent/sessions/$SESSION_ID/events" \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H "Last-Event-ID: $LAST_EVENT_ID" ``` | Frame detail | Purpose | | --- | --- | | `retry:` | Server hint for reconnection delay. | | `id:` on `message` | Cursor to retain for reconnect. | | `Last-Event-ID` request header | Resume persisted message replay from the database. | See [Agent Sessions API](./agent-api.html) for session creation, turns and event payloads. ## When to use streaming Use SSE for a live status display or agent conversation. Use [long-poll](./jobs.html#long-poll-with-wait) for a worker that simply needs the finished job: it receives a normal JSON response and can repeat a timed-out wait. Neither transport changes the generation, its deadline or its credit hold. ## Error responses | Operation | Status | Meaning | | --- | --- | --- | | Mint ticket | `401` | Missing or invalid bearer token. | | Mint ticket | `404` | Job not found for this caller. | | Open stream | `401` | Missing, expired or already-used ticket. | | Open stream | `403` | The ticket is bound to a different job and has been consumed. Mint a fresh ticket for the correct job. | | Either job SSE endpoint | `503` | Job streaming is not configured. | These authentication and stream-availability problems do not carry a `GenerationErrorCode`. A generation failure is delivered as a terminal `complete` event with `failure_code`, rather than changing the already-open stream's HTTP status. ## Next steps :::cards - [Asynchronous: submit and poll](./jobs.html): Submit a request and retrieve its asset. - [Agent Sessions API](./agent-api.html): Follow a persistent agent conversation. ::: --- # Callbacks and webhooks Source: https://docs.nolgia.ai/guides/callbacks.md There is no customer webhook today. What shipped is a signed provider callback that wakes the poller to read a video job's current state; it does not send a notification to your application. ## What the provider callback does Nolgia creates a per-job signed callback URL at video submission and gives it to the provider. `POST /callbacks/{provider}` verifies the provider signature and the Nolgia job token before waking an immediate read from the media proxy. The callback body is never the result: the normal completion and credit-settlement path handles the fresh read. Kling is the first provider with callbacks. ![A signed provider callback wakes the poller; the app observes completion through wait](../assets/diagrams/callback-wakeup.svg) | Method | Path | What it does | | --- | --- | --- | | [POST](../api/#tag/jobs/post/callbacks/{provider}) | `/callbacks/{provider}` | Receive a render provider's completion callback (a wake-up signal). | This is a provider endpoint, not a route your application needs to call. Duplicate callbacks and callbacks for settled jobs are no-ops; the poller continues to reconcile if callbacks stop arriving. ## What it changes for you | Behavior | Before | With provider callbacks | Evidence | | --- | --- | --- | --- | | Status reads per render | About 15 | About 3 with a working callback | Measured on production. | | Callback-enabled video reconciliation | 5-second polling cadence | Immediate read on callback; 20-second polling backstop | Configuration verified in the poller and measured on production. | | Broken callback host | Completion depends on polling | Still settles within about 26 seconds in the exercised case | Measured on production; not a latency guarantee. | Jobs without callbacks keep their 5-second polling cadence. A lower provider-read count does not change your request, job id, result format or cost. ## How to be notified today ### Long-poll Use this in a worker waiting for the result. `GET /jobs/{id}/wait` returns a terminal `Job`, or `408` when its wait window closes; repeat the wait on the same id. The SDK loops below are copied from the proven Quick Start programs. ```bash tab="curl" $ curl --fail-with-body -sS "https://api.nolgia.ai/v1/jobs/$JOB_ID/wait?timeout_seconds=120" \ -H "Authorization: Bearer $NOLGIA_TOKEN" ``` ```bash tab="CLI" $ nolgia wait "$JOB_ID" ``` ```ts tab="TypeScript" 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}`); } } console.log(await finish(process.env.JOB_ID!, 120)); ``` ```python tab="Python" import os from nolgia import AuthenticatedClient from nolgia.api.jobs import wait_for_job from nolgia.models import Job 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)}") print(finish(os.environ["JOB_ID"], 120)) ``` ```rust tab="Rust" use nolgia_client::{ApiError, ClientBuilder}; #[tokio::main] async fn main() -> Result<(), Box> { let client = ClientBuilder::new("https://api.nolgia.ai/v1") .bearer_token(std::env::var("NOLGIA_TOKEN")?) .build()?; let asset = finish(&client, &std::env::var("JOB_ID")?, 120).await?; println!("{}", asset.signed_url); 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> { loop { match client.wait_for_job().id(id.parse::()?).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 terminal response is the same `Job` returned by a normal status read. Here is the captured successful response: ```json { "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-4…", "size_bytes": 418584, "status": "ready", "tags": [ ], "thumbnail_url": "https://storage.googleapis.com/nolgia-generations-prod/dad27b53-a85b-4…", "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" } ``` | Field | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | Yes | | | `status` | `JobStatus` | Yes | | | `asset` | `Asset` | No | | | `failure` | `JobFailure` | No | | | `progress` | number, nullable | No | | | `created_at` | string | Yes | | | `updated_at` | string | Yes | | | `completed_at` | string, nullable | No | | For `queued`, `running` and failed examples, see [Check status](./jobs.html#check-status). #### Parameters | Parameter | In | Required | Description | | --- | --- | --- | --- | | `id` | path | Yes | Job UUID. | | `timeout_seconds` | query | No | | ### Server-sent events Use this for a live status display without client polling. Mint a single-use ticket with your bearer token, then open the ticket-authenticated stream; terminal work produces a `complete` event. The SDK stream readers below are examples and have not been proven against production. Rust requires `futures-util` and `reqwest`'s `stream` feature. ```bash tab="curl" $ curl -sS -X POST "https://api.nolgia.ai/v1/jobs/$JOB_ID/sse-ticket" -H "Authorization: Bearer $NOLGIA_TOKEN" $ curl -N "https://api.nolgia.ai/v1/jobs/$JOB_ID/sse?ticket=" ``` ```bash tab="CLI" # A job SSE command is not available in the CLI; use long-poll instead. $ nolgia wait "$JOB_ID" ``` ```ts tab="TypeScript" title="example" import { createNolgiaClient } from "@nolgia/sdk"; const nolgia = createNolgiaClient(process.env.NOLGIA_TOKEN!); const id = process.env.JOB_ID!; const { data, error } = await nolgia.POST("/jobs/{id}/sse-ticket", { params: { path: { id } }, }); if (error) throw new Error(`${error.title}: ${error.detail ?? ""}`); const response = await fetch(`https://api.nolgia.ai/v1/jobs/${id}/sse?ticket=${encodeURIComponent(data.ticket)}`); if (!response.ok || !response.body) throw new Error(`stream HTTP ${response.status}`); const reader = response.body.pipeThrough(new TextDecoderStream()).getReader(); for (;;) { const { done, value } = await reader.read(); if (done) break; process.stdout.write(value); } ``` ```python tab="Python" title="example" import os import httpx from nolgia import AuthenticatedClient from nolgia.api.jobs import create_job_sse_ticket from nolgia.models import SSETicket client = AuthenticatedClient(base_url="https://api.nolgia.ai/v1", token=os.environ["NOLGIA_TOKEN"]) job_id = os.environ["JOB_ID"] ticket = create_job_sse_ticket.sync(job_id, client=client) if not isinstance(ticket, SSETicket): raise SystemExit(f"refused: {ticket}") with httpx.stream("GET", f"https://api.nolgia.ai/v1/jobs/{job_id}/sse", params={"ticket": ticket.ticket}, timeout=None) as response: response.raise_for_status() for line in response.iter_lines(): print(line) ``` ```rust tab="Rust" title="example" use std::io::Write; use futures_util::StreamExt; use nolgia_client::ClientBuilder; #[tokio::main] async fn main() -> Result<(), Box> { let client = ClientBuilder::new("https://api.nolgia.ai/v1") .bearer_token(std::env::var("NOLGIA_TOKEN")?) .build()?; let id = std::env::var("JOB_ID")?.parse::()?; let ticket = client.create_job_sse_ticket().id(id).send().await?.into_inner(); let response = reqwest::Client::new() .get(format!("https://api.nolgia.ai/v1/jobs/{id}/sse")) .query(&[("ticket", ticket.ticket)]) .send().await?.error_for_status()?; let mut stream = response.bytes_stream(); while let Some(chunk) = stream.next().await { std::io::stdout().write_all(&chunk?)?; std::io::stdout().flush()?; } Ok(()) } ``` The ticket response: ```json { "ticket": "…", "expires_at": "2026-09-21T03:34:15.423881465Z" } ``` | Field | Type | Required | Description | | --- | --- | --- | --- | | `ticket` | string | Yes | Opaque single-use secret to pass as the `ticket` query parameter. | | `expires_at` | string | Yes | Instant after which the ticket can no longer be redeemed. | The recorded stream: ```text event: status data: {"status":"queued"} event: status data: {"status":"running","progress":0} event: complete data: {"status":"succeeded","progress":0,"asset_url":"https://storage.googleapis.com/nolgia-generations-prod/dad27b53-a85b-4…"} ``` | Event | Fields | Meaning | | --- | --- | --- | | `status` | `status`, optional `progress` | Current state immediately, then updates while `queued` or `running`; `progress` is a number from 0 to 1 when known. | | `complete` on success | `status: succeeded`, optional `progress`, `asset_url` when available | Terminal success; the stream closes. `asset_url` may be a `gs://` URI or an expired signed URL. Fetch `GET /jobs/{id}` or `GET /assets/{id}` with bearer authentication for a fresh asset `signed_url` before downloading. | | `complete` on failure | `status: failed`, optional `error`, `failure_kind`, `failure_code`, `credits_refunded` | Terminal failure; read the typed failure and recorded refund result. | | `complete` on cancellation | `status: canceled`, optional `error` (the plain statement of what the cancel did), `credits_refunded` once the hold settles | The job was canceled with `POST /jobs/{id}/cancel`; read `GET /jobs/{id}` for the full `cancellation`. | #### Parameters | Parameter | In | Required | Description | | --- | --- | --- | --- | | `id` | path | Yes | Job UUID. | | Parameter | In | Required | Description | | --- | --- | --- | --- | | `id` | path | Yes | Job UUID. | | `ticket` | query | Yes | Single-use ticket from `POST /jobs/{id}/sse-ticket`. | > [!TIP] > Make terminal-state handling idempotent using the job id: reconnects or repeated reads can show the same completed job. Fetch the job or asset from the authenticated API before using its result URL; never trust a URL from a notification you did not verify through the API. ### Error responses | Response | Meaning | What to do | | --- | --- | --- | | `408 timeout` on wait | Job did not finish in this wait window. | Wait again. | | `401` on ticket creation | Invalid bearer token. | Replace the token; do not retry unchanged. | | `404` on ticket creation | Job was not found for the caller. | Check the job id and account context. | | `401` on the stream | Ticket is absent, expired or consumed. | Mint a new ticket. | | `403` on the stream | Ticket is for a different job. | Mint a fresh ticket for the correct job; the mismatched request consumed the previous ticket. | | `503` on either SSE endpoint | Job streaming is unavailable. | Use long-poll. | | Terminal failed job or `complete` frame | Generation ended with `job_failed`, `timeout`, `prompt_nsfw` or `ip_detected`. | Inspect the typed failure and recorded refund outcome. | ## What a customer webhook would need Customer webhooks are not offered; [contact us](https://nolgia.ai/contact) to ask for them. ## Next steps :::cards - [Asynchronous: submit and poll](./jobs.html): Follow the complete job lifecycle. - [Streaming](./streaming.html): Read the event contract and reconnect behavior. - [Reliability](./reliability.html): Understand retries, parking and deadlines. ::: --- # Reliability Source: https://docs.nolgia.ai/guides/reliability.md Once a generation has a job id, the server owns its progress and credit settlement. Your client can disconnect, reconnect and read that same job; the way you wait does not change how the model runs. ## The poller The poller reads jobs in batches of up to 20 and processes up to 10 at a time per poller. Jobs without a provider callback use a 5-second polling cadence. For callback-enabled video jobs, a verified provider callback wakes an immediate status read and a 20-second polling backstop reconciles missed callbacks; the next eligible poll time is persisted with the job. ![A provider callback wakes an immediate read; the poller remains a backstop](../assets/diagrams/callback-wakeup.svg) | Observation | Result | Scope | | --- | --- | --- | | Working provider callback | About 3 status reads per render, previously about 15 | Measured on production. | | Broken callback host | Job still settled within about 26 seconds in the exercised case | Measured on production; not a latency guarantee. | | First provider with callbacks | Kling | Other jobs keep their existing polling path. | Your client does not need to duplicate this provider polling. Follow the Nolgia job with a status read, [long-poll](./jobs.html#long-poll-with-wait) or [SSE](./streaming.html). ## Automatic retries For queued image and audio execution, the server allows up to two transient re-attempts. The same credit hold stays attached across attempts; only the terminal outcome settles it. This applies to transient execution failures, not a guarantee that an invalid request or a content-policy refusal will be retried successfully. For video, the poller follows the provider's accepted request, checks the deadline and completes or fails the Nolgia job. A provider's own internal retries are not a Nolgia retry setting. If an initial video submission meets a provider capacity wall, the job can remain queued while the poller re-attempts that submission within its deadline. | Situation | Server behavior | Client behavior | | --- | --- | --- | | Transient image/audio execution failure | Up to two re-attempts under the same hold. | Keep following the same job. | | Accepted video still rendering | Continue status reconciliation until completion or deadline. | Keep following the same job. | | Provider capacity before video acceptance | Queue and retry the upstream submit within the model deadline. | Display `status_message`; do not create a replacement. | | Client request refused with `429 rate_limit` | No generation accepted by that request. | Wait, then resubmit with the same key. | | Duplicate body and key within five minutes | `409` points to the accepted job; no second bill. | Follow the returned `job_id`. | ## Provider outages A queued job can expose `status_detail` so you can distinguish an unavailable provider from a healthy provider with no capacity. Neither value is a terminal error; the existing credit hold stays held while the job waits. Show the accompanying `status_message` instead of diagnosing the provider yourself. | `status_detail` | Why the job waits | Bound and terminal outcome | | --- | --- | --- | | `provider_down` | The provider is unavailable; parked work resumes when it recovers. | The default outage parking bound is 6 hours from `created_at`; expiry fails the job and fully refunds the hold. | | `upstream_at_capacity` | The provider is healthy but all its simultaneous-generation slots are occupied. | Submission is re-attempted within the ordinary model poll deadline; expiry fails with a timeout and refund. | A capacity wait can become outage parking if the provider becomes unavailable. The six-hour parking bound applies to the outage state, not to every queued job. > [!NOTE] > You are never charged for an operational failure: provider errors end as `job_failed`, and an exhausted render deadline or outage parking deadline ends as `timeout`, with a full refund. A provider-billed content-policy refusal (`prompt_nsfw` or `ip_detected`) can be charged. The recorded `failure.credits_refunded` is authoritative: `true` means refunded, `false` means charged, and absent or null means the ledger outcome is not known. See [Pricing and credits](./billing.html). | Field | Type | Required | Description | | --- | --- | --- | --- | | `code` | `GenerationErrorCode` | No | Stable refinement of `kind`, present on every job that failed after this field shipped and derived from the recorded reason for older ones.… | | `kind` | string | Yes | What stopped the job.… | | `message` | string | Yes | Human-readable reason, safe to show the customer as-is. The same text as `error.detail`. | | `credits_refunded` | boolean, nullable | No | What the credit ledger actually did with this job's credit hold, recorded when the hold settled.… | ## Deadlines The default video deadline is 15 minutes from `created_at`. Certain models have a 25-minute override; video restore starts with a 25-minute base and scales with source duration. These are server render budgets, distinct from how long one client HTTP request stays open. | Work or request | Deadline or window | Outcome when it expires | | --- | --- | --- | | Default asynchronous video job | 15 minutes from `created_at` | Job fails with `timeout`; credits are refunded. | | `minimax-h3` | 25 minutes | Job fails with `timeout`; credits are refunded. | | `wan-3.0` | 25 minutes | Job fails with `timeout`; credits are refunded. | | `wan-3.0-prime` | 25 minutes | Job fails with `timeout`; credits are refunded. | | `seedance-2.5` | 25 minutes | Job fails with `timeout`; credits are refunded. | | Video restore | 25-minute base plus 4 minutes per whole source second, rounded up, capped at 6 hours; unknown source duration uses the base | Job fails with `timeout`; credits are refunded. | | Provider-outage parking | Default 6 hours from `created_at` | Explicit failure and a full refund. | | One `GET /jobs/{id}/wait` request | `timeout_seconds`, at most 900 seconds | HTTP `408 timeout`; the job can still be running. | | Parameter | In | Required | Description | | --- | --- | --- | --- | | `id` | path | Yes | Job UUID. | | `timeout_seconds` | query | No | | > [!TIP] > A `408` is a closed wait window, not a failed generation. Wait again on the same id; starting a new generation would create separate work. ## Model fallbacks There is no general model fallback; the narrow exception is a content-policy refusal on OpenRouter-routed Seedance rows, which is re-routed to an alternative route. ## Backup domains There is no backup domain; the production API host is `https://api.nolgia.ai/v1`. The spec also publishes staging as a separate environment, not as a failover destination: | Environment | Base URL | | --- | --- | | Production | `https://api.nolgia.ai/v1` | | Staging | `https://api.stg.nolgia.ai/v1` | | Local development | `http://localhost:8080/v1` | ## The ten-minute window after a deploy > [!WARNING] > For about ten minutes after an API deploy, a brand-new model id can answer `400 validation` with “unknown … model” from an instance on the previous revision while another instance already accepts it. This was observed during rollout. Verify the id against the catalog, wait about a minute and retry; do not “fix” a newly published id that is already correct. Ordinary validation errors still require correcting the request. ## Timeouts, side by side | Timeout | Where it runs | What stops | What continues | | --- | --- | --- | --- | | `timeout_seconds` on `/jobs/{id}/wait` | Server, for one HTTP request | The wait returns `408 timeout`. | The generation and credit hold. | | The helper's `maxPollTime` (TypeScript) or `max_poll_time` (Python) | Client; default 1,800,000 milliseconds / 1,800 seconds | Client polling stops with a timeout error. | Server generation; successful work still spends credits. | | Poller render deadline | Server | The generation job is failed and the hold refunded. | The terminal job remains readable. | | Provider-outage parking deadline | Server | Waiting for provider recovery ends with a failure and refund. | The terminal job remains readable. | The `subscribe`/`submit` helpers (TypeScript and Python 0.1.2; the Rust crate keeps its own) enforce only their client wait budget. Their timeout does not cancel a submitted job; see [Synchronous: subscribe](./subscribe.html). ## Error responses | Code | Where to read it | What to do | | --- | --- | --- | | `rate_limit` | HTTP `429` problem at submit | Wait for capacity or the quota reset, then retry. | | `timeout` | HTTP `408` problem from wait | Wait again; the generation has not necessarily failed. | | `timeout` | `failure.code` on a failed job | The render budget is exhausted; inspect the refund and decide whether to submit again. | | `job_failed` | `failure.code` on a failed job | Read `failure.message` and the recorded refund outcome. | | `prompt_nsfw`, `ip_detected` | `failure.code` on a failed job, or `422` on an immediate refusal | Change the content or references; refund behavior comes from the recorded settlement. | | `validation` | HTTP `400` problem at submit | Correct the request, except for the verified new-model rollout case above. | ## Next steps :::cards - [Concurrency limits](./concurrency-limits.html): Read your plan's live generation ceiling. - [Errors](./errors.html): Branch on precise problem and failure codes. - [Callbacks and webhooks](./callbacks.html): Understand callback wake-ups and how to observe completion. ::: --- # Concurrency limits Source: https://docs.nolgia.ai/guides/concurrency-limits.md Your plan sets how many generations can run at once. Read the live count before scheduling a batch, and treat a concurrency refusal as a signal to wait for a slot. ## How it works One shared limit counts active generations across image, audio and video, including queued and running jobs whose credit hold owns a slot. The API checks the limit when you submit; already queued jobs are not dropped when you reach it. A refused request has no new job to poll, so wait for an existing job to finish before submitting that request again. ## Limits by plan | Plan | Concurrent generations | | --- | --- | | Free | 1 | | Starter | 2 | | Pro | 4 | | Studio | 8 | | Team | 8 | | Enterprise | 8 | These limits apply across the three modalities together, rather than separately to each model. For example, an image and a video running at the same time use both of a Starter plan's slots. > [!NOTE] > A job can occupy a slot while queued, including when it is parked waiting for a provider. Do not count only jobs whose visible status is `running`; use the account's live `concurrent_active` value. ## Viewing your limit Call `GET /me` and read `generation_limits.concurrent_max` and `generation_limits.concurrent_active`. The examples print only that part of the account response. ```bash tab="curl" $ curl --fail-with-body -sS https://api.nolgia.ai/v1/me \ -H "Authorization: Bearer $NOLGIA_TOKEN" ``` ```bash tab="CLI" $ nolgia account me ``` ```ts tab="TypeScript" import { createNolgiaClient } from "@nolgia/sdk"; const nolgia = createNolgiaClient(process.env.NOLGIA_TOKEN!); const { data: me, error } = await nolgia.GET("/me"); if (error) throw new Error(`${error.title}: ${error.detail ?? ""}`); console.log({ generation_limits: me.generation_limits }); ``` ```python tab="Python" import os import json from nolgia import AuthenticatedClient from nolgia.api.auth import get_current_user from nolgia.models import GenerationLimits, User client = AuthenticatedClient(base_url="https://api.nolgia.ai/v1", token=os.environ["NOLGIA_TOKEN"]) me = get_current_user.sync(client=client) if not isinstance(me, User): raise SystemExit(f"refused: {me}") if not isinstance(me.generation_limits, GenerationLimits): raise SystemExit("generation limits were not returned") print(json.dumps({"generation_limits": me.generation_limits.to_dict()}, indent=2)) ``` ```rust tab="Rust" title="src/main.rs" use nolgia_client::ClientBuilder; #[tokio::main] async fn main() -> Result<(), Box> { let client = ClientBuilder::new("https://api.nolgia.ai/v1") .bearer_token(std::env::var("NOLGIA_TOKEN")?) .build()?; let me = client.get_current_user().send().await?.into_inner(); println!("{:#?}", me.generation_limits); Ok(()) } ``` This is the `generation_limits` portion of the production account response captured on 2026-09-21. ```json title="200 OK — generation_limits" { "generation_limits": { "concurrent_active": 0, "concurrent_max": 8 } } ``` | Field | Type | Required | Description | | --- | --- | --- | --- | | `concurrent_max` | integer | Yes | Maximum generations this account may run at once on its effective plan. | | `concurrent_active` | integer | Yes | Generations currently running across image, audio, and video. | ### Parameters `GET /me` has no path or query parameters. Send your bearer token; the response describes the authenticated account in its current organization context. ## When you hit it The generation submit returns `429 Too Many Requests` with problem code `rate_limit`. This example is built from the `Error` schema and the handler's exact detail text for two active generations on a two-slot plan; it is not a captured response. ```json title="429 — example from the Error schema" { "type": "about:blank", "title": "Generation Concurrency Limit Reached", "status": 429, "detail": "You have 2 generations running, which is the concurrent maximum of 2 for your plan. Wait for one to finish or upgrade your plan to run more at once.", "code": "rate_limit" } ``` | Field | Type | Required | Description | | --- | --- | --- | --- | | `code` | string | No | Machine-readable error code.… | | `type` | string | Yes | A URI reference identifying the problem type. | | `title` | string | Yes | | | `status` | integer | Yes | | | `detail` | string | No | | | `request_id` | string | No | | Wait for a running job to finish, then resubmit. `rate_limit` means refused for now, not refused outright: retry later and the same request will be accepted when capacity is available. Keep the same `Idempotency-Key` for retries so a previously accepted request is not turned into an intentional second generation. There is a separate per-account quota refusal with the same `429` status and `rate_limit` code. When that response includes `Retry-After-Reset`, its value is an RFC 3339 reset timestamp; wait until that time before retrying. A concurrency refusal does not promise that header or a fixed delay. > [!TIP] > Use [long-polling](./jobs.html#long-poll-with-wait) or [SSE](./streaming.html) to learn when an existing job finishes, then release the next request from your own work queue. Do not keep submitting in a tight loop while all slots are occupied. ### Error responses | Operation | HTTP status | Code | Action | | --- | --- | --- | --- | | Read your account | `401` | No generation code is required | Replace the invalid or expired token; do not retry it unchanged. | | Submit at the concurrency ceiling | `429` | `rate_limit` | Wait for a running job to finish, then resubmit. | | Submit above an account quota | `429` | `rate_limit` | Honor `Retry-After-Reset` when supplied. | ## Increasing your limit Choose a plan with more concurrent generations on [Pricing](https://nolgia.ai/pricing). In an organization, the organization's plan determines the effective limit; a member's personal subscription does not raise it. See [Organizations](./organizations.html) for organization context and shared credits. ## Next steps :::cards - [Reliability](./reliability.html): Understand retries, deadlines and provider outages. - [Pricing and credits](./billing.html): Read your balance and follow charges and refunds. ::: --- # Platform headers Source: https://docs.nolgia.ai/guides/headers.md Use bearer authentication for API calls, an idempotency key for generation retries, and a request id to connect a problem response to your logs. The two query parameters below control a wait or authorize a status stream; they are not headers. There are no priority, runner-hint, retry-config or payload-storage headers. The examples use the same client construction as the [Quick Start](./getting-started.html). Set `NOLGIA_TOKEN` on your server and `JOB_ID` to a job you own. TypeScript examples run on the server; see [Proxy setup](./proxy-setup.html) before calling from a browser. ## Authorization | Property | Value | | --- | --- | | Type | HTTP request header; string | | Default | None; authenticate protected routes | | Values | `Bearer` followed by a PAT beginning `nol_` or a JWT | | Applies to | Authenticated API routes; the job SSE route uses a ticket instead | | SDK parameter | TypeScript client token; Python `token`; Rust builder `bearer_token` | The token identifies the caller and its available permissions. Put it in the environment, never in a checked-in source file. ```bash tab="curl" $ curl -s https://api.nolgia.ai/v1/me \ -H "Authorization: Bearer $NOLGIA_TOKEN" ``` ```ts tab="TypeScript" import { createNolgiaClient } from "@nolgia/sdk"; const nolgia = createNolgiaClient(process.env.NOLGIA_TOKEN!); const { data, error } = await nolgia.GET("/me"); if (error) throw new Error(`${error.title}: ${error.detail ?? ""}`); console.log(data); ``` ```python tab="Python" import os from nolgia import AuthenticatedClient from nolgia.api.auth import get_current_user from nolgia.models import User client = AuthenticatedClient(base_url="https://api.nolgia.ai/v1", token=os.environ["NOLGIA_TOKEN"]) account = get_current_user.sync(client=client) if not isinstance(account, User): raise SystemExit(f"refused: {account}") print(account.to_dict()) ``` > [!WARNING] > A `401` means the credential is missing, bad or expired. Fix authentication before trying again. A `402` with `code: out_of_credits` means the wallet cannot pay for the generation; replacing a valid token will not replenish it. ## Content-Type | Property | Value | | --- | --- | | Type | HTTP request header; media type | | Default | Typed clients set it when serializing a JSON body | | Values | `application/json` for the generation and quote examples | | Applies to | API requests carrying JSON | | SDK parameter | TypeScript `body`; Python generated request model passed as `body` | Declare the representation you send. The typed clients serialize the request and set the JSON content type for you. ```bash tab="curl" $ curl -s 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"}' ``` ```ts tab="TypeScript" import { createNolgiaClient } from "@nolgia/sdk"; const nolgia = createNolgiaClient(process.env.NOLGIA_TOKEN!); 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 ?? ""}`); console.log(image.id); ``` ```python tab="Python" import os from nolgia import AuthenticatedClient from nolgia.api.generate import generate_image from nolgia.models import GenerateImageRequest, ImageModel, Job client = AuthenticatedClient(base_url="https://api.nolgia.ai/v1", token=os.environ["NOLGIA_TOKEN"]) 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}") print(job.id) ``` ## Idempotency-Key | Property | Value | | --- | --- | | Type | HTTP request header; string | | Default | Without a key, identical request bodies still share the duplicate window | | Values | A stable caller-chosen key for one intended generation | | Applies to | Generation submission; five-minute claim window | | SDK parameter | TypeScript client `options.headers`; Python client `headers`; Rust builder `idempotency_key` | The same body with the same key, or with no key both times, returns `409 Conflict` inside five minutes and names the earlier `job_id`. A different key means a deliberate new generation of the same body and can return `202`. The `409` is never billed. The CLI accepts `--idempotency-key` or `NOLGIA_IDEMPOTENCY_KEY`. ```bash tab="curl" $ curl -s https://api.nolgia.ai/v1/generate/image \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: mountain-take-1" \ -d '{"model":"flux-pro","prompt":"a paper-cut mountain range at dawn"}' ``` ```ts tab="TypeScript" import { createNolgiaClient } from "@nolgia/sdk"; const nolgia = createNolgiaClient(process.env.NOLGIA_TOKEN!, undefined, { headers: { "Idempotency-Key": "mountain-take-1" }, }); 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 ?? ""}`); console.log(image.id); ``` ```python tab="Python" import os from nolgia import AuthenticatedClient from nolgia.api.generate import generate_image from nolgia.models import GenerateImageRequest, ImageModel, Job client = AuthenticatedClient(base_url="https://api.nolgia.ai/v1", token=os.environ["NOLGIA_TOKEN"], headers={"Idempotency-Key": "mountain-take-1"}) 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}") print(job.id) ``` > [!TIP] > On a duplicate response, follow `job_id`. Changing the key to make a retry succeed creates another paid generation; only do that when you want another take. ## X-Nolgia-Surface | Property | Value | | --- | --- | | Type | Optional HTTP request header; string | | Default | Omitted; a server-authenticated platform agent can still receive `hermes` attribution | | Values | The known calling surfaces below; the header is not an enum allow-list | | Applies to | Calling-surface attribution on generation and related writes | | SDK parameter | TypeScript client `options.headers`; Python client `headers`; Rust builder `surface` | | Known value | Calling surface | | --- | --- | | `cli` | Nolgia CLI | | `claude-code` | Claude Code integration | | `codex` | Codex integration | | `hermes` | Legacy value naming the platform's own agent (NOLGIA Agent). Kept for compatibility; not a product name. | | `pipeline` | Pipeline runtime | The CLI sets this header. Values are trimmed, lowercased and capped at 64 characters when persisted with a generation. Use `pipeline` for your own pipeline. `hermes` names the platform's agent: sending that string does not turn a customer token into a server-minted agent credential. The header itself is client-supplied attribution, not proof of identity. ```bash tab="curl" $ curl -s https://api.nolgia.ai/v1/generate/image \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H "Content-Type: application/json" \ -H "X-Nolgia-Surface: pipeline" \ -d '{"model":"flux-pro","prompt":"a paper-cut mountain range at dawn"}' ``` ```ts tab="TypeScript" import { createNolgiaClient } from "@nolgia/sdk"; const nolgia = createNolgiaClient(process.env.NOLGIA_TOKEN!, undefined, { headers: { "X-Nolgia-Surface": "pipeline" }, }); 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 ?? ""}`); console.log(image.id); ``` ```python tab="Python" import os from nolgia import AuthenticatedClient from nolgia.api.generate import generate_image from nolgia.models import GenerateImageRequest, ImageModel, Job client = AuthenticatedClient(base_url="https://api.nolgia.ai/v1", token=os.environ["NOLGIA_TOKEN"], headers={"X-Nolgia-Surface": "pipeline"}) 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}") print(job.id) ``` ## X-Request-Id | Property | Value | | --- | --- | | Type | Optional HTTP request header; string | | Default | Request middleware assigns an id when absent | | Values | Your request correlation id | | Applies to | Request tracing and the `request_id` field in problem responses | | SDK parameter | TypeScript client `options.headers`; Python client `headers` | Save the request id with your own logs. The API accepts the header, includes it in its CORS allow-list, and echoes the middleware request id as `request_id` in problem bodies. Use `Idempotency-Key`, described above, when intentionally grouping retries of one generation. ```bash tab="curl" $ curl -s https://api.nolgia.ai/v1/me \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H "X-Request-Id: mountain-request-1" ``` ```ts tab="TypeScript" import { createNolgiaClient } from "@nolgia/sdk"; const nolgia = createNolgiaClient(process.env.NOLGIA_TOKEN!, undefined, { headers: { "X-Request-Id": "mountain-request-1" }, }); const { data, error } = await nolgia.GET("/me"); if (error) throw new Error(`${error.title}: ${error.detail ?? ""}`); console.log(data); ``` ```python tab="Python" import os from nolgia import AuthenticatedClient from nolgia.api.auth import get_current_user from nolgia.models import User client = AuthenticatedClient(base_url="https://api.nolgia.ai/v1", token=os.environ["NOLGIA_TOKEN"], headers={"X-Request-Id": "mountain-request-1"}) account = get_current_user.sync(client=client) if not isinstance(account, User): raise SystemExit(f"refused: {account}") print(account.to_dict()) ``` ## timeout_seconds | Property | Value | | --- | --- | | Type | Query parameter; integer seconds | | Default | OpenAPI declares 300 seconds; the current handler uses 30 seconds when omitted, so pass an explicit value | | Values | 1 through 900 | | Applies to | `GET /jobs/{id}/wait` | | SDK parameter | TypeScript `params.query.timeout_seconds`; Python `timeout_seconds`; Rust request builder `timeout_seconds` | The server holds this request until the job is terminal or the wait window closes. This parameter changes how long you wait for a response, not the job's generation deadline. ```bash tab="curl" $ curl -s "https://api.nolgia.ai/v1/jobs/$JOB_ID/wait?timeout_seconds=120" \ -H "Authorization: Bearer $NOLGIA_TOKEN" ``` ```ts tab="TypeScript" import { createNolgiaClient } from "@nolgia/sdk"; const nolgia = createNolgiaClient(process.env.NOLGIA_TOKEN!); const { data, error, response } = await nolgia.GET("/jobs/{id}/wait", { params: { path: { id: process.env.JOB_ID! }, query: { timeout_seconds: 120 } }, }); if (response.status === 408) console.log("Still running; wait again."); else if (error) throw new Error(`${error.title}: ${error.detail ?? ""}`); else console.log(data); ``` ```python tab="Python" import os from nolgia import AuthenticatedClient from nolgia.api.jobs import wait_for_job from nolgia.models import Job client = AuthenticatedClient(base_url="https://api.nolgia.ai/v1", token=os.environ["NOLGIA_TOKEN"]) response = wait_for_job.sync_detailed(os.environ["JOB_ID"], client=client, timeout_seconds=120) if response.status_code == 408: print("Still running; wait again.") elif isinstance(response.parsed, Job): print(response.parsed.to_dict()) else: raise SystemExit(f"refused: {response.parsed}") ``` > [!NOTE] > A `408` from `wait` is not a job failure. Wait again with the same job id; do not submit the generation again. | Parameter | In | Required | Description | | --- | --- | --- | --- | | `id` | path | Yes | Job UUID. | | `timeout_seconds` | query | No | | ## ticket | Property | Value | | --- | --- | | Type | Query parameter; opaque string | | Default | None; required for the job stream | | Values | A fresh ticket from `POST /jobs/{id}/sse-ticket`, valid for 60 seconds and one use | | Applies to | `GET /jobs/{id}/sse` | | SDK parameter | Ticket endpoint's `ticket` response field, passed in the stream URL | Create the ticket with your bearer token, then use it as the stream's only credential. Do not add a bearer header to the stream request. On reconnect, issue a new ticket; a consumed ticket cannot be reused. The TypeScript and Python stream readers below are examples that print the received SSE bytes; [Streaming](./streaming.html) explains the frames. ```bash tab="curl" $ curl -sS -X POST "https://api.nolgia.ai/v1/jobs/$JOB_ID/sse-ticket" -H "Authorization: Bearer $NOLGIA_TOKEN" $ curl -N "https://api.nolgia.ai/v1/jobs/$JOB_ID/sse?ticket=" ``` ```ts tab="TypeScript" title="example" import { createNolgiaClient } from "@nolgia/sdk"; const nolgia = createNolgiaClient(process.env.NOLGIA_TOKEN!); const id = process.env.JOB_ID!; const { data, error } = await nolgia.POST("/jobs/{id}/sse-ticket", { params: { path: { id } }, }); if (error) throw new Error(`${error.title}: ${error.detail ?? ""}`); const response = await fetch(`https://api.nolgia.ai/v1/jobs/${id}/sse?ticket=${encodeURIComponent(data.ticket)}`); if (!response.ok || !response.body) throw new Error(`stream: ${response.status}`); const reader = response.body.pipeThrough(new TextDecoderStream()).getReader(); for (;;) { const { done, value } = await reader.read(); if (done) break; process.stdout.write(value); } ``` ```python tab="Python" title="example" import os import httpx from nolgia import AuthenticatedClient from nolgia.api.jobs import create_job_sse_ticket from nolgia.models import SSETicket client = AuthenticatedClient(base_url="https://api.nolgia.ai/v1", token=os.environ["NOLGIA_TOKEN"]) job_id = os.environ["JOB_ID"] ticket = create_job_sse_ticket.sync(job_id, client=client) if not isinstance(ticket, SSETicket): raise SystemExit(f"refused: {ticket}") with httpx.stream("GET", f"https://api.nolgia.ai/v1/jobs/{job_id}/sse", params={"ticket": ticket.ticket}, timeout=None) as response: response.raise_for_status() for line in response.iter_lines(): print(line) ``` ## Response headers These headers describe a response; do not send them to configure model retries. | Header | Value and where it appears | What to do | | --- | --- | --- | | `Retry-After` | Seconds until retry: public share resolver `429` responses; OAuth retry responses use `5`; busy edit sessions use `2` | Wait at least that interval before retrying the refused operation | | `Retry-After-Reset` | RFC 3339 reset timestamp on the per-account generation-quota `429` | Wait until the quota resets; a concurrent-generation refusal does not supply this header | | `Content-Type: application/problem+json` | Authentication and handler problem responses | Parse the problem body and branch on `status` and, when present, `code` | | `Content-Type: application/json` | Ordinary JSON API responses | Parse the endpoint's response schema | | `Content-Type: text/event-stream` | Job and agent SSE streams | Consume event frames until the stream closes | | Field | Type | Required | Description | | --- | --- | --- | --- | | `code` | string | No | Machine-readable error code.… | | `type` | string | Yes | A URI reference identifying the problem type. | | `title` | string | Yes | | | `status` | integer | Yes | | | `detail` | string | No | | | `instance` | string | No | | | `job_id` | string | No | The job this refusal points at, so a client can follow it without reading `detail`.… | | `request_id` | string | No | | ## Next steps :::cards - [Asynchronous: submit and poll](./jobs.html): Submit once, follow the id and read the finished asset. - [Concurrency limits](./concurrency-limits.html): Understand the two kinds of generation rate-limit refusal. - [Errors](./errors.html): Read typed problems and choose whether to retry. ::: --- # Common model arguments Source: https://docs.nolgia.ai/guides/model-arguments.md The request schemas describe the shared vocabulary; the model catalog tells you which parts a particular model accepts. Read the catalog before choosing a duration, quality tier or reference input, then quote the exact body with `POST /jobs/cost` before you generate. ## Read the catalog first `GET /models` publishes each model's capabilities and credit prices. A field being in the request schema does not mean every model supports it. The examples below read the catalog with the published clients. ```bash tab="curl" $ curl -s https://api.nolgia.ai/v1/models \ -H "Authorization: Bearer $NOLGIA_TOKEN" ``` ```bash tab="CLI" $ nolgia models get flux-pro ``` ```ts tab="TypeScript" import { createNolgiaClient } from "@nolgia/sdk"; const nolgia = createNolgiaClient(process.env.NOLGIA_TOKEN!); const { data, error } = await nolgia.GET("/models"); if (error) throw new Error(`${error.title}: ${error.detail ?? ""}`); console.log(data); ``` ```python tab="Python" import os from nolgia import AuthenticatedClient from nolgia.api.models import list_models from nolgia.models import ModelList client = AuthenticatedClient(base_url="https://api.nolgia.ai/v1", token=os.environ["NOLGIA_TOKEN"]) catalog = list_models.sync(client=client) if not isinstance(catalog, ModelList): raise SystemExit(f"refused: {catalog}") print(catalog.to_dict()) ``` ```rust tab="Rust" use nolgia_client::ClientBuilder; #[tokio::main] async fn main() -> Result<(), Box> { let client = ClientBuilder::new("https://api.nolgia.ai/v1") .bearer_token(std::env::var("NOLGIA_TOKEN")?) .build()?; let catalog = client.list_models().send().await?.into_inner(); println!("{catalog:?}"); Ok(()) } ``` These are complete model entries from the production catalog captured on 2026-09-21, rather than the entire catalog response. ```json title="model-flux-pro.json" { "cost": { "credits": 4, "unit": "per_image" }, "id": "flux-pro", "image": { "aspect_ratios": [ "3:1", "21:9", "16:9", "3:2", "4:3", "1:1", "3:4", "2:3", "9:16", "9:21", "1:3" ], "aura_compatible": true, "inpaint_mask": false, "num_images_max": 4, "reference_images_max": 0 }, "min_tier": "starter", "modality": "image", "quality": { "default": "native", "options": [ { "credits": 4, "id": "native", "premium": false }, { "credits": 17, "id": "2k", "premium": true }, { "credits": 54, "id": "4k", "premium": true } ] }, "recommended": false } ``` ```json title="model-veo-3.1-lite.json" { "cost": { "baseline_seconds": 5, "credits": 14, "unit": "per_clip" }, "id": "veo-3.1-lite", "min_tier": "pro", "modality": "video", "quality": { "default": "720p", "options": [ { "credits": 14, "id": "720p", "premium": false }, { "credits": 23, "id": "1080p", "premium": true } ] }, "recommended": true, "references": { "audio_refs_max": 0, "element_refs_duration_seconds": 8, "element_refs_max": 3, "end_frame": false, "start_frame": true, "start_frame_required": false, "video_refs_max": 0 }, "video": { "aspect_ratios": [ "16:9", "9:16" ], "audio": "always", "durations": [ 4, 6, 8 ], "image_input": true, "speech": "native" } } ``` | Capability block | What to read before submitting | | --- | --- | | `image.*` | `aspect_ratios`, `num_images_max`, `reference_images_max`, `inpaint_mask`, `aura_compatible`, and any `render_quality` ladder | | `video.*` | Allowed `durations` or duration range, `aspect_ratios`, `audio`, `seed`, and any `bitrate_modes` | | `references.*` | Start/end-frame support; image, video, audio and voice budgets; reference duration constraints; `video_tasks` | | `quality.*` | The model's `default`, allowed tier ids, and credits for each option | | `cost` | Base credits and billing unit; `baseline_seconds` when the price is for a baseline clip | ### Using the examples Each argument example is an independent submission and can spend credits. Install the clients and set `NOLGIA_TOKEN` as in the [Quick Start](./getting-started.html); the examples preserve its client construction and submit-error handling. They print the new job id: use the Quick Start's `finish()` loop or [Asynchronous: submit and poll](./jobs.html) to wait for the asset. Replace illustrative asset, character, project and other UUIDs with ids from your own account. For URL examples, set `REFERENCE_URL` to your own HTTPS media URL. Python's generated request `from_dict` constructs the schema's enum and UUID fields from the same JSON body shown in curl. ## model | Property | Value | | --- | --- | | Type | string | | Default | Required for image, video and audio; 3D selects `hunyuan3d-v3` when both model and quality are omitted | | Values or range | A model id published for the requested modality | | Applies to | All four generation request schemas | | CLI flag | `--model`; `gen 3d --draft` selects `trellis` | Send the catalog `id` verbatim. Model ids belong to a modality: an image model is not a video model even when the names describe the same family. CLI defaults are separate from the HTTP request contract; an explicit model makes a script easier to reproduce. ```bash tab="curl" $ curl -s 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"}' ``` ```ts tab="TypeScript" import { createNolgiaClient } from "@nolgia/sdk"; const nolgia = createNolgiaClient(process.env.NOLGIA_TOKEN!); const { data: job, error } = await nolgia.POST("/generate/image", { body: {"num_images":1,"model":"flux-pro","prompt":"a paper-cut mountain range at dawn"}, }); if (error) throw new Error(`${error.title}: ${error.detail ?? ""}`); console.log(job.id); ``` ```python tab="Python" import os from nolgia import AuthenticatedClient from nolgia.api.generate import generate_image from nolgia.models import GenerateImageRequest, Job client = AuthenticatedClient(base_url="https://api.nolgia.ai/v1", token=os.environ["NOLGIA_TOKEN"]) job = generate_image.sync(client=client, body=GenerateImageRequest.from_dict({ "model": "flux-pro", "prompt": "a paper-cut mountain range at dawn" })) if not isinstance(job, Job): raise SystemExit(f"refused: {job}") print(job.id) ``` ## prompt | Property | Value | | --- | --- | | Type | string | | Default | Required for ordinary image generation, video and audio; absent from 3D | | Values or range | Up to 4,000 characters; video and audio require at least one | | Applies to | `GenerateImageRequest`, `GenerateVideoRequest`, `GenerateAudioRequest` | | CLI flag | `--prompt` | Describe the result for images, video, music and sound effects; for text to speech, this is the text spoken and the input used for character-based billing. Image background removal and enhancement refuse a prompt because they operate on the reference pixels; image expansion accepts an optional prompt for the new margins. On reference video routes, name inputs with the provider slots published in the schema, such as `@Image1` or `@Video1`. ```bash tab="curl" $ curl -s 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"}' ``` ```ts tab="TypeScript" import { createNolgiaClient } from "@nolgia/sdk"; const nolgia = createNolgiaClient(process.env.NOLGIA_TOKEN!); const { data: job, error } = await nolgia.POST("/generate/image", { body: {"num_images":1,"model":"flux-pro","prompt":"a paper-cut mountain range at dawn"}, }); if (error) throw new Error(`${error.title}: ${error.detail ?? ""}`); console.log(job.id); ``` ```python tab="Python" import os from nolgia import AuthenticatedClient from nolgia.api.generate import generate_image from nolgia.models import GenerateImageRequest, Job client = AuthenticatedClient(base_url="https://api.nolgia.ai/v1", token=os.environ["NOLGIA_TOKEN"]) job = generate_image.sync(client=client, body=GenerateImageRequest.from_dict({ "model": "flux-pro", "prompt": "a paper-cut mountain range at dawn" })) if not isinstance(job, Job): raise SystemExit(f"refused: {job}") print(job.id) ``` ## negative_prompt | Property | Value | | --- | --- | | Type | string or null | | Default | Omitted | | Values or range | Up to 4,000 characters; an image model can publish a lower `image.negative_prompt_max_length` | | Applies to | Image and video requests; refused for image background removal | | CLI flag | `--negative-prompt` on `gen video`; no image flag | Use negative text only within the selected model's accepted limit. The server checks a model-specific image limit before reserving credits, so the shared schema maximum is not permission to exceed the catalog value. ```bash tab="curl" $ curl -s 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","negative_prompt":"blurred, illegible text"}' ``` ```ts tab="TypeScript" import { createNolgiaClient } from "@nolgia/sdk"; const nolgia = createNolgiaClient(process.env.NOLGIA_TOKEN!); const { data: job, error } = await nolgia.POST("/generate/image", { body: {"num_images":1,"model":"flux-pro","prompt":"a paper-cut mountain range at dawn","negative_prompt":"blurred, illegible text"}, }); if (error) throw new Error(`${error.title}: ${error.detail ?? ""}`); console.log(job.id); ``` ```python tab="Python" import os from nolgia import AuthenticatedClient from nolgia.api.generate import generate_image from nolgia.models import GenerateImageRequest, Job client = AuthenticatedClient(base_url="https://api.nolgia.ai/v1", token=os.environ["NOLGIA_TOKEN"]) job = generate_image.sync(client=client, body=GenerateImageRequest.from_dict({ "model": "flux-pro", "prompt": "a paper-cut mountain range at dawn", "negative_prompt": "blurred, illegible text" })) if not isinstance(job, Job): raise SystemExit(f"refused: {job}") print(job.id) ``` ## seed | Property | Value | | --- | --- | | Type | integer or null | | Default | Omitted; no universal deterministic default | | Values or range | Zero or greater | | Applies to | Image and video requests; video support is `video.seed` | | CLI flag | `--seed` on `gen video`; no image flag | A seed is only meaningful on a model whose provider takes one. For video, `video.seed: false` means sending a seed is refused with `400`; an absent capability is unverified. A common field name is not a promise that all providers reproduce identical output. ```bash tab="curl" $ curl -s 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","seed":42}' ``` ```ts tab="TypeScript" import { createNolgiaClient } from "@nolgia/sdk"; const nolgia = createNolgiaClient(process.env.NOLGIA_TOKEN!); const { data: job, error } = await nolgia.POST("/generate/image", { body: {"num_images":1,"model":"flux-pro","prompt":"a paper-cut mountain range at dawn","seed":42}, }); if (error) throw new Error(`${error.title}: ${error.detail ?? ""}`); console.log(job.id); ``` ```python tab="Python" import os from nolgia import AuthenticatedClient from nolgia.api.generate import generate_image from nolgia.models import GenerateImageRequest, Job client = AuthenticatedClient(base_url="https://api.nolgia.ai/v1", token=os.environ["NOLGIA_TOKEN"]) job = generate_image.sync(client=client, body=GenerateImageRequest.from_dict({ "model": "flux-pro", "prompt": "a paper-cut mountain range at dawn", "seed": 42 })) if not isinstance(job, Job): raise SystemExit(f"refused: {job}") print(job.id) ``` ## num_images | Property | Value | | --- | --- | | Type | integer | | Default | `1` | | Values or range | `1` through `4`, further limited by `image.num_images_max` | | Applies to | `GenerateImageRequest` | | CLI flag | No dedicated flag; `gen image` submits one image | The `flux-pro` fixture publishes a maximum of four images. Read the cap for the chosen model instead of copying that number across the catalog. Face-reference identity requests require one image, including when a character supplies the face reference. ```bash tab="curl" $ curl -s 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","num_images":2}' ``` ```ts tab="TypeScript" import { createNolgiaClient } from "@nolgia/sdk"; const nolgia = createNolgiaClient(process.env.NOLGIA_TOKEN!); const { data: job, error } = await nolgia.POST("/generate/image", { body: {"num_images":2,"model":"flux-pro","prompt":"a paper-cut mountain range at dawn"}, }); if (error) throw new Error(`${error.title}: ${error.detail ?? ""}`); console.log(job.id); ``` ```python tab="Python" import os from nolgia import AuthenticatedClient from nolgia.api.generate import generate_image from nolgia.models import GenerateImageRequest, Job client = AuthenticatedClient(base_url="https://api.nolgia.ai/v1", token=os.environ["NOLGIA_TOKEN"]) job = generate_image.sync(client=client, body=GenerateImageRequest.from_dict({ "model": "flux-pro", "prompt": "a paper-cut mountain range at dawn", "num_images": 2 })) if not isinstance(job, Job): raise SystemExit(f"refused: {job}") print(job.id) ``` ## image_size | Property | Value | | --- | --- | | Type | `ImageSize` string | | Default | Omitted; no schema default | | Values or range | The six presets below | | Applies to | `GenerateImageRequest` | | CLI flag | No `--image-size` flag; use `--aspect-ratio` with a ratio instead | These are the six size aliases accepted by the image schema. Prefer `aspect_ratio` when a model publishes a native ratio outside the square, 4:3 and 16:9 families. The CLI takes ratio strings, not these aliases. ### ImageSize values | Value | | --- | | `square` | | `square_hd` | | `portrait_4_3` | | `portrait_16_9` | | `landscape_4_3` | | `landscape_16_9` | ```bash tab="curl" $ curl -s 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","image_size":"landscape_16_9"}' ``` ```ts tab="TypeScript" import { createNolgiaClient } from "@nolgia/sdk"; const nolgia = createNolgiaClient(process.env.NOLGIA_TOKEN!); const { data: job, error } = await nolgia.POST("/generate/image", { body: {"num_images":1,"model":"flux-pro","prompt":"a paper-cut mountain range at dawn","image_size":"landscape_16_9"}, }); if (error) throw new Error(`${error.title}: ${error.detail ?? ""}`); console.log(job.id); ``` ```python tab="Python" import os from nolgia import AuthenticatedClient from nolgia.api.generate import generate_image from nolgia.models import GenerateImageRequest, Job client = AuthenticatedClient(base_url="https://api.nolgia.ai/v1", token=os.environ["NOLGIA_TOKEN"]) job = generate_image.sync(client=client, body=GenerateImageRequest.from_dict({ "model": "flux-pro", "prompt": "a paper-cut mountain range at dawn", "image_size": "landscape_16_9" })) if not isinstance(job, Job): raise SystemExit(f"refused: {job}") print(job.id) ``` ## aspect_ratio | Property | Value | | --- | --- | | Type | `ImageAspectRatio` for image; `AspectRatio` for video | | Default | Omitted; let the model choose its default | | Values or range | Schema values below, intersected with the selected model's published ratios | | Applies to | Image and video requests; `image.aspect_ratios` or `video.aspect_ratios` | | CLI flag | `--aspect-ratio` on `gen image` and `gen video` | Choose a ratio from the selected model's catalog entry. The image and video enums differ, and a value in either enum can still be refused by a particular model. For example, the video fixture above accepts `16:9` and `9:16`. ### ImageAspectRatio values | Value | | --- | | `16:9` | | `9:16` | | `1:1` | | `4:3` | | `3:4` | | `3:2` | | `2:3` | | `21:9` | | `9:21` | | `2:1` | | `1:2` | | `5:4` | | `4:5` | | `3:1` | | `1:3` | | `4:1` | | `1:4` | ### AspectRatio values | Value | | --- | | `16:9` | | `9:16` | | `1:1` | | `4:3` | | `3:4` | | `3:2` | | `2:3` | | `21:9` | ```bash tab="curl" $ curl -s 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","aspect_ratio":"16:9"}' ``` ```ts tab="TypeScript" import { createNolgiaClient } from "@nolgia/sdk"; const nolgia = createNolgiaClient(process.env.NOLGIA_TOKEN!); const { data: job, error } = await nolgia.POST("/generate/image", { body: {"num_images":1,"model":"flux-pro","prompt":"a paper-cut mountain range at dawn","aspect_ratio":"16:9"}, }); if (error) throw new Error(`${error.title}: ${error.detail ?? ""}`); console.log(job.id); ``` ```python tab="Python" import os from nolgia import AuthenticatedClient from nolgia.api.generate import generate_image from nolgia.models import GenerateImageRequest, Job client = AuthenticatedClient(base_url="https://api.nolgia.ai/v1", token=os.environ["NOLGIA_TOKEN"]) job = generate_image.sync(client=client, body=GenerateImageRequest.from_dict({ "model": "flux-pro", "prompt": "a paper-cut mountain range at dawn", "aspect_ratio": "16:9" })) if not isinstance(job, Job): raise SystemExit(f"refused: {job}") print(job.id) ``` ## quality | Property | Value | | --- | --- | | Type | string or null for image/video; enum string for 3D | | Default | The model's `quality.default`; 3D defaults to `hunyuan3d-v3` when model is also absent | | Values or range | `quality.options[].id`; 3D accepts `draft` or `standard` | | Applies to | Image, video and 3D requests | | CLI flag | `--quality` for image/video; `--draft` for 3D | The ladder and price are model-specific: the fixtures show `native`, `2k`, `4k` for `flux-pro`, and `720p`, `1080p` for `veo-3.1-lite`. Omitting the field uses the base tier. An unsupported tier is a `400`, before a credit hold. For 3D, `draft` selects `trellis` and `standard` selects `hunyuan3d-v3`; a conflicting explicit model is refused. ```bash tab="curl" $ curl -s 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","quality":"2k"}' ``` ```ts tab="TypeScript" import { createNolgiaClient } from "@nolgia/sdk"; const nolgia = createNolgiaClient(process.env.NOLGIA_TOKEN!); const { data: job, error } = await nolgia.POST("/generate/image", { body: {"num_images":1,"model":"flux-pro","prompt":"a paper-cut mountain range at dawn","quality":"2k"}, }); if (error) throw new Error(`${error.title}: ${error.detail ?? ""}`); console.log(job.id); ``` ```python tab="Python" import os from nolgia import AuthenticatedClient from nolgia.api.generate import generate_image from nolgia.models import GenerateImageRequest, Job client = AuthenticatedClient(base_url="https://api.nolgia.ai/v1", token=os.environ["NOLGIA_TOKEN"]) job = generate_image.sync(client=client, body=GenerateImageRequest.from_dict({ "model": "flux-pro", "prompt": "a paper-cut mountain range at dawn", "quality": "2k" })) if not isinstance(job, Job): raise SystemExit(f"refused: {job}") print(job.id) ``` ## render_quality | Property | Value | | --- | --- | | Type | string | | Default | `auto` | | Values or range | `auto`, `low`, `medium`, `high`, `xhigh`, `max`, restricted to the model's own ladder | | Applies to | Image only; GPT Image models publishing `image.render_quality` | | CLI flag | `--render-quality` | This controls the effort spent drawing the image. `quality` separately controls its output resolution, and the two can be combined. `low`, `medium` and `high` cost the base rate; `xhigh` and `max` add the credits published in the model's render-quality options and exist only on the GPT Image 2.5 models. A value the selected model does not publish is refused, never silently downgraded. ```bash tab="curl" $ curl -s https://api.nolgia.ai/v1/generate/image \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H "Content-Type: application/json" \ -d '{"model":"gpt-image-2","prompt":"a paper-cut mountain range at dawn","render_quality":"high"}' ``` ```ts tab="TypeScript" import { createNolgiaClient } from "@nolgia/sdk"; const nolgia = createNolgiaClient(process.env.NOLGIA_TOKEN!); const { data: job, error } = await nolgia.POST("/generate/image", { body: {"num_images":1,"model":"gpt-image-2","prompt":"a paper-cut mountain range at dawn","render_quality":"high"}, }); if (error) throw new Error(`${error.title}: ${error.detail ?? ""}`); console.log(job.id); ``` ```python tab="Python" import os from nolgia import AuthenticatedClient from nolgia.api.generate import generate_image from nolgia.models import GenerateImageRequest, Job client = AuthenticatedClient(base_url="https://api.nolgia.ai/v1", token=os.environ["NOLGIA_TOKEN"]) job = generate_image.sync(client=client, body=GenerateImageRequest.from_dict({ "model": "gpt-image-2", "prompt": "a paper-cut mountain range at dawn", "render_quality": "high" })) if not isinstance(job, Job): raise SystemExit(f"refused: {job}") print(job.id) ``` ## duration_seconds | Property | Value | | --- | --- | | Type | integer | | Default | Model-dependent; omit to select a supported duration | | Values or range | Schema `1` through `60`, restricted by `video.durations`, duration ranges and reference constraints | | Applies to | `GenerateVideoRequest`; the audio field is deprecated and ignored | | CLI flag | `--duration-seconds` on `gen video` | For a discrete duration list, the server chooses the supported value nearest the five-second baseline, resolving ties upward; the Veo fixture therefore defaults to six seconds. Reference images can pin a different duration through `references.element_refs_duration_seconds`. An explicit duration must be supported: four seconds works in the fixture, five does not. Do not use the deprecated audio field to trim a narration. ```bash tab="curl" $ curl -s https://api.nolgia.ai/v1/generate/video \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H "Content-Type: application/json" \ -d '{"model":"veo-3.1-lite","prompt":"a paper-cut mountain range at dawn, slow push-in as the sun rises","duration_seconds":4}' ``` ```ts tab="TypeScript" import { createNolgiaClient } from "@nolgia/sdk"; const nolgia = createNolgiaClient(process.env.NOLGIA_TOKEN!); const { data: job, error } = await nolgia.POST("/generate/video", { body: {"model":"veo-3.1-lite","prompt":"a paper-cut mountain range at dawn, slow push-in as the sun rises","duration_seconds":4}, }); if (error) throw new Error(`${error.title}: ${error.detail ?? ""}`); console.log(job.id); ``` ```python tab="Python" import os from nolgia import AuthenticatedClient from nolgia.api.generate import generate_video from nolgia.models import GenerateVideoRequest, Job client = AuthenticatedClient(base_url="https://api.nolgia.ai/v1", token=os.environ["NOLGIA_TOKEN"]) job = generate_video.sync(client=client, body=GenerateVideoRequest.from_dict({ "model": "veo-3.1-lite", "prompt": "a paper-cut mountain range at dawn, slow push-in as the sun rises", "duration_seconds": 4 })) if not isinstance(job, Job): raise SystemExit(f"refused: {job}") print(job.id) ``` ## generate_audio | Property | Value | | --- | --- | | Type | boolean or null | | Default | `true` on models with optional audio | | Values or range | `true` or `false`, interpreted through `video.audio` | | Applies to | `GenerateVideoRequest` | | CLI flag | `--generate-audio true` or `--generate-audio false` | Read `video.audio`: `none` returns a silent clip regardless of the flag; `optional` honors it; `always` supplies native audio and refuses `false` with `400`. The asset's generation metadata records `generate_audio_effective` so the delivered behavior remains inspectable. ```bash tab="curl" $ curl -s https://api.nolgia.ai/v1/generate/video \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H "Content-Type: application/json" \ -d '{"model":"veo-3.1-lite","prompt":"a paper-cut mountain range at dawn, slow push-in as the sun rises","duration_seconds":4,"generate_audio":true}' ``` ```ts tab="TypeScript" import { createNolgiaClient } from "@nolgia/sdk"; const nolgia = createNolgiaClient(process.env.NOLGIA_TOKEN!); const { data: job, error } = await nolgia.POST("/generate/video", { body: {"model":"veo-3.1-lite","prompt":"a paper-cut mountain range at dawn, slow push-in as the sun rises","duration_seconds":4,"generate_audio":true}, }); if (error) throw new Error(`${error.title}: ${error.detail ?? ""}`); console.log(job.id); ``` ```python tab="Python" import os from nolgia import AuthenticatedClient from nolgia.api.generate import generate_video from nolgia.models import GenerateVideoRequest, Job client = AuthenticatedClient(base_url="https://api.nolgia.ai/v1", token=os.environ["NOLGIA_TOKEN"]) job = generate_video.sync(client=client, body=GenerateVideoRequest.from_dict({ "model": "veo-3.1-lite", "prompt": "a paper-cut mountain range at dawn, slow push-in as the sun rises", "duration_seconds": 4, "generate_audio": True })) if not isinstance(job, Job): raise SystemExit(f"refused: {job}") print(job.id) ``` ## image_url | Property | Value | | --- | --- | | Type | HTTPS URL string | | Default | Omitted | | Values or range | One start/reference image; image requests cap URL length at 2,048 characters | | Applies to | Image reference input; video start-frame input; 3D front image | | CLI flag | `--input` for image/video uses a local file or asset id; 3D also has `--image-url` | An image reference needs spare `image.reference_images_max` capacity. For video, check `references.start_frame` and `start_frame_required`; text-only routes do not consume an image. For 3D, send exactly one of `image_url` and `image_asset_ids`. Prefer owned asset ids where the request offers them, so queued work receives a fresh signed URL at execution. ```bash tab="curl" $ curl -s https://api.nolgia.ai/v1/generate/image \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H "Content-Type: application/json" \ -d '{"model":"gpt-image-2","prompt":"a paper-cut mountain range at dawn","image_url":"https://example.com/reference.png"}' ``` ```ts tab="TypeScript" import { createNolgiaClient } from "@nolgia/sdk"; const nolgia = createNolgiaClient(process.env.NOLGIA_TOKEN!); const { data: job, error } = await nolgia.POST("/generate/image", { body: {"num_images":1,"model":"gpt-image-2","prompt":"a paper-cut mountain range at dawn","image_url":process.env.REFERENCE_URL!}, }); if (error) throw new Error(`${error.title}: ${error.detail ?? ""}`); console.log(job.id); ``` ```python tab="Python" import os from nolgia import AuthenticatedClient from nolgia.api.generate import generate_image from nolgia.models import GenerateImageRequest, Job client = AuthenticatedClient(base_url="https://api.nolgia.ai/v1", token=os.environ["NOLGIA_TOKEN"]) job = generate_image.sync(client=client, body=GenerateImageRequest.from_dict({ "model": "gpt-image-2", "prompt": "a paper-cut mountain range at dawn", "image_url": os.environ["REFERENCE_URL"] })) if not isinstance(job, Job): raise SystemExit(f"refused: {job}") print(job.id) ``` ## image_urls | Property | Value | | --- | --- | | Type | array of HTTPS URL strings or null | | Default | Omitted | | Values or range | Image: at most 4 combined references; video: at most 9 combined element images; each model can allow fewer | | Applies to | Image `image.reference_images_max`; video `references.element_refs_max` | | CLI flag | No direct URL-array flag; use API clients | On image requests, `image_url` is the first reference and `image_urls` adds more. On video requests, these URLs fill element-reference slots, sharing their budget with `element_asset_ids`. These fields do not create an independent budget for every way you attach media. ```bash tab="curl" $ curl -s https://api.nolgia.ai/v1/generate/image \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H "Content-Type: application/json" \ -d '{"model":"gpt-image-2","prompt":"a paper-cut mountain range at dawn","image_urls":["https://example.com/reference.png"]}' ``` ```ts tab="TypeScript" import { createNolgiaClient } from "@nolgia/sdk"; const nolgia = createNolgiaClient(process.env.NOLGIA_TOKEN!); const { data: job, error } = await nolgia.POST("/generate/image", { body: {"num_images":1,"model":"gpt-image-2","prompt":"a paper-cut mountain range at dawn","image_urls":[process.env.REFERENCE_URL!]}, }); if (error) throw new Error(`${error.title}: ${error.detail ?? ""}`); console.log(job.id); ``` ```python tab="Python" import os from nolgia import AuthenticatedClient from nolgia.api.generate import generate_image from nolgia.models import GenerateImageRequest, Job client = AuthenticatedClient(base_url="https://api.nolgia.ai/v1", token=os.environ["NOLGIA_TOKEN"]) job = generate_image.sync(client=client, body=GenerateImageRequest.from_dict({ "model": "gpt-image-2", "prompt": "a paper-cut mountain range at dawn", "image_urls": [ os.environ["REFERENCE_URL"] ] })) if not isinstance(job, Job): raise SystemExit(f"refused: {job}") print(job.id) ``` ## reference_asset_ids | Property | Value | | --- | --- | | Type | array of image-asset UUIDs or null | | Default | Omitted | | Values or range | At most 4; shared with `image_url` and `image_urls` and limited by `image.reference_images_max` | | Applies to | `GenerateImageRequest` | | CLI flag | `--input PATH_OR_UUID` supplies one owned reference | Use ids for images already in your Library. The server resolves and re-signs them when the job runs. A model with no reference input, including `flux-pro` in the fixture above, refuses any reference with `400`; it does not ignore the image. ```bash tab="curl" $ curl -s https://api.nolgia.ai/v1/generate/image \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H "Content-Type: application/json" \ -d '{"model":"gpt-image-2","prompt":"a paper-cut mountain range at dawn","reference_asset_ids":["5a2f7c58-9e5a-4f1a-9c7f-2f0b6c3d1e42"]}' ``` ```ts tab="TypeScript" import { createNolgiaClient } from "@nolgia/sdk"; const nolgia = createNolgiaClient(process.env.NOLGIA_TOKEN!); const { data: job, error } = await nolgia.POST("/generate/image", { body: {"num_images":1,"model":"gpt-image-2","prompt":"a paper-cut mountain range at dawn","reference_asset_ids":["5a2f7c58-9e5a-4f1a-9c7f-2f0b6c3d1e42"]}, }); if (error) throw new Error(`${error.title}: ${error.detail ?? ""}`); console.log(job.id); ``` ```python tab="Python" import os from nolgia import AuthenticatedClient from nolgia.api.generate import generate_image from nolgia.models import GenerateImageRequest, Job client = AuthenticatedClient(base_url="https://api.nolgia.ai/v1", token=os.environ["NOLGIA_TOKEN"]) job = generate_image.sync(client=client, body=GenerateImageRequest.from_dict({ "model": "gpt-image-2", "prompt": "a paper-cut mountain range at dawn", "reference_asset_ids": [ "5a2f7c58-9e5a-4f1a-9c7f-2f0b6c3d1e42" ] })) if not isinstance(job, Job): raise SystemExit(f"refused: {job}") print(job.id) ``` ## mask_asset_id | Property | Value | | --- | --- | | Type | image-asset UUID or null | | Default | Omitted | | Values or range | One PNG mask; exactly one reference image; matching dimensions and an alpha channel | | Applies to | Image models with `image.inpaint_mask: true` | | CLI flag | `--mask PATH_OR_UUID` with exactly one `--input` | Transparent mask pixels mark the region the model may repaint; opaque pixels describe the region to preserve. Describe the whole desired picture, including the new content. The mask is model guidance, so preserved areas are re-rendered rather than copied byte for byte. Unsupported input is refused before a hold; missing alpha or wrong dimensions discovered during execution fails the job with a full refund. ```bash tab="curl" $ curl -s https://api.nolgia.ai/v1/generate/image \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H "Content-Type: application/json" \ -d '{"model":"gpt-image-2","prompt":"a desk with a red mug on the left and a potted fern in the centre","reference_asset_ids":["5a2f7c58-9e5a-4f1a-9c7f-2f0b6c3d1e42"],"mask_asset_id":"b2fbb51e-3c33-4a54-b87b-9c2b1e4f9a11"}' ``` ```ts tab="TypeScript" import { createNolgiaClient } from "@nolgia/sdk"; const nolgia = createNolgiaClient(process.env.NOLGIA_TOKEN!); const { data: job, error } = await nolgia.POST("/generate/image", { body: {"num_images":1,"model":"gpt-image-2","prompt":"a desk with a red mug on the left and a potted fern in the centre","reference_asset_ids":["5a2f7c58-9e5a-4f1a-9c7f-2f0b6c3d1e42"],"mask_asset_id":"b2fbb51e-3c33-4a54-b87b-9c2b1e4f9a11"}, }); if (error) throw new Error(`${error.title}: ${error.detail ?? ""}`); console.log(job.id); ``` ```python tab="Python" import os from nolgia import AuthenticatedClient from nolgia.api.generate import generate_image from nolgia.models import GenerateImageRequest, Job client = AuthenticatedClient(base_url="https://api.nolgia.ai/v1", token=os.environ["NOLGIA_TOKEN"]) job = generate_image.sync(client=client, body=GenerateImageRequest.from_dict({ "model": "gpt-image-2", "prompt": "a desk with a red mug on the left and a potted fern in the centre", "reference_asset_ids": [ "5a2f7c58-9e5a-4f1a-9c7f-2f0b6c3d1e42" ], "mask_asset_id": "b2fbb51e-3c33-4a54-b87b-9c2b1e4f9a11" })) if not isinstance(job, Job): raise SystemExit(f"refused: {job}") print(job.id) ``` ## end_image_url | Property | Value | | --- | --- | | Type | HTTPS URL string or null | | Default | Omitted | | Values or range | One final frame; mutually exclusive with `end_image_asset_id` | | Applies to | Video with `references.end_frame: true`; requires the start `image_url` | | CLI flag | `--end-frame PATH_OR_UUID` resolves a file or asset; no raw end-URL flag | A final frame pins the destination of a start-to-end clip. Supply the start frame as well and check the capability: the Veo fixture above publishes `end_frame: false`. Choose a model that explicitly supports both frames. ```bash tab="curl" $ curl -s https://api.nolgia.ai/v1/generate/video \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H "Content-Type: application/json" \ -d '{"model":"seedance-2.5","prompt":"a paper-cut mountain range at dawn, slow push-in as the sun rises","image_url":"https://example.com/start.png","end_image_url":"https://example.com/end.png"}' ``` ```ts tab="TypeScript" import { createNolgiaClient } from "@nolgia/sdk"; const nolgia = createNolgiaClient(process.env.NOLGIA_TOKEN!); const { data: job, error } = await nolgia.POST("/generate/video", { body: {"model":"seedance-2.5","prompt":"a paper-cut mountain range at dawn, slow push-in as the sun rises","image_url":process.env.START_IMAGE_URL!,"end_image_url":process.env.END_IMAGE_URL!}, }); if (error) throw new Error(`${error.title}: ${error.detail ?? ""}`); console.log(job.id); ``` ```python tab="Python" import os from nolgia import AuthenticatedClient from nolgia.api.generate import generate_video from nolgia.models import GenerateVideoRequest, Job client = AuthenticatedClient(base_url="https://api.nolgia.ai/v1", token=os.environ["NOLGIA_TOKEN"]) job = generate_video.sync(client=client, body=GenerateVideoRequest.from_dict({ "model": "seedance-2.5", "prompt": "a paper-cut mountain range at dawn, slow push-in as the sun rises", "image_url": os.environ["START_IMAGE_URL"], "end_image_url": os.environ["END_IMAGE_URL"] })) if not isinstance(job, Job): raise SystemExit(f"refused: {job}") print(job.id) ``` ## end_image_asset_id | Property | Value | | --- | --- | | Type | image-asset UUID or null | | Default | Omitted | | Values or range | One owned final frame; mutually exclusive with `end_image_url` | | Applies to | Same video capability and start-frame requirement as `end_image_url` | | CLI flag | `--end-frame PATH_OR_UUID` with `--input` | This is the owned-asset form of the final-frame input. The server resolves its URL for you; use at most one of the two end-frame fields. ```bash tab="curl" $ curl -s https://api.nolgia.ai/v1/generate/video \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H "Content-Type: application/json" \ -d '{"model":"seedance-2.5","prompt":"a paper-cut mountain range at dawn, slow push-in as the sun rises","image_url":"https://example.com/start.png","end_image_asset_id":"5a2f7c58-9e5a-4f1a-9c7f-2f0b6c3d1e42"}' ``` ```ts tab="TypeScript" import { createNolgiaClient } from "@nolgia/sdk"; const nolgia = createNolgiaClient(process.env.NOLGIA_TOKEN!); const { data: job, error } = await nolgia.POST("/generate/video", { body: {"model":"seedance-2.5","prompt":"a paper-cut mountain range at dawn, slow push-in as the sun rises","image_url":process.env.START_IMAGE_URL!,"end_image_asset_id":"5a2f7c58-9e5a-4f1a-9c7f-2f0b6c3d1e42"}, }); if (error) throw new Error(`${error.title}: ${error.detail ?? ""}`); console.log(job.id); ``` ```python tab="Python" import os from nolgia import AuthenticatedClient from nolgia.api.generate import generate_video from nolgia.models import GenerateVideoRequest, Job client = AuthenticatedClient(base_url="https://api.nolgia.ai/v1", token=os.environ["NOLGIA_TOKEN"]) job = generate_video.sync(client=client, body=GenerateVideoRequest.from_dict({ "model": "seedance-2.5", "prompt": "a paper-cut mountain range at dawn, slow push-in as the sun rises", "image_url": os.environ["START_IMAGE_URL"], "end_image_asset_id": "5a2f7c58-9e5a-4f1a-9c7f-2f0b6c3d1e42" })) if not isinstance(job, Job): raise SystemExit(f"refused: {job}") print(job.id) ``` ## video_asset_ids | Property | Value | | --- | --- | | Type | array of video-asset UUIDs or null | | Default | Omitted | | Values or range | At most 10 combined with `video_urls`, further limited by `references.video_refs_max` | | Applies to | `GenerateVideoRequest` on a model accepting video references | | CLI flag | Repeat `--video-ref ASSET_ID` | Prefer owned video assets so the server can read duration and other stored metadata. Prompt references use `@Video1`, `@Video2` and so on. Duration and input-format constraints are model-specific; editing with `video_task: edit` requires a stored duration and cannot use raw URLs. ```bash tab="curl" $ curl -s https://api.nolgia.ai/v1/generate/video \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H "Content-Type: application/json" \ -d '{"model":"seedance-2.5","prompt":"a paper-cut mountain range at dawn, slow push-in as the sun rises","video_asset_ids":["5a2f7c58-9e5a-4f1a-9c7f-2f0b6c3d1e42"]}' ``` ```ts tab="TypeScript" import { createNolgiaClient } from "@nolgia/sdk"; const nolgia = createNolgiaClient(process.env.NOLGIA_TOKEN!); const { data: job, error } = await nolgia.POST("/generate/video", { body: {"model":"seedance-2.5","prompt":"a paper-cut mountain range at dawn, slow push-in as the sun rises","video_asset_ids":["5a2f7c58-9e5a-4f1a-9c7f-2f0b6c3d1e42"]}, }); if (error) throw new Error(`${error.title}: ${error.detail ?? ""}`); console.log(job.id); ``` ```python tab="Python" import os from nolgia import AuthenticatedClient from nolgia.api.generate import generate_video from nolgia.models import GenerateVideoRequest, Job client = AuthenticatedClient(base_url="https://api.nolgia.ai/v1", token=os.environ["NOLGIA_TOKEN"]) job = generate_video.sync(client=client, body=GenerateVideoRequest.from_dict({ "model": "seedance-2.5", "prompt": "a paper-cut mountain range at dawn, slow push-in as the sun rises", "video_asset_ids": [ "5a2f7c58-9e5a-4f1a-9c7f-2f0b6c3d1e42" ] })) if not isinstance(job, Job): raise SystemExit(f"refused: {job}") print(job.id) ``` ## video_urls | Property | Value | | --- | --- | | Type | array of HTTPS video URLs or null | | Default | Omitted | | Values or range | The same combined cap as `video_asset_ids` | | Applies to | Video models with `references.video_refs_max` greater than zero | | CLI flag | No raw video-URL flag; use `--video-ref` for owned assets | Use raw URLs only for externally hosted footage. They share the asset-id budget, and the server cannot validate their duration, resolution or size from stored asset metadata; provider violations still fail. Use `video_asset_ids` when the footage is already in Nolgia. ```bash tab="curl" $ curl -s https://api.nolgia.ai/v1/generate/video \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H "Content-Type: application/json" \ -d '{"model":"seedance-2.5","prompt":"a paper-cut mountain range at dawn, slow push-in as the sun rises","video_urls":["https://example.com/reference.png"]}' ``` ```ts tab="TypeScript" import { createNolgiaClient } from "@nolgia/sdk"; const nolgia = createNolgiaClient(process.env.NOLGIA_TOKEN!); const { data: job, error } = await nolgia.POST("/generate/video", { body: {"model":"seedance-2.5","prompt":"a paper-cut mountain range at dawn, slow push-in as the sun rises","video_urls":[process.env.REFERENCE_URL!]}, }); if (error) throw new Error(`${error.title}: ${error.detail ?? ""}`); console.log(job.id); ``` ```python tab="Python" import os from nolgia import AuthenticatedClient from nolgia.api.generate import generate_video from nolgia.models import GenerateVideoRequest, Job client = AuthenticatedClient(base_url="https://api.nolgia.ai/v1", token=os.environ["NOLGIA_TOKEN"]) job = generate_video.sync(client=client, body=GenerateVideoRequest.from_dict({ "model": "seedance-2.5", "prompt": "a paper-cut mountain range at dawn, slow push-in as the sun rises", "video_urls": [ os.environ["REFERENCE_URL"] ] })) if not isinstance(job, Job): raise SystemExit(f"refused: {job}") print(job.id) ``` ## audio_asset_ids | Property | Value | | --- | --- | | Type | array of audio-asset UUIDs or null | | Default | Omitted | | Values or range | At most 10 combined with `audio_urls`; `references.audio_refs_max` and any `audio_refs_max_seconds` further restrict input | | Applies to | `GenerateVideoRequest` on models accepting reference audio | | CLI flag | Repeat `--audio-ref PATH_OR_UUID` | These are your recorded audio tracks, resolved to signed URLs by the server. They are different from choosing a preset roster voice. Models that derive clip length and billing from the reference audio require an owned asset with duration metadata. ```bash tab="curl" $ curl -s https://api.nolgia.ai/v1/generate/video \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H "Content-Type: application/json" \ -d '{"model":"seedance-2.5","prompt":"a paper-cut mountain range at dawn, slow push-in as the sun rises","audio_asset_ids":["5a2f7c58-9e5a-4f1a-9c7f-2f0b6c3d1e42"]}' ``` ```ts tab="TypeScript" import { createNolgiaClient } from "@nolgia/sdk"; const nolgia = createNolgiaClient(process.env.NOLGIA_TOKEN!); const { data: job, error } = await nolgia.POST("/generate/video", { body: {"model":"seedance-2.5","prompt":"a paper-cut mountain range at dawn, slow push-in as the sun rises","audio_asset_ids":["5a2f7c58-9e5a-4f1a-9c7f-2f0b6c3d1e42"]}, }); if (error) throw new Error(`${error.title}: ${error.detail ?? ""}`); console.log(job.id); ``` ```python tab="Python" import os from nolgia import AuthenticatedClient from nolgia.api.generate import generate_video from nolgia.models import GenerateVideoRequest, Job client = AuthenticatedClient(base_url="https://api.nolgia.ai/v1", token=os.environ["NOLGIA_TOKEN"]) job = generate_video.sync(client=client, body=GenerateVideoRequest.from_dict({ "model": "seedance-2.5", "prompt": "a paper-cut mountain range at dawn, slow push-in as the sun rises", "audio_asset_ids": [ "5a2f7c58-9e5a-4f1a-9c7f-2f0b6c3d1e42" ] })) if not isinstance(job, Job): raise SystemExit(f"refused: {job}") print(job.id) ``` ## audio_urls | Property | Value | | --- | --- | | Type | array of HTTPS audio URLs or null | | Default | Omitted | | Values or range | The same combined cap as `audio_asset_ids` | | Applies to | Video models with `references.audio_refs_max` greater than zero | | CLI flag | No raw audio-URL flag; `--audio-ref` accepts a file or owned asset | Pass externally hosted reference audio through this field when the selected model accepts it. Prefer `audio_asset_ids`; a model that requires stored input duration can refuse raw URLs. ```bash tab="curl" $ curl -s https://api.nolgia.ai/v1/generate/video \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H "Content-Type: application/json" \ -d '{"model":"seedance-2.5","prompt":"a paper-cut mountain range at dawn, slow push-in as the sun rises","audio_urls":["https://example.com/reference.png"]}' ``` ```ts tab="TypeScript" import { createNolgiaClient } from "@nolgia/sdk"; const nolgia = createNolgiaClient(process.env.NOLGIA_TOKEN!); const { data: job, error } = await nolgia.POST("/generate/video", { body: {"model":"seedance-2.5","prompt":"a paper-cut mountain range at dawn, slow push-in as the sun rises","audio_urls":[process.env.REFERENCE_URL!]}, }); if (error) throw new Error(`${error.title}: ${error.detail ?? ""}`); console.log(job.id); ``` ```python tab="Python" import os from nolgia import AuthenticatedClient from nolgia.api.generate import generate_video from nolgia.models import GenerateVideoRequest, Job client = AuthenticatedClient(base_url="https://api.nolgia.ai/v1", token=os.environ["NOLGIA_TOKEN"]) job = generate_video.sync(client=client, body=GenerateVideoRequest.from_dict({ "model": "seedance-2.5", "prompt": "a paper-cut mountain range at dawn, slow push-in as the sun rises", "audio_urls": [ os.environ["REFERENCE_URL"] ] })) if not isinstance(job, Job): raise SystemExit(f"refused: {job}") print(job.id) ``` ## element_asset_ids | Property | Value | | --- | --- | | Type | array of image-asset UUIDs or null | | Default | Omitted | | Values or range | At most 9 combined with `image_urls`, further limited by `references.element_refs_max` | | Applies to | `GenerateVideoRequest` | | CLI flag | Repeat `--element ASSET_ID` | These are image references for a video, not registry element ids. Address the attached images as `@Image1` and onward in the prompt. An images-only reference request is allowed where the model supports it; attaching a video is not inherently required. ```bash tab="curl" $ curl -s https://api.nolgia.ai/v1/generate/video \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H "Content-Type: application/json" \ -d '{"model":"seedance-2.5","prompt":"a paper-cut mountain range at dawn, slow push-in as the sun rises","element_asset_ids":["5a2f7c58-9e5a-4f1a-9c7f-2f0b6c3d1e42"]}' ``` ```ts tab="TypeScript" import { createNolgiaClient } from "@nolgia/sdk"; const nolgia = createNolgiaClient(process.env.NOLGIA_TOKEN!); const { data: job, error } = await nolgia.POST("/generate/video", { body: {"model":"seedance-2.5","prompt":"a paper-cut mountain range at dawn, slow push-in as the sun rises","element_asset_ids":["5a2f7c58-9e5a-4f1a-9c7f-2f0b6c3d1e42"]}, }); if (error) throw new Error(`${error.title}: ${error.detail ?? ""}`); console.log(job.id); ``` ```python tab="Python" import os from nolgia import AuthenticatedClient from nolgia.api.generate import generate_video from nolgia.models import GenerateVideoRequest, Job client = AuthenticatedClient(base_url="https://api.nolgia.ai/v1", token=os.environ["NOLGIA_TOKEN"]) job = generate_video.sync(client=client, body=GenerateVideoRequest.from_dict({ "model": "seedance-2.5", "prompt": "a paper-cut mountain range at dawn, slow push-in as the sun rises", "element_asset_ids": [ "5a2f7c58-9e5a-4f1a-9c7f-2f0b6c3d1e42" ] })) if not isinstance(job, Job): raise SystemExit(f"refused: {job}") print(job.id) ``` ## reference_voice_ids | Property | Value | | --- | --- | | Type | array of strings or null | | Default | Omitted | | Values or range | At most 3; limited by `references.voice_refs_max` | | Applies to | Grok Imagine 1.5 reference voices on models publishing voice-reference capability | | CLI flag | No dedicated flag | These select voices from the provider's roster rather than uploading audio. Address them as `` through `` in the prompt. The schema notes provider availability restrictions and a 720p output cap; pairing these voices with a 1080p tier is refused. ```bash tab="curl" $ curl -s https://api.nolgia.ai/v1/generate/video \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H "Content-Type: application/json" \ -d '{"model":"grok-imagine-video-1.5","prompt":"A guide says Welcome to the mountains.","reference_voice_ids":["eve"],"quality":"720p"}' ``` ```ts tab="TypeScript" import { createNolgiaClient } from "@nolgia/sdk"; const nolgia = createNolgiaClient(process.env.NOLGIA_TOKEN!); const { data: job, error } = await nolgia.POST("/generate/video", { body: {"model":"grok-imagine-video-1.5","prompt":"A guide says Welcome to the mountains.","reference_voice_ids":["eve"],"quality":"720p"}, }); if (error) throw new Error(`${error.title}: ${error.detail ?? ""}`); console.log(job.id); ``` ```python tab="Python" import os from nolgia import AuthenticatedClient from nolgia.api.generate import generate_video from nolgia.models import GenerateVideoRequest, Job client = AuthenticatedClient(base_url="https://api.nolgia.ai/v1", token=os.environ["NOLGIA_TOKEN"]) job = generate_video.sync(client=client, body=GenerateVideoRequest.from_dict({ "model": "grok-imagine-video-1.5", "prompt": "A guide says Welcome to the mountains.", "reference_voice_ids": [ "eve" ], "quality": "720p" })) if not isinstance(job, Job): raise SystemExit(f"refused: {job}") print(job.id) ``` ## character_id | Property | Value | | --- | --- | | Type | character UUID or null | | Default | Omitted | | Values or range | One character belonging to the caller | | Applies to | Image, video and audio requests; visual references must fit the chosen model | | CLI flag | `--character-id` for image/video; no audio flag | On image and video requests, the character supplies its canonical description and primary reference. Image identity constraints include one output and no competing `face_reference_asset_id`. For audio, the character can supply a catalog voice for this model; an explicit `voice` wins, and clip-voice cloning is not available. ```bash tab="curl" $ curl -s https://api.nolgia.ai/v1/generate/image \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H "Content-Type: application/json" \ -d '{"model":"gpt-image-2","prompt":"a paper-cut mountain range at dawn","character_id":"5a2f7c58-9e5a-4f1a-9c7f-2f0b6c3d1e42"}' ``` ```ts tab="TypeScript" import { createNolgiaClient } from "@nolgia/sdk"; const nolgia = createNolgiaClient(process.env.NOLGIA_TOKEN!); const { data: job, error } = await nolgia.POST("/generate/image", { body: {"num_images":1,"model":"gpt-image-2","prompt":"a paper-cut mountain range at dawn","character_id":"5a2f7c58-9e5a-4f1a-9c7f-2f0b6c3d1e42"}, }); if (error) throw new Error(`${error.title}: ${error.detail ?? ""}`); console.log(job.id); ``` ```python tab="Python" import os from nolgia import AuthenticatedClient from nolgia.api.generate import generate_image from nolgia.models import GenerateImageRequest, Job client = AuthenticatedClient(base_url="https://api.nolgia.ai/v1", token=os.environ["NOLGIA_TOKEN"]) job = generate_image.sync(client=client, body=GenerateImageRequest.from_dict({ "model": "gpt-image-2", "prompt": "a paper-cut mountain range at dawn", "character_id": "5a2f7c58-9e5a-4f1a-9c7f-2f0b6c3d1e42" })) if not isinstance(job, Job): raise SystemExit(f"refused: {job}") print(job.id) ``` ## character_ids | Property | Value | | --- | --- | | Type | ordered array of character UUIDs or null | | Default | Omitted | | Values or range | Up to 4 unique characters, limited by the model's reference capacity | | Applies to | Image and video requests | | CLI flag | No cast-array flag; use API clients | Order defines the cast, with the first character as lead. Every reference counts toward the model's budget; a cast that will not fit is refused. If you also send `character_id`, it must be one of the members. A character name mentioned as `@Name` is mapped to its cast/reference slot. ```bash tab="curl" $ curl -s https://api.nolgia.ai/v1/generate/image \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H "Content-Type: application/json" \ -d '{"model":"gpt-image-2","prompt":"a paper-cut mountain range at dawn","character_ids":["5a2f7c58-9e5a-4f1a-9c7f-2f0b6c3d1e42","b2fbb51e-3c33-4a54-b87b-9c2b1e4f9a11"]}' ``` ```ts tab="TypeScript" import { createNolgiaClient } from "@nolgia/sdk"; const nolgia = createNolgiaClient(process.env.NOLGIA_TOKEN!); const { data: job, error } = await nolgia.POST("/generate/image", { body: {"num_images":1,"model":"gpt-image-2","prompt":"a paper-cut mountain range at dawn","character_ids":["5a2f7c58-9e5a-4f1a-9c7f-2f0b6c3d1e42","b2fbb51e-3c33-4a54-b87b-9c2b1e4f9a11"]}, }); if (error) throw new Error(`${error.title}: ${error.detail ?? ""}`); console.log(job.id); ``` ```python tab="Python" import os from nolgia import AuthenticatedClient from nolgia.api.generate import generate_image from nolgia.models import GenerateImageRequest, Job client = AuthenticatedClient(base_url="https://api.nolgia.ai/v1", token=os.environ["NOLGIA_TOKEN"]) job = generate_image.sync(client=client, body=GenerateImageRequest.from_dict({ "model": "gpt-image-2", "prompt": "a paper-cut mountain range at dawn", "character_ids": [ "5a2f7c58-9e5a-4f1a-9c7f-2f0b6c3d1e42", "b2fbb51e-3c33-4a54-b87b-9c2b1e4f9a11" ] })) if not isinstance(job, Job): raise SystemExit(f"refused: {job}") print(job.id) ``` ## location_id | Property | Value | | --- | --- | | Type | location UUID or null | | Default | Omitted | | Values or range | One location belonging to the caller | | Applies to | Image and video; spare image/element-reference capacity is required when the location has an image | | CLI flag | No dedicated flag | A location contributes its canonical description and, when present, its primary reference. It has its own field beside the character so a shot can place a person in a consistent room. An unknown location or insufficient capacity is a `400`, not a silently dropped reference. ```bash tab="curl" $ curl -s https://api.nolgia.ai/v1/generate/image \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H "Content-Type: application/json" \ -d '{"model":"gpt-image-2","prompt":"a paper-cut mountain range at dawn","location_id":"5a2f7c58-9e5a-4f1a-9c7f-2f0b6c3d1e42"}' ``` ```ts tab="TypeScript" import { createNolgiaClient } from "@nolgia/sdk"; const nolgia = createNolgiaClient(process.env.NOLGIA_TOKEN!); const { data: job, error } = await nolgia.POST("/generate/image", { body: {"num_images":1,"model":"gpt-image-2","prompt":"a paper-cut mountain range at dawn","location_id":"5a2f7c58-9e5a-4f1a-9c7f-2f0b6c3d1e42"}, }); if (error) throw new Error(`${error.title}: ${error.detail ?? ""}`); console.log(job.id); ``` ```python tab="Python" import os from nolgia import AuthenticatedClient from nolgia.api.generate import generate_image from nolgia.models import GenerateImageRequest, Job client = AuthenticatedClient(base_url="https://api.nolgia.ai/v1", token=os.environ["NOLGIA_TOKEN"]) job = generate_image.sync(client=client, body=GenerateImageRequest.from_dict({ "model": "gpt-image-2", "prompt": "a paper-cut mountain range at dawn", "location_id": "5a2f7c58-9e5a-4f1a-9c7f-2f0b6c3d1e42" })) if not isinstance(job, Job): raise SystemExit(f"refused: {job}") print(job.id) ``` ## product_id | Property | Value | | --- | --- | | Type | product UUID or null | | Default | Omitted | | Values or range | One owned product; its primary and additional images use the remaining reference budget | | Applies to | Image and video requests; not video regeneration with `source_video_asset_id` | | CLI flag | No dedicated flag | A product contributes its description and up to three images, primary first, within the remaining model budget. If the primary image cannot fit the request is refused; extra gallery images beyond the budget do not ride. A product with no image contributes only its description. ```bash tab="curl" $ curl -s https://api.nolgia.ai/v1/generate/image \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H "Content-Type: application/json" \ -d '{"model":"gpt-image-2","prompt":"a paper-cut mountain range at dawn","product_id":"5a2f7c58-9e5a-4f1a-9c7f-2f0b6c3d1e42"}' ``` ```ts tab="TypeScript" import { createNolgiaClient } from "@nolgia/sdk"; const nolgia = createNolgiaClient(process.env.NOLGIA_TOKEN!); const { data: job, error } = await nolgia.POST("/generate/image", { body: {"num_images":1,"model":"gpt-image-2","prompt":"a paper-cut mountain range at dawn","product_id":"5a2f7c58-9e5a-4f1a-9c7f-2f0b6c3d1e42"}, }); if (error) throw new Error(`${error.title}: ${error.detail ?? ""}`); console.log(job.id); ``` ```python tab="Python" import os from nolgia import AuthenticatedClient from nolgia.api.generate import generate_image from nolgia.models import GenerateImageRequest, Job client = AuthenticatedClient(base_url="https://api.nolgia.ai/v1", token=os.environ["NOLGIA_TOKEN"]) job = generate_image.sync(client=client, body=GenerateImageRequest.from_dict({ "model": "gpt-image-2", "prompt": "a paper-cut mountain range at dawn", "product_id": "5a2f7c58-9e5a-4f1a-9c7f-2f0b6c3d1e42" })) if not isinstance(job, Job): raise SystemExit(f"refused: {job}") print(job.id) ``` ## brand_kit_id | Property | Value | | --- | --- | | Type | brand-kit UUID or null | | Default | Omitted: the generation's project can supply its linked brand kit | | Values or range | One owned brand kit | | Applies to | Image and video requests; logo attachment depends on lane and spare capacity | | CLI flag | No dedicated flag | The brand block is appended after your prompt. A logo never displaces a reference you already supplied; it rides only when the lane and available slots permit it. Image enhancement, expansion and masked requests do not attach a logo. The asset records whether it rode as `brand_logo_attached`. ```bash tab="curl" $ curl -s https://api.nolgia.ai/v1/generate/image \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H "Content-Type: application/json" \ -d '{"model":"gpt-image-2","prompt":"a paper-cut mountain range at dawn","brand_kit_id":"5a2f7c58-9e5a-4f1a-9c7f-2f0b6c3d1e42"}' ``` ```ts tab="TypeScript" import { createNolgiaClient } from "@nolgia/sdk"; const nolgia = createNolgiaClient(process.env.NOLGIA_TOKEN!); const { data: job, error } = await nolgia.POST("/generate/image", { body: {"num_images":1,"model":"gpt-image-2","prompt":"a paper-cut mountain range at dawn","brand_kit_id":"5a2f7c58-9e5a-4f1a-9c7f-2f0b6c3d1e42"}, }); if (error) throw new Error(`${error.title}: ${error.detail ?? ""}`); console.log(job.id); ``` ```python tab="Python" import os from nolgia import AuthenticatedClient from nolgia.api.generate import generate_image from nolgia.models import GenerateImageRequest, Job client = AuthenticatedClient(base_url="https://api.nolgia.ai/v1", token=os.environ["NOLGIA_TOKEN"]) job = generate_image.sync(client=client, body=GenerateImageRequest.from_dict({ "model": "gpt-image-2", "prompt": "a paper-cut mountain range at dawn", "brand_kit_id": "5a2f7c58-9e5a-4f1a-9c7f-2f0b6c3d1e42" })) if not isinstance(job, Job): raise SystemExit(f"refused: {job}") print(job.id) ``` ## brand_kit_mode | Property | Value | | --- | --- | | Type | `BrandKitMode` string | | Default | `full` | | Values or range | `full`, `prompt_only`, `off` | | Applies to | Image and video requests | | CLI flag | No dedicated flag | Omitting the mode applies the full kit. `prompt_only` adds the brand block without the logo. `off` disables both explicit and project branding and cannot be combined with `brand_kit_id`. ### BrandKitMode values | Value | | --- | | `full` | | `prompt_only` | | `off` | ```bash tab="curl" $ curl -s 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","brand_kit_mode":"off"}' ``` ```ts tab="TypeScript" import { createNolgiaClient } from "@nolgia/sdk"; const nolgia = createNolgiaClient(process.env.NOLGIA_TOKEN!); const { data: job, error } = await nolgia.POST("/generate/image", { body: {"num_images":1,"model":"flux-pro","prompt":"a paper-cut mountain range at dawn","brand_kit_mode":"off"}, }); if (error) throw new Error(`${error.title}: ${error.detail ?? ""}`); console.log(job.id); ``` ```python tab="Python" import os from nolgia import AuthenticatedClient from nolgia.api.generate import generate_image from nolgia.models import GenerateImageRequest, Job client = AuthenticatedClient(base_url="https://api.nolgia.ai/v1", token=os.environ["NOLGIA_TOKEN"]) job = generate_image.sync(client=client, body=GenerateImageRequest.from_dict({ "model": "flux-pro", "prompt": "a paper-cut mountain range at dawn", "brand_kit_mode": "off" })) if not isinstance(job, Job): raise SystemExit(f"refused: {job}") print(job.id) ``` ## style_id | Property | Value | | --- | --- | | Type | style UUID or null | | Default | Omitted: the generation's project can contribute its pinned style | | Values or range | One saved style visible to the caller | | Applies to | Image and video requests; not video regeneration with `source_video_asset_id` | | CLI flag | No dedicated flag | A style appends its prompt fragment after your words. Reference images ride only within remaining capacity; a swatch that does not fit is omitted while the fragment remains. Video adds a swatch only when the clip already has other visual input, so a standalone swatch does not become the subject. ```bash tab="curl" $ curl -s https://api.nolgia.ai/v1/generate/image \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H "Content-Type: application/json" \ -d '{"model":"gpt-image-2","prompt":"a paper-cut mountain range at dawn","style_id":"5a2f7c58-9e5a-4f1a-9c7f-2f0b6c3d1e42"}' ``` ```ts tab="TypeScript" import { createNolgiaClient } from "@nolgia/sdk"; const nolgia = createNolgiaClient(process.env.NOLGIA_TOKEN!); const { data: job, error } = await nolgia.POST("/generate/image", { body: {"num_images":1,"model":"gpt-image-2","prompt":"a paper-cut mountain range at dawn","style_id":"5a2f7c58-9e5a-4f1a-9c7f-2f0b6c3d1e42"}, }); if (error) throw new Error(`${error.title}: ${error.detail ?? ""}`); console.log(job.id); ``` ```python tab="Python" import os from nolgia import AuthenticatedClient from nolgia.api.generate import generate_image from nolgia.models import GenerateImageRequest, Job client = AuthenticatedClient(base_url="https://api.nolgia.ai/v1", token=os.environ["NOLGIA_TOKEN"]) job = generate_image.sync(client=client, body=GenerateImageRequest.from_dict({ "model": "gpt-image-2", "prompt": "a paper-cut mountain range at dawn", "style_id": "5a2f7c58-9e5a-4f1a-9c7f-2f0b6c3d1e42" })) if not isinstance(job, Job): raise SystemExit(f"refused: {job}") print(job.id) ``` ## element_ids | Property | Value | | --- | --- | | Type | array of registry-element UUIDs or null | | Default | Omitted | | Values or range | Image: up to 4; video: up to 9; all attached images still share the model's reference budget | | Applies to | Image and video requests | | CLI flag | No registry-element flag; video `--element` maps to `element_asset_ids`, a different field | Registry elements carry a canonical description and reference images. They must be visible in your personal or active organization library. A character id can also be used here, but naming the same character here and in `character_id` is refused. Excess references are rejected before credits are held. ```bash tab="curl" $ curl -s https://api.nolgia.ai/v1/generate/image \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H "Content-Type: application/json" \ -d '{"model":"gpt-image-2","prompt":"a paper-cut mountain range at dawn","element_ids":["5a2f7c58-9e5a-4f1a-9c7f-2f0b6c3d1e42"]}' ``` ```ts tab="TypeScript" import { createNolgiaClient } from "@nolgia/sdk"; const nolgia = createNolgiaClient(process.env.NOLGIA_TOKEN!); const { data: job, error } = await nolgia.POST("/generate/image", { body: {"num_images":1,"model":"gpt-image-2","prompt":"a paper-cut mountain range at dawn","element_ids":["5a2f7c58-9e5a-4f1a-9c7f-2f0b6c3d1e42"]}, }); if (error) throw new Error(`${error.title}: ${error.detail ?? ""}`); console.log(job.id); ``` ```python tab="Python" import os from nolgia import AuthenticatedClient from nolgia.api.generate import generate_image from nolgia.models import GenerateImageRequest, Job client = AuthenticatedClient(base_url="https://api.nolgia.ai/v1", token=os.environ["NOLGIA_TOKEN"]) job = generate_image.sync(client=client, body=GenerateImageRequest.from_dict({ "model": "gpt-image-2", "prompt": "a paper-cut mountain range at dawn", "element_ids": [ "5a2f7c58-9e5a-4f1a-9c7f-2f0b6c3d1e42" ] })) if not isinstance(job, Job): raise SystemExit(f"refused: {job}") print(job.id) ``` ## motion_id | Property | Value | | --- | --- | | Type | string or null | | Default | Omitted | | Values or range | A move id from `GET /motions`, up to 64 characters | | Applies to | `GenerateVideoRequest`; works on every video model | | CLI flag | `--motion` | The server appends the selected camera move's prompt fragment as its own sentence; it does not replace or shorten your prompt. Use it for text-to-video, image-to-video or a multi-shot request. An unknown id is a `400` naming the motion library. ```bash tab="curl" $ curl -s https://api.nolgia.ai/v1/generate/video \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H "Content-Type: application/json" \ -d '{"model":"veo-3.1-lite","prompt":"a paper-cut mountain range at dawn, slow push-in as the sun rises","duration_seconds":4,"motion_id":"push-in"}' ``` ```ts tab="TypeScript" import { createNolgiaClient } from "@nolgia/sdk"; const nolgia = createNolgiaClient(process.env.NOLGIA_TOKEN!); const { data: job, error } = await nolgia.POST("/generate/video", { body: {"model":"veo-3.1-lite","prompt":"a paper-cut mountain range at dawn, slow push-in as the sun rises","duration_seconds":4,"motion_id":"push-in"}, }); if (error) throw new Error(`${error.title}: ${error.detail ?? ""}`); console.log(job.id); ``` ```python tab="Python" import os from nolgia import AuthenticatedClient from nolgia.api.generate import generate_video from nolgia.models import GenerateVideoRequest, Job client = AuthenticatedClient(base_url="https://api.nolgia.ai/v1", token=os.environ["NOLGIA_TOKEN"]) job = generate_video.sync(client=client, body=GenerateVideoRequest.from_dict({ "model": "veo-3.1-lite", "prompt": "a paper-cut mountain range at dawn, slow push-in as the sun rises", "duration_seconds": 4, "motion_id": "push-in" })) if not isinstance(job, Job): raise SystemExit(f"refused: {job}") print(job.id) ``` ## motion_strength | Property | Value | | --- | --- | | Type | `CameraMoveStrength` string | | Default | The move's `default_strength`, currently `medium` | | Values or range | `subtle`, `medium`, `strong` | | Applies to | Video requests with `motion_id`; refused without a move | | CLI flag | `--motion-strength`, requires `--motion` | Strength selects how far and how fast the camera move travels over the clip. The motion catalog provides the exact prompt fragment at each strength. ### CameraMoveStrength values | Value | | --- | | `subtle` | | `medium` | | `strong` | ```bash tab="curl" $ curl -s https://api.nolgia.ai/v1/generate/video \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H "Content-Type: application/json" \ -d '{"model":"veo-3.1-lite","prompt":"a paper-cut mountain range at dawn, slow push-in as the sun rises","duration_seconds":4,"motion_id":"push-in","motion_strength":"subtle"}' ``` ```ts tab="TypeScript" import { createNolgiaClient } from "@nolgia/sdk"; const nolgia = createNolgiaClient(process.env.NOLGIA_TOKEN!); const { data: job, error } = await nolgia.POST("/generate/video", { body: {"model":"veo-3.1-lite","prompt":"a paper-cut mountain range at dawn, slow push-in as the sun rises","duration_seconds":4,"motion_id":"push-in","motion_strength":"subtle"}, }); if (error) throw new Error(`${error.title}: ${error.detail ?? ""}`); console.log(job.id); ``` ```python tab="Python" import os from nolgia import AuthenticatedClient from nolgia.api.generate import generate_video from nolgia.models import GenerateVideoRequest, Job client = AuthenticatedClient(base_url="https://api.nolgia.ai/v1", token=os.environ["NOLGIA_TOKEN"]) job = generate_video.sync(client=client, body=GenerateVideoRequest.from_dict({ "model": "veo-3.1-lite", "prompt": "a paper-cut mountain range at dawn, slow push-in as the sun rises", "duration_seconds": 4, "motion_id": "push-in", "motion_strength": "subtle" })) if not isinstance(job, Job): raise SystemExit(f"refused: {job}") print(job.id) ``` ## aura | Property | Value | | --- | --- | | Type | boolean or null | | Default | On for person prompts on `image.aura_compatible` models; off otherwise | | Values or range | `true` or `false` | | Applies to | `GenerateImageRequest` | | CLI flag | `--aura true` or `--aura false` | Aura composes people as photographs with real skin and lighting. An explicit `false` overrides the person-prompt default. On a model without the capability, `true` is a no-op; a face reference is an identity request and cannot be combined with `aura: false`. ```bash tab="curl" $ curl -s https://api.nolgia.ai/v1/generate/image \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H "Content-Type: application/json" \ -d '{"model":"flux-pro","prompt":"a portrait of a mountain guide in natural morning light","aura":true}' ``` ```ts tab="TypeScript" import { createNolgiaClient } from "@nolgia/sdk"; const nolgia = createNolgiaClient(process.env.NOLGIA_TOKEN!); const { data: job, error } = await nolgia.POST("/generate/image", { body: {"num_images":1,"model":"flux-pro","prompt":"a portrait of a mountain guide in natural morning light","aura":true}, }); if (error) throw new Error(`${error.title}: ${error.detail ?? ""}`); console.log(job.id); ``` ```python tab="Python" import os from nolgia import AuthenticatedClient from nolgia.api.generate import generate_image from nolgia.models import GenerateImageRequest, Job client = AuthenticatedClient(base_url="https://api.nolgia.ai/v1", token=os.environ["NOLGIA_TOKEN"]) job = generate_image.sync(client=client, body=GenerateImageRequest.from_dict({ "model": "flux-pro", "prompt": "a portrait of a mountain guide in natural morning light", "aura": True })) if not isinstance(job, Job): raise SystemExit(f"refused: {job}") print(job.id) ``` ## voice | Property | Value | | --- | --- | | Type | string or null | | Default | Model-specific; an eligible character can supply its catalog voice | | Values or range | A model-specific voice id, at most 128 characters; read `audio.voices` | | Applies to | `GenerateAudioRequest` for text to speech | | CLI flag | `--voice`; discover with `nolgia voices list --model` | Select a voice published for the TTS model. A voice id from another provider is not interchangeable, and an explicit voice overrides the character's saved voice. ```bash tab="curl" $ curl -s https://api.nolgia.ai/v1/generate/audio \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H "Content-Type: application/json" \ -d '{"model":"kokoro-us-english","prompt":"The sun rises over the mountains.","voice":"af_bella"}' ``` ```ts tab="TypeScript" import { createNolgiaClient } from "@nolgia/sdk"; const nolgia = createNolgiaClient(process.env.NOLGIA_TOKEN!); const { data: job, error } = await nolgia.POST("/generate/audio", { body: {"model":"kokoro-us-english","prompt":"The sun rises over the mountains.","voice":"af_bella"}, }); if (error) throw new Error(`${error.title}: ${error.detail ?? ""}`); console.log(job.id); ``` ```python tab="Python" import os from nolgia import AuthenticatedClient from nolgia.api.generate import generate_audio from nolgia.models import GenerateAudioRequest, Job client = AuthenticatedClient(base_url="https://api.nolgia.ai/v1", token=os.environ["NOLGIA_TOKEN"]) job = generate_audio.sync(client=client, body=GenerateAudioRequest.from_dict({ "model": "kokoro-us-english", "prompt": "The sun rises over the mountains.", "voice": "af_bella" })) if not isinstance(job, Job): raise SystemExit(f"refused: {job}") print(job.id) ``` ## speed | Property | Value | | --- | --- | | Type | number or null | | Default | Omitted; `1` represents the voice's natural pace | | Values or range | Shared schema `0.7` through `1.3`; use the model's `audio.speed` range | | Applies to | Text-to-speech models publishing `audio.speed` | | CLI flag | No dedicated flag | Speed changes speaking rate without changing character-based pricing. The allowed range is per model; the schema documents an upper bound of `1.2` for ElevenLabs and `1.3` for MiniMax and Kokoro. Unsupported models or values outside their range return `400`. ```bash tab="curl" $ curl -s https://api.nolgia.ai/v1/generate/audio \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H "Content-Type: application/json" \ -d '{"model":"kokoro-us-english","prompt":"The sun rises over the mountains.","speed":1.1}' ``` ```ts tab="TypeScript" import { createNolgiaClient } from "@nolgia/sdk"; const nolgia = createNolgiaClient(process.env.NOLGIA_TOKEN!); const { data: job, error } = await nolgia.POST("/generate/audio", { body: {"model":"kokoro-us-english","prompt":"The sun rises over the mountains.","speed":1.1}, }); if (error) throw new Error(`${error.title}: ${error.detail ?? ""}`); console.log(job.id); ``` ```python tab="Python" import os from nolgia import AuthenticatedClient from nolgia.api.generate import generate_audio from nolgia.models import GenerateAudioRequest, Job client = AuthenticatedClient(base_url="https://api.nolgia.ai/v1", token=os.environ["NOLGIA_TOKEN"]) job = generate_audio.sync(client=client, body=GenerateAudioRequest.from_dict({ "model": "kokoro-us-english", "prompt": "The sun rises over the mountains.", "speed": 1.1 })) if not isinstance(job, Job): raise SystemExit(f"refused: {job}") print(job.id) ``` ## format | Property | Value | | --- | --- | | Type | `AudioFormat` string | | Default | `mp3` | | Values or range | `mp3`, `wav`, `ogg`, `flac` | | Applies to | `GenerateAudioRequest` | | CLI flag | `--format` | Request the audio format you want to download. The response is still a job; read the finished asset's MIME type and signed URL when it succeeds. ### AudioFormat values | Value | | --- | | `mp3` | | `wav` | | `ogg` | | `flac` | ```bash tab="curl" $ curl -s https://api.nolgia.ai/v1/generate/audio \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H "Content-Type: application/json" \ -d '{"model":"kokoro-us-english","prompt":"The sun rises over the mountains.","format":"wav"}' ``` ```ts tab="TypeScript" import { createNolgiaClient } from "@nolgia/sdk"; const nolgia = createNolgiaClient(process.env.NOLGIA_TOKEN!); const { data: job, error } = await nolgia.POST("/generate/audio", { body: {"model":"kokoro-us-english","prompt":"The sun rises over the mountains.","format":"wav"}, }); if (error) throw new Error(`${error.title}: ${error.detail ?? ""}`); console.log(job.id); ``` ```python tab="Python" import os from nolgia import AuthenticatedClient from nolgia.api.generate import generate_audio from nolgia.models import GenerateAudioRequest, Job client = AuthenticatedClient(base_url="https://api.nolgia.ai/v1", token=os.environ["NOLGIA_TOKEN"]) job = generate_audio.sync(client=client, body=GenerateAudioRequest.from_dict({ "model": "kokoro-us-english", "prompt": "The sun rises over the mountains.", "format": "wav" })) if not isinstance(job, Job): raise SystemExit(f"refused: {job}") print(job.id) ``` ## texture | Property | Value | | --- | --- | | Type | boolean | | Default | `true` | | Values or range | `true` or `false`; disabling texture requires `hunyuan3d-v3` | | Applies to | `Generate3DRequest` | | CLI flag | `--no-texture` sends `false` | Disable texture to produce an untextured white model. The draft model does not offer the untextured option, and PBR requires texture to remain enabled. ```bash tab="curl" $ curl -s https://api.nolgia.ai/v1/generate/3d \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H "Content-Type: application/json" \ -d '{"model":"hunyuan3d-v3","image_asset_ids":["5a2f7c58-9e5a-4f1a-9c7f-2f0b6c3d1e42"],"texture":false}' ``` ```ts tab="TypeScript" import { createNolgiaClient } from "@nolgia/sdk"; const nolgia = createNolgiaClient(process.env.NOLGIA_TOKEN!); const { data: job, error } = await nolgia.POST("/generate/3d", { body: {"model":"hunyuan3d-v3","image_asset_ids":["5a2f7c58-9e5a-4f1a-9c7f-2f0b6c3d1e42"],"texture":false}, }); if (error) throw new Error(`${error.title}: ${error.detail ?? ""}`); console.log(job.id); ``` ```python tab="Python" import os from nolgia import AuthenticatedClient from nolgia.api.generate import generate_3d from nolgia.models import Generate3DRequest, Job client = AuthenticatedClient(base_url="https://api.nolgia.ai/v1", token=os.environ["NOLGIA_TOKEN"]) job = generate_3d.sync(client=client, body=Generate3DRequest.from_dict({ "model": "hunyuan3d-v3", "image_asset_ids": [ "5a2f7c58-9e5a-4f1a-9c7f-2f0b6c3d1e42" ], "texture": False })) if not isinstance(job, Job): raise SystemExit(f"refused: {job}") print(job.id) ``` ## pbr | Property | Value | | --- | --- | | Type | boolean | | Default | `false` | | Values or range | `true` or `false`; requires `hunyuan3d-v3` and textures | | Applies to | `Generate3DRequest` | | CLI flag | `--pbr` | Request PBR materials when you need that output from the textured 3D route. Quote the exact settings first because this option changes the request's price. ```bash tab="curl" $ curl -s https://api.nolgia.ai/v1/generate/3d \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H "Content-Type: application/json" \ -d '{"model":"hunyuan3d-v3","image_asset_ids":["5a2f7c58-9e5a-4f1a-9c7f-2f0b6c3d1e42"],"texture":true,"pbr":true}' ``` ```ts tab="TypeScript" import { createNolgiaClient } from "@nolgia/sdk"; const nolgia = createNolgiaClient(process.env.NOLGIA_TOKEN!); const { data: job, error } = await nolgia.POST("/generate/3d", { body: {"model":"hunyuan3d-v3","image_asset_ids":["5a2f7c58-9e5a-4f1a-9c7f-2f0b6c3d1e42"],"texture":true,"pbr":true}, }); if (error) throw new Error(`${error.title}: ${error.detail ?? ""}`); console.log(job.id); ``` ```python tab="Python" import os from nolgia import AuthenticatedClient from nolgia.api.generate import generate_3d from nolgia.models import Generate3DRequest, Job client = AuthenticatedClient(base_url="https://api.nolgia.ai/v1", token=os.environ["NOLGIA_TOKEN"]) job = generate_3d.sync(client=client, body=Generate3DRequest.from_dict({ "model": "hunyuan3d-v3", "image_asset_ids": [ "5a2f7c58-9e5a-4f1a-9c7f-2f0b6c3d1e42" ], "texture": True, "pbr": True })) if not isinstance(job, Job): raise SystemExit(f"refused: {job}") print(job.id) ``` ## image_asset_ids | Property | Value | | --- | --- | | Type | ordered array of image-asset UUIDs | | Default | No default: supply exactly this field or `image_url` | | Values or range | One through four images in front, back, left, right order; `trellis` takes exactly one | | Applies to | `Generate3DRequest` | | CLI flag | Repeat `gen 3d --input PATH_OR_UUID` in view order | The order of views matters. Use your own stored assets, and do not combine the array with a hosted `image_url`. Multiple views are available only on the model that supports them. ```bash tab="curl" $ curl -s https://api.nolgia.ai/v1/generate/3d \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H "Content-Type: application/json" \ -d '{"model":"hunyuan3d-v3","image_asset_ids":["5a2f7c58-9e5a-4f1a-9c7f-2f0b6c3d1e42"]}' ``` ```ts tab="TypeScript" import { createNolgiaClient } from "@nolgia/sdk"; const nolgia = createNolgiaClient(process.env.NOLGIA_TOKEN!); const { data: job, error } = await nolgia.POST("/generate/3d", { body: {"model":"hunyuan3d-v3","image_asset_ids":["5a2f7c58-9e5a-4f1a-9c7f-2f0b6c3d1e42"]}, }); if (error) throw new Error(`${error.title}: ${error.detail ?? ""}`); console.log(job.id); ``` ```python tab="Python" import os from nolgia import AuthenticatedClient from nolgia.api.generate import generate_3d from nolgia.models import Generate3DRequest, Job client = AuthenticatedClient(base_url="https://api.nolgia.ai/v1", token=os.environ["NOLGIA_TOKEN"]) job = generate_3d.sync(client=client, body=Generate3DRequest.from_dict({ "model": "hunyuan3d-v3", "image_asset_ids": [ "5a2f7c58-9e5a-4f1a-9c7f-2f0b6c3d1e42" ] })) if not isinstance(job, Job): raise SystemExit(f"refused: {job}") print(job.id) ``` ## tags | Property | Value | | --- | --- | | Type | array of strings | | Default | Omitted | | Values or range | At most 10 tags; each 1–40 characters | | Applies to | All four generation requests | | CLI flag | Repeat `gen 3d --tag`; no image/video/audio generation flag | Tags are applied to the resulting assets and normalized to lowercase. They label the output; they are not provider prompt text. ```bash tab="curl" $ curl -s 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","tags":["hero","draft"]}' ``` ```ts tab="TypeScript" import { createNolgiaClient } from "@nolgia/sdk"; const nolgia = createNolgiaClient(process.env.NOLGIA_TOKEN!); const { data: job, error } = await nolgia.POST("/generate/image", { body: {"num_images":1,"model":"flux-pro","prompt":"a paper-cut mountain range at dawn","tags":["hero","draft"]}, }); if (error) throw new Error(`${error.title}: ${error.detail ?? ""}`); console.log(job.id); ``` ```python tab="Python" import os from nolgia import AuthenticatedClient from nolgia.api.generate import generate_image from nolgia.models import GenerateImageRequest, Job client = AuthenticatedClient(base_url="https://api.nolgia.ai/v1", token=os.environ["NOLGIA_TOKEN"]) job = generate_image.sync(client=client, body=GenerateImageRequest.from_dict({ "model": "flux-pro", "prompt": "a paper-cut mountain range at dawn", "tags": [ "hero", "draft" ] })) if not isinstance(job, Job): raise SystemExit(f"refused: {job}") print(job.id) ``` ## project_id | Property | Value | | --- | --- | | Type | project UUID | | Default | Omitted; agent-session context can provide filing | | Values or range | A project belonging to the caller | | Applies to | All four generation requests | | CLI flag | `--project-id` | File the generated assets in a project. An explicit project takes precedence over automatic agent-session attribution. The async video and 3D outputs are filed when the job completes; an unknown or foreign project is refused. ```bash tab="curl" $ curl -s 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","project_id":"5a2f7c58-9e5a-4f1a-9c7f-2f0b6c3d1e42"}' ``` ```ts tab="TypeScript" import { createNolgiaClient } from "@nolgia/sdk"; const nolgia = createNolgiaClient(process.env.NOLGIA_TOKEN!); const { data: job, error } = await nolgia.POST("/generate/image", { body: {"num_images":1,"model":"flux-pro","prompt":"a paper-cut mountain range at dawn","project_id":"5a2f7c58-9e5a-4f1a-9c7f-2f0b6c3d1e42"}, }); if (error) throw new Error(`${error.title}: ${error.detail ?? ""}`); console.log(job.id); ``` ```python tab="Python" import os from nolgia import AuthenticatedClient from nolgia.api.generate import generate_image from nolgia.models import GenerateImageRequest, Job client = AuthenticatedClient(base_url="https://api.nolgia.ai/v1", token=os.environ["NOLGIA_TOKEN"]) job = generate_image.sync(client=client, body=GenerateImageRequest.from_dict({ "model": "flux-pro", "prompt": "a paper-cut mountain range at dawn", "project_id": "5a2f7c58-9e5a-4f1a-9c7f-2f0b6c3d1e42" })) if not isinstance(job, Job): raise SystemExit(f"refused: {job}") print(job.id) ``` ## preset_slug | Property | Value | | --- | --- | | Type | string or null | | Default | Omitted on direct API, CLI, MCP and pipeline calls | | Values or range | At most 128 characters | | Applies to | All four generation requests | | CLI flag | No dedicated generation flag | The web app supplies this when a generation originates from a preset. It is attribution, stored verbatim without checking whether that preset still exists. Setting the slug does not load a preset or fill in missing model arguments; the example remains a complete generation body. ```bash tab="curl" $ curl -s 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","preset_slug":"cinematic-portrait"}' ``` ```ts tab="TypeScript" import { createNolgiaClient } from "@nolgia/sdk"; const nolgia = createNolgiaClient(process.env.NOLGIA_TOKEN!); const { data: job, error } = await nolgia.POST("/generate/image", { body: {"num_images":1,"model":"flux-pro","prompt":"a paper-cut mountain range at dawn","preset_slug":"cinematic-portrait"}, }); if (error) throw new Error(`${error.title}: ${error.detail ?? ""}`); console.log(job.id); ``` ```python tab="Python" import os from nolgia import AuthenticatedClient from nolgia.api.generate import generate_image from nolgia.models import GenerateImageRequest, Job client = AuthenticatedClient(base_url="https://api.nolgia.ai/v1", token=os.environ["NOLGIA_TOKEN"]) job = generate_image.sync(client=client, body=GenerateImageRequest.from_dict({ "model": "flux-pro", "prompt": "a paper-cut mountain range at dawn", "preset_slug": "cinematic-portrait" })) if not isinstance(job, Job): raise SystemExit(f"refused: {job}") print(job.id) ``` ## confirmation_token | Property | Value | | --- | --- | | Type | string | | Default | Omitted; the confirmation gate is optional | | Values or range | The token returned by `POST /jobs/cost` for this exact request and price, before `expires_at` | | Applies to | All four generation requests | | CLI flag | No dedicated flag | Quote the request, show that price to the customer, then return the quote's token with the unchanged generation body. An expired, malformed, foreign or mismatched token is refused with `422` and `code: confirmation_rejected`. Omitting the token does not activate the gate. Set `CONFIRMATION_TOKEN` to the token from your own quote, not the redacted value in an example response. ```bash tab="curl" $ curl -s 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","confirmation_token":""}' ``` ```ts tab="TypeScript" import { createNolgiaClient } from "@nolgia/sdk"; const nolgia = createNolgiaClient(process.env.NOLGIA_TOKEN!); const { data: job, error } = await nolgia.POST("/generate/image", { body: {"num_images":1,"model":"flux-pro","prompt":"a paper-cut mountain range at dawn","confirmation_token":process.env.CONFIRMATION_TOKEN!}, }); if (error) throw new Error(`${error.title}: ${error.detail ?? ""}`); console.log(job.id); ``` ```python tab="Python" import os from nolgia import AuthenticatedClient from nolgia.api.generate import generate_image from nolgia.models import GenerateImageRequest, Job client = AuthenticatedClient(base_url="https://api.nolgia.ai/v1", token=os.environ["NOLGIA_TOKEN"]) job = generate_image.sync(client=client, body=GenerateImageRequest.from_dict({ "model": "flux-pro", "prompt": "a paper-cut mountain range at dawn", "confirmation_token": os.environ["CONFIRMATION_TOKEN"] })) if not isinstance(job, Job): raise SystemExit(f"refused: {job}") print(job.id) ``` ## bitrate_mode | Property | Value | | --- | --- | | Type | `BitrateMode` string | | Default | Provider default where a model supports bitrate selection | | Values or range | `standard`, `high` | | Applies to | Video models publishing `video.bitrate_modes`; none currently published | | CLI flag | `--bitrate` | No currently published model exposes bitrate selection. The schema reserves these two values, and sending the field to a model without the capability is a `400`. These request fragments illustrate the field only; do not add it to a current generation. ### BitrateMode values | Value | | --- | | `standard` | | `high` | ```bash tab="curl" title="request fragment; unsupported by current models" # JSON field for a model that publishes bitrate selection: # "bitrate_mode": "standard" ``` ```ts tab="TypeScript" title="request fragment; unsupported by current models" const bitrate = { bitrate_mode: "standard" as const }; ``` ```python tab="Python" title="request fragment; unsupported by current models" bitrate = {"bitrate_mode": "standard"} ``` ## video_task | Property | Value | | --- | --- | | Type | `VideoTask` string or null | | Default | Omitted; the provider classifies the task from the prompt | | Values or range | `reference`, `edit`, `extend`, as published in `references.video_tasks` | | Applies to | Video requests carrying a reference video on a supporting model | | CLI flag | No dedicated flag | Choose what the input footage is for. `reference` uses its motion and timing to drive a new render; `edit` changes one thing while keeping source length and aspect ratio; `extend` continues it by the requested new duration. Edit takes one owned video with known duration of 4–30 seconds, refuses raw URLs, and uses the source duration when you omit the field. Unsupported tasks or missing reference video return `400`. ### VideoTask values | Value | | --- | | `reference` | | `edit` | | `extend` | ```bash tab="curl" $ curl -s https://api.nolgia.ai/v1/generate/video \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H "Content-Type: application/json" \ -d '{"model":"seedance-2.5","prompt":"a paper-cut mountain range at dawn, slow push-in as the sun rises","video_asset_ids":["5a2f7c58-9e5a-4f1a-9c7f-2f0b6c3d1e42"],"video_task":"reference"}' ``` ```ts tab="TypeScript" import { createNolgiaClient } from "@nolgia/sdk"; const nolgia = createNolgiaClient(process.env.NOLGIA_TOKEN!); const { data: job, error } = await nolgia.POST("/generate/video", { body: {"model":"seedance-2.5","prompt":"a paper-cut mountain range at dawn, slow push-in as the sun rises","video_asset_ids":["5a2f7c58-9e5a-4f1a-9c7f-2f0b6c3d1e42"],"video_task":"reference"}, }); if (error) throw new Error(`${error.title}: ${error.detail ?? ""}`); console.log(job.id); ``` ```python tab="Python" import os from nolgia import AuthenticatedClient from nolgia.api.generate import generate_video from nolgia.models import GenerateVideoRequest, Job client = AuthenticatedClient(base_url="https://api.nolgia.ai/v1", token=os.environ["NOLGIA_TOKEN"]) job = generate_video.sync(client=client, body=GenerateVideoRequest.from_dict({ "model": "seedance-2.5", "prompt": "a paper-cut mountain range at dawn, slow push-in as the sun rises", "video_asset_ids": [ "5a2f7c58-9e5a-4f1a-9c7f-2f0b6c3d1e42" ], "video_task": "reference" })) if not isinstance(job, Job): raise SystemExit(f"refused: {job}") print(job.id) ``` ## Every field, from the spec These generated tables are the request source of truth. The sections above explain the shared controls; the full schemas also cover specialized controls, including image guidance, explicit face references, video shots and regeneration. See the [API reference](../api/) for operation responses and the [OpenAPI document](../api/openapi.yaml) for complete descriptions and constraints. ### Image request | Field | Type | Required | Description | | --- | --- | --- | --- | | `confirmation_token` | string | No | Optional proof that this exact request and price were shown to the customer, from `POST /jobs/cost`.… | | `model` | `ImageModel` | Yes | | | `prompt` | string | No | What to generate.… | | `negative_prompt` | string, nullable | No | Negative prompt text.… | | `image_size` | `ImageSize` | No | | | `aspect_ratio` | `ImageAspectRatio` | No | | | `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. | | `seed` | integer, nullable | No | Reproducibility seed. The same seed with the same prompt and settings gives the same image on a model whose provider honors one; omit it for a different result every time. Not every provider honors it. | | `guidance_scale` | number, nullable | No | | | `image_url` | string, nullable | No | Source image (https URL) for image-to-image restyle. When set, the model transforms this image instead of generating from scratch. | | `image_urls` | array of string, nullable | No | Multiple reference images (https URLs) for models that support multi-reference input (OpenAI gpt-image models — e.g.… | | `reference_asset_ids` | array of string, nullable | No | Reference images given as ids of your own image assets — the server resolves each to a signed URL and it rides the same multi-reference path as `image_urls` (the image lane's sibling of the video lane's `element_asset_ids`).… | | `render_quality` | one of `auto`, `low`, `medium`, `high`, `xhigh`, `max` | No | How much detail the model spends on the render itself, on models that publish `image.render_quality` (the GPT Image family).… | | `mask_asset_id` | string, nullable | No | Edit only PART of the reference image.… | | `quality` | string, nullable | No | Quality/resolution tier for the selected model.… | | `tags` | array of string | No | Applied to the resulting asset(s); normalized to lowercase. | | `project_id` | string | No | Files the generated asset(s) into this project at creation.… | | `preset_slug` | string, nullable | No | Slug of the preset this generation was launched from, if any.… | | `aura` | boolean, nullable | No | Applies Aura, the Nolgia character engine: server-side photoreal composition layered onto the prompt (people render as photographs, real skin, real light, no AI gloss).… | | `face_reference_asset_id` | string, nullable | No | Image asset (owned by the caller) whose face conditions the render for identity.… | | `face_check_consent` | boolean, nullable | No | Records your consent to the face identity check for the `face_reference_asset_id` photo (once per photo; it stays recorded for later requests).… | | `character_id` | string, nullable | No | One of your characters (`GET /characters`, created on the Create Characters page).… | | `character_ids` | array of string, nullable | No | An ORDERED cast of up to 4 of your characters rendered together (two-person dialogue scenes, duets, family commercials).… | | `location_id` | string, nullable | No | One of your locations (`GET /locations`): the place this render is set in.… | | `brand_kit_id` | string, nullable | No | One of your brand kits (`GET /brand-kits`).… | | `brand_kit_mode` | `BrandKitMode` | No | | | `product_id` | string, nullable | No | One of your products (`GET /products`): the product this render shows.… | | `style_id` | string, nullable | No | One of your saved styles (`GET /styles`).… | | `element_ids` | array of string, nullable | No | Registry elements (`GET /elements`) to condition this render: each element's reference images are attached as image references (after any `image_url`/`image_urls` and any face/character reference, which keeps its slot) and its `canonical_description` rides into the prompt verbatim with the binding clause ("100% matches the reference") - the belt-and-braces continuity stack.… | ### Video request | Field | Type | Required | Description | | --- | --- | --- | --- | | `confirmation_token` | string | No | Optional proof that this exact request and price were shown to the customer, from `POST /jobs/cost`.… | | `model` | `VideoModel` | Yes | | | `prompt` | string | Yes | Text prompt.… | | `negative_prompt` | string, nullable | No | What to keep OUT of the clip.… | | `image_url` | string, nullable | No | Required for image-to-video models; ignored for text-to-video. | | `end_image_url` | string, nullable | No | Final-frame image (https URL) for start+end frame pinning on models that support it (Seedance 2.0 Pro i2v, Seedance 2.5, Seedance 2.0 Fast and Mini, FLUX 3 Video, MiniMax Hailuo 3, MiniMax H3 Max i2v).… | | `end_image_asset_id` | string, nullable | No | One of your image assets to use as the final frame (server resolves it to a signed URL). Same semantics as `end_image_url`; provide at most one of the two. | | `video_asset_ids` | array of string, nullable | No | Reference videos for models with `references.video_refs_max > 0` on `GET /models` (`seedance-2.5`: up to 10; `seedance-2.0-pro-r2v`: up to 3; `minimax-h3` takes none), given as ids of your video assets — the server resolves each to a signed URL and forwards them as the provider's `video_urls`.… | | `video_urls` | array of string, nullable | No | Raw https reference-video URLs, for callers that host media outside Nolgia.… | | `video_task` | `VideoTask` | No | | | `element_asset_ids` | array of string, nullable | No | Element/reference images for reference-to-video models, given as ids of your image assets — resolved to signed URLs and forwarded as the provider's `image_urls`.… | | `image_urls` | array of string, nullable | No | Raw https element-image URLs. Counted against the same 9-image cap as `element_asset_ids`. Prefer `element_asset_ids`. | | `audio_asset_ids` | array of string, nullable | No | Reference audio tracks for models with `references.audio_refs_max > 0` on `GET /models` (`seedance-2.5`: up to 10 with no per-track ceiling; `minimax-h3`: up to 3, each at most 15 seconds; Seedance 2.0 Pro r2v takes none, its primary route has no reference-audio slot), given as ids of your audio assets — resolved to signed URLs and forwarded as the provider's `audio_urls`.… | | `audio_urls` | array of string, nullable | No | Raw https reference-audio URLs. Counted against the same per-model cap as `audio_asset_ids` (`references.audio_refs_max`, never more than 10). Prefer `audio_asset_ids`. | | `reference_voice_ids` | array of string, nullable | No | Preset voices for Grok Imagine 1.5 reference-to-video (models with `references.voice_refs_max > 0` on `GET /models`), each a `voice_id` from xAI's Text-to-Speech roster (for example `eve`, the default; case-insensitive, validated by xAI).… | | `bitrate_mode` | `BitrateMode` | No | | | `aspect_ratio` | `AspectRatio` | No | | | `duration_seconds` | integer | No | Clip length in seconds.… | | `seed` | integer, nullable | No | Reproducibility seed.… | | `generate_audio` | boolean, nullable | No | Ask the model to generate a synchronized audio track (dialogue/ambient/SFX).… | | `strip_audio` | boolean, nullable | No | Deliver the clip with NO audio stream at all: every audio track the model rendered is removed on delivery by a stream copy (the video stream is untouched), and the asset records `has_audio: false`.… | | `quality` | string, nullable | No | Quality/resolution tier for the selected model (e.g.… | | `shots` | array of `VideoShot`, nullable | No | Multi-shot sequence: divide the clip into sequential shots, each with its own prompt, duration, and optional sound direction.… | | `tags` | array of string | No | Applied to the resulting asset(s); normalized to lowercase. | | `project_id` | string | No | Files the generated asset into this project at creation (the async video asset lands in the project when the job completes).… | | `preset_slug` | string, nullable | No | Slug of the preset this generation was launched from, if any.… | | `source_video_asset_id` | string, nullable | No | Regenerate one of your completed videos at the model's top resolution: the referenced video asset is re-rendered at 2K via the provider's native regeneration flow (models with `video.regeneration` on `GET /models` — MiniMax Hailuo 3's 768P→2K Video Regeneration).… | | `character_id` | string, nullable | No | One of your characters (`GET /characters`).… | | `use_character_voice` | boolean, nullable | No | Attaches the lead character's (character_id, or the first of character_ids) voice clip as a reference audio track (audio_asset_ids, emitted after your own audio tracks and audio_urls, taking the next @Audio slot) and adds a voice line to the prompt.… | | `character_ids` | array of string, nullable | No | An ORDERED cast of up to 4 of your characters rendered together in one clip.… | | `location_id` | string, nullable | No | One of your locations (`GET /locations`): the place this clip is set in.… | | `brand_kit_id` | string, nullable | No | One of your brand kits (`GET /brand-kits`).… | | `brand_kit_mode` | `BrandKitMode` | No | | | `product_id` | string, nullable | No | One of your products (`GET /products`): the product this clip shows.… | | `style_id` | string, nullable | No | One of your saved styles (`GET /styles`).… | | `element_ids` | array of string, nullable | No | Registry elements (`GET /elements`) to condition this clip: each element's reference images are appended to the element-reference slots (before any `character_id` reference, which stays appended last) and its `canonical_description` rides into the prompt verbatim with the binding clause ("100% matches the reference") - the belt-and-braces continuity stack.… | | `motion_id` | string, nullable | No | A camera move from the library on `GET /motions` (for example `push-in`, `orbit-left`, `crane-up`, `rack-focus`).… | | `motion_strength` | `CameraMoveStrength` | No | | ### Audio request | Field | Type | Required | Description | | --- | --- | --- | --- | | `confirmation_token` | string | No | Optional proof that this exact request and price were shown to the customer, from `POST /jobs/cost`.… | | `model` | `AudioModel` | Yes | | | `prompt` | string | Yes | For TTS this is the text to speak; for music/SFX it is the description.… | | `voice` | string, nullable | No | TTS voice id (model-specific): one of the model's `audio.voices`, or the `voice_id` of one of your custom voices (GET /voices) on a model whose `audio.custom_voices` is true.… | | `speed` | number, nullable | No | Speaking rate as a multiple of the voice's natural pace; 1 is natural.… | | `character_id` | string, nullable | No | One of your characters.… | | `duration_seconds` | integer, nullable | No | Currently ignored; never forwarded to audio providers. | | `format` | `AudioFormat` | No | | | `tags` | array of string | No | Applied to the resulting asset(s); normalized to lowercase. | | `project_id` | string | No | Files the generated asset into this project at creation.… | | `preset_slug` | string, nullable | No | Slug of the preset this generation was launched from, if any.… | ### 3D request | Field | Type | Required | Description | | --- | --- | --- | --- | | `confirmation_token` | string | No | Optional proof that this exact request and price were shown to the customer, from `POST /jobs/cost`.… | | `model` | `Generate3DModel` | No | Defaults to hunyuan3d-v3 when both model and quality are omitted. | | `quality` | one of `draft`, `standard` | No | Draft selects trellis; standard selects hunyuan3d-v3. A value that contradicts an explicit model returns 400. | | `image_asset_ids` | array of string | No | Your image assets in front, back, left, right order. Trellis takes exactly one. Supply exactly one of image_asset_ids and image_url. | | `image_url` | string | No | Hosted HTTPS front image. Supply exactly one of image_asset_ids and image_url. | | `texture` | boolean | No | Defaults to true. False creates an untextured white model and is available only with hunyuan3d-v3. | | `pbr` | boolean | No | Defaults to false. PBR materials require hunyuan3d-v3 with textures enabled. | | `tags` | array of string | No | Applied to the 3D asset; normalized to lowercase. | | `project_id` | string | No | Files the 3D asset into this project when the job completes. The project must exist and belong to the caller (400 otherwise). | | `preset_slug` | string, nullable | No | Slug of the preset this generation was launched from, if any. Same semantics as on `POST /generate/video`. | ## Traps > [!WARNING] > A public-figure likeness refusal can fail with `ip_detected`. The observed refunded refusal reports `failure.credits_refunded: true`; read that field on your own job instead of inferring a refund from the code. A provider-billed moderation failure can report `false`. > [!NOTE] > For about ten minutes after an API deploy, a brand-new model id can answer `400 validation` with “unknown … model” from an instance on the previous revision while the new revision already accepts it. If the new id is in the catalog, retry after a minute instead of changing a correct model id. > [!TIP] > `POST /jobs/cost` validates the generation body before quoting it and refuses invalid requests with the same generation error code as submission. Use it to catch an unsupported tier, duration or reference combination before starting a job. There is no safety-checker toggle or prompt-expansion flag on generation requests; the server composes the image prompt itself, `enhanced_prompt` on the asset shows what ran, and `POST /generate/video/enhance-prompt` is a separate one-credit helper. ## Error responses | Situation | Response | | --- | --- | | Unknown model, unsupported field value or invalid reference combination | `400`, `code: validation`; correct the request, except for the new-model deploy window above | | Expired, foreign, malformed or mismatched confirmation token | `422`, `code: confirmation_rejected` | | Wallet cannot pay for the request | `402`, `code: out_of_credits` | | Submitted job later fails | Read `failure.code`, `failure.message` and `failure.credits_refunded` on the job | ## Next steps :::cards - [Model APIs](./models.html): Browse the catalog and choose an inference method. - [Uploads](./uploads.html): Put references in your Library before generating. - [Errors](./errors.html): Handle validation, policy failures and refunds explicitly. ::: --- # Uploads and files Source: https://docs.nolgia.ai/guides/uploads.md Upload a file once, keep its asset id, and reuse it as a reference. Uploaded media lives in the same Library as generated media and can be filed into a project. ![Uploads](../assets/art/uploads.jpg) ## Two ways to upload | Route | Use it for | Limit | | --- | --- | --- | | Base64 JSON: `POST /assets` | Small PNG, JPEG or WebP images | About 10 MB decoded; the `data` field accepts at most 14,000,000 base64 characters | | Signed PUT: `POST /assets/uploads`, PUT bytes, then complete | Images, video, audio and GLB models | Images: 100 MiB; video: 50 GiB; audio: 5 GiB; 3D: 200 MiB | The base64 route returns a stored asset immediately. The signed route first creates an `uploading` asset; only completion verifies the bytes and makes it `ready`. The CLI's upload command selects the upload flow for the file. ## Small files | Property | Value | | --- | --- | | Endpoint | `POST /assets` | | Required fields | `content_type`, `data` | | File types | `image/png`, `image/jpeg`, `image/webp` | | Optional filing | `filename`, `project_id` | | Success | `201` with an `Asset` | These are examples, not live upload captures. Set `NOLGIA_TOKEN` as in the [Quick Start](./getting-started.html) and replace `reference.png` with a small local image. The curl command uses macOS `base64 -i`; on GNU systems use `base64 reference.png`. Rust also uses the `base64` crate for encoding. ```bash tab="curl" title="Small image upload example" $ curl -sS https://api.nolgia.ai/v1/assets \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H "Content-Type: application/json" \ -d '{"content_type":"image/png","filename":"reference.png","data":""}' ``` ```bash tab="CLI" title="Local file upload example" $ nolgia --json assets upload reference.png ``` ```ts tab="TypeScript" title="Small image upload example" import { readFile } from "node:fs/promises"; import { createNolgiaClient } from "@nolgia/sdk"; const nolgia = createNolgiaClient(process.env.NOLGIA_TOKEN!); const { data: asset, error } = await nolgia.POST("/assets", { body: { content_type: "image/png", filename: "reference.png", data: (await readFile("reference.png")).toString("base64"), }, }); if (error) throw new Error(`${error.title}: ${error.detail ?? ""}`); console.log(asset.id); ``` ```python tab="Python" title="Small image upload example" import base64 import os from pathlib import Path from nolgia import AuthenticatedClient from nolgia.api.assets import upload_asset from nolgia.models import Asset, UploadAssetRequest client = AuthenticatedClient(base_url="https://api.nolgia.ai/v1", token=os.environ["NOLGIA_TOKEN"]) asset = upload_asset.sync(client=client, body=UploadAssetRequest.from_dict({ "content_type": "image/png", "filename": "reference.png", "data": base64.b64encode(Path("reference.png").read_bytes()).decode("ascii"), })) if not isinstance(asset, Asset): raise SystemExit(f"refused: {asset}") print(asset.id) ``` ```rust tab="Rust" title="Small image upload example" use base64::{engine::general_purpose::STANDARD, Engine}; use nolgia_client::ClientBuilder; #[tokio::main] async fn main() -> Result<(), Box> { let client = ClientBuilder::new("https://api.nolgia.ai/v1") .bearer_token(std::env::var("NOLGIA_TOKEN")?) .build()?; let encoded = STANDARD.encode(std::fs::read("reference.png")?); let asset = client.upload_asset() .body_map(|b| b.content_type("image/png").data(encoded)) .send().await?.into_inner(); println!("{}", asset.id); Ok(()) } ``` The HTTP response is an `Asset`; this schema-built excerpt shows a stored image. The snippets print its reusable id. The omitted `model` field records upload provenance, not a generation model from the catalog. ```json title="201 Asset — schema-built excerpt" { "id": "11111111-1111-4111-8111-111111111111", "user_id": "22222222-2222-4222-8222-222222222222", "modality": "image", "display_name": "reference", "mime_type": "image/png", "signed_url": "https://storage.googleapis.com/example/reference.png?…", "expires_at": "2026-09-21T05:00:00Z", "status": "ready", "created_at": "2026-09-21T03:30:00Z" } ``` | Response field | Meaning | | --- | --- | | `id` | Store this id and pass it to a supported asset-reference field | | `user_id`, `created_at` | Who uploaded the file and when | | `modality` | The media kind | | `display_name`, `mime_type` | Human-readable name and stored media type | | `signed_url`, `expires_at` | Temporary download location and its real expiry | | `status` | `ready` means the file is stored and usable | ## Large files, step by step ![Upload handshake: create an upload, PUT the bytes, and complete the asset](../assets/diagrams/upload-handshake.svg) ### 1. Create the upload | Property | Value | | --- | --- | | Endpoint | `POST /assets/uploads` | | Success | `201` with an `AssetUpload` | | Required values | Original filename, exact MIME type and exact byte size | | Lifetime | The signed PUT URL lasts 30 minutes | | Field | Type | Required | Description | | --- | --- | --- | --- | | `filename` | string | Yes | Original filename, kept as asset metadata. | | `content_type` | one of `video/mp4`, `video/quicktime`, `video/webm`, `audio/mpeg`, `audio/wav`, `audio/ogg`, `audio/webm`, `audio/mp4`, `image/png`, `image/jpeg`, `image/webp`, `model/gltf-binary` | Yes | MIME type of the file. The signed PUT URL is bound to it, so the upload request must send exactly this Content-Type header. | | `size_bytes` | integer | Yes | Exact size of the file in bytes. Verified against the uploaded object on completion. Limits: 50 GiB for video, 5 GiB for audio, 100 MiB for images, 200 MiB for 3D models. | | `tags` | array of string | No | Tags to apply to the asset; trimmed, lowercased, and de-duplicated. | | `display_name` | string, nullable | No | Optional human-readable display name stored on the pre-created asset (trimmed). Defaults to empty. | | `project_id` | string, nullable | No | Files the uploaded asset into this caller-owned project directly (no tag matching involved); an unknown or foreign project returns 400. Without it, the asset lands in the default "Library" project. | For a local `final-cut.mp4`, measure the size instead of estimating it. Keep the response in `upload` for the next two calls. ```bash title="Create signed upload example" $ curl -sS https://api.nolgia.ai/v1/assets/uploads \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H "Content-Type: application/json" \ -d '{"filename":"final-cut.mp4","content_type":"video/mp4","size_bytes":}' ``` ```json title="201 AssetUpload — schema-built example" { "upload_id": "33333333-3333-4333-8333-333333333333", "asset_id": "44444444-4444-4444-8444-444444444444", "upload_url": "https://storage.googleapis.com/example/final-cut.mp4?…", "expires_at": "2026-09-21T04:00:00Z" } ``` | Field | Type | Required | Description | | --- | --- | --- | --- | | `upload_id` | string | Yes | Identifier for `POST /assets/uploads/{id}/complete`. | | `asset_id` | string | Yes | The pre-created asset (status `uploading`) the bytes will belong to. | | `upload_url` | string | Yes | Signed PUT URL. Upload with `PUT ` and a `Content-Type` header exactly matching the declared `content_type`; no Authorization header is needed. | | `expires_at` | string | Yes | Expiry of `upload_url` (30 minutes after creation). | ### 2. PUT the bytes | Property | Value | | --- | --- | | Destination | The returned `upload_url`, used as-is | | Method and body | `PUT` with the raw file bytes | | Header | `Content-Type: video/mp4`, exactly matching the declaration | | Authentication | The URL contains its authorization; send no bearer token | ```bash title="PUT the file example" $ curl -sS -X PUT "" -H "Content-Type: video/mp4" --upload-file final-cut.mp4 ``` ```http title="Storage PUT success — example" HTTP/1.1 200 OK Content-Length: 0 ``` The successful storage PUT has no JSON body. It does not complete the asset; the API still needs the verification call below. > [!WARNING] > The PUT's `Content-Type` must match `content_type` exactly because the signature covers it. A different type, an expired URL or an incomplete file is not fixed by calling complete. Create a new upload if its 30-minute URL has expired. ### 3. Complete the asset | Property | Value | | --- | --- | | Endpoint | `POST /assets/uploads/{id}/complete`; `{id}` is `upload_id` | | Verification | The stored object's size must equal declared `size_bytes` | | Success | `200` with the `ready` asset | | Retry behavior | Completing an already completed upload returns the same asset | | Incomplete upload | Missing object or mismatched size returns `409` | ```bash title="Complete signed upload example" $ curl -sS -X POST "https://api.nolgia.ai/v1/assets/uploads//complete" -H "Authorization: Bearer $NOLGIA_TOKEN" ``` ```json title="200 Asset — schema-built completion excerpt" { "id": "44444444-4444-4444-8444-444444444444", "user_id": "22222222-2222-4222-8222-222222222222", "modality": "video", "mime_type": "video/mp4", "signed_url": "https://storage.googleapis.com/example/final-cut.mp4?…", "expires_at": "2026-09-21T05:00:00Z", "status": "ready", "created_at": "2026-09-21T03:30:00Z" } ``` | Response field | Meaning | | --- | --- | | `id` | The same id as the create response's `asset_id` | | `status` | Changes from `uploading` to `ready` after verification | | `signed_url`, `expires_at` | Download URL, separate from the upload URL | | `modality`, `mime_type` | The stored video's kind and media type | | `user_id`, `created_at` | Owner and original creation time | Default `GET /assets` listings hide unfinished uploads. Media duration, audio detection and thumbnails may arrive later through the media sweep; `ready` does not mean every optional metadata field is already populated. ## Using your own storage Pass your own HTTPS URL to a request's supported `*_url` field, or a Library asset id to its `*_asset_id` / `*_asset_ids` field. The URL must remain reachable when processing starts. Reference support still depends on the model; see [Common model arguments](./model-arguments.html). These two image request bodies illustrate the same choice. Use a reference-capable model and replace the URL or id with your own. ```json title="POST /generate/image with image_url — request example" { "model": "gpt-image-2", "prompt": "Place this product on a plain white background", "image_url": "https://media.example.com/product.png" } ``` ```json title="POST /generate/image with reference_asset_ids — request example" { "model": "gpt-image-2", "prompt": "Place this product on a plain white background", "reference_asset_ids": ["11111111-1111-4111-8111-111111111111"] } ``` Both submit a job. This schema-built response applies to either request; follow the id with [submit and poll](./jobs.html). ```json title="202 Job — schema-built example" { "id": "55555555-5555-4555-8555-555555555555", "user_id": "22222222-2222-4222-8222-222222222222", "modality": "image", "model": "gpt-image-2", "status": "queued", "created_at": "2026-09-21T03:30:00Z", "updated_at": "2026-09-21T03:30:00Z" } ``` | Response field | Meaning | | --- | --- | | `id` | The durable job id to poll | | `status` | `queued` means accepted, not finished | | `user_id`, `modality`, `model` | Caller and selected generation route | | `created_at`, `updated_at` | Acceptance and latest job-update times | > [!TIP] > Prefer asset ids for files already in your Library. The server re-signs those references at execution, so a queued job does not start with an expired saved download URL. An external URL remains your responsibility to keep accessible. ## Upload details | Property | Value | | --- | --- | | Base64 limit | About 10 MB decoded; PNG, JPEG and WebP only | | Signed-upload image limit | 100 MiB | | Signed-upload video limit | 50 GiB | | Signed-upload audio limit | 5 GiB | | Signed-upload 3D limit | 200 MiB | | PUT content type | Must exactly match the declared `content_type` | | Signed-upload tags | Up to 10; trimmed, lowercased and deduplicated; completion applies tag-based project associations | | Direct project filing | Set `project_id`; unknown or foreign project returns `400`; omitted means the default Library project | | Display name | Supply `filename` for a recognizable name; signed uploads also accept `display_name` | Accepted MIME types for signed uploads are generated from the request schema: - `video/mp4` - `video/quicktime` - `video/webm` - `audio/mpeg` - `audio/wav` - `audio/ogg` - `audio/webm` - `audio/mp4` - `image/png` - `image/jpeg` - `image/webp` - `model/gltf-binary` ## Google Drive Import a media file into the Library or export an asset to Google Drive. Both calls use a short-lived Google OAuth token with the `drive.file` scope for that request only. Import accepts the signed-upload media types and size limits; Google Docs, Sheets, and Slides are not media imports. These calls are available on Studio plans, Team, and Enterprise. | Method | Path | What it does | | --- | --- | --- | | [POST](../api/#tag/assets/post/assets/imports/google-drive) | `/assets/imports/google-drive` | Import a Google Drive file into the library. | | [POST](../api/#tag/assets/post/assets/{id}/exports/google-drive) | `/assets/{id}/exports/google-drive` | Save an asset to Google Drive. | ## Expiry and access Upload URLs and download URLs have different lifetimes. Read [Storage and data retention](./storage.html) for signed URL renewal and deletion, and [File access controls](./file-access.html) for Library scope and share links that remain useful after a download URL expires. :::cards - [Storage and data retention](./storage.html): Refresh download URLs and manage trash or permanent deletion. - [File access controls](./file-access.html): Share a file with an expiry and a revoke. - [Common model arguments](./model-arguments.html): Choose a supported reference field for your model. ::: --- # Storage and data retention Source: https://docs.nolgia.ai/guides/storage.md Keep asset ids in your application and fetch download URLs when you need them. A signed URL's expiry controls access to the bytes; it is separate from when the asset is deleted. ## Generated media Uploaded and generated assets are stored in Google Cloud Storage (GCS). `signed_url` is stable for about an hour: reads within the same clock hour return the same signed URL so the media can be cached across refreshes. Each asset read signs for the current window, with at least one hour of validity remaining; `expires_at` reports the real expiry. | Property | Value | | --- | --- | | Durable identifier | `Asset.id` | | Download location | `signed_url` | | Exact deadline | `expires_at` | | Refresh | Read `GET /assets/{id}` again | | Thumbnail | `thumbnail_url` for an image downscale or video poster; null when absent | | Attachment download | `GET /assets/{id}?disposition=attachment` | > [!WARNING] > Never store a signed URL as the permanent address of a file. Store the asset id, re-read the asset for a current URL, and follow `expires_at`. An expired URL does not mean that the asset has been deleted. Use a [share link](./file-access.html#share-links) when another person needs durable access. | Field | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | Yes | | | `signed_url` | string | Yes | Time-limited GCS signed URL for download.… | | `expires_at` | string | Yes | Expiry of `signed_url`. | | `mime_type` | string | No | | | `size_bytes` | integer, nullable | 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.… | | `thumbnail_url` | string, nullable | No | Time-limited signed URL for a server-generated thumbnail (image downscale or video poster frame).… | | `status` | `AssetStatus` | No | | | `deleted_at` | string, nullable | No | When the asset was moved to the trash (soft-deleted). Omitted for live assets; only trash listings (`GET /assets?trashed=true`) return trashed assets. Trashed assets are purged permanently 30 days after this timestamp. | | `created_at` | string | Yes | | Optional media metadata can lag behind the bytes. Uploaded or older videos may have no duration, audio verdict or poster until the asynchronous media sweep has inspected them. A missing `thumbnail_url` is not a failed asset. ## Request payloads The asset stores the prompt. For images, `prompt` preserves the customer's words; `enhanced_prompt` contains the composed prompt that actually ran when it differs. For video and audio, `prompt` is the dispatched prompt and `enhanced_prompt` is absent or null. Uploaded assets may have no prompt. There is no switch to stop storing prompts and no per-request expiry header. | Field | What is retained | | --- | --- | | `prompt` | Customer image prompt, or dispatched video/audio prompt | | `enhanced_prompt` | Composed image prompt, only when different from `prompt` | | `created_at` | When the asset was created | | `deleted_at` | When it entered trash; omitted on live assets | ## Trash and deletion The lifecycle is **live → trash → permanent deletion**. `DELETE /assets/{id}` moves an asset to trash and keeps its stored bytes for recovery. The API purges trashed assets 30 days after `deleted_at`, or you can call the permanent-delete endpoint earlier. | Action | Endpoint | Result | | --- | --- | --- | | Move to trash | `DELETE /assets/{id}` | `204`; hidden from default reads and listings | | List trash | `GET /assets?trashed=true` | Only soft-deleted assets | | Restore | `POST /assets/{id}/restore` | `200` asset with a current signed URL; already-live assets are an idempotent no-op | | Delete forever | `DELETE /assets/{id}/permanent` | `204`; works on both live and trashed assets | The trash filter is **`trashed=true`**. The separate `status` parameter chooses `uploading` or `ready`; it does not select trash. Deleting an already-trashed asset returns `404`. ### Move an asset to trash and restore it | Property | Value | | --- | --- | | Input | `ASSET_ID`, an asset in your current Library scope | | Delete response | `204`, no response body | | Restore response | `200`, an `Asset` | | Recovery window | Restore before the 30-day purge or a permanent deletion | These examples perform both operations in order. The CLI supports `assets delete`; it has no restore command, so its tab uses the HTTP restore call. The Rust example configures an empty-body `Content-Length` for the generated bodyless POST. ```bash tab="curl" title="Trash and restore example" $ curl --fail-with-body -sS -X DELETE "https://api.nolgia.ai/v1/assets/$ASSET_ID" \ -H "Authorization: Bearer $NOLGIA_TOKEN" $ curl --fail-with-body -sS -X POST "https://api.nolgia.ai/v1/assets/$ASSET_ID/restore" \ -H "Authorization: Bearer $NOLGIA_TOKEN" --data '' ``` ```bash tab="CLI" title="Trash and HTTP restore example" $ nolgia assets delete "$ASSET_ID" $ curl --fail-with-body -sS -X POST "https://api.nolgia.ai/v1/assets/$ASSET_ID/restore" \ -H "Authorization: Bearer $NOLGIA_TOKEN" --data '' ``` ```ts tab="TypeScript" title="Trash and restore example" import { createNolgiaClient } from "@nolgia/sdk"; const nolgia = createNolgiaClient(process.env.NOLGIA_TOKEN!); const params = { path: { id: process.env.ASSET_ID! } }; const { error: deleteError } = await nolgia.DELETE("/assets/{id}", { params }); if (deleteError) throw new Error(`${deleteError.title}: ${deleteError.detail ?? ""}`); const { data: asset, error } = await nolgia.POST("/assets/{id}/restore", { params }); if (error) throw new Error(`${error.title}: ${error.detail ?? ""}`); console.log(asset.id, asset.signed_url); ``` ```python tab="Python" title="Trash and restore example" import os from http import HTTPStatus from uuid import UUID from nolgia import AuthenticatedClient from nolgia.api.assets import delete_asset, restore_asset from nolgia.models import Asset client = AuthenticatedClient(base_url="https://api.nolgia.ai/v1", token=os.environ["NOLGIA_TOKEN"]) asset_id = UUID(os.environ["ASSET_ID"]) deleted = delete_asset.sync_detailed(asset_id, client=client) if deleted.status_code != HTTPStatus.NO_CONTENT: raise SystemExit(f"refused: {deleted.parsed}") asset = restore_asset.sync(asset_id, client=client) if not isinstance(asset, Asset): raise SystemExit(f"refused: {asset}") print(asset.id, asset.signed_url) ``` ```rust tab="Rust" title="Trash and restore example" use nolgia_client::Client; use reqwest::header::{HeaderMap, HeaderValue, AUTHORIZATION, CONTENT_LENGTH}; #[tokio::main] async fn main() -> Result<(), Box> { let mut headers = HeaderMap::new(); headers.insert(AUTHORIZATION, HeaderValue::from_str( &format!("Bearer {}", std::env::var("NOLGIA_TOKEN")?), )?); headers.insert(CONTENT_LENGTH, HeaderValue::from_static("0")); let http = reqwest::Client::builder().default_headers(headers).build()?; let client = Client::new_with_client("https://api.nolgia.ai/v1", http); let id = std::env::var("ASSET_ID")?.parse::()?; client.delete_asset().id(id).send().await?; let asset = client.restore_asset().id(id).send().await?.into_inner(); println!("{} {}", asset.id, asset.signed_url); Ok(()) } ``` Deletion returns `204 No Content`, with no JSON. Restoration returns an asset; this is a schema-built excerpt, not a captured restore. The omitted `model` field records upload provenance for this uploaded image. ```json title="200 restored Asset — schema-built excerpt" { "id": "11111111-1111-4111-8111-111111111111", "user_id": "22222222-2222-4222-8222-222222222222", "modality": "image", "signed_url": "https://storage.googleapis.com/example/reference.png?…", "expires_at": "2026-09-21T05:00:00Z", "status": "ready", "created_at": "2026-09-20T03:30:00Z" } ``` | Response field | Meaning | | --- | --- | | `id`, `user_id` | The original asset and its creator | | `modality`, `status` | Media kind and upload-completion state | | `signed_url`, `expires_at` | Current download URL and deadline | | `created_at` | Original creation time, preserved by restoration | | `deleted_at` | Cleared by restoration and omitted for this live asset | > [!WARNING] > Permanent deletion cannot be undone. It removes the asset row and deletes the stored bytes immediately when possible, otherwise through the deferred cleanup queue. A share link to trashed or deleted media answers `410`. | Method | Path | What it does | | --- | --- | --- | | [GET](../api/#tag/assets/get/assets/{id}) | `/assets/{id}` | Fetch one of the current user's assets with a fresh signed URL. | | [PATCH](../api/#tag/assets/patch/assets/{id}) | `/assets/{id}` | Update one of the current user's assets (tags, display name, prompt, metadata, favorite). | | [DELETE](../api/#tag/assets/delete/assets/{id}) | `/assets/{id}` | Move one of the current user's assets to the trash (soft delete). | | [POST](../api/#tag/assets/post/assets/{id}/restore) | `/assets/{id}/restore` | Restore a trashed asset back into the library. | | [DELETE](../api/#tag/assets/delete/assets/{id}/permanent) | `/assets/{id}/permanent` | Permanently delete one of the current user's assets. | ## Bring your own storage Enterprise organizations can connect an S3-compatible bucket. An owner or admin creates the connection, tests its credentials, and enables mirroring so assets are copied as they become ready. Existing ready assets can be backfilled, and objects in the bucket can be imported into the Library. The original Nolgia asset remains the id you use with the API. | Property | Value | | --- | --- | | Availability | Enterprise organization; owner or admin manages connections | | Providers | AWS S3 or compatible endpoints such as R2, MinIO and GCS interoperability | | Automatic copy | Enable `mirror_enabled` on the connection | | Destination layout | `///.` | | Progress | Read the asset's `mirrors` array on `GET /assets/{id}` | | Existing assets | Queue a backfill for ready, non-trashed organization assets | `mirrors` is present only for an organization asset with at least one storage connection. Each entry names the connection, its copy status and, once available, the remote object key. Read that status before assuming the copy has completed. | Field | Type | Required | Description | | --- | --- | --- | --- | | `connection_id` | string | Yes | | | `status` | `AssetMirrorStatus` | Yes | | | `remote_key` | string, nullable | No | Object key in the customer's bucket once copied. | | `last_error` | string, nullable | No | Last copy failure; the worker retries up to five times with backoff before settling on `error`. | | `updated_at` | string | Yes | | | Method | Path | What it does | | --- | --- | --- | | [GET](../api/#tag/organizations/get/organizations/{id}/storage-connections) | `/organizations/{id}/storage-connections` | List the organization's bring-your-own storage connections (owner or admin; Enterprise). | | [POST](../api/#tag/organizations/post/organizations/{id}/storage-connections) | `/organizations/{id}/storage-connections` | Connect an S3-compatible bucket to the organization (owner or admin; Enterprise). | | [PATCH](../api/#tag/organizations/patch/organizations/{id}/storage-connections/{connection_id}) | `/organizations/{id}/storage-connections/{connection_id}` | Update a storage connection (owner or admin; Enterprise). | | [DELETE](../api/#tag/organizations/delete/organizations/{id}/storage-connections/{connection_id}) | `/organizations/{id}/storage-connections/{connection_id}` | Disconnect a storage connection (owner or admin; Enterprise). | | [POST](../api/#tag/organizations/post/organizations/{id}/storage-connections/{connection_id}/test) | `/organizations/{id}/storage-connections/{connection_id}/test` | Probe a storage connection (owner or admin; Enterprise). | | [POST](../api/#tag/organizations/post/organizations/{id}/storage-connections/{connection_id}/import) | `/organizations/{id}/storage-connections/{connection_id}/import` | Import objects from the connected bucket into the organization library (owner or admin; Enterprise). | | [POST](../api/#tag/organizations/post/organizations/{id}/storage-connections/{connection_id}/backfill) | `/organizations/{id}/storage-connections/{connection_id}/backfill` | Queue every ready organization asset for mirroring to this connection (owner or admin; Enterprise). | ## Summary | Data type | Default retention or lifetime | Control | | --- | --- | --- | | Live uploaded or generated asset | Kept in the Library; download URL expiry does not delete it | Trash or permanently delete the asset | | Trashed asset and its bytes | Purged 30 days after `deleted_at` | Restore before purge, or permanently delete sooner | | Asset download and thumbnail URLs | Stable for about an hour; exact download expiry in `expires_at` | Re-read the asset for a current signature | | Signed upload URL | 30 minutes | Create a new upload after expiry | | Prompt and enhanced image prompt | Stored with the asset | No storage opt-out or per-request expiry header | | Public share link | 30 days by default; 1–365 days at creation | Set `expires_in_days` or revoke it | | Organization mirror | A copy in your connected bucket | Manage the storage connection and your bucket's own lifecycle | :::cards - [Uploads and files](./uploads.html): Put small or large reference media into the Library. - [File access controls](./file-access.html): Choose Library access or a durable share link. - [Teams and organizations](./organizations.html): Manage a shared Library and storage connections. ::: --- # File access controls Source: https://docs.nolgia.ai/guides/file-access.md Library access follows the caller's personal or organization scope. To give someone access without an account, create a share link with a deadline, keep the returned URL, and revoke the link when it is no longer needed. There is no per-file ACL on the storage object itself; access is the Library scope plus share links. ## How access works | Request and caller | Result | Why | | --- | --- | --- | | `GET /assets/{id}` for your live asset in personal scope | `200` with an asset and signed URL | The asset is in your Library | | `GET /assets/{id}` for a teammate's live asset in your active organization | `200` with an asset and signed URL | Organization assets belong to the shared Library; `user_id` still identifies the creator | | Share management for an asset outside the caller's Library scope | `404` | The target must be visible in the current scope | | Create a share link as a viewer or billing role | `403 Read-only role` | Reading the Library does not grant sharing authority | | Create a share link as a member for another teammate's asset | `403 Forbidden` | Members may share only assets they created; owners/admins may share any | | Public resolver for an active share link | `302` to temporary media | No account or bearer token is required | | Public resolver for a revoked/expired link or trashed/deleted media | `410` | The link no longer provides access | | Public resolver for an unknown token | `404` | No matching link exists | | Public resolver over its per-IP limit | `429`, `Retry-After: 60` | Wait before trying again | The `302` is the resolver response, not the media body. Follow its `Location` to fetch the bytes. ## Library scope Personal assets belong to your personal Library. In organization context, teammates can read the shared Library, while the asset's `user_id` continues to name its creator. Choose the active context with `PUT /me/active-organization`; organization API keys stay bound to their organization. See [Teams and organizations](./organizations.html) for switching context and roles. | Role or scope | Create a share link | Revoke a share link | | --- | --- | --- | | Personal asset owner | Own ready, untrashed assets | Links on own assets | | Organization owner/admin | Any ready, untrashed asset in that organization | Links on any asset in that organization | | Organization member | Assets they created | Links they created or links on assets they can change | | Organization viewer/billing | Refused with `403 Read-only role` | Refused with `403 Read-only role` | > [!NOTE] > Being able to see a teammate's asset does not mean you can share it. A member must be its creator; an owner or admin can share any visible organization asset. Creating a link for unfinished or trashed media is refused with `409`. ## Share links ![Create a share link, resolve it to a short-lived media URL, and revoke it to return 410](../assets/diagrams/share-link-lifecycle.svg) ### Create a link | Property | Value | | --- | --- | | Endpoint | `POST /assets/{id}/share` | | Default lifetime | 30 days | | Custom lifetime | `expires_in_days`, an integer from 1 through 365 | | Success | `201` with a `ShareLink` | | Returned once | `token` and `url`; later listings cannot recover either | | Field | Type | Required | Description | | --- | --- | --- | --- | | `expires_in_days` | integer | No | Lifetime of the link in days from now. Defaults to 30. Ignored when `never_expires` is true. | | `never_expires` | boolean | No | A link with no end date. Refused (`400`, code `share_expiry_exceeds_organization_limit`) when the organization sets a maximum lifetime. | | `password` | string | No | Viewers must enter this password before the media is served. Stored only as an argon2id hash; never returned. | | `view_only` | boolean | No | Turn downloads off for this link. The share page offers no Download and the resolver refuses `download=true`. A viewer who can play media can still capture it, so treat this as a deterrent, not copy protection. | Set `ASSET_ID` to a ready asset you are allowed to share. These request examples use a seven-day lifetime. The CLI currently has no share command; its tab uses curl with the same `NOLGIA_TOKEN` credential. ```bash tab="curl" title="Create share link example" $ curl --fail-with-body -sS "https://api.nolgia.ai/v1/assets/$ASSET_ID/share" \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H "Content-Type: application/json" -d '{"expires_in_days":7}' ``` ```bash tab="CLI" title="Share through the HTTP API example" $ curl --fail-with-body -sS "https://api.nolgia.ai/v1/assets/$ASSET_ID/share" \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H "Content-Type: application/json" -d '{"expires_in_days":7}' ``` ```ts tab="TypeScript" title="Create share link example" import { createNolgiaClient } from "@nolgia/sdk"; const nolgia = createNolgiaClient(process.env.NOLGIA_TOKEN!); const { data: link, error } = await nolgia.POST("/assets/{id}/share", { params: { path: { id: process.env.ASSET_ID! } }, body: { expires_in_days: 7 }, }); if (error) throw new Error(`${error.title}: ${error.detail ?? ""}`); console.log(link.id, link.url); ``` ```python tab="Python" title="Create share link example" import os from uuid import UUID from nolgia import AuthenticatedClient from nolgia.api.sharing import create_asset_share_link from nolgia.models import CreateShareLinkRequest, ShareLink client = AuthenticatedClient(base_url="https://api.nolgia.ai/v1", token=os.environ["NOLGIA_TOKEN"]) link = create_asset_share_link.sync( UUID(os.environ["ASSET_ID"]), client=client, body=CreateShareLinkRequest(expires_in_days=7), ) if not isinstance(link, ShareLink): raise SystemExit(f"refused: {link}") print(link.id, link.url) ``` ```rust tab="Rust" title="Create share link example" use nolgia_client::ClientBuilder; #[tokio::main] async fn main() -> Result<(), Box> { let client = ClientBuilder::new("https://api.nolgia.ai/v1") .bearer_token(std::env::var("NOLGIA_TOKEN")?) .build()?; let id = std::env::var("ASSET_ID")?.parse::()?; let link = client.create_asset_share_link().id(id) .body_map(|b| b.expires_in_days(7_u64)) .send().await?.into_inner(); println!("{} {:?}", link.id, link.url); Ok(()) } ``` This is the production create response from `share-create.json`; the token and the URL's token are redacted. The SDK snippets are examples, not separate live captures. ```json title="201 — share-create.json" { "access_count": 0, "created_at": "2026-09-21T03:33:52.874514Z", "created_by": "dad27b53-a85b-4e3d-8fd6-b152c803a27c", "expires_at": "2026-09-28T03:33:52.873377Z", "id": "26d786f5-437c-4da4-9f04-60342191299b", "kind": "asset", "target_id": "f7bc037c-d7d7-434a-8843-26675574de0d", "token": "…", "token_prefix": "EJIkUdv1", "url": "https://nolgia.ai/s/…" } ``` | Field | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | Yes | | | `kind` | `ShareLinkKind` | Yes | | | `target_id` | string | Yes | The shared asset's id, or the shared render's id. | | `token` | string | No | The share token (base64url, 128 bits of entropy). Create response only. | | `token_prefix` | string | Yes | The first characters of the token, for recognising a link in a listing. | | `url` | string | No | The public share URL to hand out (the nolgia.ai share page). Create response only. | | `expires_at` | string | No | When the link stops resolving. Absent for a link that never expires. | | `has_password` | boolean | No | Viewers must enter a password. | | `view_only` | boolean | No | Downloads are turned off for this link. | | `created_at` | string | Yes | | | `created_by` | string | Yes | The user who created the link. | | `access_count` | integer | Yes | Successful public resolutions so far. | | `last_accessed_at` | string | No | When the link was last resolved. Absent until the first visit. | | `revoked_at` | string | No | Set once the link has been revoked. Listings only return unrevoked links. | > [!WARNING] > Save the returned share URL when creating the link. Tokens are stored hashed, so a later list call cannot reconstruct the URL. Anyone who has the link can resolve it until it expires or is revoked; keep it out of public logs unless you intend to publish it. ### List active links | Property | Value | | --- | --- | | Endpoint | `GET /assets/{id}/share` | | Order | Newest first | | Included | Unrevoked, unexpired links | | Omitted | Tokens and URLs | | Management handle | Use each link's `id` to revoke it | ```bash title="List share links example" $ curl --fail-with-body -sS "https://api.nolgia.ai/v1/assets/$ASSET_ID/share" \ -H "Authorization: Bearer $NOLGIA_TOKEN" ``` ```json title="200 — share-list.json" { "links": [ { "access_count": 1, "created_at": "2026-09-21T03:33:52.874514Z", "created_by": "dad27b53-a85b-4e3d-8fd6-b152c803a27c", "expires_at": "2026-09-28T03:33:52.873377Z", "id": "26d786f5-437c-4da4-9f04-60342191299b", "kind": "asset", "last_accessed_at": "2026-09-21T03:33:53.01188Z", "target_id": "f7bc037c-d7d7-434a-8843-26675574de0d", "token_prefix": "EJIkUdv1" } ] } ``` | Response field | Meaning | | --- | --- | | `links` | Active links, each using the `ShareLink` fields above without `token` or `url` | | `links[].id`, `links[].token_prefix` | Revoke handle and short recognition aid | | `links[].kind`, `links[].target_id` | Whether the target is an asset/render and which target it is | | `links[].created_by`, `links[].created_at`, `links[].expires_at` | Creator, creation time and share deadline | | `links[].access_count`, `links[].last_accessed_at` | Successful public resolutions and the latest one; metadata reads do not increment the count | ### Resolve the media | Property | Value | | --- | --- | | Endpoint | `GET /share/{token}`; no authentication | | Success | `302` with a fresh signed media URL in `Location` | | Media URL lifetime | About 15 minutes | | Caching | `Cache-Control: no-store` on the redirect | | Download | Add `?download=true` for a `Content-Disposition: attachment` media URL | Set `SHARE_TOKEN` to the token returned at creation. This command shows the resolver response without following it; add `-L` to fetch the media. ```bash title="Inspect public resolver example" $ curl -sS -D - -o /dev/null "https://api.nolgia.ai/v1/share/$SHARE_TOKEN" ``` These are the real response headers from `share-resolve.headers`; `location` is truncated. ```http title="302 — share-resolve.headers" HTTP/2 302 access-control-allow-headers: Authorization, Content-Type, X-Request-Id, If-Match, Idempotency-Key access-control-allow-methods: GET, POST, PUT, PATCH, DELETE, OPTIONS access-control-allow-origin: * cache-control: no-store content-type: text/html; charset=utf-8 location: https://storage.googleapis.com/nolgia-generations-prod/dad27b53-a85b-4e… x-cloud-trace-context: 978e41fdddedef4cd8db4b39631ad0cd date: Mon, 21 Sep 2026 03:33:53 GMT server: Google Frontend content-length: 941 via: 1.1 google alt-svc: h3=":443"; ma=2592000 ``` | Response field | Meaning | | --- | --- | | HTTP status `302` | Follow the redirect; this response is not the media | | `location` | Short-lived download URL, refreshed on each resolution | | `cache-control` | `no-store`; do not cache the resolver response | | `content-type`, `content-length` | The redirect's HTML body, not the media's MIME type or size | | `date`, trace and infrastructure headers | Response metadata, not share-link settings | | `access-control-*` | Cross-origin response headers | A valid share URL can outlast many media URLs. Keep the share URL and resolve it when needed; do not persist the `Location` as a replacement for the share link. ### Revoke a link | Property | Value | | --- | --- | | Endpoint | `DELETE /assets/{id}/share/{token}` | | `{token}` accepts | The original token **or the link id from the listing** | | Success | `204`, no response body; already revoked is also `204` | | Wrong target | `404` if the link does not exist or belongs to another asset | | Next public resolution | `410 Gone` | The path parameter is named `token`, but the captured revoke used the link id `26d786f5-437c-4da4-9f04-60342191299b` and returned `204`. Use your own listing's id as `SHARE_LINK_ID`. ```bash title="Revoke by link id example" $ curl --fail-with-body -sS -X DELETE \ "https://api.nolgia.ai/v1/assets/$ASSET_ID/share/$SHARE_LINK_ID" \ -H "Authorization: Bearer $NOLGIA_TOKEN" ``` ```http title="Revoke result" HTTP/2 204 ``` There is no JSON body. The same public resolver request now returns the real problem captured in `share-gone.json`: ```bash title="Resolve a revoked link example" $ curl -sS "https://api.nolgia.ai/v1/share/$SHARE_TOKEN" ``` ```json title="410 — share-gone.json" { "detail": "this share link has been revoked", "request_id": "localhost/7bnUUXx7b2-007297", "status": 410, "title": "Gone", "type": "about:blank" } ``` | Response field | Meaning | | --- | --- | | `status`, `title` | `410 Gone`: the link no longer resolves | | `detail` | This capture identifies revocation; expired links and deleted media give their own reason | | `request_id` | Correlation id to include when asking for help | | `type` | Problem type; `about:blank` here | Revocation stops future resolutions. A previously issued signed media URL has its own short expiry; revoking the share does not change that URL's signature. ### Preview metadata and renders `GET /share/{token}/meta` returns the title, modality, media URL and expiry for a preview page or embed, with an optional description, thumbnail, duration and size. It has the same unknown/revoked/rate-limit behavior as the resolver, but does not increment the link's access count. | Field | Type | Required | Description | | --- | --- | --- | --- | | `kind` | `ShareLinkKind` | Yes | | | `title` | string | Yes | The asset's display name (or a trimmed prompt), or the composition's name for a render. | | `description` | string | No | A short description when one is known (the generation prompt, or the composition's description). | | `model` | string | No | Catalog id of the model that generated the shared media (render it with the catalog's display name, never raw).… | | `preset` | `ShareLinkPreset` | No | | | `modality` | `Modality` | Yes | | | `mime_type` | string | No | | | `media_url` | string | Yes | Freshly signed short-lived URL of the media itself. Play or embed it now; never store it. | | `media_expires_at` | string | Yes | When `media_url` (and `thumbnail_url`) stop working. Fetch the metadata again for a new one. | | `thumbnail_url` | string | No | Freshly signed short-lived poster image, when the media has one. | | `duration_seconds` | number | No | Media duration for video and audio, when known. | | `size_bytes` | integer | No | | | `expires_at` | string | No | When the share link itself stops resolving. Absent for a link that never expires. | | `view_only` | boolean | No | Downloads are turned off for this link; offer no Download. | | `created_at` | string | Yes | | | `creator_insider` | `InsiderBadge` | No | Present only when the link's owner is an active NOLGIA Insider (who chose a public profile) and the link is a personal one: their badge, linking to nolgia.ai/@handle. Absent for everyone else. | Renders use the same create, list and revoke pattern under `/renders/{id}/share` and `/renders/{id}/share/{token}`. Their visibility follows the composition's Library scope. Both kinds resolve through the same public `/share/{token}` and `/share/{token}/meta` routes. | Method | Path | What it does | | --- | --- | --- | | [GET](../api/#tag/sharing/get/assets/{id}/share) | `/assets/{id}/share` | List the active share links on an asset. | | [POST](../api/#tag/sharing/post/assets/{id}/share) | `/assets/{id}/share` | Create a durable public share link for an asset. | | [DELETE](../api/#tag/sharing/delete/assets/{id}/share/{token}) | `/assets/{id}/share/{token}` | Revoke a share link on an asset. | | [GET](../api/#tag/sharing/get/renders/{id}/share) | `/renders/{id}/share` | List the active share links on a render. | | [POST](../api/#tag/sharing/post/renders/{id}/share) | `/renders/{id}/share` | Create a durable public share link for a finished render. | | [DELETE](../api/#tag/sharing/delete/renders/{id}/share/{token}) | `/renders/{id}/share/{token}` | Revoke a share link on a render. | | [GET](../api/#tag/sharing/get/me/share-links) | `/me/share-links` | List every active share link in the caller's library, newest first. | | [GET](../api/#tag/sharing/get/me/share-links/policy) | `/me/share-links/policy` | The share-link rules that apply in the caller's current space. | | [GET](../api/#tag/sharing/get/share/{token}) | `/share/{token}` | Public resolver (no authentication) that redirects to the shared media. | | [GET](../api/#tag/sharing/get/share/{token}/meta) | `/share/{token}/meta` | Public metadata (no authentication) for a share link's preview page. | | [POST](../api/#tag/sharing/post/share/{token}/unlock) | `/share/{token}/unlock` | Trade a share link's password for a short-lived viewing grant (no authentication). | :::cards - [Storage and data retention](./storage.html): Understand signed URLs, trash and permanent deletion. - [Teams and organizations](./organizations.html): Choose Library scope and manage organization roles. ::: --- # Model errors Source: https://docs.nolgia.ai/guides/errors.md A refused submission returns an HTTP problem. An accepted generation can fail later, in which case the job remains readable and explains the failure. Read the machine-readable code to choose an action and the recorded refund outcome to tell the customer what happened to their credits. ![Errors](../assets/art/errors.jpg) ## The problem object The API uses the RFC 7807 `Error` object. On a submit refusal, `code` is a field of that problem body; on an accepted job, the typed code is `failure.code`. An HTTP `200` from `GET /jobs/{id}` can contain a job whose `status` is `failed`. | Field | Type | Required | Description | | --- | --- | --- | --- | | `code` | string | No | Machine-readable error code.… | | `type` | string | Yes | A URI reference identifying the problem type. | | `title` | string | Yes | | | `status` | integer | Yes | | | `detail` | string | No | | | `instance` | string | No | | | `job_id` | string | No | The job this refusal points at, so a client can follow it without reading `detail`.… | | `request_id` | string | No | | This is the production `400` response for an unknown image model, captured on 2026-09-21. ```json title="400 Bad Request — production response" { "code": "validation", "detail": "unknown image model", "request_id": "localhost/7bnUUXx7b2-007287", "status": 400, "title": "Bad Request", "type": "about:blank" } ``` | Field in this response | Meaning | | --- | --- | | `code` | `validation`: change the request before submitting again. | | `detail` | Human-readable explanation; do not parse it for application logic. | | `request_id` | Identifier to include with a support report. | | `status` | The HTTP refusal status, `400`. | | `title`, `type` | The problem's short title and type URI. | ## Guidance Branch on `code`, never on `detail` or `failure.message`. Show the explanation to the customer and retain `request_id` for support. Treat an unfamiliar generation code as `job_failed`; if no code is supplied, use the status and structured fields such as `job_id`. For a failed job, `failure.credits_refunded: true` means the hold was released and the job cost nothing. `false` means charged. An absent or null value does not prove either outcome: the hold may still be settling, no hold may exist, or the job may predate the field. | Field | Type | Required | Description | | --- | --- | --- | --- | | `code` | `GenerationErrorCode` | No | Stable refinement of `kind`, present on every job that failed after this field shipped and derived from the recorded reason for older ones.… | | `kind` | string | Yes | What stopped the job.… | | `message` | string | Yes | Human-readable reason, safe to show the customer as-is. The same text as `error.detail`. | | `credits_refunded` | boolean, nullable | No | What the credit ledger actually did with this job's credit hold, recorded when the hold settled.… | > [!WARNING] > `401` means authentication is missing, invalid or expired. `402` with `out_of_credits` means the wallet cannot pay; a separate `402 Upgrade Required` can mean the model needs a higher plan. Replacing a valid token does not fix a balance or plan refusal. There is no separate runner-error taxonomy or error-type response header; use the problem status and code, or the job's typed failure. ## Error codes These eleven values come from the generation contract. The sections below distinguish an HTTP refusal from a code on a failed job; an upstream status inside `job.error` is not the status of your successful job read. | Value | Meaning | | --- | --- | | `out_of_credits` | the wallet cannot pay for the job. Nothing was submitted and nothing was charged. Top up, or submit a cheaper model or fewer seconds. | | `rate_limit` | refused for now, not refused outright - the caller's own concurrency ceiling, or the provider's. Retry later; the same request will be accepted. | | `prompt_nsfw` | a content filter refused the request or the result it produced, on safety grounds. Editing the prompt or the reference media is the fix. Whether the credits were refunded is `failure.credits_refunded`, not this code. | | `ip_detected` | a content filter refused it for a real person's likeness or a protected work, rather than for safety. The fix is different from `prompt_nsfw` - change the reference image or the named subject, not the tone of the prompt - which is why it is its own code. | | `job_failed` | the job ran and did not produce an asset for any other reason, including a provider error. The default for an unclassified failure. | | `timeout` | the job ran past its time budget, or a `GET /jobs/{id}/wait` returned before the job reached a terminal status. On a failed job the work is over; on a `wait` the job may still be running. | | `validation` | the request itself is not acceptable - a missing or malformed field, a model that does not support the capability asked of it, a duration the model will not render. Nothing was submitted and nothing was charged. Retrying unchanged will fail identically. | | `confirmation_rejected` | the cost confirmation gate did not pass. Either a client showed the customer a quote and the customer declined, or a supplied confirmation token was refused. A submit that carries no confirmation token is never refused for this reason. | | `canceled` | the job was canceled by its owner (`POST /jobs/{id}/cancel`), not broken. It is the code the SDK wait helpers raise for a `canceled` job; `cancellation` on the job says what the provider did and what was refunded. A canceled job carries no `failure`. | | `job_not_cancellable` | `POST /jobs/{id}/cancel` refused (`409`) because the job already finished, or its finished result is already being delivered. The job will reach `succeeded` or `failed` on its own; nothing was changed. | | `approval_required` | refused with `402` (title `Approval Needed`) because the generation would take an agent run past the price its customer approved (the `credit_ceiling` a guided preset's review card showed, sent with the brief). Nothing was submitted and nothing was charged. The detail names the generation's cost and the new run total; the agent must ask the customer to approve that total before it continues, never retry, split the job or switch models to fit. | | `run_ended` | refused with `409` (title `Run Ended`) because the agent run this generation was started for (the turn its turn-scoped credential names) has already ended: the platform failed, swept or stopped the turn, or it finished and delivered its reply. Nothing was submitted and nothing was charged. The agent must stop working on that run, never retry or switch models; the customer's chat already shows how it ended. | ### out_of_credits The available wallet balance or organization member budget cannot cover the generation. The server refuses the request before starting billable work. | Property | Value | | --- | --- | | HTTP status | `402 Payment Required`; `402 Budget Exceeded` for an organization member budget. | | Refund | No refund needed: nothing submitted and nothing charged. | | Retry unchanged? | Only after adding credits or restoring budget. | | What to do | Check the spendable balance, top up, or quote a cheaper model or shorter duration. | ```json title="402 — example from the Error schema" { "type": "about:blank", "title": "Payment Required", "status": 402, "detail": "The available credits cannot cover this generation.", "code": "out_of_credits" } ``` | Field | Meaning | | --- | --- | | `type`, `title`, `status` | Problem metadata; the request was refused with `402`. | | `code` | `out_of_credits` distinguishes the wallet refusal from another payment requirement. | | `detail` | Explanation for the customer; this schema-built example is not a production capture. | ### rate_limit The caller has reached a generation concurrency ceiling or quota, or capacity is temporarily unavailable. A refused submit has no new job; an already accepted job waiting on provider capacity keeps its existing id. | Property | Value | | --- | --- | | HTTP status | `429 Too Many Requests`. | | Refund | No refund needed for a local concurrency or quota refusal: no new hold. | | Retry unchanged? | Yes, after capacity becomes available or the quota resets. | | What to do | Honour `Retry-After` when supplied and `Retry-After-Reset` on quota refusals; otherwise use backoff. | The example uses the handler's detail for two active generations on a two-slot plan. ```json title="429 — example from the Error schema" { "type": "about:blank", "title": "Generation Concurrency Limit Reached", "status": 429, "detail": "You have 2 generations running, which is the concurrent maximum of 2 for your plan. Wait for one to finish or upgrade your plan to run more at once.", "code": "rate_limit" } ``` | Field | Meaning | | --- | --- | | `type`, `title`, `status` | Problem metadata; the submit was refused with `429`. | | `code` | `rate_limit` signals a temporary refusal. | | `detail` | Active count and ceiling in this example. Read live counts from `GET /me`, not by parsing this sentence. | See [Concurrency limits](./concurrency-limits.html) for the limits and [Platform headers](./headers.html#response-headers) for reset timing. ### prompt_nsfw A provider's safety filter refused the input or blocked the result it generated. The name does not mean the prompt alone caused it: reference media and output moderation can also trigger this code. | Property | Value | | --- | --- | | Where it appears | `failure.code` on a failed job; also an HTTP problem for an immediate refusal. | | HTTP status | `400` for a remembered policy refusal repeated locally; `422` when an immediate upstream content-policy rejection reaches the submit error handler. | | Refund | Yes, unless the provider billed the refused attempt. A refusal whose provider answer shows no billable work, or says nothing either way, is refunded. GPT Image refusals are always refunded, including a block of the generated image, because OpenAI bills only the images it delivers. Read `failure.credits_refunded`. A local repeat refusal costs nothing. | | Retry unchanged? | No automatic retry; change the relevant prompt or media. | | What to do | Check whether the explanation identifies the input or generated output, then revise that part. | ```json title="Failed-job excerpt — example from JobFailure, refunded input refusal" { "status": "failed", "failure": { "kind": "moderated", "code": "prompt_nsfw", "message": "Your request was rejected by the provider's safety system. Your credits were not charged: the provider refused this request before it did any billable work. Edit your prompt or reference image and try again.", "credits_refunded": true } } ``` | Field | Meaning | | --- | --- | | `status` | The job is terminal; this excerpt is not a complete `Job`. | | `failure.kind`, `failure.code` | A moderated job, narrowed to a safety refusal. | | `failure.message` | Customer-facing explanation from the refunded refusal path. | | `failure.credits_refunded` | `true` confirms a release in this example; do not infer it from the code. | > [!NOTE] > The server remembers policy refusals for a default 30-minute window. Repeating the same request, or the refused reference set on the same model, can return `400` with the same moderation code without calling the provider or charging again. A fresh `Idempotency-Key` does not bypass that content check. ### ip_detected A content filter identified a recognizable person's likeness or a protected work. Changing a safety-related word in the prompt may not fix a refusal of the reference image or named subject. | Property | Value | | --- | --- | | Where it appears | `failure.code` on a failed job; also an HTTP problem for an immediate refusal. | | HTTP status | `400` for a remembered likeness refusal repeated locally; `422` when an immediate upstream likeness rejection reaches the submit error handler. | | Refund | Yes, unless the provider billed the refused attempt. A likeness refusal the model provider raises on the finished output is always refunded. `failure.credits_refunded` is authoritative. | | Retry unchanged? | No automatic retry. | | What to do | Change the reference image or named subject and follow the provider-specific explanation. | ```json title="Failed-job excerpt — example from JobFailure, refunded likeness refusal" { "status": "failed", "failure": { "kind": "moderated", "code": "ip_detected", "message": "The reference was refused because it looks like a recognizable real person or a protected work.", "credits_refunded": true } } ``` | Field | Meaning | | --- | --- | | `status` | Terminal failure; this is a schema-built excerpt, not a captured job. | | `failure.kind`, `failure.code` | A moderation failure specifically about likeness or protected work. | | `failure.message` | The explanation to show; the example is shortened. | | `failure.credits_refunded` | `true` is the refund outcome illustrated here. | > [!WARNING] > On some video models, the model provider can moderate famous faces on output, after you have received an accepted job. That likeness refusal is refunded: ordinary people and your own character sheets are supported, while public figures, celebrities and protected works can be refused. Do not treat every `ip_detected` on every provider as a refund guarantee; read the job's recorded outcome. ### job_failed The generation did not produce an asset for an operational or otherwise unclassified reason. This is also the fallback for an unfamiliar generation error code. | Property | Value | | --- | --- | | Where it appears | `failure.code` on the failed job, or a submit problem when submission itself fails. | | HTTP status | Submit handler: `502` for an upstream failure, `500` for an internal failure; `422` for an upstream client or provider-billing rejection without a more specific code. | | Job read | `GET /jobs/{id}` can still return `200`; the job's `error.status` describes its failure, commonly `502`. | | Refund | Yes for an operationally failed job. Confirm the settled outcome in `failure.credits_refunded`. | | Retry unchanged? | With backoff for a temporary operational failure; first check any existing job. A `422` needs its explanation addressed. | | What to do | Keep the job id and request id, read the failure, and submit a new attempt only when the previous job is terminal. | The message and refund below match the observed Gemini video failure used in the [Jobs guide](./jobs.html#failed). No complete failed-job production fixture was saved; this is a schema-built excerpt. ```json title="Failed-job excerpt — example from JobFailure" { "status": "failed", "failure": { "kind": "error", "code": "job_failed", "message": "Video generation failed due to an internal server issue…", "credits_refunded": true } } ``` | Field | Meaning | | --- | --- | | `status` | Terminal job failure. | | `failure.kind`, `failure.code` | An operational or unclassified failure. | | `failure.message` | Provider failure explanation, shortened here. | | `failure.credits_refunded` | The credit hold was released. | ### timeout Distinguish a closed HTTP wait window from an exhausted generation deadline. Only the latter ends the job. | Property | Value | | --- | --- | | HTTP status | `408 Request Timeout` from `GET /jobs/{id}/wait`. | | On the job | `failure.code: timeout` means the generation reached a terminal deadline. | | Refund | A wait request never charges and does not settle the generation hold. A terminal generation timeout refunds it. | | Retry unchanged? | Repeat the wait on the same job id. For a terminal timeout, decide whether to start a new generation. | | What to do | Read the job's `status` before treating the generation as failed. | ```json title="408 — example from the Error schema" { "type": "about:blank", "title": "Request Timeout", "status": 408, "detail": "job did not finish before timeout", "code": "timeout" } ``` | Field | Meaning | | --- | --- | | `type`, `title`, `status` | This HTTP wait ended with `408`. | | `code` | `timeout` describes the wait request in this example. | | `detail` | The job did not finish inside that wait window; it may still be running. | > [!TIP] > A wait timeout never creates an extra charge and never cancels a submitted generation. If that generation later succeeds, its original hold is still settled normally. Continue with the same job id. ### validation A field is missing or malformed, a model is unknown, or the chosen model does not support a requested duration, capability or field combination. | Property | Value | | --- | --- | | HTTP status | `400 Bad Request`. | | Refund | No refund needed: nothing submitted and nothing charged. | | Retry unchanged? | No, except the observed new-model rollout case below. | | What to do | Compare the request to `GET /models` and [Common model arguments](./model-arguments.html); correct the offending fields. | ```json title="400 — production validation response" { "code": "validation", "detail": "unknown image model", "request_id": "localhost/7bnUUXx7b2-007287", "status": 400, "title": "Bad Request", "type": "about:blank" } ``` | Field | Meaning | | --- | --- | | `code`, `status` | A `400 validation` refusal. | | `detail` | The image model was not recognized. | | `request_id` | Identifier for support correlation. | | `title`, `type` | Standard problem metadata. | > [!WARNING] > For about ten minutes after an API deploy, a brand-new model id can receive `400 validation` from an instance still on the previous revision. This was observed during rollout. If the published catalog confirms the id, wait about a minute and retry; ordinary validation errors still require correcting the request. See [Reliability](./reliability.html#the-ten-minute-window-after-a-deploy). ### confirmation_rejected The optional cost confirmation did not match the request being submitted: it expired, was invalid, belonged to another account, or the body, model or price changed. The enum also covers a client declining a quote. A submit without a confirmation token is never refused for this reason. | Property | Value | | --- | --- | | HTTP status | `422 Unprocessable Entity`. | | Refund | No refund needed: the confirmation gate runs before the credit reservation. | | Retry unchanged? | No; obtain a fresh quote first. | | What to do | Call `POST /jobs/cost`, show the new price, and submit that exact body with its new token before `expires_at`. | ```json title="422 — example from the Error schema" { "type": "about:blank", "title": "Unprocessable Entity", "status": 422, "detail": "this quote has expired — ask for a fresh price and try again", "code": "confirmation_rejected" } ``` | Field | Meaning | | --- | --- | | `type`, `title`, `status` | The confirmation gate refused the submit with `422`. | | `code` | `confirmation_rejected` means re-quote. | | `detail` | The handler's explanation for an expired quote. | > [!WARNING] > Confirmation tokens live for ten minutes; use the quote's `expires_at` as the deadline. Do not remove a rejected token just to force the old quote through: re-quote and show the current price before submitting. ### canceled The job's owner canceled it with `POST /jobs/{id}/cancel`. It is not a failure: the job carries `status: canceled` and a `cancellation` object, never a `failure`. The SDK wait helpers raise this code for a canceled job. | Property | Value | | --- | --- | | HTTP status | None: it is a job state. The cancel call itself answers `200`. | | On the job | `status: canceled`, with `cancellation.stage`, `provider_cancel`, `settlement` and `message`. | | Refund | `cancellation.settlement`: `refunded` before the job reached the provider or when the provider stopped it without billing, `partially_refunded` when the provider bills only the part it rendered, `charged` when it finished and billed, `pending` until the provider answers. | | Retry unchanged? | Submit a new job if you still want the result; a canceled job is never delivered. | | What to do | Show `cancellation.message` as written, and read the job again while `settlement` is `pending`. | ### job_not_cancellable `POST /jobs/{id}/cancel` refused because the job already finished, or the provider already finished it and the result is being delivered. | Property | Value | | --- | --- | | HTTP status | `409 Conflict`. | | Refund | Nothing changed: the job settles under the usual rules when it reaches `succeeded` or `failed`. | | Retry unchanged? | No; the job will not become cancelable. | | What to do | Read the job; it reaches its own terminal status shortly. | ```json title="409 — example from the Error schema" { "type": "about:blank", "title": "Conflict", "status": 409, "detail": "This job already finished, so there is nothing to cancel.", "code": "job_not_cancellable" } ``` | Field | Meaning | | --- | --- | | `type`, `title`, `status` | The cancel was refused with `409`. | | `code` | `job_not_cancellable` means the job is finished or finishing. | | `detail` | Which of the two applies. | ### approval_required An agent run started from a guided preset carries the price its review card showed ("Up to N", sent as `credit_ceiling` with the brief). A generation that would take the run past that price is refused before anything is submitted, so the agent stops and asks the customer to approve the new total instead of spending it. | Property | Value | | --- | --- | | HTTP status | `402 Approval Needed`. | | Refund | No refund needed: nothing submitted and nothing charged. | | Retry unchanged? | No. Ask the customer to approve the new total; do not retry, split the job or switch to a cheaper model to fit. | | What to do | Tell the customer what the next generation makes, what it costs and the run's new total, and continue once they approve. | ```json title="402 — example from the Error schema" { "type": "about:blank", "title": "Approval Needed", "status": 402, "detail": "This generation costs 65 credits and would bring this run to 163 credits, above the 147 the customer approved. Nothing was charged. Stop and ask the customer to approve 163 credits for this run before you continue; do not retry, split the job or switch to a cheaper model to fit.", "code": "approval_required" } ``` | Field | Meaning | | --- | --- | | `type`, `title`, `status` | The generation was refused with `402`; the account can pay, the run's approved price cannot. | | `code` | `approval_required` distinguishes the run price from an empty wallet (`out_of_credits`) and a plan limit. | | `detail` | The generation's cost, the run's new total and the approved price. | ### run_ended A generation submitted with an agent run's turn-scoped credential after the platform has already settled that run: the turn failed, the platform swept it, the customer stopped it, or it finished and its reply was delivered. The run's pod can keep working for a while after that, and anything it started now would be charged for work nobody receives as the run's reply, so it is refused before anything is submitted. | Property | Value | | --- | --- | | HTTP status | `409 Run Ended`. | | Refund | No refund needed: nothing submitted and nothing charged. | | Retry unchanged? | No. The run is over; retrying, switching models or splitting the job is refused the same way. | | What to do | Stop working on that run. The customer's chat already shows how it ended, and anything the run finished is in their Library; they continue by sending a new message. | ```json title="409 — example from the Error schema" { "type": "about:blank", "title": "Run Ended", "status": 409, "detail": "This agent run already ended, so this generation was refused. Nothing was charged. Stop working on this run: do not retry, switch models or start new generations for it. The customer's chat already shows how the run ended; they can send a new message to continue.", "code": "run_ended" } ``` | Field | Meaning | | --- | --- | | `type`, `title`, `status` | The generation was refused with `409`; the account can pay, but the run it belongs to is over. | | `code` | `run_ended` distinguishes an ended run from the run's price (`approval_required`) and an empty wallet (`out_of_credits`). | | `detail` | Whether the run ended or finished, and what the agent does next. | ## Agent error codes Agent endpoints and failed turns use a separate enum. See [Agent Sessions API](./agent-api.html) for the session and turn flow. | Value | Meaning | | --- | --- | | `session_busy` | the session already has a turn in flight or too many turns are pending; wait or steer. | | `access_denied` | the credential or organization role may not do this. | | `insufficient_credits` | the wallet cannot pay for the turn. | | `backend_error` | the agent could not be reached or failed. | | `timeout` | the turn ran past its time budget. | ## Duplicate submissions The same generation body and `Idempotency-Key`, or the same body with no key both times, is refused within five minutes with `409` and the existing `job_id`. It carries no `code`: the status and job identifier are the contract. That refusal is never billed. ```json title="409 Conflict — production response" { "detail": "this exact request was already submitted as job 35b6ff8d-0c78-440b-bdb3-bfe476b56d76 less than 5m0s ago and has not been billed twice — check it with GET /jobs/35b6ff8d-0c78-440b-bdb3-bfe476b56d76. To run it again anyway, resubmit with a different Idempotency-Key header.", "job_id": "35b6ff8d-0c78-440b-bdb3-bfe476b56d76", "request_id": "localhost/7bnUUXx7b2-007291", "status": 409, "title": "Conflict", "type": "about:blank" } ``` | Field | Type | Required | Description | | --- | --- | --- | --- | | `type` | string | Yes | A URI reference identifying the problem type. | | `title` | string | Yes | | | `status` | integer | Yes | | | `detail` | string | No | | | `job_id` | string | No | The job this refusal points at, so a client can follow it without reading `detail`.… | | `request_id` | string | No | | Follow `job_id` with a job read or wait. Keep the same key on retries; use a distinct key only for a deliberate new generation of identical input. Sets have separate per-member behavior; see [Sets](./sets.html). ## Retrying safely Retry `408` waits on the existing job. Retry temporary `429`, `502` and `503` refusals with bounded exponential backoff and jitter, keeping the same `Idempotency-Key` on a generation submit. Honour any `Retry-After` delay and `Retry-After-Reset` time. A lost response may have accepted the work, so a resulting `409` is your route back to that job. Fix authentication, balance, access or validation problems before retrying. For a failed job, inspect `failure.code`, `failure.message` and `failure.credits_refunded` before creating another attempt. [Request errors](./request-errors.html) covers the complete HTTP status table. :::cards - [Request errors](./request-errors.html): Choose an action from the HTTP status and retry headers. - [Reliability](./reliability.html): Understand server retries, deadlines and credit settlement. - [Pricing and credits](./billing.html): Read quotes, balances and the credit ledger. ::: --- # Request errors Source: https://docs.nolgia.ai/guides/request-errors.md Use the HTTP status to decide whether to correct a request, restore access, follow an existing job, or retry later. A failed HTTP request and a failed generation are separate events: a successful job read can report a failed job. ## Response structure Problem responses use the same `Error` object as [Model errors](./errors.html). `status` describes this HTTP response; `code`, when supplied, gives a stable reason. Show `detail` to the customer and keep `request_id` for support. Do not require a code or request id on every error: the captured `401` below has neither. | Field | Type | Required | Description | | --- | --- | --- | --- | | `code` | string | No | Machine-readable error code.… | | `type` | string | Yes | A URI reference identifying the problem type. | | `title` | string | Yes | | | `status` | integer | Yes | | | `detail` | string | No | | | `instance` | string | No | | | `job_id` | string | No | The job this refusal points at, so a client can follow it without reading `detail`.… | | `request_id` | string | No | | There is no separate runner-error object or error-type header; the problem object and the job's `failure` are the available error surfaces. ## Status reference The example routes below declare these responses in the OpenAPI contract. `500` is covered by the route's `default` problem response and verified in its handler. Other routes can use the same status for a different refusal, so read their response contract too. | Status | When it happens | Retry unchanged? | Example endpoint | | --- | --- | --- | --- | | `400` | Invalid input, unsupported fields, or a malformed upload. A generation may supply `validation` or a remembered moderation code. | No; correct the request. See the [new-model rollout exception](./errors.html#validation). | `POST /assets` | | `401` | Authentication is missing, invalid or expired. | No; obtain a valid credential first. | `GET /me` | | `402` | Insufficient credits, a member budget ceiling, or a plan requirement. | No; resolve the balance, budget or plan. | `POST /generate/image` | | `403` | The credential or organization role lacks permission. | No; resolve access with the owner or administrator. | `POST /assets/{id}/share` | | `404` | A resource is unknown or not visible in the current scope. | No; check its id and scope. | `GET /jobs/{id}` | | `408` | The long-poll window closed before the job became terminal. | Yes; wait again on the same job id. | `GET /jobs/{id}/wait` | | `409` | Duplicate generation submission; other endpoints also use it for busy or conflicting state. | Follow the supplied `job_id`; do not create a replacement automatically. | `POST /generate/image` | | `410` | A share link expired, was revoked, or points to trashed/deleted media. | No; ask the owner for a new usable link. | `GET /share/{token}` | | `413` | The uploaded file exceeds the accepted size. | No; reduce the size or use the appropriate upload flow. | `POST /assets`, `POST /assets/uploads` | | `422` | A generation or cost-confirmation gate refused the request. | No blind retry; inspect `code`. Re-quote for `confirmation_rejected`. | `POST /generate/image` | | `429` | A rate limit was reached; generation routes also use it for concurrency or quota. | Yes, after the supplied delay or reset time, with backoff. | `GET /share/{token}` | | `500` | The server could not complete its operation, such as a job lookup failure. | Retry a read with backoff; establish whether a write took effect before repeating it. | `GET /jobs/{id}` (`default` problem response) | | `502` | An upstream model or service failed. | Yes for a temporary failure, with backoff and the same generation key. | `POST /generate/video/enhance-prompt` | | `503` | A required service or feature is temporarily unavailable. | Yes with backoff; persistent configuration unavailability needs support. | `POST /jobs/{id}/sse-ticket` | This unauthenticated request returned the following production response on 2026-09-21. ```json title="401 Unauthorized — production response" { "type": "about:blank", "title": "Unauthorized", "status": 401, "detail": "valid authentication is required" } ``` | Field | Meaning | | --- | --- | | `type` | Problem type URI. | | `title`, `status` | The request was unauthorized; this is not a credit refusal. | | `detail` | Authenticate before retrying. | A repeated generation returned this production `409`. The missing `code` is deliberate: `status` and `job_id` fully identify the duplicate. ```json title="409 Conflict — production response" { "detail": "this exact request was already submitted as job 35b6ff8d-0c78-440b-bdb3-bfe476b56d76 less than 5m0s ago and has not been billed twice — check it with GET /jobs/35b6ff8d-0c78-440b-bdb3-bfe476b56d76. To run it again anyway, resubmit with a different Idempotency-Key header.", "job_id": "35b6ff8d-0c78-440b-bdb3-bfe476b56d76", "request_id": "localhost/7bnUUXx7b2-007291", "status": 409, "title": "Conflict", "type": "about:blank" } ``` | Field | Meaning | | --- | --- | | `job_id` | The already accepted generation to read or wait for. | | `request_id` | This refusal's correlation id for support. | | `status`, `title`, `type` | The duplicate was refused with `409`; there was no second bill. | | `detail` | Explanation for the customer; use `job_id` directly instead of extracting it from this text. | ## Handling request errors Retry `408`, `429`, `502` and `503` with bounded exponential backoff and jitter. A `408` from wait retries only the wait, not generation submission. On `429`, pause your queue so parallel workers do not immediately consume the next available slot together. Stop after a bounded number of attempts and preserve the last problem and request id. Do not automatically retry `401`, `402`, `403` or `404`. They require a valid credential, sufficient balance or entitlement, permission, or the correct resource and scope. Resolve `400`, `410`, `413` and `422` using the status table rather than repeating the same input. | Header | Where it is supplied | How to use it | | --- | --- | --- | | `Retry-After` | Public share-link `429` responses use `60` seconds. OAuth token/revocation `503` responses also declare it. | Do not retry before the indicated delay. Generic HTTP clients should also handle the standard HTTP-date form. | | `Retry-After-Reset` | Generation quota refusals when a reset time is available. | Wait until that RFC 3339 UTC instant before submitting again. | | Neither | Some concurrency and transient service errors do not include a retry header. | Use bounded exponential backoff with jitter; a missing header does not mean retry immediately. | > [!WARNING] > A network timeout can hide a successful submit. Keep the same request body and `Idempotency-Key` when retrying that intended generation. If the API returns `409`, follow its `job_id`. A new key requests a new billable generation; duplicate protection is a five-minute window, not a permanent replay guarantee. If you already have a job id, keep following that job through temporary read errors. Read `failure.code` and `failure.credits_refunded` only when the generation itself fails; a failed status request does not prove that happened. See [Model errors](./errors.html) for typed refusals and job failures. :::cards - [Model errors](./errors.html): Interpret typed generation failures and refunds. - [Concurrency limits](./concurrency-limits.html): Read your current limit and avoid a submit burst. ::: --- # Pricing and credits Source: https://docs.nolgia.ai/guides/billing.md Price a generation before submitting it, show the customer that price, and follow its hold through to a charge or refund. Published model prices, quotes and the itemized ledger all use credits. ![Credits reserved and settled as work completes](../assets/art/billing.jpg) ## Per-model pricing Each model publishes its billing unit. Read the current catalog rather than keeping a second price list in your application. | Modality | Billing unit | How the charge is calculated | | --- | --- | --- | | Image | `per_image` | The selected model and quality-tier price for each image. | | Video | `per_clip` | The published price assumes `baseline_seconds`, normally 5 seconds. Charge `ceil(credits × duration_seconds / baseline_seconds)` for the requested duration. | | Text to speech | `per_character` | The headline `credits` compares a 1,000-character script. Actual charge is `max(minimum_credits, ceil(characters / characters_per_credit))`; the minimum bills a short line as 200 characters. Count Unicode characters, not bytes. | | 3D and other flat-rate generation | `per_generation` | One published rate per generation; inspect the selected model's `cost.unit`. | | Image | Video | Audio | 3D | | --- | --- | --- | --- | | 48 | 74 | 17 | 2 | See [Model APIs](./models.html) for the full per-model tables. A missing `cost` means the price is not published; display an unknown price, not zero. Nolgia does not expose a GPU-time billing fallback. > [!WARNING] > For speech, do not scale the 1,000-character headline price to price another length. Use `characters_per_credit` and `minimum_credits`, or request an exact quote. ## What you pay for ### Generations Nolgia takes a credit hold when the request is accepted. Delivery consumes that hold; it does not create a second charge. A failed job normally releases the hold, subject to the policy-refusal exception below. | Setting | Effect on credits | | --- | --- | | Image count | Each requested image contributes its model price. | | Video duration | Scales the selected per-clip price by `duration_seconds / baseline_seconds`, rounded up. | | Speech text | Uses the Unicode character count and the published minimum. | | `quality` | Selects the complete per-tier price from `quality.options[].credits`; do not add that value to the base price. Video tiers use the same clip baseline. | | `render_quality` | GPT Image 2.5's `xhigh` and `max` add the published credits **per image**, independently of the native/2k/4k quality tier. `auto`, `low`, `medium` and `high` do not add credits. | | `generate_audio` | Where `audio_surcharge` is published, the default price includes it. `generate_audio=false` subtracts it before duration scaling. | ### Agent turns and Studio renders | Work | Billing behavior | | --- | --- | | Agent turn | A separate `agent_turn` ledger row. A turn charges at least the published per-turn rate; a more expensive turn uses `max(flat_rate(model), ceil(provider_cost_usd / 0.01))`. Its row supplies `tokens` and `provider_cost_usd` when those explain a charge above the flat rate. | | Generation started by an agent | Its own generation hold and ledger row, separate from the turn that requested it. | | Studio render | The ledger reserves a `render` kind, but composition renders currently take no credit hold. Generating the source images, clips or narration is billed separately. | ![Credits move from available to held, then charged or refunded](../assets/diagrams/credits-hold-settle.svg) ## What you are not charged for | Outcome | What happens to credits | | --- | --- | | Ordinary failed generation | The hold is released; check `failure.credits_refunded`. | | `409` duplicate submission | No new hold and no second charge. Follow the existing `job_id`. | | `408` request or wait timeout | The timeout response itself adds no charge. A wait timeout does not cancel or refund the accepted job; that job can still finish and settle its existing hold. | | Quote with `POST /jobs/cost` | No job, reservation or charge. | | Checking a job's status or waiting again | No additional generation charge. | > [!WARNING] > A failed status alone does not prove a refund. A content-policy refusal is refunded unless the provider billed the refused attempt; one the provider billed for consumes the hold. `failure.credits_refunded: true` means the job cost nothing; `false` means it was charged. An absent or null value means the ledger outcome is not available yet, or was never recorded. See [Model errors](./errors.html). ## Checking prices programmatically ### Published model prices | Property | Value | | --- | --- | | Endpoint | `GET /pricing/models` | | Authentication | Public; no token required | | Response | `ModelPricingList`, with the same prices as `GET /models` | | Caching | `Cache-Control: public, max-age=300` | ```bash title="Read published prices" $ curl --fail-with-body -sS https://api.nolgia.ai/v1/pricing/models ``` This trimmed `flux-pro` entry preserves the real `cost` and `quality` values from the production model fixture. The public response wraps entries in `models`; other models and other pricing fields are omitted here. ```json title="Published price excerpt" { "models": [ { "id": "flux-pro", "modality": "image", "min_tier": "starter", "cost": { "credits": 4, "unit": "per_image" }, "quality": { "default": "native", "options": [ { "credits": 4, "id": "native", "premium": false }, { "credits": 17, "id": "2k", "premium": true }, { "credits": 54, "id": "4k", "premium": true } ] } } ] } ``` | Field | Type | Required | Description | | --- | --- | --- | --- | | `models` | array of `ModelPricing` | Yes | | | Field | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | Yes | The catalog id a generation request sends as `model`. | | `display_name` | string | Yes | What to call this model on a customer-facing surface. Always present: no published model is nameless, because a raw id is sometimes a provider route path and must never be rendered. | | `maker` | string | No | The company that made the model (`Google`, `Kuaishou`, `Black Forest Labs`).… | | `summary` | string | No | One sentence about the model. Absent until written; copy arrives incrementally, and an absent summary renders as nothing. | | `recommended` | boolean | No | Mirrors `Model.recommended` — the pick for its modality. | | `video` | `VideoCapabilities` | No | | | `audio` | `AudioCapabilities` | No | | | `image` | `ImageCapabilities` | No | | | `references` | `ReferenceCapabilities` | No | | | `restore` | boolean | No | Mirrors `Model.restore`. | | `remove_background` | boolean | No | Mirrors `Model.remove_background`. | | `image_enhance` | boolean | No | Mirrors `Model.image_enhance`. | | `image_expand` | boolean | No | Mirrors `Model.image_expand`. | | `modality` | `Modality` | Yes | | | `min_tier` | `SubscriptionTier` | Yes | Minimum subscription plan required to run this model; `starter` means every plan can.… | | `cost` | `ModelCost` | No | | | `quality` | `QualityCapabilities` | No | | | `three_d` | `ThreeDCapabilities` | No | | | `aspect_ratios` | array of string | Yes | The output aspect ratios this model renders (`video.aspect_ratios` for video models, `image.aspect_ratios` for image models), as plain strings so one field spans both enums.… | | `render_quality` | `RenderQualityCapabilities` | No | Mirrors `image.render_quality` from `GET /models`.… | | `inpaint_mask` | boolean | No | Mirrors `image.inpaint_mask` from `GET /models`: this model can edit only PART of an image, guided by a mask.… | | `capabilities` | array of `VideoCapabilityTag` | No | Mirrors `Model.capabilities`: the video capability CLASSES this model exists to serve, as opposed to the individual inputs it accepts.… | | Field | Type | Required | Description | | --- | --- | --- | --- | | `credits` | integer | Yes | Credits charged per unit at the model's DEFAULT audio state.… | | `unit` | `ModelCostUnit` | Yes | | | `audio_surcharge` | integer, nullable | No | Extra credits (per the model's `unit`) that a soundtrack adds, when the provider charges more to render audio.… | | `video_input_credits` | integer, nullable | No | `credits` for a request that carries a reference video, at the model's default tier, when the provider bills a video input at a different rate.… | | `input_image_credits` | integer, nullable | No | Extra credits for each input image past `free_input_images`, on a model that charges for input images.… | | `free_input_images` | integer, nullable | No | Present with `input_image_credits`: how many input images a request carries before the surcharge starts. | | `baseline_seconds` | integer, nullable | No | Present only when unit is per_clip. The clip duration in seconds that `credits` assumes. The actual charge scales with the requested duration as ceil(credits * duration_seconds / baseline_seconds). | | `characters_per_credit` | integer, nullable | No | Present only when unit is per_character.… | | `minimum_credits` | integer, nullable | No | Present only when unit is per_character.… | | Field | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | Yes | Tier identifier accepted by the `quality` request parameter. | | `credits` | integer | Yes | Credits charged per the model's `cost.unit` when this tier is selected (for video, per `baseline_seconds` clip), at the model's DEFAULT audio state.… | | `audio_surcharge` | integer, nullable | No | Extra credits this tier's soundtrack adds when the provider charges more for audio.… | | `video_input_credits` | integer, nullable | No | Credits per `baseline_seconds` clip at this tier when the request carries a reference video (`video_asset_ids` / `video_urls`), for a model whose provider bills a video input at a different rate than a text or image render.… | | `premium` | boolean | Yes | Marks a premium (highest-quality, higher credit cost) tier so clients can present it distinctly from standard options. | | `aspect_ratios` | array of string | No | When present, the tier renders at these aspect ratios ONLY (a subset of the model's `video.aspect_ratios`); a request pairing the tier with any other `aspect_ratio` is refused with a 400 before any credits are held.… | ### Quote the exact request | Property | Value | | --- | --- | | Endpoint | `POST /jobs/cost` | | Input | `kind` and the matching generation body, such as `image` | | Result | The exact credits the same settings will hold, plus a confirmation token you may return on submit | | Re-quote when | The request changes or `expires_at` passes | These examples use the generated clients. The CLI currently has no dedicated quote command; its tab uses curl with the same `NOLGIA_TOKEN`. The response below is a production capture, not a claim that each language example was run live. ```bash tab="curl" title="Quote an image example" $ curl --fail-with-body -sS https://api.nolgia.ai/v1/jobs/cost \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H "Content-Type: application/json" \ -d '{"kind":"image","image":{"model":"flux-pro","prompt":"a paper-cut mountain range at dawn"}}' ``` ```bash tab="CLI" title="Quote from the terminal example" $ # No dedicated nolgia quote command; call the quote endpoint directly. $ curl --fail-with-body -sS https://api.nolgia.ai/v1/jobs/cost \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H "Content-Type: application/json" \ -d '{"kind":"image","image":{"model":"flux-pro","prompt":"a paper-cut mountain range at dawn"}}' ``` ```ts tab="TypeScript" title="Quote an image example" import { createNolgiaClient } from "@nolgia/sdk"; const nolgia = createNolgiaClient(process.env.NOLGIA_TOKEN!); const { data: quote, error } = await nolgia.POST("/jobs/cost", { body: { kind: "image", image: { model: "flux-pro", prompt: "a paper-cut mountain range at dawn", num_images: 1 }, }, }); if (error) throw new Error(`${error.title}: ${error.detail ?? ""}`); console.log(quote.credits, quote.expires_at); ``` ```python tab="Python" title="Quote an image example" import os from nolgia import AuthenticatedClient from nolgia.api.jobs import quote_job_cost from nolgia.models import GenerateImageRequest, ImageModel, JobCostKind, JobCostQuote, JobCostRequest client = AuthenticatedClient(base_url="https://api.nolgia.ai/v1", token=os.environ["NOLGIA_TOKEN"]) quote = quote_job_cost.sync(client=client, body=JobCostRequest( kind=JobCostKind("image"), image=GenerateImageRequest(model=ImageModel("flux-pro"), prompt="a paper-cut mountain range at dawn"), )) if not isinstance(quote, JobCostQuote): raise SystemExit(f"refused: {quote}") print(quote.credits_, quote.expires_at) ``` ```rust tab="Rust" title="Quote an image example" use nolgia_client::{types, ClientBuilder}; #[tokio::main] async fn main() -> Result<(), Box> { 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 image: types::GenerateImageRequest = types::GenerateImageRequest::builder() .model("flux-pro").prompt(Some(prompt)).num_images(1u64).try_into()?; let quote = client.quote_job_cost() .body_map(|b| b.kind(types::JobCostKind::Image).image(Some(image))) .send().await?.into_inner(); println!("{} {}", quote.credits, quote.expires_at); Ok(()) } ``` The real `cost-image.json` capture quotes one native `flux-pro` image. Its prompt differs from the reusable example above, and its opaque confirmation token is redacted. ```json title="200 quote response" { "balance_credits": 2573, "basis": "one_generation", "confirmation_token": "…", "credits": 4, "expires_at": "2026-09-21T03:43:14.171728407Z", "kind": "image", "model": "flux-pro", "settings": [ { "label": "Model", "value": "flux-pro" } ], "sufficient_credits": true } ``` | Field | Type | Required | Description | | --- | --- | --- | --- | | `kind` | `JobCostKind` | Yes | | | `image` | `GenerateImageRequest` | No | | | `video` | `GenerateVideoRequest` | No | | | `audio` | `GenerateAudioRequest` | No | | | `three_d` | `Generate3DRequest` | No | | | `set` | `GenerateSetRequest` | No | | | Field | Type | Required | Description | | --- | --- | --- | --- | | `kind` | `JobCostKind` | Yes | | | `model` | string | Yes | The model id the quote is for, after defaulting — not necessarily the one you sent. | | `credits` | integer | Yes | Display credits. This is the number the submit will hold, computed by the same pricer the submit path uses, for exactly these settings. It is what to show the customer. | | `basis` | one of `one_generation`, `set_run` | Yes | Whether `credits` covers one generation or a whole set run. | | `members` | integer, nullable | No | Number of set members `credits` covers. Present only when `basis` is `set_run`. | | `duration_seconds` | integer, nullable | No | The billed duration the price is for, on a video quote. | | `quality` | string, nullable | No | The resolved quality tier the price is for, when the model has one. | | `settings` | array of `JobCostSetting` | Yes | The settings that made this price, in the order a dialog should show them. Human-readable; do not parse it. | | `balance_credits` | integer, nullable | No | The wallet balance this quote was compared against, when one could be read. | | `sufficient_credits` | boolean | Yes | Whether the balance covers `credits` right now. Advisory — the submit re-checks. | | `confirmation_token` | string | Yes | Short-lived, single-request proof that this price was quoted. Send it back as `confirmation_token` on the matching generate request. Opaque: do not parse or construct it. | | `expires_at` | string | Yes | After this the token is refused and a submit carrying it fails with `confirmation_rejected`. Re-quote. | ![Confirmation gate: quote the request, show the price, and submit with its token](../assets/diagrams/confirmation-gate.svg) Return `confirmation_token` on the unchanged generation body if you want submission bound to the quoted request and price. The token is optional; when supplied, a stale, malformed, foreign or mismatched token fails with `422 confirmation_rejected`. Quote again. `sufficient_credits` is advisory because submission checks the balance again. ## Balances and the ledger ### Spendable balances Personal Access Tokens you create spend `available_for_api`. The app, the Nolgia Agent, organization API keys and connected assistants (ChatGPT, Claude or any MCP client connected through OAuth) spend `available_for_app`. `available_for_credential` is the figure for the credential that made the request, and `credential_channel` names its channel. Monthly `app_subscription` credits expire at the subscription cycle end; `shared_topup` credits do not expire and are spendable by either surface. ```bash title="Read your balance" $ curl --fail-with-body -sS https://api.nolgia.ai/v1/billing/credits \ -H "Authorization: Bearer $NOLGIA_TOKEN" ``` This schema-built example shows a personal account with 100 subscription credits and 200 top-up credits. ```json title="200 balance response example" { "user_id": "00000000-0000-4000-8000-000000000001", "scope": "personal", "app_subscription": 100, "shared_topup": 200, "total": 300, "available_for_app": 300, "available_for_api": 200, "buckets": [ { "wallet_id": "00000000-0000-4000-8000-000000000002", "type": "app_subscription", "balance": 100, "expires_at": "2026-10-01T00:00:00Z" }, { "wallet_id": "00000000-0000-4000-8000-000000000003", "type": "shared_topup", "balance": 200, "expires_at": null } ] } ``` | Field | Type | Required | Description | | --- | --- | --- | --- | | `user_id` | string | Yes | | | `app_subscription` | integer | Yes | Monthly app-only credits that expire at the subscription cycle end. | | `shared_topup` | integer | Yes | Shared app/API prepaid credits that do not expire. | | `total` | integer | Yes | | | `available_for_app` | integer | Yes | App-visible credits; subscription bucket plus shared top-up overflow. | | `available_for_api` | integer | Yes | API prepaid credits; shared top-up only at launch. | | `credential_channel` | `CreditChannel` | No | | | `available_for_credential` | integer | No | What the credential that made this request can spend: `available_for_app` on the app channel, `available_for_api` on the API channel (see `credential_channel`).… | | `buckets` | array of `CreditBalanceBucket` | Yes | | | `scope` | `BillingScope` | No | | | `organization_id` | string | No | Set when `scope` is `organization`: the balances above are the ORGANIZATION's shared pool (the caller's personal wallets are not consulted inside an organization) and `user_id` is the caller. | | `seats` | integer | No | Seats on the organization's subscription; organization scope only. | | `seat_limit` | integer, nullable | No | The organization's seat cap (`null` = unlimited); organization scope only. | | `member_budget` | integer, nullable | No | The caller's monthly credit budget in the organization (`null` = unlimited); organization scope only. | | `member_spent_this_month` | integer | No | What the caller has spent from the organization's pool this UTC calendar month (held plus consumed reservations); organization scope only. | | Field | Type | Required | Description | | --- | --- | --- | --- | | `wallet_id` | string | Yes | | | `type` | `CreditWalletType` | Yes | | | `balance` | integer | Yes | | | `expires_at` | string, nullable | No | | ### Itemized transactions `GET /billing/transactions` returns newest-first rows. The debit when a hold is taken **is** the charge; completion writes no second debit. Releasing it writes a `refund` for the same amount. `balance_after` belongs to the particular `wallet_id`, not to the combined account balance. ```bash title="Read the ledger" $ curl --fail-with-body -sS 'https://api.nolgia.ai/v1/billing/transactions?limit=20' \ -H "Authorization: Bearer $NOLGIA_TOKEN" ``` This schema-built example uses the documented Quick Start amounts: a 12-credit `veo-3.1-lite` four-second charge and its 12-credit refund. The row ids, timestamps and wallet balances are illustrative; this is not a captured ledger response. ```json title="200 ledger response example" { "items": [ { "id": "00000000-0000-4000-8000-000000000005", "occurred_at": "2026-09-21T03:36:00Z", "kind": "refund", "wallet": "topup", "wallet_id": "00000000-0000-4000-8000-000000000003", "credits": 12, "balance_after": 200, "description": "Refund · Video generation · veo-3.1-lite · 4 s", "model": "veo-3.1-lite", "job_id": "00000000-0000-4000-8000-000000000006" }, { "id": "00000000-0000-4000-8000-000000000004", "occurred_at": "2026-09-21T03:35:00Z", "kind": "generation", "wallet": "topup", "wallet_id": "00000000-0000-4000-8000-000000000003", "credits": -12, "balance_after": 188, "description": "Video generation · veo-3.1-lite · 4 s", "model": "veo-3.1-lite", "job_id": "00000000-0000-4000-8000-000000000006" } ], "next_cursor": null, "scope": "personal" } ``` | Parameter | In | Required | Description | | --- | --- | --- | --- | | `cursor` | query | No | Opaque pagination cursor returned by a prior response. | | `limit` | query | No | Rows per page. | | `from` | query | No | Inclusive window start (RFC 3339). Omit for no lower bound. | | `to` | query | No | Exclusive window end (RFC 3339). Omit for no upper bound. | | `kind` | query | No | Only rows of this kind. | | `wallet` | query | No | Only rows on this wallet type (default `all`). | | `member_id` | query | No | Organization context only: rows caused by this member. Owner, admin and billing roles may name any member; other roles only themselves. `400` in the personal space. | | Field | Type | Required | Description | | --- | --- | --- | --- | | `items` | array of `CreditTransaction` | Yes | Rows newest first. | | `next_cursor` | string, nullable | Yes | Pass as `cursor` for the next page; `null` on the last page. | | `scope` | `BillingScope` | Yes | | | `organization_id` | string | No | Set when `scope` is `organization`. | | Field | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | Yes | | | `occurred_at` | string | Yes | | | `kind` | `CreditTransactionKind` | Yes | | | `wallet` | `CreditTransactionWallet` | Yes | | | `wallet_id` | string | Yes | | | `wallet_expires_at` | string, nullable | No | When the wallet's credits expire (subscription wallets); `null` for the top-up wallet. | | `credits` | integer | Yes | Signed movement. Negative for charges and debits, positive for grants, top-ups, redemptions and refunds. | | `balance_after` | integer | Yes | The wallet's balance after this row (running sum of the wallet identified by `wallet_id`). | | `description` | string | Yes | Plain-language label, for example `Video generation · seedance-2.5 · 5 s` or `Refund · Agent turn`. Never contains an em dash. | | `model` | string | No | Catalog model id when the row charges or refunds a generation, and the agent brain (for example `claude-opus-5-5`) when it charges or refunds an agent turn. Label it client-side. | | `preset` | string | No | Preset slug when the generation was launched from a preset. | | `job_id` | string | No | The generation job behind a `generation` charge or its refund. | | `session_id` | string | No | The agent chat session behind an `agent_turn` charge or its refund. | | `tokens` | integer | No | Tokens the agent turn behind this row reported, across every iteration of its reasoning loop.… | | `provider_cost_usd` | number | No | Measured provider cost in US dollars of the agent turn behind this row.… | | `render_id` | string | No | The render behind a `render` charge or its refund. | | `member` | `CreditTransactionMember` | No | | | `meter` | `CreditTransactionMeter` | No | | | Kind | Meaning | | --- | --- | | `grant` | Monthly plan credits or credits granted by Nolgia. | | `topup` | A credit purchase. | | `redeem` | A credit-code redemption. | | `generation` | A generation charge. | | `agent_turn` | A chat turn charge. | | `render` | Reserved for composition-render charges; renders currently take no hold. | | `refund` | Credits returned for a prior charge. | | `adjustment` | A plan correction or other manual movement. | ### Usage totals `GET /billing/transactions/summary` rolls up the same rows and scope. It defaults to the current UTC month; `credits_used` and `credits_refunded` are positive totals, while `by_kind[].credits` is signed. ```bash title="Read credit usage" $ curl --fail-with-body -sS https://api.nolgia.ai/v1/billing/transactions/summary \ -H "Authorization: Bearer $NOLGIA_TOKEN" ``` This schema-built response illustrates the charge and refund above, with no other movements in the month. ```json title="200 usage summary example" { "from": "2026-09-01T00:00:00Z", "to": "2026-10-01T00:00:00Z", "scope": "personal", "credits_used": 12, "credits_refunded": 12, "credits_added": 0, "by_day": [ { "date": "2026-09-21", "credits_used": 12, "credits_refunded": 12 } ], "by_kind": [ { "kind": "generation", "credits": -12, "count": 1 }, { "kind": "refund", "credits": 12, "count": 1 } ] } ``` | Field | Type | Required | Description | | --- | --- | --- | --- | | `from` | string | Yes | | | `to` | string | Yes | | | `scope` | `BillingScope` | Yes | | | `organization_id` | string | No | Set when `scope` is `organization`. | | `member_id` | string | No | Set when the summary is narrowed to one member's rows. | | `credits_used` | integer | Yes | | | `credits_refunded` | integer | Yes | | | `credits_added` | integer | Yes | Grants, top-ups and redemptions in the window. | | `by_day` | array of `CreditUsageDay` | Yes | | | `by_kind` | array of `CreditUsageKindTotal` | Yes | | ## Plans, top-ups and codes See [nolgia.ai/pricing](https://nolgia.ai/pricing) for current plans and credit purchases. Subscriptions supply monthly app credits; top-ups supply shared prepaid credits. A model's `min_tier` and its credit price are separate: adding credits does not unlock an out-of-plan model. | Operation | Use it for | | --- | --- | | `GET /billing/subscription` | Read the plan, status and current period end. | | `POST /billing/portal-link` | Open the returned expiring Stripe Customer Portal URL to manage billing. | | `POST /credits/redeem` | Redeem an issued code into shared top-up credits, once per account. | | `GET /billing/auto-refresh` | Read automatic top-up settings. | | `PUT /billing/auto-refresh` | Update whether auto-refresh is enabled, its threshold and purchase amount. | | Method | Path | What it does | | --- | --- | --- | | [GET](../api/#tag/billing/get/billing/subscription) | `/billing/subscription` | Get the current user's subscription state. | | [GET](../api/#tag/billing/get/billing/trial) | `/billing/trial` | The account's free trial state and whether it can start one. | | [POST](../api/#tag/billing/post/billing/trial) | `/billing/trial` | Start the account's seven-day free trial. | | [POST](../api/#tag/billing/post/billing/trial/confirm) | `/billing/trial/confirm` | Start the free trial from an emailed confirmation link. | | [POST](../api/#tag/billing/post/billing/portal-link) | `/billing/portal-link` | Create a Stripe Customer Portal session and return its URL. | | [GET](../api/#tag/billing/get/billing/credits) | `/billing/credits` | Get the current user's credit wallet balances. | | [GET](../api/#tag/billing/get/billing/transactions) | `/billing/transactions` | List the itemized credit ledger, newest first. | | [GET](../api/#tag/billing/get/billing/transactions/summary) | `/billing/transactions/summary` | Credits used per day and totals per kind over a window. | | [GET](../api/#tag/billing/get/billing/auto-refresh) | `/billing/auto-refresh` | Get the current user's auto-refresh settings. | | [PUT](../api/#tag/billing/put/billing/auto-refresh) | `/billing/auto-refresh` | Update the current user's auto-refresh settings. | | [POST](../api/#tag/billing/post/credits/redeem) | `/credits/redeem` | Redeem a credit code for shared top-up credits. | A code refusal returns `404` for an unknown code, `410` for a deactivated, expired or exhausted code, `409` if this account already redeemed it, and `429` for rate-limited attempts. A repeated redemption can never grant credits twice. ## Organizations In an organization, spending uses the organization's wallets, not your personal balance. `GET /organizations/{id}/credits` reports the shared pool, seats, and monthly member budgets. An unlimited budget is `null`; the member's month-to-date spend includes both held and consumed reservations. | Role | Credit and usage visibility | | --- | --- | | Owner, admin, billing | The organization pool and every member's rows. | | Member, viewer | The shared pool and their own member row or spend. | | Non-member | `404`; the organization is not exposed. | `GET /organizations/{id}/usage` groups consumed credits by member, model or UTC day; it defaults to the current UTC calendar month and excludes held and released reservations. [Usage and activity](./usage.html#organization-usage) explains why these totals differ from gross ledger charges. For organization contracts, see the Enterprise option on [Pricing](https://nolgia.ai/pricing). :::cards - [Usage and activity](./usage.html): Find spend totals, recent work and the audit trail. - [Concurrency limits](./concurrency-limits.html): Read the limits on simultaneous generation. - [Model APIs](./models.html): Compare the catalog's models and published prices. ::: --- # Usage and activity Source: https://docs.nolgia.ai/guides/usage.md There are no per-request logs or latency analytics. Use the activity feed to follow work, the credit ledger to account for spending, and the organization audit trail to see administrative changes. Each surface answers a different question; none is an HTTP request log. | Surface | Question it answers | Scope | | --- | --- | --- | | `GET /activity` | What was created, completed or changed? | The caller's resources, optionally narrowed by project or agent session. | | `GET /billing/transactions/summary` | What credits moved in this period? | Personal wallets or the current organization, subject to role. | | `GET /agent/usage` | What generations did my agent's token create? | The caller's agent. | | `GET /organizations/{id}/usage` | What credits did organization members consume? | The organization's consumed reservations. | | `GET /organizations/{id}/audit-events` | Who changed the organization? | Owner or admin access. | | `GET /asset-usage` | Which compositions reference this asset? | The caller's current composition documents. | ## Activity feed The feed is assembled at read time from existing jobs, assets, compositions and renders. It stores no separate event history. Every event has a human-readable `summary` of at most 160 characters and resource ids in `refs`. | Property | Value | | --- | --- | | Order | Newest first | | Incremental polling | Pass the newest `at` already seen as `since`; only strictly newer events are returned. | | Page size | `limit` defaults to 50, maximum 100. | | Project filter | `project_id`; a generation joins a project through its produced asset. | | Session filter | `agent_session_id`; excludes composition and render events without session attribution. | ```bash title="Read recent activity" $ curl --fail-with-body -sS 'https://api.nolgia.ai/v1/activity?limit=20' \ -H "Authorization: Bearer $NOLGIA_TOKEN" ``` This schema-built example shows a completed image job. It is not a production capture. ```json title="200 activity response example" { "events": [ { "at": "2026-09-21T03:34:00Z", "kind": "generation.succeeded", "summary": "Image generation succeeded (flux-pro)", "refs": { "job_id": "00000000-0000-4000-8000-000000000001", "asset_id": "00000000-0000-4000-8000-000000000002" } } ] } ``` | Parameter | In | Required | Description | | --- | --- | --- | --- | | `project_id` | query | No | Restrict the feed to events belonging to this project. | | `since` | query | No | Return only events strictly newer than this timestamp. Pass the newest `at` already seen to poll for increments. | | `agent_session_id` | query | No | Restrict the feed to events caused by the given chat session — the job's `agent_session_id` or an agent upload's `metadata.agent_session_id`. Scoped to the caller's own events. Composition and render events, which carry no session attribution, are excluded when this is set. | | `limit` | query | No | Maximum events to return. | | Field | Type | Required | Description | | --- | --- | --- | --- | | `events` | array of `ActivityEvent` | Yes | Events newest first. | | Field | Type | Required | Description | | --- | --- | --- | --- | | `at` | string | Yes | When the event happened (source-row timestamp). | | `kind` | `ActivityEventKind` | Yes | | | `summary` | string | Yes | Server-built human-readable one-liner. | | `error_detail` | string, nullable | No | User-facing failure reason for `generation.failed`, or the cancellation's plain message (what was refunded or charged) for `generation.canceled`; absent for every other event kind. | | `refs` | `ActivityEventRefs` | Yes | | | Field | Type | Required | Description | | --- | --- | --- | --- | | `job_id` | string | No | | | `asset_id` | string | No | | | `composition_id` | string | No | | | `render_id` | string | No | | | `project_id` | string | No | | | `agent_session_id` | string | No | The chat session whose agent turn caused this event's job or asset — the job's `agent_session_id`, or an agent upload's `metadata.agent_session_id`.… | > [!NOTE] > A project-filtered feed cannot show a queued or running generation that has not produced an asset, or one that failed before output. Use the unfiltered feed or poll the job directly when you need those states. ## Credit usage summary The summary is the roll-up behind the billing usage chart. It counts the same ledger rows and uses the same access rules as `GET /billing/transactions`: in organization scope, owner, admin and billing roles can see everyone, while members and viewers see their own rows. | Property | Value | | --- | --- | | Default window | Current UTC calendar month | | Custom window | `from` inclusive, `to` exclusive, both RFC 3339; maximum 366 days | | `credits_used` | Generation, agent-turn and reserved render charge kinds, as a positive total | | `credits_refunded` | Refunds of those charges, as a positive total | | `credits_added` | Grants, top-ups and redemptions | | `by_day` | UTC dates with activity; fill missing days with zero in your chart | | `by_kind` | Signed net credits and row count for each kind present | ```bash title="Summarize a month" $ curl --fail-with-body -sS \ 'https://api.nolgia.ai/v1/billing/transactions/summary?from=2026-09-01T00%3A00%3A00Z&to=2026-10-01T00%3A00%3A00Z' \ -H "Authorization: Bearer $NOLGIA_TOKEN" ``` This schema-built example has one 12-credit charge and its full refund. ```json title="200 credit summary example" { "from": "2026-09-01T00:00:00Z", "to": "2026-10-01T00:00:00Z", "scope": "personal", "credits_used": 12, "credits_refunded": 12, "credits_added": 0, "by_day": [ { "date": "2026-09-21", "credits_used": 12, "credits_refunded": 12 } ], "by_kind": [ { "kind": "generation", "credits": -12, "count": 1 }, { "kind": "refund", "credits": 12, "count": 1 } ] } ``` | Parameter | In | Required | Description | | --- | --- | --- | --- | | `from` | query | No | Inclusive window start (RFC 3339). Defaults to the first instant of the current UTC month. | | `to` | query | No | Exclusive window end (RFC 3339). Defaults to the first instant of the next UTC month. | | `member_id` | query | No | Organization context only; same rules as on `GET /billing/transactions`. | | Field | Type | Required | Description | | --- | --- | --- | --- | | `from` | string | Yes | | | `to` | string | Yes | | | `scope` | `BillingScope` | Yes | | | `organization_id` | string | No | Set when `scope` is `organization`. | | `member_id` | string | No | Set when the summary is narrowed to one member's rows. | | `credits_used` | integer | Yes | | | `credits_refunded` | integer | Yes | | | `credits_added` | integer | Yes | Grants, top-ups and redemptions in the window. | | `by_day` | array of `CreditUsageDay` | Yes | | | `by_kind` | array of `CreditUsageKindTotal` | Yes | | | Field | Type | Required | Description | | --- | --- | --- | --- | | `date` | string | Yes | UTC calendar day, `YYYY-MM-DD`. | | `credits_used` | integer | Yes | Generation, agent-turn and render charges taken that day, as a positive number. | | `credits_refunded` | integer | Yes | Refunds credited that day, as a positive number. | | Field | Type | Required | Description | | --- | --- | --- | --- | | `kind` | `CreditTransactionKind` | Yes | | | `credits` | integer | Yes | Signed net of every row of this kind in the window. | | `count` | integer | Yes | | A generation charge appears when its hold is taken, before the job finishes. For settled organization spending, use [Organization usage](#organization-usage). For individual debits, refunds and resulting wallet balances, use the [ledger](./billing.html#itemized-transactions). ## Agent usage `GET /agent/usage` summarizes generations created with the current user's agent token. `credits_spent` counts generation credits consumed; it is not a count of all agent-turn charges. Use the billing ledger for the turn charges themselves. ```bash title="Read agent usage" $ curl --fail-with-body -sS https://api.nolgia.ai/v1/agent/usage \ -H "Authorization: Bearer $NOLGIA_TOKEN" ``` This is a trimmed production `agent-usage.json` capture. Totals are intact; `recent_jobs` is shortened to its first job, and that job's provider request id and user id are omitted. ```json title="200 agent usage excerpt" { "credits_spent": 15038, "jobs_total": 288, "recent_jobs": [ { "completed_at": "2026-09-10T19:08:57.621139Z", "created_at": "2026-09-10T19:07:27.251676Z", "id": "4bd62272-67e1-4ce4-b9a7-13d982be3a49", "modality": "video", "model": "seedance-2.0-pro-t2v", "progress": 0, "status": "succeeded", "updated_at": "2026-09-10T19:08:57.621139Z" } ] } ``` | Field | Type | Required | Description | | --- | --- | --- | --- | | `credits_spent` | integer | Yes | Total credits consumed by generations the agent's token created. | | `jobs_total` | integer | Yes | Number of jobs created with the agent's token. | | `recent_jobs` | array of `Job` | Yes | | | Field | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | Yes | | | `modality` | `Modality` | Yes | | | `model` | string | Yes | | | `status` | `JobStatus` | Yes | | | `progress` | number, nullable | No | | | `created_at` | string | Yes | | | `updated_at` | string | Yes | | | `completed_at` | string, nullable | No | | ## Organization usage `GET /organizations/{id}/usage` counts **consumed** reservations against the organization's wallets, created within the requested window. Held reservations and released holds are excluded. Its `total_credits` therefore answers a different question from the ledger summary's gross `credits_used`. | Property | Value | | --- | --- | | `group_by` | `member` (default), `model`, or `day` | | Default window | Current UTC month; custom `from`/`to` use an inclusive start and exclusive end, up to 366 days | | Owner, admin, billing | All organization spending | | Member, viewer | Only their own spending | | Non-member | `404` | ```bash title="Group organization spending by model" $ curl --fail-with-body -sS \ "https://api.nolgia.ai/v1/organizations/$ORGANIZATION_ID/usage?group_by=model" \ -H "Authorization: Bearer $NOLGIA_TOKEN" ``` This schema-built example groups three consumed image reservations into one model bucket. ```json title="200 organization usage example" { "organization_id": "00000000-0000-4000-8000-000000000010", "from": "2026-09-01T00:00:00Z", "to": "2026-10-01T00:00:00Z", "group_by": "model", "total_credits": 12, "items": [ { "key": "flux-pro", "credits": 12, "jobs": 3 } ] } ``` | Parameter | In | Required | Description | | --- | --- | --- | --- | | `id` | path | Yes | Organization UUID. | | `from` | query | No | Inclusive window start (RFC 3339). Defaults to the first instant of the current UTC month. | | `to` | query | No | Exclusive window end (RFC 3339). Defaults to the first instant of the next UTC month. | | `group_by` | query | No | | | Field | Type | Required | Description | | --- | --- | --- | --- | | `organization_id` | string | Yes | | | `from` | string | Yes | | | `to` | string | Yes | | | `group_by` | `OrganizationUsageGroupBy` | Yes | | | `total_credits` | integer | Yes | | | `items` | array of `OrganizationUsageItem` | Yes | | | Field | Type | Required | Description | | --- | --- | --- | --- | | `key` | string | Yes | The member's user id, the model id (`agent_turn` for agent chat turns), or the UTC day as `YYYY-MM-DD`. | | `label` | string | No | The member's email for the `member` grouping; absent otherwise. | | `credits` | integer | Yes | Consumed credits in this bucket. | | `jobs` | integer | Yes | Consumed reservations in this bucket (one per generation or agent turn). | `items[].key` is a member id, model id, or UTC `YYYY-MM-DD` date. The model grouping uses `agent_turn` for agent chat turns. `jobs` counts consumed reservations, so it includes agent turns as well as generations. ## Organization audit trail Owners and admins can read organization events newest first, filtered by exact `action` or `actor` user id. The audit trail records the actor and target of a change; it is separate from generation activity and billing usage. ```bash title="Read organization audit events" $ curl --fail-with-body -sS \ "https://api.nolgia.ai/v1/organizations/$ORGANIZATION_ID/audit-events?action=member.invited" \ -H "Authorization: Bearer $NOLGIA_TOKEN" ``` This schema-built example shows an invitation event. The ids and address are illustrative. ```json title="200 audit response example" { "items": [ { "id": "12345", "organization_id": "00000000-0000-4000-8000-000000000010", "actor_user_id": "00000000-0000-4000-8000-000000000011", "actor_email": "owner@example.com", "actor_api_key_id": null, "action": "member.invited", "target_type": "invitation", "target_id": "00000000-0000-4000-8000-000000000012", "metadata": {}, "ip": null, "user_agent": null, "created_at": "2026-09-21T03:30:00Z" } ], "next_cursor": null } ``` | Parameter | In | Required | Description | | --- | --- | --- | --- | | `id` | path | Yes | Organization UUID. | | `cursor` | query | No | Opaque pagination cursor returned by a prior response. | | `limit` | query | No | Maximum items to return. | | `action` | query | No | Exact action filter (for example `member.invited`). | | `actor` | query | No | Filter on the acting user's id. | | Field | Type | Required | Description | | --- | --- | --- | --- | | `items` | array of `OrganizationAuditEvent` | Yes | | | `next_cursor` | string, nullable | Yes | | | Field | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | Yes | Monotonic event id (also the pagination cursor). | | `organization_id` | string | Yes | | | `actor_user_id` | string, nullable | Yes | | | `actor_email` | string, nullable | Yes | Who acted: the member's email at the time of the event, the literal value `NOLGIA staff` for an action NOLGIA's own operators took on the organization (support or administration), or null when the actor is not resolvable.… | | `actor_api_key_id` | string, nullable | Yes | | | `action` | string | Yes | Dotted action, for example `member.invited`. | | `target_type` | string, nullable | Yes | | | `target_id` | string, nullable | Yes | | | `metadata` | object | Yes | | | `ip` | string, nullable | Yes | | | `user_agent` | string, nullable | Yes | | | `created_at` | string | Yes | | ### Export the trail | Property | Value | | --- | --- | | Endpoint | `GET /organizations/{id}/audit-events/export` | | Access | Owner or admin on an Enterprise organization | | Format | `text/csv`, all events newest first | | Team-plan refusal | `402 Upgrade Required`; buying credits does not unlock this plan feature | ```bash title="Export the audit trail" $ curl --fail-with-body -sS \ "https://api.nolgia.ai/v1/organizations/$ORGANIZATION_ID/audit-events/export" \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -o audit-events.csv ``` The response is a CSV file rather than JSON. This schema-built example uses the verified export column order and the illustrative event above. ```csv title="200 CSV export example" id,created_at,action,actor_user_id,actor_email,actor_api_key_id,target_type,target_id,ip,user_agent,metadata 12345,2026-09-21T03:30:00Z,member.invited,00000000-0000-4000-8000-000000000011,owner@example.com,,invitation,00000000-0000-4000-8000-000000000012,,,{} ``` | Columns | Meaning | | --- | --- | | `id`, `created_at`, `action` | Event identity, timestamp and dotted action. | | `actor_user_id`, `actor_email`, `actor_api_key_id` | Actor and credential attribution when present. | | `target_type`, `target_id` | Resource affected by the change. | | `ip`, `user_agent` | Recorded request context when present. | | `metadata` | Event-specific JSON data encoded as a CSV field. | ## Where assets are used `GET /asset-usage` scans the caller's newest composition documents for asset references, including HTML files and the edits overlay. Repeat `ids` to narrow the result to particular assets. ```bash title="Find compositions using an asset" $ curl --fail-with-body -sS \ "https://api.nolgia.ai/v1/asset-usage?ids=$ASSET_ID" \ -H "Authorization: Bearer $NOLGIA_TOKEN" ``` This schema-built example shows one asset referenced by one composition. ```json title="200 asset usage example" { "usage": [ { "asset_id": "00000000-0000-4000-8000-000000000002", "compositions": [ { "id": "00000000-0000-4000-8000-000000000020", "name": "Launch film", "project_id": null, "project_name": null } ] } ], "complete": true } ``` | Parameter | In | Required | Description | | --- | --- | --- | --- | | `ids` | query | No | Return usage only for these asset ids (repeat the param per id). | | Field | Type | Required | Description | | --- | --- | --- | --- | | `usage` | array of `AssetUsageEntry` | Yes | | | `complete` | boolean | Yes | | | Field | Type | Required | Description | | --- | --- | --- | --- | | `asset_id` | string | Yes | | | `compositions` | array of object | Yes | The user's compositions whose current document references the asset. | | Composition field | Meaning | | --- | --- | | `id`, `name` | The composition whose current document references the asset. | | `project_id`, `project_name` | Optional, nullable project association. | > [!WARNING] > This is advisory usage data. `complete: false` means the scan could not cover everything, such as when more than 200 compositions exist or documents could not be read. Do not treat an incomplete result as proof that an asset is unused. :::cards - [Pricing and credits](./billing.html): Read balances, quotes, charges and refunds. - [Teams and organizations](./organizations.html): Understand shared pools, roles and budgets. - [Agent Sessions API](./agent-api.html): Follow a conversation and its generated assets. ::: --- # Workflows Source: https://docs.nolgia.ai/guides/workflows.md There is no single endpoint to chain arbitrary models and no workflow event stream; each of these workflows has its own status you poll. Choose the surface that matches your outcome. A preset prepares a request, a set coordinates several image jobs, a composition turns a timeline into a deliverable, and an edit session keeps the history of successive image edits. | Kind | What it connects | Start with | What you poll | How it bills | | --- | --- | --- | --- | --- | | Preset | Intake answers, a prompt and its target model | `POST /presets/{slug}/assemble`, then the returned generate endpoint | The generated `Job` through `GET /jobs/{id}` | Slot filling is free; doctrine assembly has a one-credit hold and meters writer cost. The generation bills separately. | | Set | Two to eight image jobs using shared references and settings | `POST /generate/set` | `GET /sets/{id}`; each member also has a `Job` | Each member has its own credit hold, settlement and refund. | | Composition | Timeline media, edits, grades and text into an MP4 or still | `POST /compositions/{id}/render` | `GET /renders/{id}` | Renders currently take no credit hold. Generating source media is billed separately. | | Edit session | An existing image and a sequence of new image edits | `POST /edit-sessions`, then `POST /edit-sessions/{id}/steps` | `GET /edit-sessions/{id}` or the step's `GET /jobs/{id}` | Each step is priced as an image generation; failed or canceled steps are refunded and excluded from `chain_credits`. | ## Presets ![Read a preset, assemble its prompt, submit to its model and follow the job](../assets/diagrams/workflow-presets.svg) | Property | Value | | --- | --- | | Input | A catalog preset slug, its intake answers, optional chips and a brief | | Preparation | Read `GET /presets/{slug}/intake/context`, then call `POST /presets/{slug}/assemble` | | Output | `prompt`, optional `negative_prompt`, `params`, `endpoint` and the assembly charge | | Execution | Submit the assembled fields to the returned generate endpoint; assembly itself creates no media | | Progress | Follow the returned generation job, not the preset catalog record | Public presets describe an outcome and the model or Studio lane that makes it. For `create_image`, `create_video` and `create_audio` targets, assembly validates the answers against the preset's intake and model limits. It keeps the target model fixed. Studio-intent targets do not use this assembly path; their agent turns and generated media bill separately. > [!NOTE] > Receiving an assembled prompt is not an accepted generation. Submit it explicitly and store the resulting job id before waiting for media. :::cards - [Presets](./presets.html): Discover the catalog, resolve intake and assemble a runnable prompt. ::: ## Sets ![A set fans out into independent image jobs and collects their outputs](../assets/diagrams/workflow-sets.svg) | Property | Value | | --- | --- | | Input | Two to eight labelled prompts plus a shared image model and visual settings | | Kinds | `pack` for a coordinated collection; `variants` for an axis such as emotion or concept | | Submit response | `202` with an `OutputSet`, not a single `Job` | | Progress | `generating`, then `ready`, `partial` or `failed`; inspect each member's job | | Refused members | `problems` records refused labels; accepted jobs continue and further members are not submitted | Keep the set id to display the group and each member's job id to investigate an individual failure. The set is a collection of independent generation outcomes; a failure or refusal does not roll back outputs that already succeeded. :::cards - [Sets](./sets.html): Submit a pack or variants, poll its members and handle partial outcomes. ::: ## Compositions ![Render a composition, poll the render record and read its output asset](../assets/diagrams/workflow-compositions.svg) | Property | Value | | --- | --- | | Input | A stored HTML timeline composition and its edits overlay | | Start | `POST /compositions/{id}/render` | | Output | MP4 by default; `target: still` produces one PNG frame | | Progress | The separate render record at `GET /renders/{id}` | | Inspection | Read `warnings` as well as the produced asset; unsupported timeline features can be skipped | | Other operations | Clone through `POST /compositions/{id}/clone`; import through `POST /compositions/{id}/imports/figma`; export an editor ZIP through `GET /compositions/{id}/export` | A render uses the composition's effective timeline, including supported placement, edits, grades, masks, media motion and text. It does not execute arbitrary page scripts. Follow the render id until completion, then read the produced asset for a fresh download URL. Editor export accepts `format=fcpxml` or `format=aejsx` and is separate from server rendering. :::cards - [Compositions and Studio export](./compositions.html): Render an MP4 or still and export a timeline for a desktop editor. ::: ## Edit sessions ![Create an edit session, estimate the next edit, append a step and follow its job](../assets/diagrams/workflow-edit-sessions.svg) | Property | Value | | --- | --- | | Start | `POST /edit-sessions` with `source_asset_id` for an image in your library | | Next edit | `POST /edit-sessions/{id}/steps` queues an image edit from the current head | | Read | `GET /edit-sessions/{id}` returns the ordered steps, their job ids and their statuses | | Quote | `GET /edit-sessions/{id}/estimate` returns `next_step_credits`, the resolved settings and `chain_credits` | | Revert | `POST /edit-sessions/{id}/revert` with an earlier `step_id`, or `null` to use the source | | Narrow tweaks | `GET /tweak-scopes` lists measured scopes, including unavailable scopes and their reasons | An edit step can use a freeform `prompt`, or a supported `scope` plus `detail` for a local change. A scoped edit cannot also carry `style_id`; its input and prompt are constrained to preserve the selected image. Read the available scope and model choices before submitting. | Field | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | Yes | | | `source_asset_id` | string | Yes | | | `head_step_id` | string, nullable | No | Null means the source is the head. | | `head_asset_id` | string | Yes | The head output, or the source as a fallback while the output is unavailable. | | `steps` | array of `EditSessionStep` | Yes | Steps in position order. | | `chain_credits` | integer | Yes | Sum of queued, running and succeeded step quotes. Failed and canceled steps are refunded and excluded. | | Field | Type | Required | Description | | --- | --- | --- | --- | | `model` | string | Yes | | | `quality` | string | Yes | | | `render_quality` | string | Yes | | | `next_step_credits` | integer | Yes | | | `chain_credits` | integer | Yes | | | `steps` | array of object | Yes | | > [!WARNING] > Wait for the current head before adding another step. An append returns `409` when the head is still generating, failed or its image is unavailable; `402` means the account cannot cover the edit. Reverting moves the head without deleting steps or assets, so it does not undo a charge for an earlier successful edit. Each step has its own job status. The session itself is the history and head pointer; it has no aggregate `status` field. Estimates show the cost of the next step and the chain total. Failed and canceled steps are excluded from that total. :::cards - [Edit sessions API](../api/): Create an edit history, append steps, estimate the next edit and revert the head. ::: ## Next steps :::cards - [Presets](./presets.html): Assemble a prompt for an outcome-named workflow. - [Sets](./sets.html): Coordinate a pack or explore labelled variants. - [Compositions and Studio export](./compositions.html): Turn the resulting media into a finished timeline. ::: --- # Presets Source: https://docs.nolgia.ai/guides/presets.md Presets describe an outcome and the model or Studio lane that makes it. Read the public catalog to choose one, resolve its intake to collect the right inputs, and assemble a prompt before generating. The catalog's time and credit hints help you browse; a current estimate or quote supplies the price for a run. ![Read a preset, assemble its prompt, submit to its model and follow the job](../assets/diagrams/workflow-presets.svg) | Method | Path | What it does | | --- | --- | --- | | [GET](../api/#tag/presets/get/presets) | `/presets` | List presets ordered by `sort_order`. Anonymous callers see the public catalog of ROOT presets (children hang under their parent and carry `parent_slug`; each root carries `children_count`); admin accounts see every preset, private (held) ones and children included, so a console can draw the tree. | | [GET](../api/#tag/presets/get/presets/{slug}) | `/presets/{slug}` | Get a preset's detail, with its family (`parent`, `children`, `inherited`). Anonymous callers read public presets; private presets 404 for anonymous and non-admin callers. | | [GET](../api/#tag/presets/get/presets/{slug}/intake/context) | `/presets/{slug}/intake/context` | Everything the app needs to render a preset's guided intake flow in one read (signed in). | | [POST](../api/#tag/presets/post/presets/{slug}/intake/estimate) | `/presets/{slug}/intake/estimate` | Price one run of a preset for the customer's own answers (signed in). | | [POST](../api/#tag/presets/post/presets/{slug}/assemble) | `/presets/{slug}/assemble` | Assemble a preset's final generation prompt from intake answers. | | [POST](../api/#tag/presets/post/presets/{slug}/suggestions) | `/presets/{slug}/suggestions` | Ask the AI for three to five directions tailored to a preset and the customer's brief (signed in; 1 credit). | ## List and read The public catalog is anonymous. `GET /presets` lists public root presets in ascending `sort_order`, with slug as the tiebreaker. Children belong to a parent's detail instead of appearing as root cards. Admin accounts can also see private presets and children. | Property | Value | | --- | --- | | Catalog | `GET /presets` | | Detail | `GET /presets/{slug}` | | Authentication | Optional for public catalog reads | | Featured filter | `featured=true` for featured only, `false` for non-featured only; omit for both | | Private preset | Omitted from the public catalog; direct reads return `404` for anonymous and non-admin callers | | Price hint | `estimated_credits` is human-written; use intake estimates or a cost quote for the actual settings | The next two commands select a few fields for readability. Their JSON is built from the `Preset` schema and illustrates a catalog entry; it is not a captured catalog response. Choose `PRESET_SLUG` from your own catalog result. ```bash title="example — read the anonymous catalog" $ curl -sS https://api.nolgia.ai/v1/presets ``` ```json title="200 OK — example projection of Preset[]" [ { "slug": "logo-design", "name": "Design a logo", "output_type": "image", "target": { "kind": "create_image", "params": {"model": "gpt-image-2.5-flare"} }, "estimated_credits": "See the current intake estimate" } ] ``` | Field | Type | Required | Description | | --- | --- | --- | --- | | `slug` | string | Yes | Stable identity of the preset; also the studio intent for agent-driven presets. | | `name` | string | Yes | | | `output_type` | `PresetOutputType` | Yes | | | `estimated_credits` | string | Yes | Rough human-written credit hint, e.g. "~300–600 credits". Authoritative per-model costs live on `GET /models`. | | `target` | `PresetTarget` | Yes | | | Field | Type | Required | Description | | --- | --- | --- | --- | | `kind` | `PresetTargetKind` | Yes | | | `params` | object | Yes | Kind-specific link parameters.… | ```bash title="example — read one preset's page content" $ curl -sS "https://api.nolgia.ai/v1/presets/$PRESET_SLUG" ``` ```json title="200 OK — example projection of Preset" { "slug": "logo-design", "long_description": "Create a logo from your brief and reference images.", "use_cases": ["Explore a wordmark for a coffee roaster"], "model_ids": ["gpt-image-2.5-flare"], "options": [{"label": "Wordmark", "description": "The name set in type, nothing else."}] } ``` | Field | Type | Required | Description | | --- | --- | --- | --- | | `slug` | string | Yes | Stable identity of the preset; also the studio intent for agent-driven presets. | | `options` | array of `PresetOption` | No | Customer-visible pick-one chips surfacing the option menus the preset's guardrails carry (named styles, archetypes, effects, casting, formats). Empty when the preset has no options surface. | | `long_description` | string | No | Long-form customer copy for the preset's own page (preset pages phase 2): what the preset makes, how it works, what to bring.… | | `use_cases` | array of string | No | Short customer-facing use cases, one per entry, rendered as a list on the preset page ("Launch teasers for a new drop").… | | `model_ids` | array of string | No | Machine ids of the models this preset runs on, as published by `GET /models` (e.g.… | | `parent` | `PresetParentRef`, nullable | No | The parent of a CHILD preset; absent for a root. Emitted on `GET /presets/{slug}` and on the write responses, not on the catalog list. | | `children` | array of `PresetChildSummary` | No | The directions under this preset ascending by `sort_order`: the "Choose a direction" list a preset page renders above its "describe your own" path.… | | `inherited` | `PresetInheritance` | No | What a CHILD carries verbatim from its parent, so the page can say "shares the family guardrails". Emitted on `GET /presets/{slug}` for a child; absent for a root. | The same detail contains the preset family, authored examples and page content. `/presets/{slug}/page` is a `PATCH` route for authorized page authors; there is no separate `GET` page route. Customers read page content through `GET /presets/{slug}`. ## Intake and assemble ### Read the intake context | Property | Value | | --- | --- | | Endpoint | `GET /presets/{slug}/intake/context` | | Authentication | Signed-in JWT or PAT | | Intake | The preset's own flow, or its parent's when it has none | | Model limits | Current reference slots, supported input kinds and identity capabilities | | Estimate | One run at the preset's default settings; a Studio intent estimates an agent turn and bills its generated media separately | | Missing flow | `404` for a missing or inaccessible preset, or one with no intake to resolve | Use `intake.steps` to build the questions and `model` to enforce supported input choices. The following response is a shortened, schema-built form of the spec's `logo-design` intake example, with one text step to keep the sequence readable. ```bash title="example — resolve the guided intake" $ curl --fail-with-body -sS "https://api.nolgia.ai/v1/presets/$PRESET_SLUG/intake/context" \ -H "Authorization: Bearer $NOLGIA_TOKEN" ``` ```json title="200 OK — example from PresetIntakeContext" { "slug": "logo-design", "intake_from_parent": false, "intake": { "version": 1, "steps": [ { "id": "brand", "kind": "text", "label": "What is the brand?", "required": true, "max_chars": 600, "binds_to": "prompt_slot:THE BRAND" } ], "submit": {"kind": "generate"} }, "target": { "kind": "create_image", "model": "gpt-image-2.5-flare", "endpoint": "POST /generate/image" }, "options": [], "prompt_slots": ["THE BRAND"], "assembly": {"doctrine": false, "doctrine_from_parent": false, "hold_credits": 0} } ``` | Field | Type | Required | Description | | --- | --- | --- | --- | | `assembly` | `PresetIntakeContextAssembly` | No | | | `slug` | string | Yes | | | `intake` | `PresetIntake` | Yes | | | `intake_from_parent` | boolean | Yes | `true` when `intake` is the parent's (the preset has none of its own). | | `target` | `PresetIntakeContextTarget` | Yes | | | `model` | `PresetIntakeModelLimits`, nullable | No | The target model's limits; null for a `studio_intent` target or a model the registry does not know. | | `options` | array of `PresetOption` | Yes | The card's option chips, what `option:` bindings resolve against. | | `prompt_slots` | array of string | Yes | The labelled slots found in the baked prompt, in order of first appearance (`THE MARK`, `THE STYLE`); empty for a `studio_intent`. | | `estimate` | `PresetIntakeEstimate`, nullable | No | | | Field | Type | Required | Description | | --- | --- | --- | --- | | `members` | array of `OutputSetMemberInput` | No | | | `set_kind` | `OutputSetKind` | No | | | `kind` | `PresetTargetKind` | Yes | | | `model` | string | No | The target model id (`target.params.model`) for a `create_*` target. | | `intent` | string | No | The studio intent (`target.params.intent`) for a `studio_intent` target. | | `endpoint` | string | No | The generate endpoint a `generate` submit calls, for display and routing. | ### Assemble the prompt | Property | Value | | --- | --- | | Endpoint | `POST /presets/{slug}/assemble` | | Authentication | Signed-in JWT or PAT; paid doctrine assembly also requires an organization library writer | | Supported targets | `create_image`, `create_video`, `create_audio`, including intakes that submit through an agent | | Invalid target | Studio-intent targets and targets without a model return `400` | | Private preset | `404` for non-admin callers | | Output model | Always the preset target model; assembly never changes it | | References | Library-scoped image bindings get fresh signed URLs; outside scope is `404`, unavailable resolution is `503` | Send answers keyed by the actual step ids from the returned intake. `chips` contains published option labels; unknown labels are refused and repeated choices are deduplicated. The example below continues the illustrative one-step intake above. ```bash title="example — assemble without generating media" $ curl --fail-with-body -sS "https://api.nolgia.ai/v1/presets/$PRESET_SLUG/assemble" \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H "Content-Type: application/json" \ -d '{"answers":{"brand":"Ember, a small coffee roaster: warm, quiet, honest"},"brief":"A simple wordmark on a plain background","count":1}' \ | tee assembled.json ``` ```json title="200 OK — example from AssemblePresetPromptResponse" { "prompt": "THE BRAND: Ember, a small coffee roaster: warm, quiet, honest. A simple wordmark on a plain background", "params": {"model": "gpt-image-2.5-flare", "num_images": 1}, "notes": ["Your answers were placed into the preset's own prompt as written."], "mode": "slot_fill", "credits_charged": 0, "model": "gpt-image-2.5-flare", "endpoint": "POST /generate/image" } ``` | Field | Type | Required | Description | | --- | --- | --- | --- | | `prompt` | string | Yes | | | `negative_prompt` | string | No | | | `params` | object | Yes | Generate body without prompt and negative_prompt. Its model is always the preset target model. | | `notes` | array of string | Yes | | | `mode` | one of `slot_fill`, `doctrine` | Yes | | | `credits_charged` | integer | Yes | | | `model` | string | Yes | The preset target model, never the assembly writer model. | | `endpoint` | string | Yes | | | `variants` | array of string | No | Further complete prompts, count minus one, only when doctrine is present and count exceeds one. | | `writer_model` | string | No | | | `doctrine_from_parent` | boolean | No | | | Field | Type | Required | Description | | --- | --- | --- | --- | | `answers` | object | No | Step id to an intake answer (text, string list, or table), using the same loose shapes as nolgia_run_preset. | | `brief` | string | No | | | `chips` | array of string | No | Card option labels; unknown labels are refused and repeated selections are deduplicated. | | `reference_asset_ids` | array of string | No | Extra library image references, mapped to reference_asset_ids for images or element_asset_ids for video; refused for audio. | | `count` | integer | No | How many prompts to write (1 to 4, 1 when omitted); more than one needs the preset's prompt doctrine. | | `project_id` | string | No | | ### slot_fill | Property | Value | | --- | --- | | Used when | The preset and its parent have no authored prompt doctrine | | Behavior | Place answers into the baked prompt and append the brief | | Cost | Zero credits; no AI call and no assembly rate limit | | Variations | A `count` above one adds a note; no `variants` are returned | ### doctrine | Property | Value | | --- | --- | | Used when | The preset has non-empty prompt doctrine, or inherits it from its parent | | Behavior | A writer follows that doctrine, guardrails and model limits to produce the final prompt | | Cost | Hold one credit, then meter provider cost with a one-credit floor | | Variations | `count` 2–4 returns `count - 1` further complete prompts in `variants` | | Refusals | `402` for insufficient credits; shared suggestions rate limit returns `429`, or `503` if unavailable | | Failure | Provider or validation failure refunds the hold and returns `502` | > [!NOTE] > Assembly does not generate media. `credits_charged` is the assembly charge only; the subsequent image, video or audio job has its own price. A doctrine writer's model can differ from the target model, but `params.model` stays fixed to the preset target. ### Generate from the assembled result For an assembly whose `endpoint` is `POST /generate/image`, merge `prompt` and optional `negative_prompt` into `params`. Keep the returned reference and model settings intact. Choose the corresponding video or audio endpoint when assembly names one of those instead. ```bash title="example — submit an assembled image request" $ curl -sS https://api.nolgia.ai/v1/generate/image \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H "Content-Type: application/json" \ -d '{"model":"","prompt":""}' ``` ```json title="202 Accepted — example from Job" { "id": "9b09f1a0-e856-4275-bb83-7a6cc5e304d8", "user_id": "75976388-3210-42e6-9b39-bf6290a15d4c", "model": "gpt-image-2.5-flare", "modality": "image", "status": "queued", "created_at": "2026-09-21T04:00:00Z", "updated_at": "2026-09-21T04:00:00Z" } ``` | Field | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | Yes | | | `user_id` | string | Yes | | | `modality` | `Modality` | Yes | | | `model` | string | Yes | | | `status` | `JobStatus` | Yes | | | `created_at` | string | Yes | | | `updated_at` | string | Yes | | Follow that job through [submit and poll](./jobs.html). If the intake's target kind is `set`, use the [set request](./sets.html) instead; the assembly operation documented here supports the three `create_*` targets. ## Suggestions Ask for directions before choosing a final prompt. This is `POST /presets/{slug}/suggestions`; it is not a catalog read. | Property | Value | | --- | --- | | Authentication | Signed-in caller; private presets return `404` to non-admins | | Input | Optional `brief` and `count` from 3 to 5, default 4 | | Output | Validated titles, descriptions, runnable prompts, selected option chips and an optional matching public child slug | | Rate limit | 20 calls per minute per account; `429` when exceeded | | Credit hold | One credit before calling the writer; `402` if unavailable | | Settlement | `ceil(provider cost / $0.018)`, minimum one credit; provider failure refunds the hold | The writer uses the preset's own description, guardrails, options and children. Invalid suggestions are dropped, so fewer than `count` can be returned. A `child_slug` points to an existing public child when one matches; it is `null` for a new direction. The following is an illustrative schema-built response with one validated direction remaining. ```bash title="example — ask for directions" $ curl --fail-with-body -sS "https://api.nolgia.ai/v1/presets/$PRESET_SLUG/suggestions" \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H "Content-Type: application/json" \ -d '{"brief":"Ember, a small coffee roaster: warm, quiet, honest","count":3}' ``` The response excerpt omits `model`, which names the suggestion writer, not the generation model the preset selects. ```json title="200 OK — schema-built PresetSuggestionsResponse excerpt" { "suggestions": [ { "title": "Quiet wordmark", "description": "A warm, restrained wordmark for the coffee roaster.", "prompt": "Design a logo for Ember, a small coffee roaster. Set the word Ember in warm, restrained lettering on a plain background. Keep the letterforms clear at a small size.", "options": {}, "child_slug": null } ], "credits_charged": 1 } ``` | Field | Type | Required | Description | | --- | --- | --- | --- | | `suggestions` | array of `PresetSuggestion` | Yes | The validated suggestions, at most `count`.… | | `credits_charged` | integer | Yes | Credits taken for this call: `ceil(provider cost / $0.018)` with a floor of 1. On the current brain a call is 1 credit. | | `model` | string | Yes | The brain that wrote the suggestions. | | Field | Type | Required | Description | | --- | --- | --- | --- | | `title` | string | Yes | A short name for the direction. | | `description` | string | Yes | One or two plain-English sentences on what the customer would get. | | `prompt` | string | Yes | A complete prompt the customer can run on this preset as it is, written to the preset's guardrails and runnable on its model. | | `options` | object | Yes | The preset's option chips this direction picks, keyed by the chip's `label` with the chip's published `description` as the value.… | | `child_slug` | string, nullable | Yes | The existing public child preset that is the best match for this direction, when there is one; `null` when the direction is new. | | Field | Type | Required | Description | | --- | --- | --- | --- | | `brief` | string | No | What the customer wants, in their own words: the brand, the mood, the occasion, anything. Optional; without it the suggestions are directions the preset itself is good for. | | `count` | integer | No | How many suggestions to return (3 to 5). | The ledger labels this charge `Preset suggestions · {slug}`. Suggestions return prompt options; they do not create a generation job or a finished asset. ## Presets that run in an app on your computer Film assistant presets (`page_category` `film-assistant`, target kind `desktop_app`) do their work inside a desktop app on your own computer, such as Blender. NOLGIA generates nothing and charges nothing when one runs: the preset carries a written workflow, `instructions`, which your agent follows in the app through the NOLGIA plugin for that app. Anything the workflow makes with NOLGIA along the way, like a 3D model or a background, is priced as usual. | Property | Value | | --- | --- | | Target | `desktop_app`; `target.params` names the `app`, an optional `min_app_version` and `beta` | | Workflow | `instructions`: the app's shared rules first, then the preset's own steps | | Starting it | `starter_prompt`: the sentence to paste into your agent | | Intake and estimate | None: `/presets/{slug}/intake/context` and `/presets/{slug}/intake/estimate` answer `404` | | Assemble and suggestions | Not available: both answer `400` | | Field | Type | Required | Description | | --- | --- | --- | --- | | `instructions` | string | No | The workflow a `desktop_app` preset hands the agent, markdown: the app's shared rules (every workflow for that app follows them: check the app is connected, read the open document, make a safety copy, keep the work editable, preview every change, say the credit cost before generating, how to finish) followed by this preset's own steps.… | | `starter_prompt` | string | No | The sentence or two a person pastes into their agent to start this preset, shown on the preset page and used as the MCP prompt text (in Claude Code the preset appears as `/mcp__nolgia__`).… | In an MCP client, every public Film assistant preset is also a prompt; see [Run MCP](./mcp.html#prompts). ## Next steps :::cards - [Workflows](./workflows.html): Choose between preset preparation, sets, compositions and edit sessions. - [Playground](./playground.html): Explore the model and settings behind a preset before integrating it. ::: --- # Sets Source: https://docs.nolgia.ai/guides/sets.md A set groups two to eight labelled image prompts under one model and shared visual settings. Use it for carousel slides, ad variants or an exploration along a named axis. Each member remains its own job with its own credit hold and refund. ![A set fans out into independent image jobs and collects their outputs](../assets/diagrams/workflow-sets.svg) | Method | Path | What it does | | --- | --- | --- | | [POST](../api/#tag/generate/post/generate/set) | `/generate/set` | Generate a coordinated set of image Outputs. | | [GET](../api/#tag/sets/get/sets) | `/sets` | List sets in your current library, newest first. | | [GET](../api/#tag/sets/get/sets/{id}) | `/sets/{id}` | Get a set with its jobs and Ready Outputs. | ## Choose a kind ### pack | Property | Value | | --- | --- | | Value | `pack` | | Default | Used when `kind` is omitted | | Purpose | A coordinated collection, such as carousel slides or ad variants | | Shared settings | Model, references, characters, location, saved style, brand kit, product and image settings | | Member input | Its own `label` and `prompt` | Repeat the visual rules each member must preserve in its prompt, and supply shared references and settings on the set. A shared visual system does not make one member's finished image the input to the next. ### variants | Property | Value | | --- | --- | | Value | `variants` | | Purpose | Explore a named axis, such as emotion or concept | | Axis field | `axis`, a string up to 40 characters | | Member labels | Name each direction so the resulting jobs and outputs remain identifiable | | Billing | One independently priced image generation per accepted member | ## Submit a set Use a label for each output and keep the same model and visual settings at the top level. These requests and responses are schema-built examples, not production captures. The installed CLI has no set-generation command; use the HTTP endpoint or a generated client. ```bash title="example — submit a pack" $ curl --fail-with-body -sS https://api.nolgia.ai/v1/generate/set \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H "Content-Type: application/json" \ -d '{"model":"flux-pro","name":"Paper mountains","kind":"pack","members":[{"label":"Dawn","prompt":"A paper-cut mountain range at dawn, warm paper, blue shadows"},{"label":"Night","prompt":"A paper-cut mountain range at night, warm paper, blue shadows"}]}' ``` ```json title="202 Accepted — example from OutputSet" { "id": "b6a72f92-4e05-46b9-a28d-3c69c6d6e1e9", "name": "Paper mountains", "kind": "pack", "model": "flux-pro", "status": "generating", "labels": ["Dawn", "Night"], "members": [ { "label": "Dawn", "prompt": "A paper-cut mountain range at dawn, warm paper, blue shadows", "sort_order": 0, "job": { "id": "6df58eed-0617-4f3c-8e87-e2f28d05e37d", "user_id": "75976388-3210-42e6-9b39-bf6290a15d4c", "model": "flux-pro", "modality": "image", "status": "queued", "created_at": "2026-09-21T04:00:00Z", "updated_at": "2026-09-21T04:00:00Z" } }, { "label": "Night", "prompt": "A paper-cut mountain range at night, warm paper, blue shadows", "sort_order": 1, "job": { "id": "a432a04c-cbf1-4cfe-8fdc-ebd4d7a504e8", "user_id": "75976388-3210-42e6-9b39-bf6290a15d4c", "model": "flux-pro", "modality": "image", "status": "queued", "created_at": "2026-09-21T04:00:01Z", "updated_at": "2026-09-21T04:00:01Z" } } ], "created_at": "2026-09-21T04:00:00Z", "updated_at": "2026-09-21T04:00:01Z" } ``` | Field | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | Yes | | | `name` | string | Yes | | | `kind` | `OutputSetKind` | Yes | | | `axis` | string | No | | | `preset_slug` | string | No | | | `project_id` | string | No | | | `model` | string | Yes | | | `shared` | object | No | | | `labels` | array of string | Yes | | | `status` | `OutputSetStatus` | Yes | | | `members` | array of `OutputSetMember` | Yes | | | `problems` | array of `OutputSetProblem` | No | | | `created_at` | string | Yes | | | `updated_at` | string | Yes | | | Field | Type | Required | Description | | --- | --- | --- | --- | | `label` | string | Yes | | | `prompt` | string | Yes | | | `sort_order` | integer | Yes | | | `job` | `Job` | Yes | | | `asset` | `Asset` | No | | The nested `job` uses the [Job response](./jobs.html#submit-a-request). When it succeeds, the member's `asset` contains the ready output. Store the set id for the group and the member job ids for progress or support. ### Request fields | Field | Type | Required | Description | | --- | --- | --- | --- | | `confirmation_token` | string | No | Optional proof that this exact request and price were shown to the customer, from `POST /jobs/cost`.… | | `model` | `ImageModel` | Yes | | | `members` | array of `OutputSetMemberInput` | Yes | | | `name` | string | No | | | `kind` | `OutputSetKind` | No | | | `axis` | string | No | A named axis for a variants set, such as emotion or concept. | | `preset_slug` | string, nullable | No | | | `project_id` | string | No | | | `tags` | array of string | No | | | `reference_asset_ids` | array of string, nullable | No | | | `face_reference_asset_id` | string, nullable | No | | | `face_check_consent` | boolean, nullable | No | Applied to every member exactly as on `POST /generate/image`: records your consent to the face identity check for the `face_reference_asset_id` photo (once per photo; it stays recorded for later requests).… | | `character_ids` | array of string, nullable | No | | | `location_id` | string, nullable | No | | | `brand_kit_id` | string, nullable | No | | | `product_id` | string, nullable | No | | | `style_id` | string, nullable | No | | | `negative_prompt` | string, nullable | No | | | `aspect_ratio` | `ImageAspectRatio` | No | | | `quality` | string, nullable | No | | | `render_quality` | one of `auto`, `low`, `medium`, `high`, `xhigh`, `max` | No | | | Field | Type | Required | Description | | --- | --- | --- | --- | | `label` | string | Yes | | | `prompt` | string | Yes | | `members` must contain 2–8 entries; each label is 1–40 characters and each prompt 1–4,000 characters. `kind` defaults to `pack`. A `variants` request can name its axis. `confirmation_token` comes from a set quote through `POST /jobs/cost`; it covers the exact set request and price. ## Poll the outputs `GET /sets/{id}` returns an `OutputSet` with the latest jobs and ready assets. The following command projects the fields a progress list needs; the JSON is a schema-built completed example of that projection. ```bash title="example — poll and project the member outcomes" $ curl -sS "https://api.nolgia.ai/v1/sets/$SET_ID" -H "Authorization: Bearer $NOLGIA_TOKEN" ``` ```json title="200 OK — example projection of OutputSet" { "id": "b6a72f92-4e05-46b9-a28d-3c69c6d6e1e9", "status": "ready", "outputs": [ { "label": "Dawn", "job_id": "6df58eed-0617-4f3c-8e87-e2f28d05e37d", "status": "succeeded", "asset_id": "c87cf95c-2975-4d88-976c-234060f95fbf" }, { "label": "Night", "job_id": "a432a04c-cbf1-4cfe-8fdc-ebd4d7a504e8", "status": "succeeded", "asset_id": "d25f50c0-ced7-4baa-ad9a-03bdb31766fd" } ], "problems": null } ``` | Projected field | Source | Meaning | | --- | --- | --- | | `id` | `OutputSet.id` | Group identifier to retain and poll. | | `status` | `OutputSet.status` | Overall progress; values below. | | `outputs[].label` | `members[].label` | Your name for the requested output. | | `outputs[].job_id` | `members[].job.id` | The individual generation to follow. | | `outputs[].status` | `members[].job.status` | That member's job outcome. | | `outputs[].asset_id` | `members[].asset.id` | Ready library output; the projection gives `null` before it exists. | | `problems` | `OutputSet.problems` | Refused labels when present; absent when no member was refused. | ### Set status | `OutputSetStatus` | Meaning | Next action | | --- | --- | --- | | `generating` | Member jobs are still running. | Poll the same set. | | `ready` | All outputs are ready. | Read or download the member assets. | | `partial` | Some jobs failed. | Keep successful outputs; inspect failed jobs and any refused labels. | | `failed` | All jobs failed. | Inspect each job's failure and refund before deciding what to resubmit. | ### List previous sets `GET /sets` reads sets in your current library, newest first. `project_id` filters by project; `limit` accepts 1–100 and defaults to 25. The empty result below is a schema-built example. ```bash title="example — list sets" $ curl --fail-with-body -sS 'https://api.nolgia.ai/v1/sets?limit=25' \ -H "Authorization: Bearer $NOLGIA_TOKEN" ``` ```json title="200 OK — example from OutputSetList" {"sets": []} ``` | Field | Type | Required | Description | | --- | --- | --- | --- | | `sets` | array of `OutputSet` | Yes | | | Parameter | In | Required | Description | | --- | --- | --- | --- | | `project_id` | query | No | | | `limit` | query | No | Maximum sets to return, 25 when omitted. | ## Partial acceptance and billing If a later member is refused, accepted members keep running and further members are not submitted. The set's `problems` array lists refused labels, each with its HTTP status and detail. A refusal is different from a member that was accepted and later failed: the latter has a job whose `failure.code` and `failure.credits_refunded` describe the outcome. | Field | Type | Required | Description | | --- | --- | --- | --- | | `label` | string | Yes | | | `status` | integer | Yes | | | `detail` | string | Yes | | | Outcome | What to inspect | Credit result | | --- | --- | --- | | Member accepted | Its nested `job` | Its own hold, settled on completion. | | Member refused at submission | `problems[].label`, `status`, `detail` | No accepted generation for that member; accepted siblings continue. | | Accepted member fails | `members[].job.failure` | Its recorded refund is independent of siblings; read `credits_refunded`. | | Remaining members were not submitted | Compare requested labels with `members` and `problems` | No job or hold for the unsubmitted work. | > [!WARNING] > `Idempotency-Key` on `POST /generate/set` is ignored per member. Do not retry the whole set to repair a partial result: first inspect the returned set and its accepted jobs, or you can create and pay for another copy of work already running. Quote a set before submission using `POST /jobs/cost` with `kind: set` and the full `set` request. That quote covers the run's members at their shared image settings. It does not make acceptance atomic or pool their refunds. ## Next steps :::cards - [Asynchronous: submit and poll](./jobs.html): Follow an individual member's job and read its failure outcome. - [Pricing and credits](./billing.html): Quote the whole run and reconcile member charges and refunds. ::: --- # Characters and locations Source: https://docs.nolgia.ai/guides/characters.md Keep a subject's identity and a place's appearance in reusable Library records. A character carries its name, canonical description and reference images; a location does the same for a room, storefront or set. Generation attaches their references and folds their canonical descriptions into the prompt verbatim. ## Characters | Property | Value | | --- | --- | | Resource | `/characters` and `/characters/{id}` | | Reference images | Up to eight; `primary_reference_asset_id` selects the identity anchor and defaults to the first reference | | Continuity text | `canonical_description`, falling back to `description` on older records | | Generation input | `character_id` for one subject, or ordered `character_ids` for a cast | | Library | Personal or active organization; an organization record carries `organization_id` | | Method | Path | What it does | | --- | --- | --- | | [POST](../api/#tag/characters/post/characters/autopilot) | `/characters/autopilot` | Roll a new Aura character and start its portrait render. | | [POST](../api/#tag/characters/post/characters/{id}/sheets) | `/characters/{id}/sheets` | Generate a one-tap character sheet (split, five-view turnaround, or expression). | | [GET](../api/#tag/characters/get/characters) | `/characters` | List the current user's characters, newest first. | | [POST](../api/#tag/characters/post/characters) | `/characters` | Create a reusable character from existing image assets. | | [GET](../api/#tag/characters/get/characters/{id}) | `/characters/{id}` | Fetch one of the current user's characters with fresh signed reference URLs. | | [PATCH](../api/#tag/characters/patch/characters/{id}) | `/characters/{id}` | Update a character's name, description, or reference images. | | [DELETE](../api/#tag/characters/delete/characters/{id}) | `/characters/{id}` | Delete one of the current user's characters. | Read `readiness` before selecting a reference: `ready` means one front-facing face. `needs_front_view`, `no_face`, `multiple_faces` and `unverified` are advisory states, not a promise that the identity gate will pass. `GET /characters/{id}` also includes recent `fidelity_history`; list and write responses omit that history. | Field | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | Yes | | | `name` | string | Yes | | | `canonical_description` | string | No | The character's canonical written description - the asset bible entry (face, hair, build, wardrobe, anchor) reused VERBATIM by every downstream generation.… | | `primary_reference_asset_id` | string, nullable | No | The reference asset that anchors the character's identity: sheet actions attach it as the character reference and identity scoring runs against it. Defaults to the first reference asset when unset. | | `reference_assets` | array of `Asset` | Yes | Reference images (image assets) in display order, each with a fresh signed URL. | | `voice` | `CharacterVoice` | No | Used by POST /generate/audio (character_id) and by video generation with use_character_voice true; absent when the character has no voice. | | `readiness` | string | No | Whether the character's PRIMARY reference can anchor its identity, derived from the face verdict stored when the reference joined the character.… | | `fidelity_history` | array of `CharacterFidelitySample` | No | The character's most recent identity-gated renders, newest first: the `identity_score` the Aura gate measured against the primary reference on each image or video generated with this `character_id`.… | ## Locations | Property | Value | | --- | --- | | Resource | `/locations` and `/locations/{id}` | | Reference images | Up to eight; the primary reference anchors the place | | Continuity text | `canonical_description`, falling back to `description` when empty | | Generation input | `location_id`, alongside a character or cast | | Identity scoring | None: the identity gate scores faces, not places | | Method | Path | What it does | | --- | --- | --- | | [GET](../api/#tag/locations/get/locations) | `/locations` | List the current user's locations, newest first. | | [POST](../api/#tag/locations/post/locations) | `/locations` | Create a reusable location from existing image assets. | | [GET](../api/#tag/locations/get/locations/{id}) | `/locations/{id}` | Fetch one of the current user's locations with fresh signed reference URLs. | | [PATCH](../api/#tag/locations/patch/locations/{id}) | `/locations/{id}` | Update a location's name, descriptions, primary anchor or reference images. | | [DELETE](../api/#tag/locations/delete/locations/{id}) | `/locations/{id}` | Delete one of the current user's locations. | A location needs no training. Its canonical description stays unchanged across scenes, and its primary reference uses an image-reference slot on an image model or an element-reference slot on a video model. A location without an image contributes its description only. ## Use them in a generation | Input | Image behavior | Video behavior | | --- | --- | --- | | `character_id` | Primary image becomes the face reference; requires an Aura-compatible model with room for the reference and `num_images: 1` | Primary image becomes an element reference | | `character_ids` | Ordered cast of up to four; lead supplies the face reference, then the other cast references follow | Ordered cast of up to four; references keep cast order after the caller's own references; `@Name` is rewritten to the member's `@ImageN` slot | | `use_character_voice` | Not an image field | Opt in to the lead's clip voice as the next audio reference; requires a model with an available audio slot and an acceptable clip length | | `location_id` | Adds the place's description and reference | Adds the place's description and an element reference | The image request fields are generated from the contract: | Field | Type | Required | Description | | --- | --- | --- | --- | | `character_id` | string, nullable | No | One of your characters (`GET /characters`, created on the Create Characters page).… | | `character_ids` | array of string, nullable | No | An ORDERED cast of up to 4 of your characters rendered together (two-person dialogue scenes, duets, family commercials).… | | `location_id` | string, nullable | No | One of your locations (`GET /locations`): the place this render is set in.… | The video fields include the explicit voice opt-in: | Field | Type | Required | Description | | --- | --- | --- | --- | | `character_id` | string, nullable | No | One of your characters (`GET /characters`).… | | `use_character_voice` | boolean, nullable | No | Attaches the lead character's (character_id, or the first of character_ids) voice clip as a reference audio track (audio_asset_ids, emitted after your own audio tracks and audio_urls, taking the next @Audio slot) and adds a voice line to the prompt.… | | `character_ids` | array of string, nullable | No | An ORDERED cast of up to 4 of your characters rendered together in one clip.… | | `location_id` | string, nullable | No | One of your locations (`GET /locations`): the place this clip is set in.… | > [!WARNING] > Check the model's reference budgets before casting. A cast or location that cannot fit is refused with `400`, never silently dropped. Do not combine `character_id` with `face_reference_asset_id`; they supply competing identities. Duplicate cast members and foreign ids are also refused. When both `character_id` and `character_ids` are supplied, the single id must be a member of the cast. The first member is the lead, including for `use_character_voice`. Voice opt-in may change the cost on models billed by the reference audio's length; quote the full request first. ## Autopilot | Property | Value | | --- | --- | | Start | `POST /characters/autopilot` | | Returns | `202` with a portrait `job`, `canonical_description` and eight rolled `axes` | | Model | `grok-imagine-image` | | Billing | The equivalent image generation price | | Next step | Wait for the portrait, then create the character with `POST /characters` | Autopilot rolls heritage, age band, hair color, build, hair style, eye color, wardrobe and an anchor detail. Consecutive rolls differ on at least two of the four core axes. It starts a portrait job; it does not create the character record for you. Store its returned `canonical_description` unchanged when creating that record. ```bash title="Autopilot request — example" $ curl -sS -X POST https://api.nolgia.ai/v1/characters/autopilot \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H "Content-Type: application/json" \ -d '{}' ``` This illustrative response is built from `AutopilotCharacterResponse`, not a production capture. ```json title="202 — example from the response schema" { "job": { "id": "b9f4319a-239e-4907-a9bb-406be1f0c2b8", "user_id": "4961eaef-70a6-4e9b-b746-c86ad3e62077", "modality": "image", "model": "grok-imagine-image", "status": "queued", "created_at": "2026-09-21T04:00:00Z", "updated_at": "2026-09-21T04:00:00Z" }, "canonical_description": "A middle-aged woman with short dark curls, brown eyes, a sturdy build, a navy work jacket and a small silver brooch.", "axes": { "heritage": "mixed heritage", "age_band": "middle-aged", "hair_color": "dark", "build": "sturdy", "hair_style": "short curls", "eye_color": "brown", "wardrobe": "navy work jacket", "anchor": "small silver brooch" } } ``` | Field | Type | Required | Description | | --- | --- | --- | --- | | `job` | `Job` | Yes | | | `canonical_description` | string | Yes | The rolled canonical written description. Store it on the character record unchanged when creating the character from this roll - it is the continuity contract for every downstream generation. | | `axes` | object | Yes | The variety-engine roll: all 8 axis values (heritage, age_band, hair_color, build, hair_style, eye_color, wardrobe, anchor).… | | Field | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | Yes | | | `user_id` | string | Yes | | | `modality` | `Modality` | Yes | | | `model` | string | Yes | | | `status` | `JobStatus` | Yes | | | `asset` | `Asset` | No | | | `failure` | `JobFailure` | No | | | `created_at` | string | Yes | | | `updated_at` | string | Yes | | For an existing character, `POST /characters/{id}/sheets` creates a `split`, `turnaround` or `expression` sheet on `gpt-image-2`, billed like that model's image generation. It needs at least one reference image. Wearable references and a named wardrobe outfit share a budget of three extra references; overflow is refused instead of trimming the outfit. | Field | Type | Required | Description | | --- | --- | --- | --- | | `kind` | `CharacterSheetKind` | Yes | | | `wearable_reference_asset_ids` | array of string | No | Image assets (owned by the caller) showing exact wearable items - garments, jewelry, eyewear, footwear - that must render EXACTLY as shown on the character in every view: never redesigned, restyled, recolored, or reinterpreted.… | | `outfit` | string, nullable | No | The name of one of the character's wardrobe outfits (`Character.wardrobe`).… | | `project_id` | string, nullable | No | Files the generated sheet into this caller-owned project. | ## The identity gate | Property | Value | | --- | --- | | Score | ArcFace-family cosine similarity, from −1 to 1 | | Passing threshold | `identity_score >= 0.60` | | Automatic retry | At most one re-roll; the better-scoring attempt is delivered | | Multi-character result | `identity_score` is the weakest cast member's score | | Video sampling | Frames at 2 fps; each member uses its best matching face/frame | | Unavailable scoring | Identity fields are absent or null; the reference still conditioned the generation | | Field | Type | Required | Description | | --- | --- | --- | --- | | `identity_score` | number, nullable | No | ArcFace-family cosine similarity against the generation's identity reference (Aura identity gate).… | | `identity_rerolls` | integer, nullable | No | Number of completed automatic identity re-rolls behind this render (0 or 1 — the gate re-rolls at most once, then delivers the better-scoring attempt). Present only alongside `identity_score`. | | `identity_gate_passed` | boolean, nullable | No | Whether `identity_score` clears the 0.60 identity gate.… | | `character_scores` | array of `AssetCharacterScore`, nullable | No | Per-character identity outcome of a multi-character cast render (`character_ids`), in cast order: each member's own ArcFace-family cosine (its best-matching face in the render, or the best frame of a clip) and whether it clears the 0.60 gate.… | | Field | Type | Required | Description | | --- | --- | --- | --- | | `character_id` | string | Yes | The cast member (`GET /characters/{id}`). | | `identity_score` | number | Yes | This member's cosine against the delivered render. | | `identity_gate_passed` | boolean | Yes | Whether this member clears the 0.60 identity gate. | > [!NOTE] > A delivered asset can have `identity_gate_passed: false`: both attempts missed the threshold and the better attempt was delivered. Check the boolean and per-member `character_scores` before treating a cast render as approved. An absent score does not mean it passed. Characters without a reference image contribute only their description and remain unscored. Locations are not scored by the face gate. ## Related reusable inputs [Products](../api/#tag/products) carry reusable product images and details into a generation through `product_id`. [Brand kits](./styles.html#brand-kits) carry palettes, fonts, logos and never-rules through `brand_kit_id`. [Elements](../api/#tag/elements) provide reusable reference inputs through `element_ids`; they count against the selected model's reference budget. ## Next steps :::cards - [Common model arguments](./model-arguments.html): Check model capabilities and compose compatible reference inputs. - [Styles and looks](./styles.html): Add a saved look, brand rules and a camera move. ::: --- # Styles and looks Source: https://docs.nolgia.ai/guides/styles.md Use a saved style to repeat a look, a brand kit to carry brand rules, a named motion to direct the camera, and a color preset to grade a Studio composition. These inputs affect different stages of the result and can be combined within the chosen model's capabilities. ## Saved styles Saved styles are a reusable look: a prompt fragment plus reference images, applied with `style_id`. | Property | Value | | --- | --- | | Resource | `/styles` and `/styles/{id}` | | Generation input | `style_id` on image or video requests | | Prompt | `prompt_fragment` is appended after your own words; it never replaces them | | Images | Up to four reference images; the first is the style's `swatch` | | Model hint | A client suggestion only; the API never switches the requested model | | Default | The output project's pinned style applies when `style_id` is omitted | | Method | Path | What it does | | --- | --- | --- | | [GET](../api/#tag/styles/get/styles) | `/styles` | List saved styles in your library, newest first. | | [POST](../api/#tag/styles/post/styles) | `/styles` | Create a saved style. | | [GET](../api/#tag/styles/get/styles/{id}) | `/styles/{id}` | Fetch a saved style with fresh signed reference URLs. | | [PATCH](../api/#tag/styles/patch/styles/{id}) | `/styles/{id}` | Update a saved style, replacing references only when provided. | | [DELETE](../api/#tag/styles/delete/styles/{id}) | `/styles/{id}` | Delete a saved style. | An explicit `style_id` overrides the project's style for one generation. Style images follow the subject references and use only the remaining reference budget. An image that does not fit is left out; the prompt fragment still applies. > [!NOTE] > For video, the style's swatch rides only when the clip already has another visual input, such as a character, reference image, start frame or reference video. A text-only clip gets the fragment alone, so a lone swatch does not become the scene's subject. `source_video_asset_id` regeneration cannot take an explicit style and skips the project's pinned style. ```bash title="List saved styles — example" $ curl -sS https://api.nolgia.ai/v1/styles \ -H "Authorization: Bearer $NOLGIA_TOKEN" ``` This response is built from `SavedStyleList` and `SavedStyle`, not a production capture. ```json title="200 — example from the response schemas" { "styles": [ { "id": "60825d85-9c86-4146-9715-386a1aa2743c", "user_id": "4961eaef-70a6-4e9b-b746-c86ad3e62077", "name": "Paper cut", "prompt_fragment": "Layered paper shapes with visible cut edges and soft side lighting.", "model_hint": "flux-pro", "reference_images": [], "created_at": "2026-09-21T04:00:00Z", "updated_at": "2026-09-21T04:00:00Z" } ] } ``` | Field | Type | Required | Description | | --- | --- | --- | --- | | `styles` | array of `SavedStyle` | Yes | | | Field | Type | Required | Description | | --- | --- | --- | --- | | `swatch` | `Asset` | No | The style key image: the first reference image, attached first when the style rides a generation. Omitted when the style has no reference images. | | `id` | string | Yes | | | `user_id` | string | Yes | | | `organization_id` | string | No | | | `name` | string | Yes | | | `prompt_fragment` | string | Yes | Appended after your own prompt when style_id is passed, never replaces it. | | `model_hint` | string | Yes | An ImageModel id the look renders best on, or empty. Clients default the picker to it, the API never switches models for you. | | `reference_images` | array of `Asset` | Yes | Reference images in display order with fresh signed URLs, appended within the model caps. | | `created_at` | string | Yes | | | `updated_at` | string | Yes | | ## Brand kits Brand kits are reusable palettes, fonts, logos and never-rules for generation, scoped to the personal library or the active organization. | Property | Value | | --- | --- | | Resource | `/brand-kits` and `/brand-kits/{id}` | | Generation input | `brand_kit_id` and `brand_kit_mode` on image or video requests | | Default kit | The explicit project's kit, or the active agent session's project kit | | Prompt | The compact brand block is appended after your own words | | Logo | Attaches only when supported and a spare reference slot remains; never displaces a subject reference | | Outcome | Asset generation metadata records `brand_logo_attached` | | Method | Path | What it does | | --- | --- | --- | | [GET](../api/#tag/brandkits/get/brand-kits) | `/brand-kits` | List the current user's brand kits, newest first. | | [POST](../api/#tag/brandkits/post/brand-kits) | `/brand-kits` | Create a reusable brand kit from brand rules and existing image assets. | | [GET](../api/#tag/brandkits/get/brand-kits/{id}) | `/brand-kits/{id}` | Fetch one of the current user's brand kits with fresh signed logo URLs. | | [PUT](../api/#tag/brandkits/put/brand-kits/{id}) | `/brand-kits/{id}` | Replace a brand kit's full contents. | | [DELETE](../api/#tag/brandkits/delete/brand-kits/{id}) | `/brand-kits/{id}` | Delete one of the current user's brand kits. | ### brand_kit_mode | Value | Behavior | | --- | --- | | `full` | Default: append the brand block and attach the logo where it fits | | `prompt_only` | Append the brand block without attaching the logo | | `off` | Disable both an explicit kit and the project's kit; cannot be combined with `brand_kit_id` | Image enhancement, expansion and masked requests do not attach a brand logo. Video edit and extend tasks do not attach it either; regeneration with `source_video_asset_id` refuses an explicit kit and skips an implied project kit. The model's reference limits still apply. | Field | Type | Required | Description | | --- | --- | --- | --- | | `brand_kit_id` | string, nullable | No | One of your brand kits (`GET /brand-kits`).… | | `brand_kit_mode` | `BrandKitMode` | No | | | `style_id` | string, nullable | No | One of your saved styles (`GET /styles`).… | | Field | Type | Required | Description | | --- | --- | --- | --- | | `brand_kit_id` | string, nullable | No | One of your brand kits (`GET /brand-kits`).… | | `brand_kit_mode` | `BrandKitMode` | No | | | `style_id` | string, nullable | No | One of your saved styles (`GET /styles`).… | ## Camera moves The camera-move library is public, embedded and identical for every caller. Send its stable `id` as `motion_id` on a video request; the server appends the chosen strength's exact prompt fragment as a sentence without replacing your direction. | Property | Value | | --- | --- | | Catalog | `GET /motions`, anonymous | | Input | `motion_id` and optional `motion_strength` | | Strengths | `subtle`, `medium`, `strong`; omitted strength uses the move's `default_strength` | | Models | All video models, including text-to-video, image-to-video and `shots[]` requests | | Preview clips | `preview_url` is reserved and currently null | | Method | Path | What it does | | --- | --- | --- | | [GET](../api/#tag/motions/get/motions) | `/motions` | List the camera-move library for video generation. Public, embedded, identical for every caller. | ```bash title="Read the camera-move library" $ curl -sS https://api.nolgia.ai/v1/motions ``` This is the first complete entry from an anonymous production read; the rest of `motions` is omitted. ```json title="200 — production camera-move entry, trimmed catalog" { "motions": [ { "category": "dolly", "default_strength": "medium", "description": "The camera moves toward the subject, tightening the frame.", "id": "push-in", "name": "Push-in", "preview_url": null, "strengths": [ { "prompt_fragment": "Camera: a gentle, barely perceptible push-in toward the subject on a smooth dolly, the framing tightening only slightly by the last frame.", "strength": "subtle" }, { "prompt_fragment": "Camera: a steady push-in toward the subject on a smooth dolly, the framing tightening from a medium shot to a close-up over the clip.", "strength": "medium" }, { "prompt_fragment": "Camera: a fast, decisive push-in that drives from a wide shot to a tight close-up on the subject, momentum building to the last frame.", "strength": "strong" } ] } ] } ``` | Field | Type | Required | Description | | --- | --- | --- | --- | | `motions` | array of `CameraMove` | Yes | Moves in display order. | | Field | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | Yes | The stable `motion_id` a request sends. | | `name` | string | Yes | The customer-facing label. | | `description` | string | Yes | One sentence on what the move does on screen (the text description standing in for a preview clip). | | `category` | string | Yes | Chip grouping (`dolly`, `orbit`, `crane`, `pan`, `focus`, `handheld`, `zoom`, `static`). | | `default_strength` | `CameraMoveStrength` | Yes | | | `strengths` | array of `CameraMoveStrengthOption` | Yes | One entry per strength, subtle first. | | `preview_url` | string, nullable | Yes | An example clip of the move. Reserved; `null` on every entry today. | | Field | Type | Required | Description | | --- | --- | --- | --- | | `strength` | `CameraMoveStrength` | Yes | | | `prompt_fragment` | string | Yes | The exact sentence the server appends to the prompt at this strength. | > [!WARNING] > A `motion_strength` without `motion_id` is refused with `400`. Use a published move id; an unknown id is also `400`. If an enhanced prompt already contains the exact fragment, the server does not append it twice. ## Color presets Built-in color-grade presets are LUT looks for Studio compositions. Their catalog is public and identical for every caller; the render job bakes the grade into the exported MP4. | Property | Value | | --- | --- | | Catalog | `GET /color-presets`, anonymous | | Grade input | `data-color-grade` on timed HTML elements or the canvas, and `colorGrade` in `nolgia-edits.json` | | Stable identity | The preset's `slug`, used as `colorGrade.preset` | | Groups | Present `film` first; `looks` contains the legacy stylized set | | LUT download | `GET /color-presets/{slug}/cube`, a public 33-point `.cube` text file | | Method | Path | What it does | | --- | --- | --- | | [GET](../api/#tag/colorpresets/get/color-presets) | `/color-presets` | List the built-in color-grade presets ("LUT looks") for studio compositions. Public — the embedded catalog is identical for every caller. | | [GET](../api/#tag/colorpresets/get/color-presets/{slug}/cube) | `/color-presets/{slug}/cube` | Download a color preset's 33-point `.cube` LUT — the pure preset op chain (no manual adjustments), baked on demand from the embedded manifest. Public, aggressively cacheable. | ```bash title="Read the built-in grade catalog" $ curl -sS https://api.nolgia.ai/v1/color-presets ``` This is the first complete preset from an anonymous production read; the rest of `presets` is omitted. ```json title="200 — production color preset, trimmed catalog" { "version": 2, "presets": [ { "category": "motion", "description": "The modern film workhorse: tungsten balanced, warm shadows, soft highlight roll.", "group": "film", "name": "Kodak Vision3 500T", "slug": "kodak-vision3-500t" } ] } ``` | Field | Type | Required | Description | | --- | --- | --- | --- | | `version` | integer | Yes | Manifest version; bumps when the recipe set changes. | | `presets` | array of `ColorPreset` | Yes | Presets in manifest order. | | Field | Type | Required | Description | | --- | --- | --- | --- | | `slug` | string | Yes | Stable identity of the preset — the value a `colorGrade`'s `preset` field references. | | `name` | string | Yes | Display name. | | `description` | string | Yes | One-line description of the look. | | `category` | string | No | Editorial category within its group. Film-stock presets use motion \| stills \| mono \| finish \| camera; the legacy looks use look. Empty on presets recorded before the field existed. | | `group` | string | No | Catalog group. `film` is the primary set (real film stock emulations, round 39); `looks` is the legacy stylized set kept as a secondary group. Clients should present `film` first. | The `.cube` download contains the pure preset operations; manual grade adjustments are not included. Composition exports include their color-grade LUTs under `luts/`. ## Next steps :::cards - [Characters and locations](./characters.html): Keep the subject and place consistent across generations. - [Compositions and Studio export](./compositions.html): Bake looks into renders and export editable timelines. ::: --- # Compositions and Studio export Source: https://docs.nolgia.ai/guides/compositions.md A composition is an HTML timeline stored as a file bundle: `index.html`, optional sub-compositions, media files and metadata. Studio edits live in `nolgia-edits.json`. Rendering and editor export both use the effective timeline: authored timing plus that edit overlay. | Method | Path | What it does | | --- | --- | --- | | [GET](../api/#tag/compositions/get/compositions) | `/compositions` | List the current user's compositions, newest first. | | [POST](../api/#tag/compositions/post/compositions) | `/compositions` | Create an empty composition. | | [GET](../api/#tag/compositions/get/compositions/{id}) | `/compositions/{id}` | Fetch one of the current user's compositions, including its file inventory. | | [PATCH](../api/#tag/compositions/patch/compositions/{id}) | `/compositions/{id}` | Update a composition's name, description, project link, or meta. | | [DELETE](../api/#tag/compositions/delete/compositions/{id}) | `/compositions/{id}` | Delete a composition and all of its files. | | [PUT](../api/#tag/compositions/put/compositions/{id}/file) | `/compositions/{id}/file` | Upload or overwrite one file in a composition. | | [DELETE](../api/#tag/compositions/delete/compositions/{id}/file) | `/compositions/{id}/file` | Delete one file from a composition. | | [POST](../api/#tag/compositions/post/compositions/{id}/files:signed-urls) | `/compositions/{id}/files:signed-urls` | Mint fresh signed GET URLs for composition files and referenced platform assets. | | [POST](../api/#tag/compositions/post/compositions/{id}/clone) | `/compositions/{id}/clone` | Clone a composition, copying its full file bundle. | | [POST](../api/#tag/compositions/post/compositions/{id}/imports/figma) | `/compositions/{id}/imports/figma` | Import a Figma frame into a composition as text layers and PNG stills. | | [POST](../api/#tag/compositions/post/compositions/{id}/render) | `/compositions/{id}/render` | Queue a server-side MP4 or single-frame PNG export of the composition's effective timeline. | | [GET](../api/#tag/compositions/get/compositions/{id}/renders) | `/compositions/{id}/renders` | List the composition's renders, newest first (at most 20). | | [GET](../api/#tag/compositions/get/compositions/{id}/export) | `/compositions/{id}/export` | Export the composition for editing in Premiere Pro, DaVinci Resolve or After Effects. | | [GET](../api/#tag/compositions/get/renders/{id}) | `/renders/{id}` | Fetch one render, including its status, warnings, and produced asset id. | | [POST](../api/#tag/compositions/post/renders/blocks) | `/renders/blocks` | Assemble ordered clip and narration pairs into one MP4. | ## Render ![A composition queues a render, which is polled until it produces an asset](../assets/diagrams/workflow-compositions.svg) | Property | Value | | --- | --- | | Start | `POST /compositions/{id}/render` | | Immediate response | `202` with a `Render`, not a generation `Job` | | Poll | `GET /renders/{id}` | | Statuses | `queued`, `running`, `succeeded`, `failed` | | Default output | MP4 video asset | | Still output | `target: still` produces an `image/png` asset | | Result | `asset_id` on success; `error` on failure; inspect `warnings` either way | Set `COMPOSITION_ID` to a composition you own. The request and response examples below are built from the schemas; they are not captured render runs. ```bash title="Queue an MP4 render — example" $ curl -sS -X POST "https://api.nolgia.ai/v1/compositions/$COMPOSITION_ID/render" \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H "Content-Type: application/json" \ -d '{}' ``` ```json title="202 — example from the Render schema" { "id": "6250ce92-d911-4b47-9f0c-a884a481586f", "composition_id": "dcb61d59-59bc-41c1-b5cb-7b7adf22d02c", "user_id": "4961eaef-70a6-4e9b-b746-c86ad3e62077", "status": "queued", "params": {}, "warnings": [], "created_at": "2026-09-21T04:00:00Z", "updated_at": "2026-09-21T04:00:00Z" } ``` | Field | Type | Required | Description | | --- | --- | --- | --- | | `target` | `RenderTarget` | No | | | `still` | `RenderStillOptions` | No | | | `audio_mix` | `AudioMix` | No | | | `params` | object, nullable | No | Reserved for future render options; must be empty or omitted today. | | Field | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | Yes | | | `composition_id` | string | Yes | | | `user_id` | string | Yes | | | `status` | `RenderStatus` | Yes | | | `params` | object | Yes | Empty for a video composition render.… | | `asset_id` | string, nullable | No | The produced asset, a video for an MP4 render and an image/png for a still, set when status is succeeded. | | `warnings` | array of string | Yes | Human-readable notes about skipped/unsupported timeline nodes and per-block narration speed-up or last-frame hold. | | `error` | string, nullable | No | Failure reason, set when status is failed (truncated to 2000 characters). | | `created_at` | string | Yes | | | `started_at` | string, nullable | No | | | `finished_at` | string, nullable | No | | | `updated_at` | string | Yes | | Save the returned `id` as `RENDER_ID` and poll until the render is terminal. A delayed worker trigger can leave it queued until the scheduled worker picks it up; keep polling the same render. ```bash title="Poll a render — example" $ curl -sS "https://api.nolgia.ai/v1/renders/$RENDER_ID" \ -H "Authorization: Bearer $NOLGIA_TOKEN" ``` ```json title="200 — example of a completed Render" { "id": "6250ce92-d911-4b47-9f0c-a884a481586f", "composition_id": "dcb61d59-59bc-41c1-b5cb-7b7adf22d02c", "user_id": "4961eaef-70a6-4e9b-b746-c86ad3e62077", "status": "succeeded", "params": {}, "asset_id": "86997121-220d-4206-a70e-64ec8474c058", "warnings": [], "created_at": "2026-09-21T04:00:00Z", "started_at": "2026-09-21T04:00:02Z", "finished_at": "2026-09-21T04:00:30Z", "updated_at": "2026-09-21T04:00:30Z" } ``` | Response field | Meaning | | --- | --- | | `id`, `composition_id`, `user_id` | Render, source composition and requester ids | | `status` | Poll while `queued` or `running`; stop at `succeeded` or `failed` | | `params` | Empty for a video composition render; records normalized still options for a still | | `asset_id` | Read `GET /assets/{id}` for a fresh download URL; can be null if the output was later deleted | | `warnings` | Notes about skipped or unsupported elements; success does not imply every timeline element was rendered | | `created_at`, `started_at`, `finished_at`, `updated_at` | Render lifecycle timestamps | | `error` | Failure reason when the render fails | > [!WARNING] > Rendering supports a documented timeline subset. It does not execute scripts or GSAP tweens, and authored CSS is not applied. Unsupported elements and unresolvable media are skipped with warnings. Read `warnings` and inspect the output before treating a successful render as an exact copy of the browser preview. Supported output includes timed video, audio and images, Studio edit timing and track toggles, grades, masks, sampled clip motion, and supported text. Text additions use Studio `textStyle`; authored plain text uses the renderer's caption treatment. The API reference above describes the exact subset and its limits. ### Still frames | Property | Value | | --- | --- | | Request | `target: still` plus optional `still` options | | Frame selection | `at_seconds` or zero-based `frame`, never both; neither means frame 0 | | Background | `opaque` by default; `transparent` preserves uncovered canvas alpha | | Downscale | `max_dimension` from 64 to 3840 pixels | | Resolution cap | Native still output is capped at 3840×2160; downscale larger canvases | | Past the end | Renders the last frame and adds a warning | ```bash title="Queue a transparent still — example" $ curl -sS "https://api.nolgia.ai/v1/compositions/$COMPOSITION_ID/render" \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H "Content-Type: application/json" \ -d '{"target":"still","still":{"at_seconds":2,"background":"transparent","max_dimension":1280}}' ``` ```json title="202 — schema-built still render example" { "id": "2cc38557-afc8-4dd2-8b3c-f335a36a4967", "composition_id": "dcb61d59-59bc-41c1-b5cb-7b7adf22d02c", "user_id": "4961eaef-70a6-4e9b-b746-c86ad3e62077", "status": "queued", "params": {"mode":"still","at_seconds":2,"background":"transparent","max_dimension":1280}, "warnings": [], "created_at": "2026-09-21T04:01:00Z", "updated_at": "2026-09-21T04:01:00Z" } ``` | Response field | Meaning | | --- | --- | | `id`, `composition_id`, `user_id` | Render, source and requester ids, as for video | | `status` | Still renders use the same asynchronous lifecycle | | `params.mode` | `still` identifies a single-frame render | | `params.at_seconds`, `params.background`, `params.max_dimension` | The normalized options the worker will apply | | `warnings` | Notes about unsupported content or clamping the requested timestamp | | `created_at`, `updated_at` | Render timestamps | | `asset_id` | Appears on success and points to the PNG asset | | Field | Type | Required | Description | | --- | --- | --- | --- | | `at_seconds` | number | No | Timestamp rounded to the nearest frame using the plan FPS in the worker, then clamped to the last frame with a warning when past the end.… | | `frame` | integer | No | Zero-based frame index, clamped to the last frame with a warning when past the end. Cannot accompany at_seconds. Invalid values return 400 with "still.frame must be between 0 and 2592000". | | `background` | one of `opaque`, `transparent` | No | Opaque includes the black backdrop; omitted means opaque.… | | `max_dimension` | integer | No | Downscale the finished still so its long edge is at most this many pixels, preserving aspect ratio.… | ## Export `GET /compositions/{id}/export?format=fcpxml|aejsx` synchronously returns a ZIP for an editor. It uses the same effective timeline as MP4 rendering. ### fcpxml | Property | Value | | --- | --- | | Editors | Premiere Pro and DaVinci Resolve | | Project file | `sequence.xml` | | Actual interchange | Final Cut Pro 7 XML, `xmeml` version 5, 30 fps | | Text | Text additions become PNG stills under `text/` | ### aejsx | Property | Value | | --- | --- | | Editor | After Effects | | Project file | `build.jsx` | | Open | File → Scripts → Run Script File | | Editable features | Native text layers, keyframed transforms and masks | Both formats contain `luts/*.cube`, `README.txt` describing fidelity limits, and `manifest.json`. Authored HTML, CSS and GSAP animation do not map into either editor. ```bash title="Download an editor ZIP — example" $ curl --fail-with-body -sS \ "https://api.nolgia.ai/v1/compositions/$COMPOSITION_ID/export?format=fcpxml" \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -o composition.zip ``` The response is `200 application/zip` with an attachment disposition, not JSON. These are the documented fields in the ZIP's `manifest.json`: | Manifest field | Meaning | | --- | --- | | `media[]` | Every referenced media file to download separately | | `media[].ref` | The source media reference | | `media[].asset_id` | The referenced platform asset id | | `media[].path` | Destination path relative to the export folder | | `media[].url` | Signed download URL, valid for one hour | | `media[].expires_at` | Actual expiry of that download URL | > [!WARNING] > Media bytes are not in the ZIP. Download every `manifest.json` media URL into its listed path beside the project before opening it. Complete those downloads before the one-hour URLs expire. Export returns `404` for an unknown or foreign composition, `400` for an unknown format, `422` for a timeline without exportable clips, and `413` when its documents exceed the export limits. ## Versions and Figma imports | Surface | What it does | Trap | | --- | --- | --- | | `POST /compositions/{id}/clone` | Copies the full file bundle into a new composition; `meta_patch` shallow-merges metadata | `nolgia-edits.json` is deliberately omitted so the clone starts with a clean edit overlay | | `meta.series` and `meta.version` | Group versions; filter with `GET /compositions?series=…` | Clients assign and increment versions; the API stores them verbatim | | `POST /compositions/{id}/imports/figma` | Imports a frame as text-layer descriptions and PNG still assets | Returns layer descriptions without modifying the composition; the client writes its overlay | Figma imports require a Figma PAT with `file_content:read`. It is used for that request only and is not stored or logged. An import supports at most 40 layers. The token belongs in the authenticated request body, never in a composition file or URL. ## Share a render Use `POST /renders/{id}/share` once the render is `succeeded` and its output asset still exists. The link resolves to the rendered MP4; its preview uses the composition name. Read-only roles cannot share, organization members may share their own requested renders, and owners or admins may share any visible render. ```bash title="Share a finished render — example" $ curl -sS "https://api.nolgia.ai/v1/renders/$RENDER_ID/share" \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H "Content-Type: application/json" \ -d '{"expires_in_days":30}' ``` This is a schema-built example; the token and URL are placeholders, not working links. ```json title="201 — example from the ShareLink schema" { "id": "93b16566-1998-4fc0-8b1c-7e4c77e224ee", "kind": "render", "target_id": "6250ce92-d911-4b47-9f0c-a884a481586f", "token": "…", "token_prefix": "…", "url": "https://nolgia.ai/share/…", "expires_at": "2026-10-21T04:02:00Z", "created_at": "2026-09-21T04:02:00Z", "created_by": "4961eaef-70a6-4e9b-b746-c86ad3e62077", "access_count": 0 } ``` | Field | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | Yes | | | `kind` | `ShareLinkKind` | Yes | | | `target_id` | string | Yes | The shared asset's id, or the shared render's id. | | `token` | string | No | The share token (base64url, 128 bits of entropy). Create response only. | | `token_prefix` | string | Yes | The first characters of the token, for recognising a link in a listing. | | `url` | string | No | The public share URL to hand out (the nolgia.ai share page). Create response only. | | `expires_at` | string | No | When the link stops resolving. Absent for a link that never expires. | | `has_password` | boolean | No | Viewers must enter a password. | | `view_only` | boolean | No | Downloads are turned off for this link. | | `created_at` | string | Yes | | | `created_by` | string | Yes | The user who created the link. | | `access_count` | integer | Yes | Successful public resolutions so far. | | `last_accessed_at` | string | No | When the link was last resolved. Absent until the first visit. | | `revoked_at` | string | No | Set once the link has been revoked. Listings only return unrevoked links. | Save `url` when creating it: tokens and URLs are returned once. `GET /renders/{id}/share` lists active links without either secret; `DELETE /renders/{id}/share/{token}` accepts the share token or link id and revokes it. See [File access controls](./file-access.html) for expiry, public resolution and revocation behavior. ## Next steps :::cards - [Styles and looks](./styles.html): Choose the grades and looks that a composition renders. - [Storage and data retention](./storage.html): Keep asset ids, refresh signed URLs and manage deletion. ::: --- # 3D Source: https://docs.nolgia.ai/guides/3d.md Submit product or object photos to `POST /generate/3d`, keep the returned job id, then follow the job to its GLB asset. The output uses `model/gltf-binary` and the `.glb` extension; a preview thumbnail is included when the engine returns one. ## Choose an engine | Model id | Modality | Plan | Credits | | --- | --- | --- | --- | | `hunyuan3d-v3` | 3d | starter | 21 per generation | | `trellis` | 3d | starter | 2 per generation | ### hunyuan3d-v3 | Property | Value | | --- | --- | | Quality | `standard`; the default when both `model` and `quality` are omitted | | Input | One to four image assets, in front, back, left, right order; or one hosted HTTPS front image | | Textures | On by default; `texture: false` makes a white untextured model | | PBR | Optional; requires textures | | Base price | 21 credits textured; 13 untextured | | Extra views | Add 9 credits once, whether there are one, two or three extra views | | PBR surcharge | Add 9 credits | Textured totals are 21 credits, 30 with PBR or extra views, and 39 with both. Untextured totals are 13 credits, or 22 with extra views. Use a [quote](./billing.html#checking-prices-programmatically) for the exact request before submitting. ### trellis | Property | Value | | --- | --- | | Quality | `draft` | | Input | Exactly one image | | Textures | Always enabled | | PBR | Not supported | | Price | 2 credits per generation | > [!WARNING] > Send exactly one of `image_asset_ids` and `image_url`. `quality: draft` selects `trellis` and `quality: standard` selects `hunyuan3d-v3`; a quality that contradicts an explicit model is `400 validation`. Trellis also refuses extra views, `texture: false` and `pbr: true`. ## Submit photos Use [Uploads and files](./uploads.html) to create the input assets first. The UUID below is illustrative: replace it with one of your image asset ids. No text prompt is part of the 3D request. | Field | Type | Required | Description | | --- | --- | --- | --- | | `confirmation_token` | string | No | Optional proof that this exact request and price were shown to the customer, from `POST /jobs/cost`.… | | `model` | `Generate3DModel` | No | Defaults to hunyuan3d-v3 when both model and quality are omitted. | | `quality` | one of `draft`, `standard` | No | Draft selects trellis; standard selects hunyuan3d-v3. A value that contradicts an explicit model returns 400. | | `image_asset_ids` | array of string | No | Your image assets in front, back, left, right order. Trellis takes exactly one. Supply exactly one of image_asset_ids and image_url. | | `image_url` | string | No | Hosted HTTPS front image. Supply exactly one of image_asset_ids and image_url. | | `texture` | boolean | No | Defaults to true. False creates an untextured white model and is available only with hunyuan3d-v3. | | `pbr` | boolean | No | Defaults to false. PBR materials require hunyuan3d-v3 with textures enabled. | | `tags` | array of string | No | Applied to the 3D asset; normalized to lowercase. | | `project_id` | string | No | Files the 3D asset into this project when the job completes. The project must exist and belong to the caller (400 otherwise). | | `preset_slug` | string, nullable | No | Slug of the preset this generation was launched from, if any. Same semantics as on `POST /generate/video`. | ```bash title="3D submit — example" $ curl -sS https://api.nolgia.ai/v1/generate/3d \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H "Content-Type: application/json" \ -d '{"model":"hunyuan3d-v3","image_asset_ids":["3be6408a-cbf2-4964-b314-d7cbbf9a8c75"],"texture":true}' ``` This is a schema-built example of the accepted job, not a production capture. ```json title="202 Accepted — example from the Job schema" { "id": "2ab37473-80b3-4c6c-b070-7f5e1677e172", "user_id": "4961eaef-70a6-4e9b-b746-c86ad3e62077", "modality": "3d", "model": "hunyuan3d-v3", "status": "queued", "created_at": "2026-09-21T04:00:00Z", "updated_at": "2026-09-21T04:00:00Z" } ``` | Field | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | Yes | | | `user_id` | string | Yes | | | `modality` | `Modality` | Yes | | | `model` | string | Yes | | | `status` | `JobStatus` | Yes | | | `asset` | `Asset` | No | | | `failure` | `JobFailure` | No | | | `created_at` | string | Yes | | | `updated_at` | string | Yes | | ## Follow the job Use `GET /jobs/{id}` to poll, or `GET /jobs/{id}/wait` to hold a request until completion. A wait timeout leaves the generation running; keep following the same id. [Asynchronous: submit and poll](./jobs.html) covers the full loop, failures and refund reporting. ```bash title="Read the job — example" $ curl -sS "https://api.nolgia.ai/v1/jobs/$JOB_ID" \ -H "Authorization: Bearer $NOLGIA_TOKEN" ``` The schema-built response below shows a job still running; on `succeeded`, read its `asset`. ```json title="200 — example from the Job schema" { "id": "2ab37473-80b3-4c6c-b070-7f5e1677e172", "user_id": "4961eaef-70a6-4e9b-b746-c86ad3e62077", "modality": "3d", "model": "hunyuan3d-v3", "status": "running", "created_at": "2026-09-21T04:00:00Z", "updated_at": "2026-09-21T04:00:02Z" } ``` | Response field | Meaning | | --- | --- | | `id`, `user_id` | The durable job id and its owner | | `modality`, `model` | `3d` and the resolved engine | | `status` | Continue polling while `queued` or `running`; `succeeded`, `failed` and `canceled` are terminal | | `created_at`, `updated_at` | Job timestamps | | `asset` | Present on success; read the GLB's `signed_url`, `expires_at` and optional `thumbnail_url` | | `failure` | Machine-readable failure and refund outcome when generation fails | | Field | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | Yes | | | `modality` | `Modality` | Yes | | | `signed_url` | string | Yes | Time-limited GCS signed URL for download.… | | `expires_at` | string | Yes | Expiry of `signed_url`. | | `mime_type` | string | No | | | `thumbnail_url` | string, nullable | No | Time-limited signed URL for a server-generated thumbnail (image downscale or video poster frame).… | | `status` | `AssetStatus` | No | | Store the asset id, then re-read the asset when a fresh download URL is needed. A signed URL is temporary. Duplicate submissions inside the five-minute window return `409` with the original job id and are not billed; change `Idempotency-Key` only for a deliberate new take. ## Next steps :::cards - [Common model arguments](./model-arguments.html): Understand reference ids, project filing and confirmation tokens. - [Pricing and credits](./billing.html): Quote each engine and follow credit holds and refunds. ::: --- # FAQ Source: https://docs.nolgia.ai/guides/faq.md Find the answer you need, then follow the linked guide for the commands and API details. ## Which languages have a client? TypeScript, Python and Rust have published clients; the Go client requires access to its private repository. See [Libraries, APIs and community](./client-libraries.html). ## Do I need a subscription to use the API? You can use eligible models with prepaid top-up credits without a subscription; models with a higher plan requirement still require that plan. See [Billing](./billing.html). ## How do I know the price before I submit? `POST /jobs/cost` quotes the exact credits your request will hold without reserving or charging anything. See [Billing](./billing.html). ## What happens when a generation fails? Read `failure.code` and `failure.credits_refunded`: `true` means refunded, `false` means charged, and an absent value does not confirm either outcome. A provider-billed content-filter refusal can be charged; see [Errors](./errors.html). ## How long does a signed URL last? An asset's `signed_url` stays stable for about an hour and has at least an hour of validity left when issued; `expires_at` gives its exact expiry. Read the asset again for a fresh signature. Store its id rather than the temporary URL. A public share resolver instead redirects to a URL lasting about 15 minutes. See [Storage and data retention](./storage.html). ## Can I stop the API from storing my prompt? No. The asset stores its prompt and, when an image prompt was changed before rendering, `enhanced_prompt`. There is no switch to stop storing them and no per-request expiry header. See [Storage and data retention](./storage.html#request-payloads). ## Can I share a file with someone who has no account? Yes. Create a share link with `POST /assets/{id}/share` or `POST /renders/{id}/share`; its public URL works without a Nolgia account until it expires or is revoked. Save the returned URL when you create it: the full URL and token are returned only once. Links default to 30 days, with `expires_in_days` from 1 to 365. See [File access controls](./file-access.html). ## How is video billed? Video costs `ceil(credits × duration_seconds / baseline_seconds)`. Read the model's current cost and selected quality tier; the baseline is published with the price, commonly five seconds. For a model with an audio surcharge, `generate_audio=false` deducts it before duration scaling. `POST /jobs/cost` quotes the exact request without charging. See [Pricing and credits](./billing.html). ## What refunds? An operationally failed generation or terminal generation timeout refunds its credit hold. A content-policy refusal is refunded unless the provider billed the refused attempt, in which case it can be charged. Check `failure.credits_refunded`: `true` means refunded, `false` means charged, and absent or null is not proof of either. A quote, duplicate `409`, or wait `408` costs nothing itself, so there is no new charge to refund; an already accepted job can still finish and bill normally after a wait timeout. See [Model errors](./errors.html) and [Pricing and credits](./billing.html). ## Why did I get 409 on a retry? Submitting the same request twice within five minutes returns `409` naming the earlier job so the retry cannot bill twice. Send a fresh `Idempotency-Key` to run it again on purpose; see [Quick Start](./getting-started.html). ## Can I cancel a job? Yes: `POST /jobs/{id}/cancel`. A job that has not reached the model provider yet is refunded in full. For a render already at the provider, Nolgia asks the provider to stop it where the provider allows it and settles on what the provider actually billed; the job's `cancellation` says which. A canceled job is never added to your library. Interrupting an agent turn with `POST /agent/sessions/{id}/interrupt` is separate and does not cancel a generation the turn already submitted. See [Asynchronous: submit and poll](./jobs.html#cancel-a-request). ## How many jobs can I run at once? Your plan sets the concurrent generation limit. Read `generation_limits` on `GET /me` for your current maximum and active count; [Concurrency limits](./concurrency-limits.html) lists the plans and explains a `429` refusal. | Field | Type | Required | Description | | --- | --- | --- | --- | | `concurrent_max` | integer | Yes | Maximum generations this account may run at once on its effective plan. | | `concurrent_active` | integer | Yes | Generations currently running across image, audio, and video. | ## Is there a webhook? There is no customer webhook today. Provider callbacks wake Nolgia's poller; use long-poll or SSE to receive your result. See [Callbacks and webhooks](./callbacks.html) for the distinction and working examples. ## How do I use my own images? Upload an image, then pass its id in a supported `*_asset_id` or `*_asset_ids` field. The server re-signs asset references at execution so a queued job does not inherit an expired URL. Your own HTTPS URLs also work in supported `*_url` fields. See [Uploads and files](./uploads.html) for the upload flows and accepted formats. ## Can my team share credits? Inside an organization, your requests use its shared credit pool and respect member budgets. See [Teams and organizations](./organizations.html). ## How do I connect Claude Code or Cursor? Connect to the MCP server with a Personal Access Token. Follow the client configuration in [Run MCP](./mcp.html). :::cards - [Quick Start](./getting-started.html) icon=spot-library: Generate and download your first image and video. - [Get your API key](./authentication.html) icon=spot-keys: Create a token and test it. - [API reference](../api/) icon=spot-jobs: Read the full endpoint and schema contract. ::: --- # API reference Source: https://docs.nolgia.ai/guides/api-reference.md Everything Nolgia does is one HTTP API. The clients, the CLI and the MCP server are all wrappers over the same endpoints, so anything you can do in one you can do in all of them. ## Which one should I use | If you are | Use | Start here | | --- | --- | --- | | Writing a script or a backend in TypeScript | `@nolgia/sdk` | [Client libraries](./client-libraries.html) | | Writing Python | `nolgia` | [Client libraries](./client-libraries.html) | | Working in a terminal, or in CI | The CLI | [The CLI](./cli.html) | | Using a coding agent (Claude Code, Cursor, Codex) | The MCP server | [Run MCP](./mcp.html) | | In another language, or want no dependency at all | REST directly | [Browse the endpoints](../api/) | If you are not sure, start with REST. Every example on this site shows a `curl` call beside the language tabs, so you can always see exactly what goes over the wire. ## REST | Environment | Base URL | | --- | --- | | Production | `https://api.nolgia.ai/v1` | | Staging | `https://api.stg.nolgia.ai/v1` | | Local development | `http://localhost:8080/v1` | The full reference is generated from the same OpenAPI document the server validates against, so it can never describe an endpoint we do not serve: [browse the endpoints](../api/) or download [`openapi.yaml`](../api/openapi.yaml) and generate your own client. Authentication is a bearer token on every request. See [Authentication](./authentication.html) for how to create one, and [Errors](./errors.html) for the shape of a failure. ## Client libraries | Client | Package | Version | | --- | --- | --- | | TypeScript | `@nolgia/sdk` | 0.1.4 | | Python | `nolgia` on PyPI | 0.1.4 | | Rust | `nolgia-client` | published with the CLI release; see crates.io | Both published clients are generated from the OpenAPI document, so their types move when the spec moves. [Client libraries](./client-libraries.html) has installation and a first call in each. ## CLI From `nolgia --help` in release 0.2.30, published 2026-09-21: The CLI signs in once and then generates from a terminal or a CI job. [The CLI](./cli.html) covers install, `nolgia auth login` and the generate commands. ## MCP server The MCP server exposes the same API as tools a coding agent can call directly, grouped by what they do. [Run MCP](./mcp.html) lists every tool and has the install snippets for Claude Code, Cursor and Codex. ## Models Every model, with what it costs and what it can do, is generated from the live catalog: | Image | Video | Audio | 3D | | --- | --- | --- | --- | | 48 | 74 | 17 | 2 | Browse them by what you want to make — [image generation](./models-image-generation.html), [image editing](./models-image-editing.html), [text to video](./models-text-to-video.html), [image to video](./models-image-to-video.html), [audio and speech](./models-audio-and-speech.html), [restore and upscale](./models-restore-and-upscale.html), [utilities](./models-utilities.html) or [3D](./models-3d.html). ## Reading this reference from an agent Every page here is also served as Markdown at the same path with a `.md` extension, and the whole site is concatenated at [`llms-full.txt`](../llms-full.txt). [Agent-readable surfaces](./agent-surfaces.html) lists all of them. --- # Teams and organizations Source: https://docs.nolgia.ai/guides/organizations.md Share a Library and credit pool with your team. You will be able to switch between personal and organization work, manage members and access, and read your team's credits, usage and audit trail. ![Shared organization work connected around one context](../assets/art/organizations.jpg) ## Personal space or one active organization ![Your active organization connects the app, CLI, MCP and agent to shared resources](../assets/diagrams/organization-context.svg) Use `PUT /me/active-organization` to choose your context. This is server state: the app, CLI, MCP and your NOLGIA Agent credentials follow it on their next request. Send `organization_id: null` to return to your personal space. You must belong to an organization to select it. Organization API keys stay bound to the organization they were created for. You can create a `team` organization with `POST /organizations`; it becomes your active organization and gets a default Library project. Enterprise organizations are managed through a contract. ![Settings, Organization on nolgia.ai: the personal space, with Create a team to start an organization (1)](../assets/screens/organization.jpg) > [!NOTE] > Agent credentials cannot change organizations. Agent PATs and turn tokens receive `403` with `agent_cannot_change_organization`: "The NOLGIA Agent cannot change your organization. Do it in the app." ## Roles | Role | What you can do | | --- | --- | | `owner` | Manage everything, including deletion and ownership transfer. | | `admin` | Manage members, invites, domains, SSO, storage, budgets and any Library item, and read the audit log. | | `billing` | Manage seats and invoices, and read members. | | `member` | Create, generate and manage your own items, and read the whole Library. | | `viewer` | Read the Library without consuming a seat. | ## Endpoints | Method | Path | What it does | | --- | --- | --- | | [GET](../api/#tag/organizations/get/organizations) | `/organizations` | List the organizations the current user belongs to, with their role in each. | | [POST](../api/#tag/organizations/post/organizations) | `/organizations` | Create a team organization owned by the current user. | | [GET](../api/#tag/organizations/get/organizations/{id}) | `/organizations/{id}` | Get one organization the current user belongs to. | | [PATCH](../api/#tag/organizations/patch/organizations/{id}) | `/organizations/{id}` | Update an organization's name or settings (owner or admin). | | [DELETE](../api/#tag/organizations/delete/organizations/{id}) | `/organizations/{id}` | Soft-delete an organization (owner only). | | [GET](../api/#tag/organizations/get/organizations/{id}/audit-events) | `/organizations/{id}/audit-events` | List the organization's audit trail, newest first (owner or admin). | | [GET](../api/#tag/organizations/get/organizations/{id}/audit-events/export) | `/organizations/{id}/audit-events/export` | Download the whole audit trail as CSV (owner or admin; enterprise plan only). | | [GET](../api/#tag/organizations/get/organizations/{id}/credits) | `/organizations/{id}/credits` | Get an organization's shared credit pool and per-member budgets. | | [GET](../api/#tag/organizations/get/organizations/{id}/usage) | `/organizations/{id}/usage` | Roll up an organization's consumed credits by member, model or day. | ## Running the organization Adding people, letting them in, authenticating on the organization's behalf and choosing where its files live are all owner and admin work, and they have their own page: [Managing your team](./organization-admin.html) covers members, invites, verified domains and SSO, organization API keys, and bringing your own storage. ## Credits and usage Read `GET /organizations/{id}/credits` for the shared pool, subscription period, seats and monthly member budgets. Owner, admin and billing roles can see every member's budget and spend; member and viewer roles see their own row. Budget usage counts held and consumed reservations in the current UTC calendar month. Read `GET /organizations/{id}/usage` for consumed credits grouped by member, model or day. This excludes held and released reservations. The default window is the current UTC calendar month, and a custom window can span up to 366 days. Owner, admin and billing roles see the whole organization; member and viewer roles see their own spend. ## Audit events As an owner or admin, list events with `GET /organizations/{id}/audit-events`, newest first. Use the `action` and `actor` filters to narrow the list. On an enterprise plan, `GET /organizations/{id}/audit-events/export` downloads the whole trail as CSV. :::cards - [Authentication](./authentication.html): Create tokens and organization API keys. - [Billing](./billing.html): Follow shared balances, charges and refunds. - [Agent Sessions API](./agent-api.html): Run conversations in your active context. - [Managing your team](./organization-admin.html): Members, invites, domains, keys and storage. ::: --- # Managing your team Source: https://docs.nolgia.ai/guides/organization-admin.md Everything an owner or admin does after the organization exists: who is in it, how they get in, what authenticates on its behalf, and where its files live. [Teams and organizations](./organizations.html) covers the context itself, the roles, and the shared credits and usage every member can read. ![Shared organization work connected around one context](../assets/art/organizations.jpg) > [!NOTE] > Every write on this page needs the `owner` or `admin` role, and none of them can be made by an agent credential: those receive `403` with `agent_cannot_change_organization`. Do it in the app or with your own token. ## Members | Method | Path | What it does | | --- | --- | --- | | [GET](../api/#tag/organizations/get/organizations/{id}/members) | `/organizations/{id}/members` | List an organization's members (any member may read). | | [PATCH](../api/#tag/organizations/patch/organizations/{id}/members/{user_id}) | `/organizations/{id}/members/{user_id}` | Change a member's role or monthly credit budget (owner or admin). | | [DELETE](../api/#tag/organizations/delete/organizations/{id}/members/{user_id}) | `/organizations/{id}/members/{user_id}` | Remove a member (owner or admin), or leave the organization (self). | | [POST](../api/#tag/organizations/post/organizations/{id}/transfer-ownership) | `/organizations/{id}/transfer-ownership` | Transfer ownership to another member (owner only). | Any member may read the member list. As an owner or admin you can change a member's role or their monthly credit budget, and remove them. A member can also remove themselves, which is how you leave an organization. The owner is the exception: transfer ownership first, with `POST /organizations/{id}/transfer-ownership`, and the previous owner becomes an admin. Removing someone also stops their organization API keys from authenticating, so revoking access is one action rather than two. ## Invites | Method | Path | What it does | | --- | --- | --- | | [GET](../api/#tag/organizations/get/organizations/{id}/invites) | `/organizations/{id}/invites` | List pending invites (owner or admin). | | [POST](../api/#tag/organizations/post/organizations/{id}/invites) | `/organizations/{id}/invites` | Invite an email address to the organization (owner or admin). | | [DELETE](../api/#tag/organizations/delete/organizations/{id}/invites/{invite_id}) | `/organizations/{id}/invites/{invite_id}` | Revoke a pending invite (owner or admin). | | [POST](../api/#tag/organizations/post/organizations/{id}/invites/{invite_id}/resend) | `/organizations/{id}/invites/{invite_id}/resend` | Rotate a pending invite's token, extend its expiry and re-send the email (owner or admin). | An invite is addressed to one email address and lasts seven days. Its plaintext token and URL are returned **once**, when the invite is created or resent, so capture them from that response; resending rotates the token and extends the expiry. The signed-in account accepting an invite has to match the address it was sent to. Pending invites that consume a seat count against the seat limit before they are accepted, so an organization cannot oversubscribe itself by inviting. Viewer invites do not consume a seat. ## Verified domains | Method | Path | What it does | | --- | --- | --- | | [GET](../api/#tag/organizations/get/organizations/{id}/domains) | `/organizations/{id}/domains` | List the organization's claimed email domains (owner or admin). | | [POST](../api/#tag/organizations/post/organizations/{id}/domains) | `/organizations/{id}/domains` | Claim an email domain (owner or admin); returns the DNS TXT record to publish. | | [PATCH](../api/#tag/organizations/patch/organizations/{id}/domains/{domain}) | `/organizations/{id}/domains/{domain}` | Toggle auto-join or SSO enforcement on a domain (owner or admin). | | [DELETE](../api/#tag/organizations/delete/organizations/{id}/domains/{domain}) | `/organizations/{id}/domains/{domain}` | Remove a claimed domain (owner or admin). | | [POST](../api/#tag/organizations/post/organizations/{id}/domains/{domain}/verify) | `/organizations/{id}/domains/{domain}/verify` | Check DNS for the verification TXT record and mark the domain verified (owner or admin). | Claiming a domain proves you control the addresses in it, which is what lets colleagues join without an individual invite. 1. `POST /organizations/{id}/domains` returns a TXT record to publish at `_nolgia.`. 2. Publish it in your DNS. 3. `POST /organizations/{id}/domains/{domain}/verify` reads DNS and marks the domain verified. A missing or mismatched record answers `409`, and so does a domain another organization has already verified. Once verified, `PATCH /organizations/{id}/domains/{domain}` turns on auto-join, so a new account with an address in that domain lands in the organization, and SSO enforcement, which requires a verified domain and an enterprise organization. ## Organization API keys | Method | Path | What it does | | --- | --- | --- | | [GET](../api/#tag/organizations/get/organizations/{id}/api-keys) | `/organizations/{id}/api-keys` | List the organization's API keys, no secrets (owner or admin). | | [POST](../api/#tag/organizations/post/organizations/{id}/api-keys) | `/organizations/{id}/api-keys` | Create an organization API key (owner or admin). The plaintext token is returned ONCE. | | [DELETE](../api/#tag/organizations/delete/organizations/{id}/api-keys/{key_id}) | `/organizations/{id}/api-keys/{key_id}` | Revoke an organization API key (owner or admin). | An organization API key authenticates as the organization rather than as a person: it is bound to the organization it was created for and never follows anyone's active context. It carries its creator's current role, and it stops working when their membership ends. The plaintext token is returned once, at creation. Store it where your application reads its secrets, and revoke it with `DELETE /organizations/{id}/api-keys/{key_id}` rather than rotating in place. [Get your API key](./authentication.html#organization-api-keys) has the token format and how it differs from a personal one. ## Bring your own storage | Method | Path | What it does | | --- | --- | --- | | [GET](../api/#tag/organizations/get/organizations/{id}/storage-connections) | `/organizations/{id}/storage-connections` | List the organization's bring-your-own storage connections (owner or admin; Enterprise). | | [POST](../api/#tag/organizations/post/organizations/{id}/storage-connections) | `/organizations/{id}/storage-connections` | Connect an S3-compatible bucket to the organization (owner or admin; Enterprise). | | [PATCH](../api/#tag/organizations/patch/organizations/{id}/storage-connections/{connection_id}) | `/organizations/{id}/storage-connections/{connection_id}` | Update a storage connection (owner or admin; Enterprise). | | [DELETE](../api/#tag/organizations/delete/organizations/{id}/storage-connections/{connection_id}) | `/organizations/{id}/storage-connections/{connection_id}` | Disconnect a storage connection (owner or admin; Enterprise). | | [POST](../api/#tag/organizations/post/organizations/{id}/storage-connections/{connection_id}/test) | `/organizations/{id}/storage-connections/{connection_id}/test` | Probe a storage connection (owner or admin; Enterprise). | | [POST](../api/#tag/organizations/post/organizations/{id}/storage-connections/{connection_id}/import) | `/organizations/{id}/storage-connections/{connection_id}/import` | Import objects from the connected bucket into the organization library (owner or admin; Enterprise). | | [POST](../api/#tag/organizations/post/organizations/{id}/storage-connections/{connection_id}/backfill) | `/organizations/{id}/storage-connections/{connection_id}/backfill` | Queue every ready organization asset for mirroring to this connection (owner or admin; Enterprise). | On an enterprise plan, an organization can keep its media in its own S3-compatible bucket. Create the connection, then `POST /organizations/{id}/storage-connections/{connection_id}/test` and read the status it returns before trusting it: a connection that cannot be probed is a configuration to fix, not something to discover at the first upload. Two directions are available once it works. `import` brings objects that are already in the bucket into the organization's Library, and `backfill` queues every ready organization asset for mirroring into the bucket. With mirroring enabled, new assets are copied as they become ready. ## The audit trail Every administrative action above is recorded. `GET /organizations/{id}/audit-events` lists them newest first, filtered by `action` or `actor`, and enterprise organizations can download the whole trail as CSV. See [Teams and organizations](./organizations.html#audit-events). :::cards - [Teams and organizations](./organizations.html) icon=spot-organizations: The active context, roles, shared credits and usage. - [Get your API key](./authentication.html) icon=spot-keys: Personal tokens, organization keys and what each one may do. - [Storage and retention](./storage.html) icon=spot-upload: Where files live, how long they last and how they are served. ::: --- # The NOLGIA Agent Source: https://docs.nolgia.ai/guides/agent.md A model API call makes one thing: you pick the model, write the prompt, submit the job and collect the file. The NOLGIA Agent does the picking. You describe what you want, and it plans the work, chooses the models, writes the prompts, submits the generations, looks at what came back, and keeps going until the work is done or it needs an answer from you. ![A conversation with the NOLGIA Agent and the Outputs it creates](../assets/art/agent.jpg) Everything it makes lands in the same Library as work you generate yourself, under the same credits, the same organization context and the same file access rules. It is another way into the platform, not a separate one. ## A turn, not a request ![An agent turn: message, events stream, optional steer or interrupt, reply](../assets/diagrams/agent-turn.svg) One message and everything the agent does to answer it is a **turn**. A turn can run for many minutes and submit many generations; it is asynchronous from the start, so you submit a message and then follow it rather than holding a request open. While it runs you can stream its steps, steer it with another message, or stop it. Turns live in **sessions**. A session keeps its transcript and the media its turns produced together, so a conversation can be resumed and its Outputs collected later. [The Agent Sessions API](./agent-api.html) is the full contract: sessions, messages, streaming, steering, interrupts, assets and errors. ## What it can do The agent's skills are **abilities**: installed packages that teach it a job, from product photography to long-form video. Every agent is provisioned with a core set and you can install more from the marketplace, pin one to a version, and see what is installed. See [Abilities](./abilities.html). It reaches the platform with its own credential, so the work it does is attributed to it, and `GET /agent/usage` reports what it spent on your behalf. ## Where it runs Each account gets one agent of its own, with its own configuration and its own long-term memory, separate from every other account's. You provision it, configure the environment it runs with, read its desired state, and take it down again. See [The operator model](./operator.html). ## Run your first turn Provision the agent once, create a session, send a message, then poll the reply until it leaves `pending`. ```bash tab="curl" $ curl -sS -X POST https://api.nolgia.ai/v1/agent \ -H "Authorization: Bearer $NOLGIA_TOKEN" $ curl -sS https://api.nolgia.ai/v1/agent/sessions \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"name":"First session"}' $ export SESSION_ID= $ curl -sS https://api.nolgia.ai/v1/agent/messages \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d "{\"session_id\":\"$SESSION_ID\",\"content\":\"Make me a 5 second clip of a paper boat on a puddle.\"}" $ export MESSAGE_ID= $ curl -sS "https://api.nolgia.ai/v1/agent/messages/$MESSAGE_ID" \ -H "Authorization: Bearer $NOLGIA_TOKEN" ``` Provisioning is asynchronous: `POST /agent` answers `201` immediately, and the agent reports `chat_connected` once it is reachable. A turn sent before that point does not wait for it, so read `GET /agent` first: the reply comes back `failed`, saying the chat endpoint is not connected yet. When the reply reaches `complete`, read its `content`, and collect what the turn made with `GET /agent/sessions/{id}/assets`. > [!NOTE] > Provisioning needs a Studio, Team or Enterprise subscription, and every turn costs credits. [Access and pricing](./agent-access.html) has both. :::cards - [Access and pricing](./agent-access.html) icon=spot-credits: Which plans can run an agent, what a turn costs, and how to choose its brain. - [Agent Sessions API](./agent-api.html) icon=spot-agent: Sessions, messages, streaming, steering and assets in full. - [Abilities](./abilities.html) icon=spot-library: Browse, install and pin what your agent can do. - [The operator model](./operator.html) icon=spot-terminal: Provision, configure and observe the agent itself. ::: --- # Access and pricing Source: https://docs.nolgia.ai/guides/agent-access.md Running an agent costs two different things: a plan that lets you provision one, and credits for the work it does. They are separate, and a refusal will tell you which one it is. ## Who can provision one Provisioning requires an active **Studio**, **Team** or **Enterprise** subscription. Starter and Pro accounts can use every model API and the rest of the platform, but `POST /agent` refuses them. One agent per account. | Method | Path | What it does | | --- | --- | --- | | [GET](../api/#tag/agent/get/agent) | `/agent` | Get the current user's NOLGIA Agent, if provisioned. | | [POST](../api/#tag/agent/post/agent) | `/agent` | Provision the current user's NOLGIA Agent (one per account; requires an active subscription). | | [DELETE](../api/#tag/agent/delete/agent) | `/agent` | Deprovision the current user's NOLGIA Agent and revoke its access token. | | [GET](../api/#tag/agent/get/agent/usage) | `/agent/usage` | Credits spent and jobs created by the current user's agent. | | [GET](../api/#tag/agent/get/agent/models) | `/agent/models` | Selectable agent brains (reasoning models) with their flat per-turn credit rates. | | [PUT](../api/#tag/agent/put/agent/model) | `/agent/model` | Choose which brain the user's agent runs; turns bill that brain's flat per-turn rate. | | [DELETE](../api/#tag/agent/delete/agent/model) | `/agent/model` | Clear the agent's model pin to use the platform default brain and rate. | `DELETE /agent` deprovisions it and revokes its credential. Sessions and transcripts are yours and are not deleted with it; provisioning again gives you a working agent against the same history. ## What a turn costs Every turn bills against the same credit balance as a generation, from the pool the credential draws on. Three rules cover it: - **A flat minimum per turn**, set by the brain the agent runs. Its rate is what `GET /agent/models` publishes for that brain, and the same catalogue marks which brain you get when you have chosen none. - **Metering above the minimum.** A turn that does an unusual amount of work bills what it actually cost rather than the minimum. An ordinary turn bills the flat rate. - **Generations are billed separately**, at their normal per-model price, exactly as if you had submitted them yourself. Credits are held when the turn starts running and settled when it finishes. A turn that fails or is interrupted has its remaining hold returned; queue time is not billed, because the clock starts when the turn does. A wallet that cannot pay refuses the turn with `insufficient_credits` rather than running a turn you cannot afford. Stopping a turn does not cancel or refund generations it already submitted: the work was done and the model was paid. [Pricing and credits](./billing.html) covers holds, settlement and refunds across the platform. ## Choose a brain The **brain** is the reasoning model your agent thinks with. It decides how the agent plans, and it sets the turn's flat rate, so it is the one pricing choice you make. Read the catalogue rather than hard-coding a rate. Rates are revised, and a brain that costs 10 credits a turn today is not promised to tomorrow. ```bash tab="curl" $ curl -sS https://api.nolgia.ai/v1/agent/models \ -H "Authorization: Bearer $NOLGIA_TOKEN" $ curl -sS -X PUT https://api.nolgia.ai/v1/agent/model \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":""}' ``` | Field | Type | Required | Description | | --- | --- | --- | --- | | `model` | string | Yes | Model identifier, as accepted by PUT /agent/model. | | `turn_credits` | integer | Yes | Flat credits billed per agent chat turn while the agent runs this brain.… | | `default` | boolean | Yes | True for the platform default brain (what an agent runs with no explicit selection). | `PUT /agent/model` takes effect on the **next turn that starts**; there is no wait and no restart, and a turn already running keeps the brain and the rate it began with. `DELETE /agent/model` returns you to the platform default. Anything outside the catalogue is refused with `400`. > [!NOTE] > A brain can be withdrawn from the catalogue. An agent already pinned to one keeps running at its own rate, and the agent read carries an advisory naming a current brain to move to. It is a suggestion: nothing switches until you select it. ## Read what it spent `GET /agent/usage` reports the credits the agent's own credential consumed and the jobs it created, newest first. | Field | Type | Required | Description | | --- | --- | --- | --- | | `credits_spent` | integer | Yes | Total credits consumed by generations the agent's token created. | | `jobs_total` | integer | Yes | Number of jobs created with the agent's token. | | `recent_jobs` | array of `Job` | Yes | | In an organization, the agent bills the organization's shared pool while that organization is your active context, and its spend appears against you in `GET /organizations/{id}/usage` like any other member's. The agent cannot change that context itself; see [Teams and organizations](./organizations.html). ## Keep a session or let it go Sessions accumulate. `PATCH /agent/sessions/{id}` archives one to keep it out of the list without losing the transcript, and unarchives it again; `DELETE /agent/sessions/{id}` deletes the session and its transcript. Media a session produced stays in your Library, because assets belong to the Library, not to the conversation that made them. :::cards - [The NOLGIA Agent](./agent.html) icon=spot-agent: What it is and how to run your first turn. - [Agent Sessions API](./agent-api.html) icon=spot-jobs: Sessions, messages, streaming and assets in full. - [Pricing and credits](./billing.html) icon=spot-credits: How holds, settlement and refunds work everywhere else. ::: --- # Agent Sessions API guide Source: https://docs.nolgia.ai/guides/agent-api.md You will be able to create persistent NOLGIA Agent sessions, send messages, follow replies, and collect the Outputs they produce. ![A conversation with the NOLGIA Agent and the Outputs it creates](../assets/art/agent.jpg) ## What the Agent Sessions API is | Method | Path | What it does | | --- | --- | --- | | [GET](../api/#tag/agent/get/agent) | `/agent` | Get the current user's NOLGIA Agent, if provisioned. | | [POST](../api/#tag/agent/post/agent) | `/agent` | Provision the current user's NOLGIA Agent (one per account; requires an active subscription). | | [DELETE](../api/#tag/agent/delete/agent) | `/agent` | Deprovision the current user's NOLGIA Agent and revoke its access token. | | [POST](../api/#tag/agent/post/agent/wake) | `/agent/wake` | Wake the current user's sleeping agent now. | | [GET](../api/#tag/agent/get/agent/usage) | `/agent/usage` | Credits spent and jobs created by the current user's agent. | | [GET](../api/#tag/agent/get/agent/models) | `/agent/models` | Selectable agent brains (reasoning models) with their flat per-turn credit rates. | | [PUT](../api/#tag/agent/put/agent/model) | `/agent/model` | Choose which brain the user's agent runs; turns bill that brain's flat per-turn rate. | | [DELETE](../api/#tag/agent/delete/agent/model) | `/agent/model` | Clear the agent's model pin to use the platform default brain and rate. | | [POST](../api/#tag/agent/post/agent/chat) | `/agent/chat` | Relay a chat message to the user's deployed NOLGIA Agent and return its reply. | | [POST](../api/#tag/agent/post/agent/messages) | `/agent/messages` | Submit a chat message to the user's NOLGIA Agent asynchronously. | | [GET](../api/#tag/agent/get/agent/messages) | `/agent/messages` | Chronological agent chat transcript for the current user. | | [GET](../api/#tag/agent/get/agent/messages/{id}) | `/agent/messages/{id}` | Fetch one transcript message. | | [PUT](../api/#tag/agent/put/agent/messages/{id}/feedback) | `/agent/messages/{id}/feedback` | Record or replace the caller's thumbs up or thumbs down on an agent reply. | | [DELETE](../api/#tag/agent/delete/agent/messages/{id}/feedback) | `/agent/messages/{id}/feedback` | Clear the caller's feedback on an agent reply. | | [POST](../api/#tag/agent/post/agent/followups) | `/agent/followups` | Suggest short next-step follow-up prompts for a chat exchange. | | [POST](../api/#tag/agent/post/agent/transcriptions) | `/agent/transcriptions` | Transcribe an uploaded audio asset to text (browser sessions only). | | [GET](../api/#tag/agent/get/agent/sessions) | `/agent/sessions` | List the current user's agent chat sessions, newest first. | | [POST](../api/#tag/agent/post/agent/sessions) | `/agent/sessions` | Create a named agent chat session. | | [GET](../api/#tag/agent/get/agent/sessions/{id}) | `/agent/sessions/{id}` | Fetch one of the current user's agent chat sessions. | | [PATCH](../api/#tag/agent/patch/agent/sessions/{id}) | `/agent/sessions/{id}` | Rename, re-link, archive, or unarchive a chat session. | | [DELETE](../api/#tag/agent/delete/agent/sessions/{id}) | `/agent/sessions/{id}` | Delete a chat session and its transcript. | | [GET](../api/#tag/agent/get/agent/sessions/{id}/assets) | `/agent/sessions/{id}/assets` | List the media a chat session's agent turns produced. | | [GET](../api/#tag/agent/get/agent/sessions/{id}/events) | `/agent/sessions/{id}/events` | Stream a chat session's transcript events over Server-Sent Events. | | [POST](../api/#tag/agent/post/agent/sessions/{id}/interrupt) | `/agent/sessions/{id}/interrupt` | Stop the session's in-flight agent turn. | | [GET](../api/#tag/agent/get/agent/steps) | `/agent/steps` | Live terminal/tool/message steps of the agent's current session. | The Agent Sessions API gives your application a persistent conversation with the NOLGIA Agent. Create a session, send a message, follow its reply, and collect the Outputs it produces. Sessions keep the transcript and its generated media together; your Library remains the place to find assets across sessions. Use the [API reference](../api/) for the full contract and the [getting started guide](./getting-started.html) for client installation. The base URL is `https://api.nolgia.ai/v1`. ## Authenticate Create a Personal Access Token at [nolgia.ai/settings/api-tokens](https://nolgia.ai/settings/api-tokens). Send it in the `Authorization` header on every request. The token inherits its account's permissions and organization role. ```bash $ export NOLGIA_TOKEN=nol_... $ curl https://api.nolgia.ai/v1/agent/sessions \ -H "Authorization: Bearer $NOLGIA_TOKEN" ``` ## Create a session `POST /agent/sessions` creates a session and returns `201` with its `id`. A `project_id` optionally links it to a project you own. ```bash $ curl -sS https://api.nolgia.ai/v1/agent/sessions \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"name":"Product launch"}' $ export SESSION_ID= ``` You can also create a session with its first message atomically by sending `new_session: {}` to `POST /agent/messages` instead of `session_id`. The accepted response includes the new `session_id` and `session`. ## Send a message ![An agent turn: message, events stream, optional steer or interrupt, reply](../assets/diagrams/agent-turn.svg) `POST /agent/messages` returns `202` with `user_message_id`, `agent_message_id`, and `session_id`. Save the agent reply id to follow this specific turn. The reply starts as `pending`. ```bash $ curl -sS https://api.nolgia.ai/v1/agent/messages \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d "{\"session_id\":\"$SESSION_ID\",\"content\":\"Plan a short product launch video.\"}" $ export MESSAGE_ID= ``` ## Wait for the reply Poll `GET /agent/messages/{id}` with backoff, starting at one second, multiplying by 1.5 and capping at ten seconds. This reads the same transcript row as `GET /agent/messages`, including feedback on rated agent replies, without repeatedly paging the transcript. ```bash $ curl -sS "https://api.nolgia.ai/v1/agent/messages/$MESSAGE_ID" \ -H "Authorization: Bearer $NOLGIA_TOKEN" ``` | Reply `status` | Meaning | | --- | --- | | `pending` | Waiting or working. `queued: true` means it is waiting for an execution slot. | | `complete` | Read `content`; check `question` before treating the conversation as finished. | | `failed` | Read `error` and its machine-readable `error_code`. | | `interrupted` | You stopped the turn. Read `error`; its remaining credit hold was returned. | > [!TIP] > Stop polling when the reply leaves `pending`. Apply an overall timeout in > your application; a polling timeout does not stop the turn. The SDK helpers > below implement this loop. ## Stream events For a live transcript, open `GET /agent/sessions/{id}/events`. Use `fetch` so you can send the bearer header; browser `EventSource` cannot send it. ```javascript const response = await fetch( `https://api.nolgia.ai/v1/agent/sessions/${sessionId}/events`, { headers: { Authorization: `Bearer ${token}`, Accept: "text/event-stream" } }, ); if (!response.ok) throw new Error(`Stream failed: ${response.status}`); // Feed response.body to an SSE parser. Chunks may split lines or frames. ``` The server sends a `retry:` hint and periodic comment heartbeats. Retain the last SSE `id:` and send it as the `Last-Event-ID` header when reconnecting. The server replays missed transcript writes before live delivery. Merge messages by id because replay can include duplicates. - `message`: the full transcript `message`, or only `message_id` when the payload is too large. Fetch that id when the row is omitted. - `step`: a step summary; fetch `GET /agent/steps` with the `session_id` and a `since` cursor for the detailed steps. - `session_status`: a session moved between `running`, `queued`, and `idle`. These frames may describe any session owned by the caller. Their states remain these three values; read the session or reply for questions. - `ready`: replay is finished. Its `replay` value is `complete`, `none`, or `truncated`. For `none` or `truncated`, hydrate the transcript with `GET /agent/messages?session_id={id}`. Retain its watermark for reconnects. Ignore event types you do not recognize. ## Steer a running turn Send another message with `steering: true` in the same session to add direction without cancelling its current turn. ```bash $ curl -sS https://api.nolgia.ai/v1/agent/messages \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d "{\"session_id\":\"$SESSION_ID\",\"content\":\"Use a warmer palette.\",\"steering\":true}" ``` When that session already has a pending turn, `202` carries only `user_message_id`. The direction can reach its running turn live; steering messages still queued when it finishes are delivered as a follow-up turn. If no turn is pending, the send creates a normal turn and returns both ids. ## Interrupt a turn `POST /agent/sessions/{id}/interrupt` stops only that session's newest running or queued turn. It needs no request body. ```bash $ curl -sS -X POST "https://api.nolgia.ai/v1/agent/sessions/$SESSION_ID/interrupt" \ -H "Authorization: Bearer $NOLGIA_TOKEN" ``` The response contains `session_id`, `interrupted`, `run_stopped`, and `credits_released`, plus `agent_message_id` and `status` when a turn exists. `interrupted: true` means that reply is now interrupted, including on repeat calls. `run_stopped: true` means the running agent acknowledged the stop. It is false for queued turns, turns whose run has not started, repeat calls, and when the agent could not be reached. Even then, the reply is marked `interrupted` and any later result is discarded. The turn's held reasoning credits are returned; `credits_released` is the amount returned by this call. Generations already submitted keep running and bill as usual. Steering messages queued behind the stopped turn remain in the transcript but do not run. Send a new message to continue. Interrupt is idempotent. Repeating it, or calling it on an idle session, returns `200` and changes nothing; repeat calls release zero credits. ## Answer the agent's questions A `complete` agent reply whose last line is a question can include: ```json { "question": { "text": "Which length feels right?", "options": [ {"label": "5 seconds", "description": "one beat, the product and one move."}, {"label": "15 seconds", "description": "room for a short story."}, {"label": "30 seconds", "description": "a fuller product introduction."} ] } } ``` The session's `turn_status` becomes `awaiting_input`, and the session exposes the same `question`. Its `options` is an ordered list and may be empty. Show the text and choices, then answer with a normal `POST /agent/messages` in the same session. A choice is an ordinary message, not a special action. The reply itself remains `complete`; `awaiting_input` is the session state. ## Queue semantics One turn is in flight per session. Another normal submit to that session returns `409` with `code: session_busy` and `pending_agent_message_id`. Wait for that reply or send a steering message instead of retrying the same normal submit immediately. Turns in other sessions are accepted and queue FIFO when your execution slots are full. At most ten turns may be pending across your sessions, including running and queued turns; the next submit returns `429` with `code: session_busy`. Back off before trying again. A pending reply's `queued` flag distinguishes waiting for a slot from active work. Steering stays within its target session. Undelivered steering messages are combined in order into a follow-up turn after the current turn settles. Interrupt consumes those queued directions into the stopped turn so they cannot unexpectedly start another one. ## Assets `GET /agent/sessions/{id}/assets` returns the session's generated assets, newest first and de-duplicated by id. Outputs can appear while the reply is still pending, and remain available if the turn fails or is interrupted. ```bash $ curl -sS "https://api.nolgia.ai/v1/agent/sessions/$SESSION_ID/assets" \ -H "Authorization: Bearer $NOLGIA_TOKEN" ``` Use these assets to build an Outputs view for the session or link into your Library. The SDK helpers collect asset ids created since the first reply row in that helper run. ## Credits Each turn has a flat rate minimum for its selected brain, with metering on actual model cost above that minimum. Credits are held when the turn starts running, then settled when it completes. Failed and interrupted turns have their remaining holds refunded. Waiting in the queue does not start the turn's time budget. Generations the NOLGIA Agent submits are billed separately under the usual generation rules. Stopping a reasoning turn does not cancel or refund those generations. See the [getting started credit guide](./getting-started.html) for generation billing and refunds. ## Errors Agent endpoint failures use RFC 7807 problem objects with `status`, `title`, and `detail`, and a machine-readable `code` when the failure has one. The submit endpoint's `409` instead returns a conflict object carrying `code: session_busy` and `pending_agent_message_id`. | Value | Meaning | | --- | --- | | `session_busy` | the session already has a turn in flight or too many turns are pending; wait or steer. | | `access_denied` | the credential or organization role may not do this. | | `insufficient_credits` | the wallet cannot pay for the turn. | | `backend_error` | the agent could not be reached or failed. | | `timeout` | the turn ran past its time budget. | A failed transcript reply exposes this classification as `error_code` next to its human-readable `error`. Interrupted replies carry `error` without an `error_code`. Other HTTP errors may omit `code`, including `401` for missing authentication and `404` for a missing message or session, or one you do not own. Router rejections can be plain text; check the response content type before decoding a problem object. ## SDK run helpers Each helper submits, polls with backoff, and returns a result containing `status`, `text`, `asset_ids`, `session_id`, and `agent_message_id`, plus `question` or `error_code` when present. Status is `complete`, `awaiting_input`, `failed`, `interrupted`, or `timeout`. For a failed or interrupted reply, `text` is its served error message. Pass no session id to create a new session. An optional question callback receives the question and options: return a non-empty answer to continue in the same session, or an empty answer to return `awaiting_input`. Without a callback, a question also returns `awaiting_input`. Python: ```python import os from nolgia import AuthenticatedClient from nolgia.agent_run import run client = AuthenticatedClient( base_url="https://api.nolgia.ai/v1", token=os.environ["NOLGIA_TOKEN"] ) result = run(client, None, "Plan a product launch video.", timeout=600.0) print(result.status, result.text, result.asset_ids) ``` TypeScript: ```typescript import { createNolgiaClient, runAgent } from "@nolgia/sdk"; const client = createNolgiaClient(process.env.NOLGIA_TOKEN ?? ""); const result = await runAgent(client, { message: "Plan a product launch video.", timeoutMs: 600_000, onQuestion: async (question) => { console.log(question.text, question.options); return null; // Display the choices and collect an answer in your UI. }, }); console.log(result.status, result.text, result.asset_ids); ``` All helpers share one overall timeout across questions and follow-up turns. A timeout returns `timeout` without fetching assets and does not interrupt the agent. To stop it, call `POST /agent/sessions/{id}/interrupt` using the returned session id, as shown above. A `409` raises or returns an error with `code: session_busy` and the pending reply id; it is never automatically retried. Other HTTP failures carry the status and problem `code`/`detail`. The final asset lookup happens once, includes only assets whose `created_at` is at or after the first reply row's `created_at`, preserves server order, and de-duplicates ids. Poll interval, maximum interval, and sleep are injectable for tests. :::cards - [Authentication](./authentication.html): Choose a credential for your integration. - [Jobs](./jobs.html): Follow the generations your application submits. - [Billing](./billing.html): Read balances and understand credit holds. - [Client libraries](./client-libraries.html): Set up an SDK, the CLI or MCP. ::: --- # Abilities Source: https://docs.nolgia.ai/guides/abilities.md Your NOLGIA Agent installs and runs ability packages from a marketplace published by admins. Browse the catalog, inspect an ability, then install and sync the packages you need. ## Catalog and installs | Method | Path | What it does | | --- | --- | --- | | [GET](../api/#tag/abilities/get/agent/abilities) | `/agent/abilities` | List the abilities installed for the current user's agent. | | [POST](../api/#tag/abilities/post/agent/abilities/{slug}) | `/agent/abilities/{slug}` | Install a published ability for the current user's agent (applied on the next ability sync). Requires the ability's entitlement (`min_tier`). Optionally pins the install to an exact published version; without a pin the install tracks the latest published version (the default). | | [DELETE](../api/#tag/abilities/delete/agent/abilities/{slug}) | `/agent/abilities/{slug}` | Uninstall an ability from the current user's agent. | | [GET](../api/#tag/abilities/get/agent/abilities/events) | `/agent/abilities/events` | Stream ability version publishes for the current user's agent over Server-Sent Events. | | [GET](../api/#tag/abilities/get/abilities) | `/abilities` | List published marketplace abilities (metadata only — content is never included in listings). Customers see the public catalog; admin accounts also see private abilities. | | [POST](../api/#tag/abilities/post/abilities) | `/abilities` | Publish a new ability version (admin only; upserts the ability by slug, versions are immutable). | | [GET](../api/#tag/abilities/get/abilities/{slug}) | `/abilities/{slug}` | Get a published ability's detail. | | [GET](../api/#tag/abilities/get/abilities/{slug}/content) | `/abilities/{slug}/content` | Download a published version's package content (base64 tar.gz) — the latest version by default, or the exact version named by `version` (how a pod materializes a pinned install). Entitlement-gated — the caller's subscription must satisfy the ability's `min_tier`. | ## The ability record | Field | Type | Required | Description | | --- | --- | --- | --- | | `slug` | string | Yes | Marketplace identity of the ability; also the install directory name under `$HERMES_HOME/skills/` (the on-disk install root is unchanged for backward compatibility). | | `name` | string | Yes | | | `description` | string | Yes | | | `required_env` | array of string | Yes | Environment variables the ability expects on the agent pod. | | `credit_cost_hint` | string | Yes | Free-text hint of how the ability spends credits. | | `min_tier` | string | Yes | Minimum subscription tier required to install or download this ability's content. Empty means free for any account. | | `visibility` | `AbilityVisibility` | Yes | | | `entitled` | boolean | Yes | Whether the calling account may download this ability's content (subscription satisfies `min_tier`, or admin). Listings are metadata-only; content download is refused (402) when false. | | `access` | `AbilityAccess` | Yes | | | `price_cents` | integer, nullable | No | For `premium_addon` abilities, the add-on price in cents. `null` for `included` abilities. Metadata only — no purchase flow enforces it yet. | | `interval` | `AbilityInterval`, nullable | No | For `premium_addon` abilities, the billing interval of `price_cents`. `null` for `included` abilities. | | `has_code` | boolean | Yes | True when the package carries a code payload — any file beyond the manifest files (`SKILL.md` and `ability.json`). Computed at publish time. Drives the marketplace "Package" vs "Ability" (instructions-only) kind badge. | | `latest_version` | string | Yes | Latest published version by SEMVER ordering (highest major.minor.patch wins), not by publish time — a 1.0.1 published after a 1.2.0 does not become latest.… | | `created_at` | string | Yes | | | `updated_at` | string | Yes | | > [!NOTE] > Publishing is admin-only and versions are immutable. Holding an ability with `visibility=private` removes it from the catalog without breaking existing installs. ## From the CLI These subcommands are listed by `nolgia ability --help`. | Subcommand | What it does | | --- | --- | | `list` | List the marketplace catalog visible to this account | | `show` | Show one marketplace ability | | `installed` | List abilities installed for this account's agent | | `install` | Install a marketplace ability for this account's agent | | `uninstall` | Uninstall a marketplace ability from this account's agent | | `sync` | Materialize installed abilities into a skills directory (what the agent pod's initContainer runs on boot) | | `init` | Scaffold a new ability authoring directory (ability.json, SKILL.md, payload/) | | `pack` | Validate an ability authoring directory and assemble the publishable package | | `publish` | Publish an ability package directory to the marketplace (admin only) | | `help` | Print this message or the help of the given subcommand(s) | :::cards - [Agent Sessions API](./agent-api.html) icon=spot-agent: Run persistent conversations with your agent. - [The operator model](./operator.html) icon=spot-jobs: Configure your agent and observe installed versions. - [The CLI](./cli.html) icon=spot-terminal: Install the CLI and script your workflow. ::: --- # The operator model Source: https://docs.nolgia.ai/guides/operator.md You provision one NOLGIA Agent per account with `POST /agent`, which requires an active Studio, Team or Enterprise subscription. Starter and Pro plans cannot provision an agent. Configure its environment, read its desired state and inspect the work it creates. ![The NOLGIA Agent and the work it creates](../assets/art/agent.jpg) ## Provision and deprovision `GET /agent` reads your agent; `POST /agent` provisions it asynchronously. `DELETE /agent` deprovisions it and revokes its access token. | Method | Path | What it does | | --- | --- | --- | | [GET](../api/#tag/agent/get/agent) | `/agent` | Get the current user's NOLGIA Agent, if provisioned. | | [POST](../api/#tag/agent/post/agent) | `/agent` | Provision the current user's NOLGIA Agent (one per account; requires an active subscription). | | [DELETE](../api/#tag/agent/delete/agent) | `/agent` | Deprovision the current user's NOLGIA Agent and revoke its access token. | | [GET](../api/#tag/agent/get/agent/usage) | `/agent/usage` | Credits spent and jobs created by the current user's agent. | | [GET](../api/#tag/agent/get/agent/manifest) | `/agent/manifest` | Return the complete desired state for the current user's agent as a single, server-computed source of truth: its installed abilities (with pinned versions), its available presets, and the pinned CLI + runtime versions. This is the one list a pod-side reconciler materializes. Increment 1 enumerates what already exists in the DB (abilities + presets + version pins); the CLI-embedded skills and the base-image skill library are a separate increment pending an object-store decision. | | [GET](../api/#tag/agent/get/agent/config) | `/agent/config` | List the agent's custom configuration entries. Secret values are redacted (empty string) in the response. | | [PUT](../api/#tag/agent/put/agent/config) | `/agent/config` | Upsert custom configuration entries (e.g. TELEGRAM_BOT_TOKEN) injected into the agent pod's environment. A running agent is flipped back to `pending_deploy` so the deploy pipeline applies the change (typically within ~5 minutes). | | [POST](../api/#tag/agent/post/agent/dashboard/ticket) | `/agent/dashboard/ticket` | Mint a short-lived ticket URL for embedding the agent's live dashboard. | | [POST](../api/#tag/agent/post/agent/heartbeat) | `/agent/heartbeat` | Report the agent pod's materialized state (installed ability versions) upstream. | ## Configure > [!NOTE] > `GET /agent/config` reads custom entries with secrets redacted. `PUT /agent/config` writes entries injected into the pod environment; a change flips a running agent to `pending_deploy` and is typically applied within about five minutes. ## Desired state | Endpoint | What it tells you | | --- | --- | | `GET /agent/manifest` | Desired abilities with pinned versions, available presets, and pinned CLI and runtime versions. | | `POST /agent/heartbeat` | The pod reports installed ability versions; compare `installed_version` with `latest_version` on `GET /agent/abilities` to observe drift. | ## Observe `GET /agent/usage` reports credits spent and jobs created. `POST /agent/dashboard/ticket` creates a 120-second ticket for the embedded dashboard and accepts browser sessions only. | Field | Type | Required | Description | | --- | --- | --- | --- | | `credits_spent` | integer | Yes | Total credits consumed by generations the agent's token created. | | `jobs_total` | integer | Yes | Number of jobs created with the agent's token. | | `recent_jobs` | array of `Job` | Yes | | :::cards - [Agent Sessions API](./agent-api.html) icon=spot-agent: Send messages, stream replies and collect assets. - [Abilities](./abilities.html) icon=spot-library: Browse, install and sync marketplace packages. ::: --- # Image generation Source: https://docs.nolgia.ai/guides/models-image-generation.md Models that make an image from a prompt. | Model | Made by | Model id | | --- | --- | --- | | [FLUX.2 Flex](./model-flux-2-flex.html) | Black Forest Labs | `flux-2-flex` | | [FLUX.2 Klein](./model-flux-2-klein.html) | Black Forest Labs | `flux-2-klein` | | [FLUX.2 Max](./model-flux-2-max.html) | Black Forest Labs | `flux-2-max` | | [FLUX.2 Pro](./model-flux-2-pro.html) | Black Forest Labs | `flux-2-pro` | | [Flux Pro](./model-flux-pro.html) | Black Forest Labs | `flux-pro` | | [FLUX Schnell](./model-flux-schnell.html) | Black Forest Labs | `flux-schnell` | | [Flux Ultra](./model-flux-ultra.html) | Black Forest Labs | `flux-ultra` | | [Grok Imagine](./model-grok-imagine-image.html) | xAI | `grok-imagine-image` | | [Grok Imagine 2.0](./model-grok-imagine-image-2.0.html) | xAI | `grok-imagine-image-2.0` | | [Grok Imagine Quality](./model-grok-imagine-image-quality.html) | xAI | `grok-imagine-image-quality` | | [Ideogram v3](./model-ideogram-v3.html) | Ideogram | `ideogram-v3` | | [Recraft v2](./model-recraft-v2.html) | Recraft | `recraft-v2` | | [Recraft v3](./model-recraft-v3.html) | Recraft | `recraft-v3` | | [Recraft V4](./model-recraft-v4.html) | Recraft | `recraft-v4` | | [Recraft V4 Pro](./model-recraft-v4-pro.html) | Recraft | `recraft-v4-pro` | | [Recraft V4.1](./model-recraft-v4.1.html) | Recraft | `recraft-v4.1` | | [Recraft V4.1 Pro](./model-recraft-v4.1-pro.html) | Recraft | `recraft-v4.1-pro` | | [Recraft V4.1 Utility](./model-recraft-v4.1-utility.html) | Recraft | `recraft-v4.1-utility` | | [Recraft V4.1 Utility Pro](./model-recraft-v4.1-utility-pro.html) | Recraft | `recraft-v4.1-utility-pro` | | [Riverflow V2.5 Fast](./model-riverflow-v2.5-fast.html) | Sourceful | `riverflow-v2.5-fast` | | [Riverflow V2.5 Pro](./model-riverflow-v2.5-pro.html) | Sourceful | `riverflow-v2.5-pro` | | [Seedream 4.5](./model-seedream-4.5.html) | ByteDance | `seedream-4.5` | | [Stable Diffusion 3.5](./model-stable-diffusion-3.5.html) | Stability AI | `stable-diffusion-3.5` | --- # FLUX.2 Flex Source: https://docs.nolgia.ai/guides/model-flux-2-flex.md Made by Black Forest Labs. | | | | --- | --- | | Model id | `flux-2-flex` | | Modality | image | | Plan | starter | | Price | 6 per image | ## What it does - Aspect ratios: `3:1`, `21:9`, `16:9`, `3:2`, `4:3`, `1:1`, `3:4`, `2:3`, `9:16`, `9:21`, `1:3`. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/image \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"flux-2-flex","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # FLUX.2 Klein Source: https://docs.nolgia.ai/guides/model-flux-2-klein.md Made by Black Forest Labs. | | | | --- | --- | | Model id | `flux-2-klein` | | Modality | image | | Plan | starter | | Price | 2 per image | ## What it does - Aspect ratios: `3:1`, `21:9`, `16:9`, `3:2`, `4:3`, `1:1`, `3:4`, `2:3`, `9:16`, `9:21`, `1:3`. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/image \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"flux-2-klein","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # FLUX.2 Max Source: https://docs.nolgia.ai/guides/model-flux-2-max.md Made by Black Forest Labs. | | | | --- | --- | | Model id | `flux-2-max` | | Modality | image | | Plan | starter | | Price | 7 per image | ## What it does - Aspect ratios: `3:1`, `21:9`, `16:9`, `3:2`, `4:3`, `1:1`, `3:4`, `2:3`, `9:16`, `9:21`, `1:3`. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/image \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"flux-2-max","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # FLUX.2 Pro Source: https://docs.nolgia.ai/guides/model-flux-2-pro.md Made by Black Forest Labs. | | | | --- | --- | | Model id | `flux-2-pro` | | Modality | image | | Plan | starter | | Price | 2 per image | ## What it does - Aspect ratios: `3:1`, `21:9`, `16:9`, `3:2`, `4:3`, `1:1`, `3:4`, `2:3`, `9:16`, `9:21`, `1:3`. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/image \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"flux-2-pro","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # Flux Pro Source: https://docs.nolgia.ai/guides/model-flux-pro.md Made by Black Forest Labs. | | | | --- | --- | | Model id | `flux-pro` | | Modality | image | | Plan | starter | | Price | 4 per image | ## What it does - Aspect ratios: `3:1`, `21:9`, `16:9`, `3:2`, `4:3`, `1:1`, `3:4`, `2:3`, `9:16`, `9:21`, `1:3`. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/image \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"flux-pro","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # FLUX Schnell Source: https://docs.nolgia.ai/guides/model-flux-schnell.md Made by Black Forest Labs. | | | | --- | --- | | Model id | `flux-schnell` | | Modality | image | | Plan | starter | | Price | 2 per image | ## What it does - Aspect ratios: `3:1`, `21:9`, `16:9`, `3:2`, `4:3`, `1:1`, `3:4`, `2:3`, `9:16`, `9:21`, `1:3`. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/image \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"flux-schnell","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # Flux Ultra Source: https://docs.nolgia.ai/guides/model-flux-ultra.md Made by Black Forest Labs. | | | | --- | --- | | Model id | `flux-ultra` | | Modality | image | | Plan | starter | | Price | 6 per image | ## What it does - Aspect ratios: `21:9`, `16:9`, `3:2`, `4:3`, `1:1`, `3:4`, `2:3`, `9:16`, `9:21`. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/image \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"flux-ultra","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # Grok Imagine Source: https://docs.nolgia.ai/guides/model-grok-imagine-image.md Made by xAI. | | | | --- | --- | | Model id | `grok-imagine-image` | | Modality | image | | Plan | starter | | Price | 2 per image | ## What it does - Aspect ratios: `1:1`, `16:9`, `9:16`, `4:3`, `3:4`, `3:2`, `2:3`, `2:1`, `1:2`. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/image \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"grok-imagine-image","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # Grok Imagine 2.0 Source: https://docs.nolgia.ai/guides/model-grok-imagine-image-2.0.md Made by xAI. | | | | --- | --- | | Model id | `grok-imagine-image-2.0` | | Modality | image | | Plan | starter | | Price | 4 per image | ## What it does - Aspect ratios: `1:1`, `16:9`, `9:16`, `4:3`, `3:4`, `3:2`, `2:3`, `2:1`, `1:2`. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/image \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"grok-imagine-image-2.0","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # Grok Imagine Quality Source: https://docs.nolgia.ai/guides/model-grok-imagine-image-quality.md Made by xAI. | | | | --- | --- | | Model id | `grok-imagine-image-quality` | | Modality | image | | Plan | starter | | Price | 5 per image | ## What it does - Aspect ratios: `1:1`, `16:9`, `9:16`, `4:3`, `3:4`, `3:2`, `2:3`, `2:1`, `1:2`. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/image \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"grok-imagine-image-quality","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # Ideogram v3 Source: https://docs.nolgia.ai/guides/model-ideogram-v3.md Made by Ideogram. | | | | --- | --- | | Model id | `ideogram-v3` | | Modality | image | | Plan | starter | | Price | 6 per image | ## What it does - Aspect ratios: `16:9`, `4:3`, `1:1`, `3:4`, `9:16`. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/image \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"ideogram-v3","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # Recraft v2 Source: https://docs.nolgia.ai/guides/model-recraft-v2.md Made by Recraft. | | | | --- | --- | | Model id | `recraft-v2` | | Modality | image | | Plan | starter | | Price | 3 per image | ## What it does - Aspect ratios: `2:1`, `16:9`, `3:2`, `4:3`, `5:4`, `1:1`, `4:5`, `3:4`, `2:3`, `9:16`, `1:2`. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/image \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"recraft-v2","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # Recraft v3 Source: https://docs.nolgia.ai/guides/model-recraft-v3.md Made by Recraft. | | | | --- | --- | | Model id | `recraft-v3` | | Modality | image | | Plan | starter | | Price | 4 per image | ## What it does - Aspect ratios: `2:1`, `16:9`, `3:2`, `4:3`, `5:4`, `1:1`, `4:5`, `3:4`, `2:3`, `9:16`, `1:2`. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/image \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"recraft-v3","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # Recraft V4 Source: https://docs.nolgia.ai/guides/model-recraft-v4.md Made by Recraft. | | | | --- | --- | | Model id | `recraft-v4` | | Modality | image | | Plan | starter | | Price | 4 per image | ## What it does - Aspect ratios: `2:1`, `16:9`, `3:2`, `4:3`, `5:4`, `1:1`, `4:5`, `3:4`, `2:3`, `9:16`, `1:2`. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/image \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"recraft-v4","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # Recraft V4 Pro Source: https://docs.nolgia.ai/guides/model-recraft-v4-pro.md Made by Recraft. | | | | --- | --- | | Model id | `recraft-v4-pro` | | Modality | image | | Plan | starter | | Price | 25 per image | ## What it does - Aspect ratios: `2:1`, `16:9`, `3:2`, `4:3`, `5:4`, `1:1`, `4:5`, `3:4`, `2:3`, `9:16`, `1:2`. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/image \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"recraft-v4-pro","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # Recraft V4.1 Source: https://docs.nolgia.ai/guides/model-recraft-v4.1.md Made by Recraft. | | | | --- | --- | | Model id | `recraft-v4.1` | | Modality | image | | Plan | starter | | Price | 4 per image | ## What it does - Aspect ratios: `2:1`, `16:9`, `3:2`, `4:3`, `5:4`, `1:1`, `4:5`, `3:4`, `2:3`, `9:16`, `1:2`. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/image \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"recraft-v4.1","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # Recraft V4.1 Pro Source: https://docs.nolgia.ai/guides/model-recraft-v4.1-pro.md Made by Recraft. | | | | --- | --- | | Model id | `recraft-v4.1-pro` | | Modality | image | | Plan | starter | | Price | 21 per image | ## What it does - Aspect ratios: `2:1`, `16:9`, `3:2`, `4:3`, `5:4`, `1:1`, `4:5`, `3:4`, `2:3`, `9:16`, `1:2`. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/image \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"recraft-v4.1-pro","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # Recraft V4.1 Utility Source: https://docs.nolgia.ai/guides/model-recraft-v4.1-utility.md Made by Recraft. | | | | --- | --- | | Model id | `recraft-v4.1-utility` | | Modality | image | | Plan | starter | | Price | 4 per image | ## What it does - Aspect ratios: `2:1`, `16:9`, `3:2`, `4:3`, `5:4`, `1:1`, `4:5`, `3:4`, `2:3`, `9:16`, `1:2`. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/image \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"recraft-v4.1-utility","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # Recraft V4.1 Utility Pro Source: https://docs.nolgia.ai/guides/model-recraft-v4.1-utility-pro.md Made by Recraft. | | | | --- | --- | | Model id | `recraft-v4.1-utility-pro` | | Modality | image | | Plan | starter | | Price | 21 per image | ## What it does - Aspect ratios: `2:1`, `16:9`, `3:2`, `4:3`, `5:4`, `1:1`, `4:5`, `3:4`, `2:3`, `9:16`, `1:2`. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/image \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"recraft-v4.1-utility-pro","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # Riverflow V2.5 Fast Source: https://docs.nolgia.ai/guides/model-riverflow-v2.5-fast.md Made by Sourceful. | | | | --- | --- | | Model id | `riverflow-v2.5-fast` | | Modality | image | | Plan | starter | | Price | 2 per image | ## What it does - Aspect ratios: `16:9`, `3:2`, `1:1`, `2:3`, `9:16`. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/image \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"riverflow-v2.5-fast","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # Riverflow V2.5 Pro Source: https://docs.nolgia.ai/guides/model-riverflow-v2.5-pro.md Made by Sourceful. | | | | --- | --- | | Model id | `riverflow-v2.5-pro` | | Modality | image | | Plan | starter | | Price | 14 per image | ## What it does - Aspect ratios: `16:9`, `3:2`, `1:1`, `2:3`, `9:16`. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/image \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"riverflow-v2.5-pro","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # Seedream 4.5 Source: https://docs.nolgia.ai/guides/model-seedream-4.5.md Made by ByteDance. | | | | --- | --- | | Model id | `seedream-4.5` | | Modality | image | | Plan | starter | | Price | 3 per image | ## What it does - Aspect ratios: `16:9`, `3:2`, `1:1`, `2:3`, `9:16`. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/image \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"seedream-4.5","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # Stable Diffusion 3.5 Source: https://docs.nolgia.ai/guides/model-stable-diffusion-3.5.md Made by Stability AI. | | | | --- | --- | | Model id | `stable-diffusion-3.5` | | Modality | image | | Plan | starter | | Price | 4 per image | ## What it does - Aspect ratios: `16:9`, `4:3`, `1:1`, `3:4`, `9:16`. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/image \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"stable-diffusion-3.5","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # Image editing Source: https://docs.nolgia.ai/guides/models-image-editing.md Models that take an image in and give a changed image back. | Model | Made by | Model id | | --- | --- | --- | | [FLUX Expand](./model-flux-expand.html) | Black Forest Labs | `flux-expand` | | [FLUX Kontext Max](./model-flux-kontext-max.html) | Black Forest Labs | `flux-kontext-max` | | [FLUX Kontext Pro](./model-flux-kontext-pro.html) | Black Forest Labs | `flux-kontext-pro` | | [GPT Image 1](./model-gpt-image-1.html) | OpenAI | `gpt-image-1` | | [GPT Image 1.5](./model-gpt-image-1.5.html) | OpenAI | `gpt-image-1.5` | | [GPT Image 2](./model-gpt-image-2.html) | OpenAI | `gpt-image-2` | | [GPT Image 2.5 Flare](./model-gpt-image-2.5-flare.html) | OpenAI | `gpt-image-2.5-flare` | | [GPT Image 2.5 Sunburst](./model-gpt-image-2.5-sunburst.html) | OpenAI | `gpt-image-2.5-sunburst` | | [Ideogram v4](./model-ideogram-v4.html) | Ideogram | `ideogram-v4` | | [MAI Image 2.5](./model-mai-image-2.5.html) | Microsoft | `mai-image-2.5` | | [MAI Image 2.5 Pro](./model-mai-image-2.5-pro.html) | Microsoft | `mai-image-2.5-pro` | | [MAI Image 2.6](./model-mai-image-2.6.html) | Microsoft | `mai-image-2.6` | | [MAI Image 2.6 Flash](./model-mai-image-2.6-flash.html) | Microsoft | `mai-image-2.6-flash` | | [MiniMax Image 01](./model-minimax-image-01.html) | MiniMax | `minimax-image-01` | | [Nano Banana 2](./model-nano-banana-2.html) | Google | `nano-banana-2` | | [Nano Banana 2 Lite](./model-nano-banana-2-lite.html) | Google | `nano-banana-2-lite` | | [Nano Banana Pro](./model-nano-banana-pro.html) | Google | `nano-banana-pro` | | [Qwen Image 3](./model-qwen-image-3.html) | Alibaba | `qwen-image-3` | | [Seedream 5.0 Pro](./model-seedream-v5-pro.html) | ByteDance | `seedream-v5-pro` | --- # FLUX Expand Source: https://docs.nolgia.ai/guides/model-flux-expand.md Made by Black Forest Labs. | | | | --- | --- | | Model id | `flux-expand` | | Modality | image | | Plan | starter | | Price | 5 per image | ## What it does - Expands the canvas beyond the original frame. - Takes up to 1 reference image(s). - Aspect ratios: `21:9`, `16:9`, `3:2`, `4:3`, `5:4`, `1:1`, `4:5`, `3:4`, `2:3`, `9:16`, `9:21`. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/image \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"flux-expand","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # FLUX Kontext Max Source: https://docs.nolgia.ai/guides/model-flux-kontext-max.md Made by Black Forest Labs. | | | | --- | --- | | Model id | `flux-kontext-max` | | Modality | image | | Plan | starter | | Price | 8 per image | ## What it does - Takes up to 1 reference image(s). - Aspect ratios: `21:9`, `16:9`, `3:2`, `4:3`, `1:1`, `3:4`, `2:3`, `9:16`, `9:21`. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/image \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"flux-kontext-max","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # FLUX Kontext Pro Source: https://docs.nolgia.ai/guides/model-flux-kontext-pro.md Made by Black Forest Labs. | | | | --- | --- | | Model id | `flux-kontext-pro` | | Modality | image | | Plan | starter | | Price | 4 per image | ## What it does - Takes up to 1 reference image(s). - Aspect ratios: `21:9`, `16:9`, `3:2`, `4:3`, `1:1`, `3:4`, `2:3`, `9:16`, `9:21`. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/image \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"flux-kontext-pro","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # GPT Image 1 Source: https://docs.nolgia.ai/guides/model-gpt-image-1.md Made by OpenAI. | | | | --- | --- | | Model id | `gpt-image-1` | | Modality | image | | Plan | starter | | Price | 14 per image | ## What it does - Takes up to 4 reference image(s). - Aspect ratios: `3:2`, `1:1`, `2:3`. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/image \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"gpt-image-1","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # GPT Image 1.5 Source: https://docs.nolgia.ai/guides/model-gpt-image-1.5.md Made by OpenAI. | | | | --- | --- | | Model id | `gpt-image-1.5` | | Modality | image | | Plan | starter | | Price | 12 per image | ## What it does - Takes up to 4 reference image(s). - Aspect ratios: `3:2`, `1:1`, `2:3`. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/image \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"gpt-image-1.5","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # GPT Image 2 Source: https://docs.nolgia.ai/guides/model-gpt-image-2.md Made by OpenAI. | | | | --- | --- | | Model id | `gpt-image-2` | | Modality | image | | Plan | starter | | Price | 22 per image | ## What it does - Takes up to 4 reference image(s). - Edits part of an image, guided by a mask. - Aspect ratios: `3:1`, `21:9`, `16:9`, `3:2`, `4:3`, `5:4`, `1:1`, `4:5`, `3:4`, `2:3`, `9:16`, `1:3`. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/image \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"gpt-image-2","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # GPT Image 2.5 Flare Source: https://docs.nolgia.ai/guides/model-gpt-image-2.5-flare.md Made by OpenAI. | | | | --- | --- | | Model id | `gpt-image-2.5-flare` | | Modality | image | | Plan | starter | | Price | 8 per image | ## What it does - Takes up to 4 reference image(s). - Edits part of an image, guided by a mask. - Aspect ratios: `3:1`, `21:9`, `16:9`, `3:2`, `4:3`, `5:4`, `1:1`, `4:5`, `3:4`, `2:3`, `9:16`, `1:3`. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/image \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"gpt-image-2.5-flare","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # GPT Image 2.5 Sunburst Source: https://docs.nolgia.ai/guides/model-gpt-image-2.5-sunburst.md Made by OpenAI. | | | | --- | --- | | Model id | `gpt-image-2.5-sunburst` | | Modality | image | | Plan | starter | | Price | 8 per image | ## What it does - Takes up to 4 reference image(s). - Edits part of an image, guided by a mask. - Aspect ratios: `3:1`, `21:9`, `16:9`, `3:2`, `4:3`, `5:4`, `1:1`, `4:5`, `3:4`, `2:3`, `9:16`, `1:3`. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/image \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"gpt-image-2.5-sunburst","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # Ideogram v4 Source: https://docs.nolgia.ai/guides/model-ideogram-v4.md Made by Ideogram. | | | | --- | --- | | Model id | `ideogram-v4` | | Modality | image | | Plan | starter | | Price | 1 per image | ## What it does - Takes up to 1 reference image(s). - Aspect ratios: `16:9`, `4:3`, `1:1`, `3:4`, `9:16`. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/image \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"ideogram-v4","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # MAI Image 2.5 Source: https://docs.nolgia.ai/guides/model-mai-image-2.5.md Made by Microsoft. | | | | --- | --- | | Model id | `mai-image-2.5` | | Modality | image | | Plan | starter | | Price | 6 per image | ## What it does - Takes up to 1 reference image(s). - Aspect ratios: `16:9`, `3:2`, `1:1`, `2:3`, `9:16`. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/image \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"mai-image-2.5","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # MAI Image 2.5 Pro Source: https://docs.nolgia.ai/guides/model-mai-image-2.5-pro.md Made by Microsoft. | | | | --- | --- | | Model id | `mai-image-2.5-pro` | | Modality | image | | Plan | starter | | Price | 10 per image | ## What it does - Takes up to 1 reference image(s). - Aspect ratios: `16:9`, `3:2`, `1:1`, `2:3`, `9:16`. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/image \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"mai-image-2.5-pro","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # MAI Image 2.6 Source: https://docs.nolgia.ai/guides/model-mai-image-2.6.md Made by Microsoft. | | | | --- | --- | | Model id | `mai-image-2.6` | | Modality | image | | Plan | starter | | Price | 6 per image | ## What it does - Takes up to 1 reference image(s). - Aspect ratios: `16:9`, `3:2`, `1:1`, `2:3`, `9:16`. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/image \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"mai-image-2.6","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # MAI Image 2.6 Flash Source: https://docs.nolgia.ai/guides/model-mai-image-2.6-flash.md Made by Microsoft. | | | | --- | --- | | Model id | `mai-image-2.6-flash` | | Modality | image | | Plan | starter | | Price | 3 per image | ## What it does - Takes up to 1 reference image(s). - Aspect ratios: `16:9`, `3:2`, `1:1`, `2:3`, `9:16`. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/image \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"mai-image-2.6-flash","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # MiniMax Image 01 Source: https://docs.nolgia.ai/guides/model-minimax-image-01.md Made by MiniMax. | | | | --- | --- | | Model id | `minimax-image-01` | | Modality | image | | Plan | starter | | Price | 1 per image | ## What it does - Takes up to 1 reference image(s). - Aspect ratios: `1:1`, `16:9`, `9:16`, `4:3`, `3:4`, `2:3`, `3:2`, `21:9`. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/image \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"minimax-image-01","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # Nano Banana 2 Source: https://docs.nolgia.ai/guides/model-nano-banana-2.md Made by Google. | | | | --- | --- | | Model id | `nano-banana-2` | | Modality | image | | Plan | starter | | Price | 4 per image | ## What it does - Takes up to 1 reference image(s). - Aspect ratios: `4:1`, `21:9`, `16:9`, `3:2`, `4:3`, `5:4`, `1:1`, `4:5`, `3:4`, `2:3`, `9:16`, `1:4`. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/image \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"nano-banana-2","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # Nano Banana 2 Lite Source: https://docs.nolgia.ai/guides/model-nano-banana-2-lite.md Made by Google. | | | | --- | --- | | Model id | `nano-banana-2-lite` | | Modality | image | | Plan | starter | | Price | 2 per image | ## What it does - Takes up to 1 reference image(s). - Aspect ratios: `21:9`, `16:9`, `3:2`, `4:3`, `5:4`, `1:1`, `4:5`, `3:4`, `2:3`, `9:16`. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/image \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"nano-banana-2-lite","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # Nano Banana Pro Source: https://docs.nolgia.ai/guides/model-nano-banana-pro.md Made by Google. | | | | --- | --- | | Model id | `nano-banana-pro` | | Modality | image | | Plan | starter | | Price | 8 per image | ## What it does - Takes up to 1 reference image(s). - Aspect ratios: `21:9`, `16:9`, `3:2`, `4:3`, `5:4`, `1:1`, `4:5`, `3:4`, `2:3`, `9:16`. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/image \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"nano-banana-pro","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # Qwen Image 3 Source: https://docs.nolgia.ai/guides/model-qwen-image-3.md Made by Alibaba. | | | | --- | --- | | Model id | `qwen-image-3` | | Modality | image | | Plan | starter | | Price | 3 per image | ## What it does - Takes up to 3 reference image(s). - Aspect ratios: `16:9`, `4:3`, `1:1`, `3:4`, `9:16`. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/image \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"qwen-image-3","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # Seedream 5.0 Pro Source: https://docs.nolgia.ai/guides/model-seedream-v5-pro.md Made by ByteDance. | | | | --- | --- | | Model id | `seedream-v5-pro` | | Modality | image | | Plan | starter | | Price | 5 per image | ## What it does - Takes up to 4 reference image(s). - Aspect ratios: `16:9`, `4:3`, `1:1`, `3:4`, `9:16`. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/image \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"seedream-v5-pro","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # Text to video Source: https://docs.nolgia.ai/guides/models-text-to-video.md Models that make a clip from a prompt alone. | Model | Made by | Model id | | --- | --- | --- | | [HappyHorse 1.0 (Video Edit)](./model-happyhorse-1.0-video-edit.html) | Alibaba | `happyhorse-1.0-video-edit` | | [HappyHorse 1.1 (Reference to Video)](./model-happyhorse-1.1-r2v.html) | Alibaba | `happyhorse-1.1-r2v` | | [HeyGen Avatar IV](./model-heygen-avatar-iv.html) | HeyGen | `heygen-avatar-iv` | | [Kling Avatar](./model-kling-avatar.html) | Kuaishou | `kling-avatar` | | [Kling v3 Motion Control](./model-kling-v3-motion-control.html) | Kuaishou | `kling-v3-motion-control` | | [Kling v3 Pro (Text→Video)](./model-kling-v3-pro-t2v.html) | Kuaishou | `kling-v3-pro-t2v` | | [Kling v3 (Text→Video)](./model-kling-v3-t2v.html) | Kuaishou | `kling-v3-t2v` | | [H3 Max (Text→Video)](./model-minimax-h3-max-t2v.html) | MiniMax | `minimax-h3-max-t2v` | | [Gemini Omni 1.1 (Video Edit)](./model-omni-1.1-flash-edit.html) | Google | `omni-1.1-flash-edit` | | [Runway Aleph 2](./model-runway-aleph-2.html) | Runway | `runway-aleph-2` | | [Seedance 2.0 Pro (Reference→Video)](./model-seedance-2.0-pro-r2v.html) | ByteDance | `seedance-2.0-pro-r2v` | | [Seedance 2.0 Pro (Text→Video)](./model-seedance-2.0-pro-t2v.html) | ByteDance | `seedance-2.0-pro-t2v` | --- # HappyHorse 1.0 (Video Edit) Source: https://docs.nolgia.ai/guides/model-happyhorse-1.0-video-edit.md Made by Alibaba. | | | | --- | --- | | Model id | `happyhorse-1.0-video-edit` | | Modality | video | | Plan | pro | | Price | 36 per clip (5 s) | ## What it does - Aspect ratios: `16:9`, `9:16`, `1:1`, `4:3`, `3:4`. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/video \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"happyhorse-1.0-video-edit","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # HappyHorse 1.1 (Reference to Video) Source: https://docs.nolgia.ai/guides/model-happyhorse-1.1-r2v.md Made by Alibaba. | | | | --- | --- | | Model id | `happyhorse-1.1-r2v` | | Modality | video | | Plan | pro | | Price | 36 per clip (5 s) | ## What it does - Aspect ratios: `16:9`, `9:16`, `1:1`, `4:3`, `3:4`. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/video \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"happyhorse-1.1-r2v","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # HeyGen Avatar IV Source: https://docs.nolgia.ai/guides/model-heygen-avatar-iv.md Made by HeyGen. | | | | --- | --- | | Model id | `heygen-avatar-iv` | | Modality | video | | Plan | pro | | Price | 14 per clip (5 s) | ## What it does - Aspect ratios: `16:9`, `9:16`, `1:1`. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/video \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"heygen-avatar-iv","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # Kling Avatar Source: https://docs.nolgia.ai/guides/model-kling-avatar.md Made by Kuaishou. | | | | --- | --- | | Model id | `kling-avatar` | | Modality | video | | Plan | pro | | Price | 16 per clip (5 s) | ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/video \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"kling-avatar","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # Kling v3 Motion Control Source: https://docs.nolgia.ai/guides/model-kling-v3-motion-control.md Made by Kuaishou. | | | | --- | --- | | Model id | `kling-v3-motion-control` | | Modality | video | | Plan | pro | | Price | 35 per clip (5 s) | ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/video \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"kling-v3-motion-control","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # Kling v3 Pro (Text→Video) Source: https://docs.nolgia.ai/guides/model-kling-v3-pro-t2v.md Made by Kuaishou. | | | | --- | --- | | Model id | `kling-v3-pro-t2v` | | Modality | video | | Plan | pro | | Price | 47 per clip (5 s) | ## What it does - Aspect ratios: `16:9`, `9:16`, `1:1`. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/video \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"kling-v3-pro-t2v","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # Kling v3 (Text→Video) Source: https://docs.nolgia.ai/guides/model-kling-v3-t2v.md Made by Kuaishou. | | | | --- | --- | | Model id | `kling-v3-t2v` | | Modality | video | | Plan | pro | | Price | 35 per clip (5 s) | ## What it does - Aspect ratios: `16:9`, `9:16`, `1:1`. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/video \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"kling-v3-t2v","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # H3 Max (Text→Video) Source: https://docs.nolgia.ai/guides/model-minimax-h3-max-t2v.md Made by MiniMax. | | | | --- | --- | | Model id | `minimax-h3-max-t2v` | | Modality | video | | Plan | pro | | Price | 23 per clip (5 s) | ## What it does - Aspect ratios: `21:9`, `16:9`, `4:3`, `1:1`, `3:4`, `9:16`. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/video \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"minimax-h3-max-t2v","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # Gemini Omni 1.1 (Video Edit) Source: https://docs.nolgia.ai/guides/model-omni-1.1-flash-edit.md Made by Google. | | | | --- | --- | | Model id | `omni-1.1-flash-edit` | | Modality | video | | Plan | pro | | Price | 50 per clip (5 s) | ## What it does - Aspect ratios: `16:9`, `9:16`. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/video \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"omni-1.1-flash-edit","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # Runway Aleph 2 Source: https://docs.nolgia.ai/guides/model-runway-aleph-2.md Made by Runway. | | | | --- | --- | | Model id | `runway-aleph-2` | | Modality | video | | Plan | pro | | Price | 78 per clip (5 s) | ## What it does - Aspect ratios: `16:9`, `4:3`, `3:2`, `1:1`, `2:3`, `3:4`, `9:16`, `21:9`. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/video \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"runway-aleph-2","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # Seedance 2.0 Pro (Reference→Video) Source: https://docs.nolgia.ai/guides/model-seedance-2.0-pro-r2v.md Made by ByteDance. | | | | --- | --- | | Model id | `seedance-2.0-pro-r2v` | | Modality | video | | Plan | pro | | Price | 85 per clip (5 s) | ## What it does - Aspect ratios: `21:9`, `16:9`, `4:3`, `1:1`, `3:4`, `9:16`. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/video \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"seedance-2.0-pro-r2v","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # Seedance 2.0 Pro (Text→Video) Source: https://docs.nolgia.ai/guides/model-seedance-2.0-pro-t2v.md Made by ByteDance. | | | | --- | --- | | Model id | `seedance-2.0-pro-t2v` | | Modality | video | | Plan | pro | | Price | 43 per clip (5 s) | ## What it does - Aspect ratios: `16:9`, `9:16`, `1:1`, `4:3`, `3:4`. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/video \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"seedance-2.0-pro-t2v","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # Image to video Source: https://docs.nolgia.ai/guides/models-image-to-video.md Models that animate a still, or continue from a frame you supply. | Model | Made by | Model id | | --- | --- | --- | | [FLUX 3 Video](./model-flux-3-video.html) | Black Forest Labs | `flux-3-video` | | [Grok Imagine Video](./model-grok-imagine-video.html) | xAI | `grok-imagine-video` | | [Grok Imagine Video 1.5](./model-grok-imagine-video-1.5.html) | xAI | `grok-imagine-video-1.5` | | [HappyHorse 1.0](./model-happyhorse-1.0.html) | Alibaba | `happyhorse-1.0` | | [HappyHorse 1.1](./model-happyhorse-1.1.html) | Alibaba | `happyhorse-1.1` | | [Kling O1](./model-kling-o1.html) | Kuaishou | `kling-o1` | | [Kling v2.5 Turbo](./model-kling-v2-5-turbo.html) | Kuaishou | `kling-v2-5-turbo` | | [Kling v2.6](./model-kling-v2-6.html) | Kuaishou | `kling-v2-6` | | [Kling v3 (Image→Video)](./model-kling-v3-i2v.html) | Kuaishou | `kling-v3-i2v` | | [Kling v3 Omni](./model-kling-v3-omni.html) | Kuaishou | `kling-v3-omni` | | [Kling v3 Omni Audio](./model-kling-v3-omni-audio.html) | Kuaishou | `kling-v3-omni-audio` | | [Kling v3 Pro (Image→Video)](./model-kling-v3-pro-i2v.html) | Kuaishou | `kling-v3-pro-i2v` | | [Kling v3 Turbo](./model-kling-v3-turbo.html) | Kuaishou | `kling-v3-turbo` | | [Hailuo 3](./model-minimax-h3.html) | MiniMax | `minimax-h3` | | [H3 Max (Image→Video)](./model-minimax-h3-max-i2v.html) | MiniMax | `minimax-h3-max-i2v` | | [Hailuo 2.3](./model-minimax-hailuo-2.3.html) | MiniMax | `minimax-hailuo-2.3` | | [Hailuo 2.3 Fast](./model-minimax-hailuo-2.3-fast.html) | MiniMax | `minimax-hailuo-2.3-fast` | | [Gemini Omni 1.1 Flash](./model-omni-1.1-flash.html) | Google | `omni-1.1-flash` | | [Runway Gen-4.5](./model-runway-gen-4.5.html) | Runway | `runway-gen-4.5` | | [Seedance 1.5 Pro](./model-seedance-1-5-pro.html) | ByteDance | `seedance-1-5-pro` | | [Seedance 2.0 Fast](./model-seedance-2.0-fast.html) | ByteDance | `seedance-2.0-fast` | | [Seedance 2.0 Mini](./model-seedance-2.0-mini.html) | ByteDance | `seedance-2.0-mini` | | [Seedance 2.0 Pro (Image→Video)](./model-seedance-2.0-pro-i2v.html) | ByteDance | `seedance-2.0-pro-i2v` | | [Seedance 2.5](./model-seedance-2.5.html) | ByteDance | `seedance-2.5` | | [Veo 3.1](./model-veo-3.1.html) | Google | `veo-3.1` | | [Veo 3.1 Fast](./model-veo-3.1-fast.html) | Google | `veo-3.1-fast` | | [Veo 3.1 Lite](./model-veo-3.1-lite.html) | Google | `veo-3.1-lite` | | [Wan 2.6](./model-wan-2.6.html) | Alibaba | `wan-2.6` | | [Wan 2.7](./model-wan-2.7.html) | Alibaba | `wan-2.7` | | [Wan 3.0](./model-wan-3.0.html) | Alibaba | `wan-3.0` | | [Wan 3.0 Prime](./model-wan-3.0-prime.html) | Alibaba | `wan-3.0-prime` | --- # FLUX 3 Video Source: https://docs.nolgia.ai/guides/model-flux-3-video.md Made by Black Forest Labs. | | | | --- | --- | | Model id | `flux-3-video` | | Modality | video | | Plan | pro | | Price | 48 per clip (5 s) | ## What it does - Aspect ratios: `21:9`, `2:1`, `16:9`, `4:3`, `1:1`, `3:4`, `9:16`. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/video \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"flux-3-video","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # Grok Imagine Video Source: https://docs.nolgia.ai/guides/model-grok-imagine-video.md Made by xAI. | | | | --- | --- | | Model id | `grok-imagine-video` | | Modality | video | | Plan | pro | | Price | 20 per clip (5 s) | ## What it does - Aspect ratios: `16:9`, `9:16`, `1:1`, `4:3`, `3:4`, `3:2`, `2:3`. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/video \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"grok-imagine-video","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # Grok Imagine Video 1.5 Source: https://docs.nolgia.ai/guides/model-grok-imagine-video-1.5.md Made by xAI. | | | | --- | --- | | Model id | `grok-imagine-video-1.5` | | Modality | video | | Plan | pro | | Price | 41 per clip (5 s) | ## What it does - Aspect ratios: `16:9`, `9:16`, `1:1`, `4:3`, `3:4`, `3:2`, `2:3`. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/video \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"grok-imagine-video-1.5","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # HappyHorse 1.0 Source: https://docs.nolgia.ai/guides/model-happyhorse-1.0.md Made by Alibaba. | | | | --- | --- | | Model id | `happyhorse-1.0` | | Modality | video | | Plan | pro | | Price | 28 per clip (5 s) | ## What it does - Aspect ratios: `16:9`, `9:16`, `1:1`, `4:3`, `3:4`, `21:9`. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/video \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"happyhorse-1.0","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # HappyHorse 1.1 Source: https://docs.nolgia.ai/guides/model-happyhorse-1.1.md Made by Alibaba. | | | | --- | --- | | Model id | `happyhorse-1.1` | | Modality | video | | Plan | pro | | Price | 28 per clip (5 s) | ## What it does - Aspect ratios: `16:9`, `9:16`, `1:1`, `4:3`, `3:4`, `21:9`. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/video \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"happyhorse-1.1","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # Kling O1 Source: https://docs.nolgia.ai/guides/model-kling-o1.md Made by Kuaishou. | | | | --- | --- | | Model id | `kling-o1` | | Modality | video | | Plan | pro | | Price | 24 per clip (5 s) | ## What it does - Clip lengths: 5 s, 10 s. - Aspect ratios: `16:9`, `9:16`, `1:1`. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/video \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"kling-o1","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # Kling v2.5 Turbo Source: https://docs.nolgia.ai/guides/model-kling-v2-5-turbo.md Made by Kuaishou. | | | | --- | --- | | Model id | `kling-v2-5-turbo` | | Modality | video | | Plan | pro | | Price | 12 per clip (5 s) | ## What it does - Clip lengths: 5 s, 10 s. - Aspect ratios: `16:9`, `9:16`, `1:1`. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/video \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"kling-v2-5-turbo","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # Kling v2.6 Source: https://docs.nolgia.ai/guides/model-kling-v2-6.md Made by Kuaishou. | | | | --- | --- | | Model id | `kling-v2-6` | | Modality | video | | Plan | pro | | Price | 12 per clip (5 s) | ## What it does - Clip lengths: 5 s, 10 s. - Aspect ratios: `16:9`, `9:16`, `1:1`. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/video \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"kling-v2-6","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # Kling v3 (Image→Video) Source: https://docs.nolgia.ai/guides/model-kling-v3-i2v.md Made by Kuaishou. | | | | --- | --- | | Model id | `kling-v3-i2v` | | Modality | video | | Plan | pro | | Price | 35 per clip (5 s) | ## What it does - Aspect ratios: `16:9`, `9:16`, `1:1`. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/video \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"kling-v3-i2v","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # Kling v3 Omni Source: https://docs.nolgia.ai/guides/model-kling-v3-omni.md Made by Kuaishou. | | | | --- | --- | | Model id | `kling-v3-omni` | | Modality | video | | Plan | pro | | Price | 24 per clip (5 s) | ## What it does - Aspect ratios: `16:9`, `9:16`, `1:1`. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/video \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"kling-v3-omni","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # Kling v3 Omni Audio Source: https://docs.nolgia.ai/guides/model-kling-v3-omni-audio.md Made by Kuaishou. | | | | --- | --- | | Model id | `kling-v3-omni-audio` | | Modality | video | | Plan | pro | | Price | 32 per clip (5 s) | ## What it does - Aspect ratios: `16:9`, `9:16`, `1:1`. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/video \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"kling-v3-omni-audio","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # Kling v3 Pro (Image→Video) Source: https://docs.nolgia.ai/guides/model-kling-v3-pro-i2v.md Made by Kuaishou. | | | | --- | --- | | Model id | `kling-v3-pro-i2v` | | Modality | video | | Plan | pro | | Price | 47 per clip (5 s) | ## What it does - Aspect ratios: `16:9`, `9:16`, `1:1`. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/video \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"kling-v3-pro-i2v","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # Kling v3 Turbo Source: https://docs.nolgia.ai/guides/model-kling-v3-turbo.md Made by Kuaishou. | | | | --- | --- | | Model id | `kling-v3-turbo` | | Modality | video | | Plan | pro | | Price | 32 per clip (5 s) | ## What it does - Aspect ratios: `16:9`, `9:16`, `1:1`. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/video \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"kling-v3-turbo","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # Hailuo 3 Source: https://docs.nolgia.ai/guides/model-minimax-h3.md Made by MiniMax. | | | | --- | --- | | Model id | `minimax-h3` | | Modality | video | | Plan | pro | | Price | 65 per clip (5 s) | ## What it does - Aspect ratios: `21:9`, `16:9`, `4:3`, `1:1`, `3:4`, `9:16`. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/video \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"minimax-h3","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # H3 Max (Image→Video) Source: https://docs.nolgia.ai/guides/model-minimax-h3-max-i2v.md Made by MiniMax. | | | | --- | --- | | Model id | `minimax-h3-max-i2v` | | Modality | video | | Plan | pro | | Price | 23 per clip (5 s) | ## What it does - Aspect ratios: `21:9`, `16:9`, `4:3`, `1:1`, `3:4`, `9:16`. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/video \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"minimax-h3-max-i2v","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # Hailuo 2.3 Source: https://docs.nolgia.ai/guides/model-minimax-hailuo-2.3.md Made by MiniMax. | | | | --- | --- | | Model id | `minimax-hailuo-2.3` | | Modality | video | | Plan | pro | | Price | 28 per clip (5 s) | ## What it does - Clip lengths: 6 s, 10 s. - Aspect ratios: `16:9`. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/video \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"minimax-hailuo-2.3","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # Hailuo 2.3 Fast Source: https://docs.nolgia.ai/guides/model-minimax-hailuo-2.3-fast.md Made by MiniMax. | | | | --- | --- | | Model id | `minimax-hailuo-2.3-fast` | | Modality | video | | Plan | pro | | Price | 16 per clip (5 s) | ## What it does - Clip lengths: 6 s, 10 s. - Aspect ratios: `16:9`. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/video \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"minimax-hailuo-2.3-fast","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # Gemini Omni 1.1 Flash Source: https://docs.nolgia.ai/guides/model-omni-1.1-flash.md Made by Google. | | | | --- | --- | | Model id | `omni-1.1-flash` | | Modality | video | | Plan | pro | | Price | 50 per clip (5 s) | ## What it does - Aspect ratios: `16:9`, `9:16`. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/video \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"omni-1.1-flash","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # Runway Gen-4.5 Source: https://docs.nolgia.ai/guides/model-runway-gen-4.5.md Made by Runway. | | | | --- | --- | | Model id | `runway-gen-4.5` | | Modality | video | | Plan | pro | | Price | 34 per clip (5 s) | ## What it does - Clip lengths: 2 s, 3 s, 4 s, 5 s, 6 s, 7 s, 8 s, 9 s, 10 s. - Aspect ratios: `16:9`, `9:16`. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/video \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"runway-gen-4.5","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # Seedance 1.5 Pro Source: https://docs.nolgia.ai/guides/model-seedance-1-5-pro.md Made by ByteDance. | | | | --- | --- | | Model id | `seedance-1-5-pro` | | Modality | video | | Plan | pro | | Price | 15 per clip (5 s) | ## What it does - Aspect ratios: `16:9`, `4:3`, `1:1`, `3:4`, `9:16`, `21:9`. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/video \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"seedance-1-5-pro","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # Seedance 2.0 Fast Source: https://docs.nolgia.ai/guides/model-seedance-2.0-fast.md Made by ByteDance. | | | | --- | --- | | Model id | `seedance-2.0-fast` | | Modality | video | | Plan | pro | | Price | 34 per clip (5 s) | ## What it does - Aspect ratios: `16:9`, `4:3`, `1:1`, `3:4`, `9:16`, `21:9`. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/video \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"seedance-2.0-fast","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # Seedance 2.0 Mini Source: https://docs.nolgia.ai/guides/model-seedance-2.0-mini.md Made by ByteDance. | | | | --- | --- | | Model id | `seedance-2.0-mini` | | Modality | video | | Plan | pro | | Price | 28 per clip (5 s) | ## What it does - Aspect ratios: `16:9`, `4:3`, `1:1`, `3:4`, `9:16`, `21:9`. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/video \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"seedance-2.0-mini","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # Seedance 2.0 Pro (Image→Video) Source: https://docs.nolgia.ai/guides/model-seedance-2.0-pro-i2v.md Made by ByteDance. | | | | --- | --- | | Model id | `seedance-2.0-pro-i2v` | | Modality | video | | Plan | pro | | Price | 43 per clip (5 s) | ## What it does - Aspect ratios: `16:9`, `9:16`, `1:1`, `4:3`, `3:4`. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/video \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"seedance-2.0-pro-i2v","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # Seedance 2.5 Source: https://docs.nolgia.ai/guides/model-seedance-2.5.md Made by ByteDance. | | | | --- | --- | | Model id | `seedance-2.5` | | Modality | video | | Plan | pro | | Price | 56 per clip (5 s) | ## What it does - Aspect ratios: `16:9`, `4:3`, `1:1`, `3:4`, `9:16`, `21:9`. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/video \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"seedance-2.5","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # Veo 3.1 Source: https://docs.nolgia.ai/guides/model-veo-3.1.md Made by Google. | | | | --- | --- | | Model id | `veo-3.1` | | Modality | video | | Plan | pro | | Price | 112 per clip (5 s) | ## What it does - Clip lengths: 4 s, 6 s, 8 s. - Aspect ratios: `16:9`, `9:16`. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/video \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"veo-3.1","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # Veo 3.1 Fast Source: https://docs.nolgia.ai/guides/model-veo-3.1-fast.md Made by Google. | | | | --- | --- | | Model id | `veo-3.1-fast` | | Modality | video | | Plan | pro | | Price | 28 per clip (5 s) | ## What it does - Clip lengths: 4 s, 6 s, 8 s. - Aspect ratios: `16:9`, `9:16`. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/video \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"veo-3.1-fast","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # Veo 3.1 Lite Source: https://docs.nolgia.ai/guides/model-veo-3.1-lite.md Made by Google. | | | | --- | --- | | Model id | `veo-3.1-lite` | | Modality | video | | Plan | pro | | Price | 14 per clip (5 s) | ## What it does - Clip lengths: 4 s, 6 s, 8 s. - Aspect ratios: `16:9`, `9:16`. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/video \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"veo-3.1-lite","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # Wan 2.6 Source: https://docs.nolgia.ai/guides/model-wan-2.6.md Made by Alibaba. | | | | --- | --- | | Model id | `wan-2.6` | | Modality | video | | Plan | pro | | Price | 28 per clip (5 s) | ## What it does - Clip lengths: 5 s, 10 s. - Aspect ratios: `16:9`, `9:16`. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/video \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"wan-2.6","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # Wan 2.7 Source: https://docs.nolgia.ai/guides/model-wan-2.7.md Made by Alibaba. | | | | --- | --- | | Model id | `wan-2.7` | | Modality | video | | Plan | pro | | Price | 28 per clip (5 s) | ## What it does - Aspect ratios: `16:9`, `9:16`, `1:1`, `4:3`, `3:4`. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/video \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"wan-2.7","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # Wan 3.0 Source: https://docs.nolgia.ai/guides/model-wan-3.0.md Made by Alibaba. | | | | --- | --- | | Model id | `wan-3.0` | | Modality | video | | Plan | pro | | Price | 28 per clip (5 s) | ## What it does - Aspect ratios: `16:9`, `4:3`, `1:1`, `3:4`, `9:16`. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/video \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"wan-3.0","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # Wan 3.0 Prime Source: https://docs.nolgia.ai/guides/model-wan-3.0-prime.md Made by Alibaba. | | | | --- | --- | | Model id | `wan-3.0-prime` | | Modality | video | | Plan | pro | | Price | 39 per clip (5 s) | ## What it does - Aspect ratios: `16:9`, `4:3`, `1:1`, `3:4`, `9:16`. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/video \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"wan-3.0-prime","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # Audio and speech Source: https://docs.nolgia.ai/guides/models-audio-and-speech.md Speech, sound effects and music. | Model | Made by | Model id | | --- | --- | --- | | [Dia TTS](./model-dia-tts.html) | Nari Labs | `dia-tts` | | [ElevenLabs Music](./model-elevenlabs-music.html) | ElevenLabs | `elevenlabs-music` | | [ElevenLabs Sound Effects v2](./model-elevenlabs-sound-effects-v2.html) | ElevenLabs | `elevenlabs-sound-effects-v2` | | [ElevenLabs Multilingual v2](./model-elevenlabs-tts-multilingual-v2.html) | ElevenLabs | `elevenlabs-tts-multilingual-v2` | | [ElevenLabs Turbo v2.5](./model-elevenlabs-tts-turbo-v2.5.html) | ElevenLabs | `elevenlabs-tts-turbo-v2.5` | | [ElevenLabs v3](./model-elevenlabs-tts-v3.html) | ElevenLabs | `elevenlabs-tts-v3` | | [ElevenLabs v3 Conversational](./model-elevenlabs-tts-v3-conversational.html) | ElevenLabs | `elevenlabs-tts-v3-conversational` | | [Inworld TTS](./model-inworld-tts.html) | Inworld AI | `inworld-tts` | | [Kokoro (US English)](./model-kokoro-us-english.html) | Hexgrad | `kokoro-us-english` | | [Lyria 3.5](./model-lyria-3.5.html) | Google | `lyria-3.5` | | [MiniMax Music v2.6](./model-minimax-music-v2.6.html) | MiniMax | `minimax-music-v2.6` | | [MiniMax Speech 2.8 HD](./model-minimax-speech-2.8-hd.html) | MiniMax | `minimax-speech-2.8-hd` | | [MiniMax Speech 2.8 Turbo](./model-minimax-speech-2.8-turbo.html) | MiniMax | `minimax-speech-2.8-turbo` | | [MMAudio v2](./model-mmaudio-v2.html) | | `mmaudio-v2` | | [Orpheus TTS](./model-orpheus-tts.html) | Canopy Labs | `orpheus-tts` | | [Stable Audio 2.5](./model-stable-audio-2.5.html) | Stability AI | `stable-audio-2.5` | | [Stable Audio 3 Medium](./model-stable-audio-3-medium.html) | Stability AI | `stable-audio-3-medium` | --- # Dia TTS Source: https://docs.nolgia.ai/guides/model-dia-tts.md Made by Nari Labs. | | | | --- | --- | | Model id | `dia-tts` | | Modality | audio | | Plan | starter | | Price | 3 per 1,000 characters | ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/audio \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"dia-tts","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # ElevenLabs Music Source: https://docs.nolgia.ai/guides/model-elevenlabs-music.md Made by ElevenLabs. | | | | --- | --- | | Model id | `elevenlabs-music` | | Modality | audio | | Plan | starter | | Price | 67 per generation | ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/audio \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"elevenlabs-music","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # ElevenLabs Sound Effects v2 Source: https://docs.nolgia.ai/guides/model-elevenlabs-sound-effects-v2.md Made by ElevenLabs. | | | | --- | --- | | Model id | `elevenlabs-sound-effects-v2` | | Modality | audio | | Plan | starter | | Price | 4 per generation | ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/audio \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"elevenlabs-sound-effects-v2","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # ElevenLabs Multilingual v2 Source: https://docs.nolgia.ai/guides/model-elevenlabs-tts-multilingual-v2.md Made by ElevenLabs. | | | | --- | --- | | Model id | `elevenlabs-tts-multilingual-v2` | | Modality | audio | | Plan | starter | | Price | 6 per 1,000 characters | ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/audio \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"elevenlabs-tts-multilingual-v2","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # ElevenLabs Turbo v2.5 Source: https://docs.nolgia.ai/guides/model-elevenlabs-tts-turbo-v2.5.md Made by ElevenLabs. | | | | --- | --- | | Model id | `elevenlabs-tts-turbo-v2.5` | | Modality | audio | | Plan | starter | | Price | 3 per 1,000 characters | ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/audio \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"elevenlabs-tts-turbo-v2.5","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # ElevenLabs v3 Source: https://docs.nolgia.ai/guides/model-elevenlabs-tts-v3.md Made by ElevenLabs. | | | | --- | --- | | Model id | `elevenlabs-tts-v3` | | Modality | audio | | Plan | starter | | Price | 6 per 1,000 characters | ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/audio \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"elevenlabs-tts-v3","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # ElevenLabs v3 Conversational Source: https://docs.nolgia.ai/guides/model-elevenlabs-tts-v3-conversational.md Made by ElevenLabs. | | | | --- | --- | | Model id | `elevenlabs-tts-v3-conversational` | | Modality | audio | | Plan | starter | | Price | 3 per 1,000 characters | ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/audio \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"elevenlabs-tts-v3-conversational","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # Inworld TTS Source: https://docs.nolgia.ai/guides/model-inworld-tts.md Made by Inworld AI. | | | | --- | --- | | Model id | `inworld-tts` | | Modality | audio | | Plan | starter | | Price | 1 per 1,000 characters | ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/audio \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"inworld-tts","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # Kokoro (US English) Source: https://docs.nolgia.ai/guides/model-kokoro-us-english.md Made by Hexgrad. | | | | --- | --- | | Model id | `kokoro-us-english` | | Modality | audio | | Plan | starter | | Price | 2 per 1,000 characters | ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/audio \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"kokoro-us-english","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # Lyria 3.5 Source: https://docs.nolgia.ai/guides/model-lyria-3.5.md Made by Google. | | | | --- | --- | | Model id | `lyria-3.5` | | Modality | audio | | Plan | starter | | Price | 5 per generation | ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/audio \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"lyria-3.5","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # MiniMax Music v2.6 Source: https://docs.nolgia.ai/guides/model-minimax-music-v2.6.md Made by MiniMax. | | | | --- | --- | | Model id | `minimax-music-v2.6` | | Modality | audio | | Plan | starter | | Price | 15 per generation | ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/audio \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"minimax-music-v2.6","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # MiniMax Speech 2.8 HD Source: https://docs.nolgia.ai/guides/model-minimax-speech-2.8-hd.md Made by MiniMax. | | | | --- | --- | | Model id | `minimax-speech-2.8-hd` | | Modality | audio | | Plan | starter | | Price | 6 per 1,000 characters | ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/audio \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"minimax-speech-2.8-hd","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # MiniMax Speech 2.8 Turbo Source: https://docs.nolgia.ai/guides/model-minimax-speech-2.8-turbo.md Made by MiniMax. | | | | --- | --- | | Model id | `minimax-speech-2.8-turbo` | | Modality | audio | | Plan | starter | | Price | 4 per 1,000 characters | ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/audio \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"minimax-speech-2.8-turbo","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # MMAudio v2 Source: https://docs.nolgia.ai/guides/model-mmaudio-v2.md | | | | --- | --- | | Model id | `mmaudio-v2` | | Modality | audio | | Plan | starter | | Price | 2 per generation | ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/audio \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"mmaudio-v2","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # Orpheus TTS Source: https://docs.nolgia.ai/guides/model-orpheus-tts.md Made by Canopy Labs. | | | | --- | --- | | Model id | `orpheus-tts` | | Modality | audio | | Plan | starter | | Price | 3 per 1,000 characters | ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/audio \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"orpheus-tts","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # Stable Audio 2.5 Source: https://docs.nolgia.ai/guides/model-stable-audio-2.5.md Made by Stability AI. | | | | --- | --- | | Model id | `stable-audio-2.5` | | Modality | audio | | Plan | starter | | Price | 20 per generation | ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/audio \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"stable-audio-2.5","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # Stable Audio 3 Medium Source: https://docs.nolgia.ai/guides/model-stable-audio-3-medium.md Made by Stability AI. | | | | --- | --- | | Model id | `stable-audio-3-medium` | | Modality | audio | | Plan | starter | | Price | 3 per generation | ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/audio \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"stable-audio-3-medium","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # Restore and upscale Source: https://docs.nolgia.ai/guides/models-restore-and-upscale.md Models that repair, denoise or enlarge footage you already have. | Model | Made by | Model id | | --- | --- | --- | | [SeedVR2 Restore](./model-seedvr2-restore.html) | ByteDance | `seedvr2-restore` | | [Artemis Restore](./model-topaz-artemis.html) | | `topaz-artemis` | | [Artemis Dehalo Restore (Low)](./model-topaz-artemis-dehalo-low.html) | | `topaz-artemis-dehalo-low` | | [Artemis Dehalo Restore (Medium)](./model-topaz-artemis-dehalo-medium.html) | | `topaz-artemis-dehalo-medium` | | [Artemis Restore (Low)](./model-topaz-artemis-low.html) | | `topaz-artemis-low` | | [Artemis Restore (Medium)](./model-topaz-artemis-medium.html) | | `topaz-artemis-medium` | | [Artemis Moire Restore](./model-topaz-artemis-moire.html) | | `topaz-artemis-moire` | | [Dione Restore](./model-topaz-dione.html) | | `topaz-dione` | | [Dione Dehalo Restore](./model-topaz-dione-dehalo.html) | | `topaz-dione-dehalo` | | [Dione DV Restore](./model-topaz-dione-dv.html) | | `topaz-dione-dv` | | [Dione Robust Dehalo Restore](./model-topaz-dione-robust-dehalo.html) | | `topaz-dione-robust-dehalo` | | [Dione TV Restore](./model-topaz-dione-tv.html) | | `topaz-dione-tv` | | [Gaia Restore](./model-topaz-gaia.html) | | `topaz-gaia` | | [Gaia CG Restore](./model-topaz-gaia-cg.html) | | `topaz-gaia-cg` | | [HDR Restore](./model-topaz-hdr.html) | | `topaz-hdr` | | [Hyperion Restore](./model-topaz-hyperion.html) | | `topaz-hyperion` | | [Art & CGI Enhance](./model-topaz-image-cgi.html) | | `topaz-image-cgi` | | [High Fidelity Enhance](./model-topaz-image-high-fidelity.html) | | `topaz-image-high-fidelity` | | [Low Resolution Enhance](./model-topaz-image-low-resolution.html) | | `topaz-image-low-resolution` | | [Standard Enhance](./model-topaz-image-standard.html) | | `topaz-image-standard` | | [Text & Shapes Enhance](./model-topaz-image-text.html) | | `topaz-image-text` | | [Iris Restore](./model-topaz-iris.html) | | `topaz-iris` | | [Iris Restore (Medium)](./model-topaz-iris-medium.html) | | `topaz-iris-medium` | | [Motion Deblur Restore](./model-topaz-motion-deblur.html) | | `topaz-motion-deblur` | | [Nyx Restore](./model-topaz-nyx.html) | | `topaz-nyx` | | [Nyx Fast Restore](./model-topaz-nyx-fast.html) | | `topaz-nyx-fast` | | [Nyx XL Restore](./model-topaz-nyx-xl.html) | | `topaz-nyx-xl` | | [Proteus Restore](./model-topaz-proteus.html) | | `topaz-proteus` | | [Proteus Natural Restore](./model-topaz-proteus-natural.html) | | `topaz-proteus-natural` | | [Rhea Restore](./model-topaz-rhea.html) | | `topaz-rhea` | | [Starlight Restore](./model-topaz-starlight.html) | | `topaz-starlight` | | [Starlight Fast Restore](./model-topaz-starlight-fast.html) | | `topaz-starlight-fast` | | [Theia Restore](./model-topaz-theia.html) | | `topaz-theia` | | [Theia Fidelity Restore](./model-topaz-theia-fidelity.html) | | `topaz-theia-fidelity` | | [Wonder Restore](./model-topaz-wonder.html) | | `topaz-wonder` | --- # SeedVR2 Restore Source: https://docs.nolgia.ai/guides/model-seedvr2-restore.md Made by ByteDance. | | | | --- | --- | | Model id | `seedvr2-restore` | | Modality | video | | Plan | starter | | Price | 32 per clip (5 s) | ## What it does - Restores and upscales footage you already have. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/../restore/video \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"seedvr2-restore","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # Artemis Restore Source: https://docs.nolgia.ai/guides/model-topaz-artemis.md | | | | --- | --- | | Model id | `topaz-artemis` | | Modality | video | | Plan | starter | | Price | 18 per clip (5 s) | ## What it does - Restores and upscales footage you already have. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/../restore/video \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"topaz-artemis","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # Artemis Dehalo Restore (Low) Source: https://docs.nolgia.ai/guides/model-topaz-artemis-dehalo-low.md | | | | --- | --- | | Model id | `topaz-artemis-dehalo-low` | | Modality | video | | Plan | starter | | Price | 18 per clip (5 s) | ## What it does - Restores and upscales footage you already have. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/../restore/video \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"topaz-artemis-dehalo-low","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # Artemis Dehalo Restore (Medium) Source: https://docs.nolgia.ai/guides/model-topaz-artemis-dehalo-medium.md | | | | --- | --- | | Model id | `topaz-artemis-dehalo-medium` | | Modality | video | | Plan | starter | | Price | 18 per clip (5 s) | ## What it does - Restores and upscales footage you already have. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/../restore/video \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"topaz-artemis-dehalo-medium","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # Artemis Restore (Low) Source: https://docs.nolgia.ai/guides/model-topaz-artemis-low.md | | | | --- | --- | | Model id | `topaz-artemis-low` | | Modality | video | | Plan | starter | | Price | 18 per clip (5 s) | ## What it does - Restores and upscales footage you already have. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/../restore/video \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"topaz-artemis-low","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # Artemis Restore (Medium) Source: https://docs.nolgia.ai/guides/model-topaz-artemis-medium.md | | | | --- | --- | | Model id | `topaz-artemis-medium` | | Modality | video | | Plan | starter | | Price | 18 per clip (5 s) | ## What it does - Restores and upscales footage you already have. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/../restore/video \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"topaz-artemis-medium","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # Artemis Moire Restore Source: https://docs.nolgia.ai/guides/model-topaz-artemis-moire.md | | | | --- | --- | | Model id | `topaz-artemis-moire` | | Modality | video | | Plan | starter | | Price | 18 per clip (5 s) | ## What it does - Restores and upscales footage you already have. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/../restore/video \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"topaz-artemis-moire","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # Dione Restore Source: https://docs.nolgia.ai/guides/model-topaz-dione.md | | | | --- | --- | | Model id | `topaz-dione` | | Modality | video | | Plan | starter | | Price | 18 per clip (5 s) | ## What it does - Restores and upscales footage you already have. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/../restore/video \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"topaz-dione","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # Dione Dehalo Restore Source: https://docs.nolgia.ai/guides/model-topaz-dione-dehalo.md | | | | --- | --- | | Model id | `topaz-dione-dehalo` | | Modality | video | | Plan | starter | | Price | 18 per clip (5 s) | ## What it does - Restores and upscales footage you already have. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/../restore/video \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"topaz-dione-dehalo","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # Dione DV Restore Source: https://docs.nolgia.ai/guides/model-topaz-dione-dv.md | | | | --- | --- | | Model id | `topaz-dione-dv` | | Modality | video | | Plan | starter | | Price | 18 per clip (5 s) | ## What it does - Restores and upscales footage you already have. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/../restore/video \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"topaz-dione-dv","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # Dione Robust Dehalo Restore Source: https://docs.nolgia.ai/guides/model-topaz-dione-robust-dehalo.md | | | | --- | --- | | Model id | `topaz-dione-robust-dehalo` | | Modality | video | | Plan | starter | | Price | 18 per clip (5 s) | ## What it does - Restores and upscales footage you already have. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/../restore/video \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"topaz-dione-robust-dehalo","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # Dione TV Restore Source: https://docs.nolgia.ai/guides/model-topaz-dione-tv.md | | | | --- | --- | | Model id | `topaz-dione-tv` | | Modality | video | | Plan | starter | | Price | 18 per clip (5 s) | ## What it does - Restores and upscales footage you already have. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/../restore/video \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"topaz-dione-tv","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # Gaia Restore Source: https://docs.nolgia.ai/guides/model-topaz-gaia.md | | | | --- | --- | | Model id | `topaz-gaia` | | Modality | video | | Plan | starter | | Price | 18 per clip (5 s) | ## What it does - Restores and upscales footage you already have. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/../restore/video \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"topaz-gaia","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # Gaia CG Restore Source: https://docs.nolgia.ai/guides/model-topaz-gaia-cg.md | | | | --- | --- | | Model id | `topaz-gaia-cg` | | Modality | video | | Plan | starter | | Price | 18 per clip (5 s) | ## What it does - Restores and upscales footage you already have. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/../restore/video \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"topaz-gaia-cg","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # HDR Restore Source: https://docs.nolgia.ai/guides/model-topaz-hdr.md | | | | --- | --- | | Model id | `topaz-hdr` | | Modality | video | | Plan | starter | | Price | 18 per clip (5 s) | ## What it does - Restores and upscales footage you already have. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/../restore/video \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"topaz-hdr","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # Hyperion Restore Source: https://docs.nolgia.ai/guides/model-topaz-hyperion.md | | | | --- | --- | | Model id | `topaz-hyperion` | | Modality | video | | Plan | starter | | Price | 80 per clip (5 s) | ## What it does - Restores and upscales footage you already have. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/../restore/video \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"topaz-hyperion","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # Art & CGI Enhance Source: https://docs.nolgia.ai/guides/model-topaz-image-cgi.md | | | | --- | --- | | Model id | `topaz-image-cgi` | | Modality | image | | Plan | starter | | Price | 7 per image | ## What it does - Enhances an image you supply rather than drawing a new one. - Takes up to 1 reference image(s). ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/image \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"topaz-image-cgi","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # High Fidelity Enhance Source: https://docs.nolgia.ai/guides/model-topaz-image-high-fidelity.md | | | | --- | --- | | Model id | `topaz-image-high-fidelity` | | Modality | image | | Plan | starter | | Price | 7 per image | ## What it does - Enhances an image you supply rather than drawing a new one. - Takes up to 1 reference image(s). ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/image \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"topaz-image-high-fidelity","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # Low Resolution Enhance Source: https://docs.nolgia.ai/guides/model-topaz-image-low-resolution.md | | | | --- | --- | | Model id | `topaz-image-low-resolution` | | Modality | image | | Plan | starter | | Price | 7 per image | ## What it does - Enhances an image you supply rather than drawing a new one. - Takes up to 1 reference image(s). ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/image \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"topaz-image-low-resolution","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # Standard Enhance Source: https://docs.nolgia.ai/guides/model-topaz-image-standard.md | | | | --- | --- | | Model id | `topaz-image-standard` | | Modality | image | | Plan | starter | | Price | 7 per image | ## What it does - Enhances an image you supply rather than drawing a new one. - Takes up to 1 reference image(s). ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/image \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"topaz-image-standard","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # Text & Shapes Enhance Source: https://docs.nolgia.ai/guides/model-topaz-image-text.md | | | | --- | --- | | Model id | `topaz-image-text` | | Modality | image | | Plan | starter | | Price | 7 per image | ## What it does - Enhances an image you supply rather than drawing a new one. - Takes up to 1 reference image(s). ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/image \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"topaz-image-text","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # Iris Restore Source: https://docs.nolgia.ai/guides/model-topaz-iris.md | | | | --- | --- | | Model id | `topaz-iris` | | Modality | video | | Plan | starter | | Price | 18 per clip (5 s) | ## What it does - Restores and upscales footage you already have. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/../restore/video \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"topaz-iris","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # Iris Restore (Medium) Source: https://docs.nolgia.ai/guides/model-topaz-iris-medium.md | | | | --- | --- | | Model id | `topaz-iris-medium` | | Modality | video | | Plan | starter | | Price | 18 per clip (5 s) | ## What it does - Restores and upscales footage you already have. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/../restore/video \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"topaz-iris-medium","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # Motion Deblur Restore Source: https://docs.nolgia.ai/guides/model-topaz-motion-deblur.md | | | | --- | --- | | Model id | `topaz-motion-deblur` | | Modality | video | | Plan | starter | | Price | 18 per clip (5 s) | ## What it does - Restores and upscales footage you already have. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/../restore/video \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"topaz-motion-deblur","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # Nyx Restore Source: https://docs.nolgia.ai/guides/model-topaz-nyx.md | | | | --- | --- | | Model id | `topaz-nyx` | | Modality | video | | Plan | starter | | Price | 18 per clip (5 s) | ## What it does - Restores and upscales footage you already have. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/../restore/video \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"topaz-nyx","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # Nyx Fast Restore Source: https://docs.nolgia.ai/guides/model-topaz-nyx-fast.md | | | | --- | --- | | Model id | `topaz-nyx-fast` | | Modality | video | | Plan | starter | | Price | 10 per clip (5 s) | ## What it does - Restores and upscales footage you already have. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/../restore/video \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"topaz-nyx-fast","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # Nyx XL Restore Source: https://docs.nolgia.ai/guides/model-topaz-nyx-xl.md | | | | --- | --- | | Model id | `topaz-nyx-xl` | | Modality | video | | Plan | starter | | Price | 18 per clip (5 s) | ## What it does - Restores and upscales footage you already have. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/../restore/video \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"topaz-nyx-xl","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # Proteus Restore Source: https://docs.nolgia.ai/guides/model-topaz-proteus.md | | | | --- | --- | | Model id | `topaz-proteus` | | Modality | video | | Plan | starter | | Price | 18 per clip (5 s) | ## What it does - Restores and upscales footage you already have. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/../restore/video \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"topaz-proteus","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # Proteus Natural Restore Source: https://docs.nolgia.ai/guides/model-topaz-proteus-natural.md | | | | --- | --- | | Model id | `topaz-proteus-natural` | | Modality | video | | Plan | starter | | Price | 18 per clip (5 s) | ## What it does - Restores and upscales footage you already have. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/../restore/video \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"topaz-proteus-natural","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # Rhea Restore Source: https://docs.nolgia.ai/guides/model-topaz-rhea.md | | | | --- | --- | | Model id | `topaz-rhea` | | Modality | video | | Plan | starter | | Price | 18 per clip (5 s) | ## What it does - Restores and upscales footage you already have. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/../restore/video \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"topaz-rhea","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # Starlight Restore Source: https://docs.nolgia.ai/guides/model-topaz-starlight.md | | | | --- | --- | | Model id | `topaz-starlight` | | Modality | video | | Plan | starter | | Price | 40 per clip (5 s) | ## What it does - Restores and upscales footage you already have. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/../restore/video \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"topaz-starlight","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # Starlight Fast Restore Source: https://docs.nolgia.ai/guides/model-topaz-starlight-fast.md | | | | --- | --- | | Model id | `topaz-starlight-fast` | | Modality | video | | Plan | starter | | Price | 20 per clip (5 s) | ## What it does - Restores and upscales footage you already have. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/../restore/video \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"topaz-starlight-fast","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # Theia Restore Source: https://docs.nolgia.ai/guides/model-topaz-theia.md | | | | --- | --- | | Model id | `topaz-theia` | | Modality | video | | Plan | starter | | Price | 18 per clip (5 s) | ## What it does - Restores and upscales footage you already have. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/../restore/video \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"topaz-theia","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # Theia Fidelity Restore Source: https://docs.nolgia.ai/guides/model-topaz-theia-fidelity.md | | | | --- | --- | | Model id | `topaz-theia-fidelity` | | Modality | video | | Plan | starter | | Price | 18 per clip (5 s) | ## What it does - Restores and upscales footage you already have. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/../restore/video \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"topaz-theia-fidelity","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # Wonder Restore Source: https://docs.nolgia.ai/guides/model-topaz-wonder.md | | | | --- | --- | | Model id | `topaz-wonder` | | Modality | video | | Plan | starter | | Price | 40 per clip (5 s) | ## What it does - Restores and upscales footage you already have. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/../restore/video \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"topaz-wonder","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # Utilities Source: https://docs.nolgia.ai/guides/models-utilities.md Zero-prompt jobs: an input in, a predictable output back. | Model | Made by | Model id | | --- | --- | --- | | [Remove Background](./model-remove-background.html) | Bria | `remove-background` | | [Remove Background (Video)](./model-remove-background-video.html) | Bria | `remove-background-video` | --- # Remove Background Source: https://docs.nolgia.ai/guides/model-remove-background.md Made by Bria. | | | | --- | --- | | Model id | `remove-background` | | Modality | image | | Plan | starter | | Price | 1 per image | ## What it does - Removes the background; zero-prompt, input in and a cut-out back. - Takes up to 1 reference image(s). ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/image \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"remove-background","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # Remove Background (Video) Source: https://docs.nolgia.ai/guides/model-remove-background-video.md Made by Bria. | | | | --- | --- | | Model id | `remove-background-video` | | Modality | video | | Plan | starter | | Price | 14 per clip (5 s) | ## What it does - Removes the background; zero-prompt, input in and a cut-out back. ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/../remove-background/video \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"remove-background-video","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # 3D Source: https://docs.nolgia.ai/guides/models-3d.md Models that produce a 3D asset. | Model | Made by | Model id | | --- | --- | --- | | [Hunyuan3D v3](./model-hunyuan3d-v3.html) | Tencent | `hunyuan3d-v3` | | [TRELLIS](./model-trellis.html) | Microsoft | `trellis` | --- # Hunyuan3D v3 Source: https://docs.nolgia.ai/guides/model-hunyuan3d-v3.md Made by Tencent. | | | | --- | --- | | Model id | `hunyuan3d-v3` | | Modality | 3d | | Plan | starter | | Price | 21 per generation | ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/3d \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"hunyuan3d-v3","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge. --- # TRELLIS Source: https://docs.nolgia.ai/guides/model-trellis.md Made by Microsoft. | | | | --- | --- | | Model id | `trellis` | | Modality | 3d | | Plan | starter | | Price | 2 per generation | ## Submit a job ```bash tab="curl" curl -X POST https://api.nolgia.ai/v1/generate/3d \ -H "Authorization: Bearer $NOLGIA_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"model":"trellis","prompt":"a red fox in the snow"}' ``` The call answers `202` with a job. Poll it with [`GET /jobs/{id}/wait`](./jobs.html), which returns the finished asset's signed URL. Prices are quoted in credits and charged when the job succeeds; see [Billing](./billing.html). This page is generated from the live catalog, so it cannot quote a price the API would not charge.