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 MB. 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 GB |
| Per-part size | 5 MiB to 64 MB. Every part except the last must be the same size as the first; the last part may be smaller. |
| 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”Pick one part size of at least 5 MiB (the SDK helper uses 64 MiB) and cut the file into parts of that size; only the last part may be smaller. The first part that is assigned a number locks that size for the session, even if those bytes never land. A later part that does not match is refused with a 400 naming the expected size. There is no way to change the size; cancel and start a new session.
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. If you send parts in parallel and a smaller last part is numbered first, later parts get that permanent 400 and the session cannot finish. 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 the same size as the first and at least 5 MiB; no part may exceed 64 MB. Upload parts one after another: they are numbered as they arrive, and complete must list them in that order. A part that does not fit the session’s layout is refused with a 400 that names the expected size.
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 MB, part_ids empty or contains an id from a different upload |
400 | invalid_request_error | A part that does not fit the session: below 5 MiB when it is not the last, a different size from the first part, or more than the declared bytes |
400 | invalid_request_error | part_ids out of upload order, listing a part twice, 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 |
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.