Skip to content

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/uploads
POST /v1/uploads/{upload_id}/parts
POST /v1/uploads/{upload_id}/complete
POST /v1/uploads/{upload_id}/cancel
ConstraintValue
Total file size8 GB
Per-part size5 MiB to 64 MB. Every part except the last must be the same size as the first; the last part may be smaller.
Session lifetime1 hour from creation
Allowed purposesvideo (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.

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.

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 Path
from 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...

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.

If you’re not using the SDK, here’s the same flow against the raw HTTP endpoints.

POST /v1/uploads
FieldTypeRequiredDescription
filenamestringYesOriginal filename
purposestringYesOne of vision, video, audio, or document
bytesintegerYesTotal declared size of the file
mime_typestringYesMime type; checked against the purpose’s allowlist
Terminal window
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"
}'
{
"id": "upload-abc123",
"object": "upload",
"bytes": 524288000,
"purpose": "video",
"filename": "clip.mp4",
"status": "pending",
"created_at": 1719450000,
"expires_at": 1719453600,
"file": null
}
POST /v1/uploads/{upload_id}/parts

Multipart 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.

Terminal window
curl https://api.aiand.com/v1/uploads/upload-abc123/parts \
-H "Authorization: Bearer sk-your-api-key" \
-F "data=@clip.part1.bin"
{
"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.

POST /v1/uploads/{upload_id}/complete
FieldTypeRequiredDescription
part_idsstring[]YesThe part.ids from step 2, in upload order. Their sizes must add up to the declared bytes.
md5stringNoOptional MD5 of the assembled file
Terminal window
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"] }'
{
"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.

POST /v1/uploads/{upload_id}/cancel

Aborts a pending session. Idempotent — already-completed or already-cancelled sessions return 404.

Terminal window
curl -X POST https://api.aiand.com/v1/uploads/upload-abc123/cancel \
-H "Authorization: Bearer sk-your-api-key"

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.

Statuserror.codeWhen
400invalid_request_errorMissing 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
400invalid_request_errorA 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
400invalid_request_errorpart_ids out of upload order, listing a part twice, or with sizes that do not add up to the declared bytes
400invalid_request_errorAdding a part to, or completing, an upload that’s no longer pending or whose session has expired
401invalid_api_keyMissing or invalid Authorization header
404not_foundUnknown upload_id, or id belongs to a different organization
500server_errorInternal 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 a video-capable model — see GET /v1/models.