Skip to content
Guides · Media Uploads

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 situationPathWhy
Video reference material already on your CDN / OSSPass urlNo upload at all — bytes never touch our gateway or storage
Video reference material is local (image / video / audio)Presigned uploadThe only path for local files
Image editing (/images/edits, /images/variations) source and maskPresigned uploadThe 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:

Presign
$POST https://api.alltoken.ai/v1/uploads/presign

Body: 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:

PUT to object storage
1PUT <upload_url>
2Content-Type: video/mp4
3Content-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_id and the URL field are mutually exclusive within one item — filling both returns 400 upload_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 purpose must match the actual use, or you also get 404 upload_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.

purposeUsed forAllowed MIMEPer-file cap
video_input_imageVideo reference image / first & last frameimage/png, image/jpeg, image/webp, image/gif20 MiB
video_input_videoVideo reference videovideo/mp4, video/quicktime, video/webm100 MiB
video_input_audioVideo reference audioaudio/wav, audio/mpeg, audio/mp4, audio/aac, audio/ogg20 MiB
image_edit_sourceImage editing sourceimage/png, image/jpeg, image/webp25 MiB
image_edit_maskImage editing maskimage/png, image/webp (no jpeg)25 MiB
image_variation_sourceImage variation sourceimage/png, image/jpeg, image/webp25 MiB

Masks reject image/jpeg because the alpha channel expresses "where to change", and jpeg has no transparency.

Time windows & quotas

WindowLengthAfter it expires
upload_url (the PUT target)5 minutesPUT returns 403 — presign again
upload_id (the asset claim)2 hoursTask 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.

QuotaThresholdOn exceed
Unused upload_id at once100429 quota_exceeded
Total bytes uploaded per UTC day2 GiB429 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:

HTTPcodeMeaning
400invalid_purposepurpose misspelled or unsupported
400invalid_content_typeMIME not in the purpose whitelist (a mask sent as jpeg lands here)
400invalid_content_md5MD5 is not valid base64 (don't send hex)
400invalid_checksumSHA-256 is not 64-char lowercase hex (don't send base64)
413payload_too_largecontent_length over the purpose cap
429quota_exceededOver 100 pending, or over 2 GiB today
503storage_unavailableStorage temporarily down — retry

Binding stage (when you create the task):

HTTPcodeMeaning
404upload_not_foundUnknown upload_id, wrong API key, or purpose mismatch
409upload_already_usedAlready bound to a task — presign again
409upload_expiredOlder than 2 hours — presign again
400upload_object_missingPresigned but never PUT before creating the task
400upload_size_mismatchActual bytes differ from content_length
409upload_modifiedObject changed after upload (ETag mismatch)
400upload_url_conflictBoth upload_id and a URL field set in one item