Skip to main content
The Assets API lets you upload files to HeyGen and receive an asset_id you can reference in other endpoints — including Video Agent, Avatar creation, Video Translation, and Lipsync. There are two ways to upload:
  • POST /v3/assets — a single multipart/form-data request. Simplest option, capped at 32 MB.
  • Direct upload — a presigned-URL flow for files larger than 32 MB. The file bytes go straight to storage and never pass through the API.

Upload via POST /v3/assets

Full schema: POST /v3/assets. Upload a file using multipart/form-data — the MIME type is auto-detected from file bytes.

Constraints

ConstraintValue
Max file size32 MB — for larger files, use direct upload
Supported imagespng, jpeg
Supported videomp4, webm
Supported audiomp3, wav
Otherpdf

Example request

curl -X POST "https://api.heygen.com/v3/assets" \
  -H "X-Api-Key: $HEYGEN_API_KEY" \
  -F "file=@./product-screenshot.png"

Response

{
  "data": {
    "asset_id": "asset_abc123def456",
    "url": "https://files.heygen.ai/assets/asset_abc123def456.png",
    "mime_type": "image/png",
    "size_bytes": 245760
  }
}
FieldTypeDescription
asset_idstringUnique identifier to reference this file in other API calls.
urlstringPublic URL of the uploaded file.
mime_typestringDetected MIME type.
size_bytesintegerFile size in bytes.

Upload large files (direct upload)

For files larger than 32 MB, use the direct upload flow. It is a three-step process — all three steps are required; the asset_id is not usable until you call the complete endpoint:
1

Initialize the upload

Call POST /v3/assets/direct-uploads with the file’s name, MIME type, and exact byte size. The response contains an asset_id, a presigned upload_url, and upload_headers.
2

PUT the file bytes

Send the raw file bytes to upload_url with an HTTP PUT, including every header from upload_headers verbatim. The URL expires after expires_in_seconds, and the byte size is signed into it — the upload fails if the file doesn’t match the declared size_bytes.
3

Complete the upload

Call POST /v3/assets/{asset_id}/complete to finalize the asset. This step is idempotent — repeated calls return the same finalized asset. The asset_id is now usable anywhere the API accepts assets.

Example

# Step 1: Initialize
SIZE=$(stat -f%z ./footage.mp4)  # use `stat -c%s` on Linux
INIT=$(curl -s -X POST "https://api.heygen.com/v3/assets/direct-uploads" \
  -H "X-Api-Key: $HEYGEN_API_KEY" \
  -H "Content-Type: application/json" \
  -d "{\"filename\": \"footage.mp4\", \"content_type\": \"video/mp4\", \"size_bytes\": $SIZE}")

ASSET_ID=$(echo "$INIT" | jq -r '.data.asset_id')
UPLOAD_URL=$(echo "$INIT" | jq -r '.data.upload_url')

# Step 2: PUT the raw bytes to the presigned URL,
# passing every header from data.upload_headers verbatim
HEADER_ARGS=$(echo "$INIT" | jq -r '.data.upload_headers | to_entries[] | "-H \"\(.key): \(.value)\""' | tr '\n' ' ')
eval curl -X PUT "$UPLOAD_URL" $HEADER_ARGS --upload-file ./footage.mp4

# Step 3: Complete
curl -X POST "https://api.heygen.com/v3/assets/$ASSET_ID/complete" \
  -H "X-Api-Key: $HEYGEN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{}'

Initialize response fields

FieldTypeDescription
asset_idstringReusable asset identifier. Becomes usable after the complete step.
upload_urlstringPresigned URL. PUT the raw file bytes here.
upload_headersobjectHeaders that must be sent verbatim on the PUT request.
expires_in_secondsintegerSeconds until upload_url expires.
max_bytesintegerMaximum allowed upload size in bytes for this flow.
statusstringAlways pending_upload at this stage.
Calling complete before the PUT has finished returns a 409 conflict (“Uploaded object not found yet”). Retry after the PUT returns 200. You can optionally pass checksum_sha256 (hex) at both the initialize and complete steps to have the stored bytes verified end to end.

Use assets in Video Agent

Once uploaded, reference the asset_id in the files array when creating a video:
{
  "prompt": "Create a product demo using the attached screenshots",
  "files": [
    { "type": "asset_id", "asset_id": "asset_abc123def456" },
    { "type": "asset_id", "asset_id": "asset_ghi789jkl012" }
  ]
}

Three ways to provide files

Video Agent and other endpoints accept files in three formats. Use whichever is most convenient for your workflow:

Asset ID

Upload once, reference by ID. Best for files you reuse across multiple videos.

HTTPS URL

Point to a publicly accessible URL. No upload step needed — HeyGen fetches the file directly. Same 32 MB per-file limit as uploads.

Base64

Inline the file content as a base64-encoded string. Useful for small files or when you want a self-contained request.

Format comparison

FormatSyntaxWhen to use
Asset ID{ "type": "asset_id", "asset_id": "asset_..." }Pre-uploaded files, reusable across requests. Only option for files over 32 MB (via direct upload).
URL{ "type": "url", "url": "https://..." }Files up to 32 MB already hosted publicly.
Base64{ "type": "base64", "media_type": "image/png", "data": "iVBOR..." }Small files, self-contained requests, no hosting needed.
The 32 MB per-file limit also applies to URL inputs — pointing at a larger self-hosted file fails with Maximum size for URL inputs of type 'video/mp4' is 32 MB. For larger files, use direct upload and pass the resulting asset_id. Base64 encoding additionally inflates payload size by ~33%, so prefer the other two formats for anything beyond a few MB.

Where assets can be used

The asset_id format is accepted anywhere the API takes file inputs:
EndpointUse case
POST /v3/video-agentsAttach reference files (images, slides, video clips, audio).
POST /v3/video-agents/{session_id}Send additional files in follow-up messages — see Interactive Sessions.
POST /v3/avatarsProvide a photo or video for avatar creation — see Create Avatar.
POST /v3/video-translationsProvide source video or custom audio — see Video Translation.
POST /v3/lipsyncsProvide source video and/or replacement audio — see Lipsync.

Example: Upload then generate

A complete workflow — upload a PDF, then use it to generate a video:
# Step 1: Upload the PDF
ASSET_ID=$(curl -s -X POST "https://api.heygen.com/v3/assets" \
  -H "X-Api-Key: $HEYGEN_API_KEY" \
  -F "file=@./quarterly-report.pdf" | jq -r '.data.asset_id')

echo "Uploaded asset: $ASSET_ID"

# Step 2: Generate a video using the uploaded PDF
curl -X POST "https://api.heygen.com/v3/video-agents" \
  -H "X-Api-Key: $HEYGEN_API_KEY" \
  -H "Content-Type: application/json" \
  -d "{
    \"prompt\": \"Summarize the key findings from this quarterly report in a 60-second video\",
    \"files\": [{ \"type\": \"asset_id\", \"asset_id\": \"$ASSET_ID\" }]
  }"