Uploads
The Uploads API lets you push large files (multi-hundred-MB videos, multi-GB clips) by splitting them into parts of 5 MiB to 64 MiB. Each part is uploaded against an upload session; calling complete assembles them into a regular File object you can reference in inference requests via file_id.
Wire-compatible with the OpenAI Uploads API: the official Python openai SDK’s client.uploads.upload_file_chunked(...) helper drives the entire sequence in one call. One difference: parts are assembled in the order they arrive, so upload them one after another and complete in that same order (see Part sizes and order).
POST /v1/uploadsPOST /v1/uploads/{upload_id}/partsPOST /v1/uploads/{upload_id}/completePOST /v1/uploads/{upload_id}/cancelLimits
Section titled “Limits”| Constraint | Value |
|---|---|
| Total file size | 8 GiB |
| Per-part size | 5 MiB to 64 MiB. Only the last part may be smaller than 5 MiB. |
| Session lifetime | 1 hour from creation |
| Allowed purposes | video (recommended). vision works but rarely needed at this size. |
Sessions that aren’t completed within 1 hour are automatically cancelled. The completed file inherits the standard 30-day expiry from the Files API.
Part sizes and order
Section titled “Part sizes and order”Cut the file into parts of at least 5 MiB (the SDK helper uses 64 MiB); only the last part may be smaller.
Parts are numbered in the order they reach the API and assembled in that order. Upload them one after another, and pass part_ids to complete in the same order. A part under 5 MiB that arrives before any other part is refused with a 400, unless it is the whole file. If parts arrive out of file order, complete is refused with a 400 rather than assembling a file in the wrong order. The sizes of the listed parts must add up exactly to the bytes you declared when creating the session.
Quick start
Section titled “Quick start”The easiest path is the Python SDK’s high-level helper, which handles chunking for you. The Node SDK has no equivalent helper; the JavaScript tab drives the raw endpoints, which is the same sequence in a few more lines.
from pathlib import Pathfrom openai import OpenAI
client = OpenAI( api_key="sk-your-api-key", base_url="https://api.aiand.com/v1",)
upload = client.uploads.upload_file_chunked( file=Path("clip.mp4"), purpose="video", mime_type="video/mp4",)
print(upload.file.id) # → file-vid789...import OpenAI, { toFile } from "openai";import fs from "node:fs/promises";
const client = new OpenAI({ apiKey: "sk-your-api-key", baseURL: "https://api.aiand.com/v1",});
const PART = 64 * 1024 * 1024;const data = await fs.readFile("clip.mp4");
const session = await client.uploads.create({ filename: "clip.mp4", purpose: "video", bytes: data.byteLength, mime_type: "video/mp4",});
const partIds = [];for (let offset = 0; offset < data.byteLength; offset += PART) { const chunk = data.subarray(offset, Math.min(offset + PART, data.byteLength)); const part = await client.uploads.parts.create(session.id, { data: await toFile(chunk, "part"), }); partIds.push(part.id);}
const upload = await client.uploads.complete(session.id, { part_ids: partIds });console.log(upload.file.id); // → file-vid789...Both paths split the file into uniform parts, open a session, upload the parts one after another, and call complete with the part ids in upload order. The returned upload.file.id is a regular file-... id you can use in chat completions exactly like any other file.
Manual flow
Section titled “Manual flow”If you’re not using the SDK, here’s the same flow against the raw HTTP endpoints.
1. Create a session
Section titled “1. Create a session”POST /v1/uploads| Field | Type | Required | Description |
|---|---|---|---|
filename | string | Yes | Original filename |
purpose | string | Yes | One of vision, video, audio, or document |
bytes | integer | Yes | Total declared size of the file |
mime_type | string | Yes | Mime type; checked against the purpose’s allowlist |
curl https://api.aiand.com/v1/uploads \ -H "Authorization: Bearer sk-your-api-key" \ -H "Content-Type: application/json" \ -d '{ "filename": "clip.mp4", "purpose": "video", "bytes": 524288000, "mime_type": "video/mp4" }'Response
Section titled “Response”{ "id": "upload-abc123", "object": "upload", "bytes": 524288000, "purpose": "video", "filename": "clip.mp4", "status": "pending", "created_at": 1719450000, "expires_at": 1719453600, "file": null}2. Upload parts
Section titled “2. Upload parts”POST /v1/uploads/{upload_id}/partsMultipart form upload with a single data field. Every part except the last must be at least 5 MiB; no part may exceed 64 MiB. Upload parts one after another: they are numbered as they arrive, and complete must list them in that order. A part larger than the declared bytes, or a first part under 5 MiB that is not the whole file, is refused with a 400.
curl https://api.aiand.com/v1/uploads/upload-abc123/parts \ -H "Authorization: Bearer sk-your-api-key" \ -F "data=@clip.part1.bin"Response
Section titled “Response”{ "id": "part-xyz789", "object": "upload.part", "upload_id": "upload-abc123", "created_at": 1719450010}Repeat until every chunk of the source file has been submitted, capturing each part.id in order.
3. Complete the upload
Section titled “3. Complete the upload”POST /v1/uploads/{upload_id}/complete| Field | Type | Required | Description |
|---|---|---|---|
part_ids | string[] | Yes | The part.ids from step 2, in upload order. Their sizes must add up to the declared bytes. |
md5 | string | No | Optional MD5 of the assembled file |
curl https://api.aiand.com/v1/uploads/upload-abc123/complete \ -H "Authorization: Bearer sk-your-api-key" \ -H "Content-Type: application/json" \ -d '{ "part_ids": ["part-xyz789", "part-uvw456", "part-rst123"] }'Response
Section titled “Response”{ "id": "upload-abc123", "object": "upload", "bytes": 524288000, "purpose": "video", "filename": "clip.mp4", "status": "completed", "created_at": 1719450000, "expires_at": 1719453600, "file": { "id": "file-vid789", "object": "file", "bytes": 524288000, "purpose": "video", "filename": "clip.mp4", "created_at": 1719450090, "expires_at": 1722042090 }}The file.id is now a regular file you can reference in chat completions. For documents, file also carries page_count, exactly as GET /v1/files/{file_id} does.
Completing is idempotent: if the response to a successful complete was lost, calling it again on the same session returns the same file.
4. Cancel (optional)
Section titled “4. Cancel (optional)”POST /v1/uploads/{upload_id}/cancelAborts a pending session. Idempotent — already-completed or already-cancelled sessions return 404.
curl -X POST https://api.aiand.com/v1/uploads/upload-abc123/cancel \ -H "Authorization: Bearer sk-your-api-key"Reference the file in chat completions
Section titled “Reference the file in chat completions”Once complete returns a file.id, use it like any other file:
{ "model": "<video-capable-model>", "messages": [ { "role": "user", "content": [ { "type": "text", "text": "Describe what happens in this clip." }, { "type": "file", "file": { "file_id": "file-vid789" } } ] } ]}See Files → Reference a file in chat completions for full examples.
Errors
Section titled “Errors”| Status | error.code | When |
|---|---|---|
400 | invalid_request_error | Missing field, unknown purpose, mime not in allowlist, declared bytes exceeds the per-purpose cap, part body > 64 MiB, part_ids empty or contains an id from a different upload |
400 | invalid_request_error | A part that does not fit the session: the first part under 5 MiB when it is not the whole file, or a part larger than the declared bytes |
400 | invalid_request_error | part_ids out of upload order, listing a part twice, listing a part under 5 MiB anywhere but last, or with sizes that do not add up to the declared bytes |
400 | invalid_request_error | Adding a part to, or completing, an upload that’s no longer pending or whose session has expired |
401 | invalid_api_key | Missing or invalid Authorization header |
403 | zero_data_retention | Your organization is on zero data retention, which refuses uploads |
404 | not_found | Unknown upload_id, or id belongs to a different organization |
500 | server_error | Internal failure. If the message says to start a new upload session, the session is now failed and cannot be retried. |
- Upload sessions and resulting files are scoped to the organization that created them.
- Each part is durably stored as soon as it’s accepted — if your client crashes mid-upload, you can resume by listing your existing
part_ids(kept until session expiry) and continuing. - A part re-sent after a lost response (the OpenAI SDKs do this automatically on connection errors and 5xx) becomes a second, unused part. Pass only the ids you want assembled; the duplicate is discarded with the session.
- The model must have a matching capability for the file’s purpose.
purpose: "video"requires avideo-capable model — seeGET /v1/models.