Model APIs
Uploads and files
On this page
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.

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 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.
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":"<the file, base64-encoded>"}'
nolgia --json assets upload reference.png
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);
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)
use base64::{engine::general_purpose::STANDARD, Engine};
use nolgia_client::ClientBuilder;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = ClientBuilder::new("https://api.nolgia.ai/v1")
.bearer_token(std::env::var("NOLGIA_TOKEN")?)
.build()?;
let 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.
{
"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 #
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.
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":<the exact file size in bytes>}'
{
"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 <upload_url> 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 |
curl -sS -X PUT "<upload_url from the response>" -H "Content-Type: video/mp4" --upload-file final-cut.mp4
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.
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 |
curl -sS -X POST "https://api.nolgia.ai/v1/assets/uploads/<upload_id from the response>/complete" -H "Authorization: Bearer $NOLGIA_TOKEN"
{
"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.
These two image request bodies illustrate the same choice. Use a reference-capable model and replace the URL or id with your own.
{
"model": "gpt-image-2",
"prompt": "Place this product on a plain white background",
"image_url": "https://media.example.com/product.png"
}
{
"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.
{
"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 |
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/mp4video/quicktimevideo/webmaudio/mpegaudio/wavaudio/oggaudio/webmaudio/mp4image/pngimage/jpegimage/webpmodel/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 | /assets/imports/google-drive |
Import a Google Drive file into the library. |
| POST | /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 for signed URL renewal and deletion, and File access controls for Library scope and share links that remain useful after a download URL expires.

