File uploads
Host your own images, videos and audio on AITOPIA to use them as model or edit input.
Models and edits take files by URL (for example an image_url model field, or asset_url for POST /v1/edit). If your file is not already at a public URL, upload it first. Uploads are not charged.
| Method | Size | Use when |
|---|---|---|
POST /v1/uploads with content_base64 | about 19 MB of file | Small files you have in memory |
POST /v1/uploads with source_url | up to 200 MB | The file is already at a public URL |
POST /v1/uploads/link, then PUT | up to 95 MB | Larger local files |
#Base64 upload
Send the file as base64 together with its type:
curl -X POST "https://api-live.aitopia.ai/v1/uploads" \
-H "Authorization: Key $AITOPIA_KEY" \
-H "Content-Type: application/json" \
-d "{\"content_base64\": \"$(base64 < photo.png | tr -d '\n')\", \"content_type\": \"image/png\", \"file_name\": \"photo.png\"}"{ "url": "https://cdn.aitopia.ai/...", "content_type": "image/png", "size": 482113 }| Field | Required | Description |
|---|---|---|
content_base64 | one of content_base64 / source_url | The file, base64-encoded. |
content_type | with content_base64 | MIME type, for example image/png. |
file_name | no | File name to keep. |
The whole request body is limited to 25 MB, which leaves room for about 19 MB of file once base64-encoded. A larger body answers 413 FILE_TOO_LARGE; use an upload link or source_url instead.
#Import from a URL
curl -X POST "https://api-live.aitopia.ai/v1/uploads" \
-H "Authorization: Key $AITOPIA_KEY" \
-H "Content-Type: application/json" \
-d '{"source_url": "https://example.com/clip.mp4", "file_name": "clip.mp4"}'AITOPIA downloads the file on its side (up to 200 MB) and answers with the same { url, content_type, size }. The URL must be a direct link to the file on a public host; private or internal addresses answer 400 URL_NOT_ALLOWED. Pass a file_name with an extension (as above) or a content_type; with neither, the upload is refused with 400 INVALID_INPUT.
Send exactly one of content_base64 or source_url; both or neither is 400 INVALID_INPUT.
#Upload link (up to 95 MB)
For larger local files, ask for a one-time upload URL and PUT the raw bytes to it.
1. Create the link
curl -X POST "https://api-live.aitopia.ai/v1/uploads/link" \
-H "Authorization: Key $AITOPIA_KEY" \
-H "Content-Type: application/json" \
-d '{"file_name": "clip.mp4", "content_type": "video/mp4"}'{
"upload_url": "https://.../uploads/...",
"file_url": null,
"expires_at": "2026-10-23T10:10:00.000Z",
"max_bytes": 99614720
}Both body fields are optional; the body may be empty ({}). file_url is always null here: the file's URL is known only after the upload.
2. PUT the file
curl -T clip.mp4 "$UPLOAD_URL"The upload_url is on mcp.aitopia.ai, not on the API host: if your servers only reach allow-listed hosts, allow mcp.aitopia.ai (and cdn.aitopia.ai, where the hosted file lives). No Authorization header is needed: the link itself is the credential. It is valid once, until expires_at (10 minutes), and only for the account that created it. If you did not pass content_type when creating the link, the type is taken from the file and the Content-Type header of the PUT.
The PUT answers with the hosted file. This answer comes from the upload host, not the API, so it differs from the rest of the API: field names are camelCase, and an error is { "error": "<message>" } (a plain string, no code; branch on the HTTP status in the table below):
{
"status": "completed",
"assetUrl": "https://cdn.aitopia.ai/...",
"assetName": "clip.mp4",
"sizeBytes": 73400320,
"contentType": "video/mp4",
"billing": "not_charged",
"billed": false
}Use assetUrl as input to models and edits.
| PUT status | Meaning |
|---|---|
| 200 | Uploaded; see assetUrl. |
| 400 | The upload was empty. |
| 410 | The link is invalid, already used, or expired. Create a new one. |
| 413 | The file is larger than max_bytes. |
| 415 | The file type is not allowed. Web pages, scripts, SVG, XML and other active content are refused; photos, video, audio, PDF and common documents are fine. |
| 429 | Uploads are busy; retry in a few seconds with the same link. |
| 502 | The upload failed on our side; retry with the same link. |
Errors from the PUT have the form { "error": "<message>" }.
#Upload errors (POST /v1/uploads)
| Status | Code | Meaning |
|---|---|---|
| 400 | INVALID_INPUT | Missing or conflicting fields, missing content_type, invalid source_url, or a body that is not valid JSON. |
| 400 | URL_NOT_ALLOWED | source_url points to a private or internal address. |
| 413 | FILE_TOO_LARGE | Over the size limit. |
| 429 | RATE_LIMIT | Uploads are busy; retry after retry_after_seconds. |
| 502 | UPSTREAM_5XX | The source_url could not be downloaded (retryable). |