Skip to main content
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.
To upload many files at once, use the batch variant: up to 100 upload slots in one call and one complete call for the whole batch.

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

S3 returns 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 (a checksum_sha256 adds x-amz-checksum-sha256).
  • Send exactly size_bytes bytes in a single PUT. 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.
If the 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

The request body is required. Send {}, 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 an x-amz-checksum-sha256 header to upload_headers, and S3 rejects any PUT whose bytes do not match.
Send the returned 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 with content_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.