Asset Uploads
Most generations reference media by public URL. When your files aren’t already hosted somewhere public — or you want a reusable, plan-aware reference you can pass to multiple jobs — upload them to sync. labs as assets and reference them by id.
Uploading is a three-step flow:
- Request a presigned URL with
POST /v2/assets/upload(tells sync. labs the file’s name, type, and size). - PUT the raw file bytes directly to that presigned URL (a plain HTTP
PUT, no auth header). - Register the asset with
POST /v2/assets, which verifies the upload, enforces plan limits on the actual file size, and returns anid.
You then use the returned id as an input[].assetId in a generation or as the assetId voice-clone sample in voice cloning.
If your file is already hosted at a publicly accessible URL, you can skip the presign + PUT steps entirely and register it directly with POST /v2/assets — see Register an already-public URL below.
The upload flow
Request a presigned URL
Call POST /v2/assets/upload with the file’s fileName, contentType (the MIME type, e.g. video/mp4), and size in bytes. You get back an uploadUrl to PUT the bytes to, the canonical url you’ll register in step 3, and expiresIn (seconds until the presigned URL expires).
A 201 response looks like:
PUT the file to the presigned URL
Upload the raw file bytes with a plain HTTP PUT to uploadUrl. Do not send your x-api-key here — the URL is already signed. You must send the same Content-Type you declared in step 1, or the upload is rejected.
The Content-Type header on the PUT must exactly match the contentType you sent to /v2/assets/upload. A mismatch (or a missing header) causes the upload to fail signature validation. The presigned URL also expires after expiresIn seconds — request a fresh one if it lapses.
Request parameters
POST /v2/assets/upload
The name of the file you’re uploading, including its extension (e.g. speaker.mp4).
The file’s MIME type (e.g. video/mp4, audio/wav, image/png). You must send this exact value as the Content-Type header when you PUT the bytes.
The file size in bytes. Single uploads are capped at 5 GB.
Returns 201 with uploadUrl (where to PUT the bytes), url (the canonical URL to register), and expiresIn (seconds until the presigned URL expires).
POST /v2/assets
The url returned by POST /v2/assets/upload, or any publicly accessible URL if you’re registering pre-hosted media directly.
The asset type — one of AUDIO, VIDEO, or IMAGE.
An optional human-readable label for the asset.
An optional project to associate the asset with.
Returns 201 with the registered Asset, including its id.
Using an assetId in a generation
Once you have an asset id, reference it from a generation’s input array with assetId instead of url. Mix asset-backed and URL-backed inputs freely in the same request.
You can also pass an asset id as the voice-clone sample (assetId) when creating a cloned voice with POST /v2/voices — see Voice Cloning.
Register an already-public URL
If your media is already hosted at a publicly accessible URL, skip the presign and PUT steps and register it directly. sync. labs fetches the file to verify it and enforce plan limits.
Limits and rate limits
Frequently asked questions
Why upload an asset instead of just passing a URL?
If your media isn’t already hosted at a publicly accessible URL, uploading is the way to get it into a generation. Assets are also reusable — register once, then reference the same id across many generations or voice clones without re-uploading or re-hosting the file. Registration also validates the file and enforces your plan’s size limits up front, so you catch oversized or unreachable media before submitting a generation.
My PUT to the presigned URL is failing. What's wrong?
The two most common causes are a Content-Type mismatch and an expired URL. The Content-Type header on your PUT must exactly match the contentType you sent to POST /v2/assets/upload — if you declared video/mp4, you must PUT with Content-Type: video/mp4. Also confirm you are not sending your x-api-key header on the PUT; the presigned URL carries its own signature and adding auth headers can break it. Finally, presigned URLs expire after expiresIn seconds — if too much time has passed, request a fresh one and retry.
When are plan size limits enforced?
At registration (POST /v2/assets), not at presign. The /upload step only generates a signed URL — sync. labs checks the actual uploaded file size against your plan limits when you register the asset, so a successful presign and PUT does not guarantee the file is within your plan’s allowance.
Related
- Voice Cloning — use an uploaded
assetIdas the sample forPOST /v2/voices. - Generate API — reference an asset as
input[].assetIdin a lip sync generation. - Media formats support — supported file types and codecs for video, audio, and image assets.

