Media Uploads
Send local images, video, and audio to AllToken with presigned direct-to-R2 uploads.
Overview
Video generation and image editing both take media assets — reference images, reference video and audio, source images, and masks. There is exactly one rule for how to send them:
- The asset is already on the public internet (your CDN / object storage) — pass its
http(s)URL directly, with zero upload overhead. Video reference materials only. - The asset is local — upload it first with a presigned direct-to-R2 upload, then reference the returned
upload_id. This is the only path for local files, and the only path for image editing (source and mask).
Warning
The data: base64 inline path was removed on 2026-08-06 for video reference materials, matching the image side (whose legacy multipart upload was removed earlier). Local media now always goes through presigned upload.
The endpoint is POST https://api.alltoken.ai/v1/uploads/presign (plural uploads). Don't confuse it with the account service's /upload/presign, which is for in-site avatars and only accepts image MIME types.
Choose your path
| Your situation | Path | Why |
|---|---|---|
| Video reference material already on your CDN / OSS | Pass url | No upload at all — bytes never touch our gateway or storage |
| Video reference material is local (image / video / audio) | Presigned upload | The only path for local files |
Image editing (/images/edits, /images/variations) source and mask | Presigned upload | The only entry point, regardless of file size |
The three-step upload
Your file never passes through our gateway. You exchange a one-time direct-upload URL, PUT the raw bytes straight to object storage, then reference the returned upload_id when you create the task. The gateway only carries a few-dozen-byte ID. The same mechanism serves video reference materials and image editing — only the purpose and the field you reference it by differ.
Step 1 — request a direct-upload URL:
$POST https://api.alltoken.ai/v1/uploads/presignBody: purpose (decides the allowed MIME and size — see below), content_type (the file MIME, lowercase), content_length (exact byte count), content_md5, and checksum_sha256.
Warning
The two digests use different encodings: content_md5 is base64, checksum_sha256 is hex. Swapping them is rejected at the presign step with a 400.
The response returns upload_id (starts with upl_), upload_url (the presigned PUT target), required_headers (replay verbatim in step 2), expires_in (URL validity, 300 s), claim_expires_at (upload_id validity, 2 h), and max_content_length.
Step 2 — PUT the raw bytes to upload_url, replaying every header in required_headers exactly. The body is the raw file bytes — not base64, not multipart:
| 1 | PUT <upload_url> |
| 2 | Content-Type: video/mp4 |
| 3 | Content-MD5: N0xUuLBP5C0uKJ2i0fA1kA== |
| 4 | |
| 5 | <raw bytes> |
Warning
Drop or rewrite any required header and object storage returns 403 — the presigned URL signs those headers too. Don't add Content-Encoding, don't gzip, don't use multipart.
Step 3 — create the task referencing upload_id. Video puts it in content[].upload_id; image editing uses the image_upload_id / mask_upload_id top-level fields. The gateway validates the byte count and ETag, moves the object into the task's permanent area, and submits an upstream-reachable address — all transparent to you; you just get 202 + task_id.
Three rules to remember:
upload_idand the URL field are mutually exclusive within one item — filling both returns 400upload_url_conflict.- Presign and task creation must use the same API key — ownership is checked as (user + API key + purpose); switching keys returns 404
upload_not_found. - The
purposemust match the actual use, or you also get 404upload_not_found.
Full request and response field tables live in the Image API and Video API reference.
Purpose reference
Each purpose fixes the allowed MIME types and a per-file size cap.
purpose | Used for | Allowed MIME | Per-file cap |
|---|---|---|---|
video_input_image | Video reference image / first & last frame | image/png, image/jpeg, image/webp, image/gif | 20 MiB |
video_input_video | Video reference video | video/mp4, video/quicktime, video/webm | 100 MiB |
video_input_audio | Video reference audio | audio/wav, audio/mpeg, audio/mp4, audio/aac, audio/ogg | 20 MiB |
image_edit_source | Image editing source | image/png, image/jpeg, image/webp | 25 MiB |
image_edit_mask | Image editing mask | image/png, image/webp (no jpeg) | 25 MiB |
image_variation_source | Image variation source | image/png, image/jpeg, image/webp | 25 MiB |
Masks reject image/jpeg because the alpha channel expresses "where to change", and jpeg has no transparency.
Time windows & quotas
| Window | Length | After it expires |
|---|---|---|
upload_url (the PUT target) | 5 minutes | PUT returns 403 — presign again |
upload_id (the asset claim) | 2 hours | Task creation returns 409 upload_expired |
So: upload as soon as you have the URL, but you then have two hours to assemble and submit the generation request.
An upload_id binds to exactly one task; reuse returns 409 upload_already_used. This is by design — one asset, one task, one bill. If task creation fails, the temporary object is reclaimed automatically.
| Quota | Threshold | On exceed |
|---|---|---|
Unused upload_id at once | 100 | 429 quota_exceeded |
| Total bytes uploaded per UTC day | 2 GiB | 429 quota_exceeded |
Normal "upload then use" pacing never hits these. For large image-editing batches, mind the daily cap: 25 MiB × ~80 files reaches 2 GiB.
Upload error codes
Presign stage:
| HTTP | code | Meaning |
|---|---|---|
| 400 | invalid_purpose | purpose misspelled or unsupported |
| 400 | invalid_content_type | MIME not in the purpose whitelist (a mask sent as jpeg lands here) |
| 400 | invalid_content_md5 | MD5 is not valid base64 (don't send hex) |
| 400 | invalid_checksum | SHA-256 is not 64-char lowercase hex (don't send base64) |
| 413 | payload_too_large | content_length over the purpose cap |
| 429 | quota_exceeded | Over 100 pending, or over 2 GiB today |
| 503 | storage_unavailable | Storage temporarily down — retry |
Binding stage (when you create the task):
| HTTP | code | Meaning |
|---|---|---|
| 404 | upload_not_found | Unknown upload_id, wrong API key, or purpose mismatch |
| 409 | upload_already_used | Already bound to a task — presign again |
| 409 | upload_expired | Older than 2 hours — presign again |
| 400 | upload_object_missing | Presigned but never PUT before creating the task |
| 400 | upload_size_mismatch | Actual bytes differ from content_length |
| 409 | upload_modified | Object changed after upload (ETag mismatch) |
| 400 | upload_url_conflict | Both upload_id and a URL field set in one item |