Direct upload sends file bytes from your client straight to S3 through a presigned URL, so they never pass through the HeyGen API. Use it for any file over the simple upload limit, for avatar training footage, and for HyperFrames project zips. The result is an ordinary asset_id you can pass to any endpoint that accepts assets.
1
Create the upload
POST /v3/assets/direct-uploads with the file’s name, MIME type, and exact byte size. Returns an asset_id, a presigned upload_url, and upload_headers.2
PUT the bytes to S3
PUT the raw file to upload_url with every header from upload_headers, within 24 hours.3
Complete the upload
POST /v3/assets/{asset_id}/complete. The asset_id is usable as soon as this returns.Size limits
The limit for your file is also returned as
max_bytes when you create the upload. A larger size_bytes is rejected at that step, before any bytes move.
Step 1: Create the upload
Request body
Send an
Idempotency-Key header to retry this call safely: a repeat within 24 hours returns the same asset_id and upload_url.
Response
Step 2: PUT the bytes to S3
200 with an empty body when the upload succeeds. The request goes to S3, not to HeyGen, so it follows S3’s rules:
- Send every header in
upload_headers, with its exact value. They are part of the URL’s signature. Read them from the response rather than hard-coding them, because the set changes with your request (achecksum_sha256addsx-amz-checksum-sha256). - Send exactly
size_bytesbytes in a singlePUT. Chunked or multipart uploads are not supported on this URL. - Do not add your HeyGen API key or any other header. Extra signed-looking headers can break the signature.
- Finish within
expires_in_seconds. After 24 hours, create a new upload.
PUT fails, S3 returns an XML error. SignatureDoesNotMatch (403) means a header is missing or changed, or the body size differs from size_bytes. A failed PUT can be retried against the same upload_url until it expires.
Step 3: Complete the upload
{}, or {"checksum_sha256": "<hex>"} to cross-check the stored bytes.
Response
Complete is idempotent: calling it again returns the same asset. HeyGen reads the stored bytes at this step, so a file whose contents do not match a supported type is rejected here even if the
PUT succeeded.
Full example
Verify file integrity
Pass the file’s SHA-256 as hex when you create the upload. HeyGen adds anx-amz-checksum-sha256 header to upload_headers, and S3 rejects any PUT whose bytes do not match.
upload_headers on the PUT as usual. You can pass the same hex value to the complete call as a final check.
Upload a HyperFrames project
Zip the project and upload it withcontent_type: "application/zip" (up to 1 GiB). Pass the asset_id as the project of POST /v3/hyperframes/renders. Reuse the same asset_id for every render of that project.
Errors
See Error codes for the response envelope.

