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 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/uploads
POST /v1/uploads/{upload_id}/parts
POST /v1/uploads/{upload_id}/complete
POST /v1/uploads/{upload_id}/cancel
ConstraintValue
Total file size8 GiB
Per-part size5 MiB to 64 MiB. Only the last part may be smaller than 5 MiB.
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.

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.

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

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 MiB, part_ids empty or contains an id from a different upload
400invalid_request_errorA 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
400invalid_request_errorpart_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
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
403zero_data_retentionYour organization is on zero data retention, which refuses uploads
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.