Media Uploads

Get a local file onto the Wireflow CDN so you can wire it into a workflow — inline for small files, presigned direct-to-R2 for anything up to 25MB.

Workflows consume media by URL. These endpoints put your file on cdn.wireflow.ai, which is already on the executor's host allowlist, so the returned url drops straight into an input:image / input:video / input:audio node or any media port.

All three doors return the same response shape, so pick one by file size and parse the result the same way:

{
  "url": "https://cdn.wireflow.ai/uploads/api/42/a1b2....png",
  "mediaId": "a1b2....png",
  "contentType": "image/png",
  "width": 1024,
  "height": 1024,
  "duration": null,
  "bytes": 812345
}

Which door to use

Your file Use Limit
Small, as a multipart file POST /media/upload with file=@... 4MB
Small, as a base64 dataUrl POST /media/upload with { "dataUrl" } 3MB
Up to 25MB, bytes in hand POST /media/upload-url → PUT → /complete 25MB
Already hosted at a public URL POST /media/upload with { "url": "..." } 25MB

Why 4MB and not 25MB inline? The API runs on serverless functions that reject a request body over roughly 4.5MB before any Wireflow code runs. Rather than let you discover that as an opaque platform error, the inline door refuses at 4MB and points you here. The presigned flow has no such ceiling because the bytes go straight from you to storage.

And why 3MB for a dataUrl? Same 4MB limit, different units. base64 puts about 4/3 of the file on the wire, and the limit is measured on the encoded body, so a dataUrl tops out around 3MB of actual file. A 3.6MB image is under "4MB" and still too big to send this way — use multipart, or the presigned flow.

Scope for every endpoint on this page: workflows:write. Uploads are listed by GET /media.

Inline upload

POST /api/v1/media/upload

Send one of:

  • multipart/form-data with a file field, up to 4MB
  • JSON { "dataUrl": "data:image/png;base64,..." }, up to 3MB of file (see above)
  • JSON { "url": "https://example.com/clip.mp4" } — we fetch and rehost it, up to 25MB
curl -X POST https://wireflow.ai/api/v1/media/upload \
  -H "Authorization: Bearer $WIREFLOW_API_KEY" \
  -F "[email protected]"

Optional width, height and duration fields are accepted as hints. They are only used when we cannot read the value out of the file itself, and they never override what we measured.

Presigned upload (up to 25MB)

Three calls. Finish within 24 hours: an upload that never reaches step 3 is swept, along with its object, by a daily cleanup. The asset does not appear in GET /media until step 3 either.

1. Ask for a URL. bytes is the exact byte length of the file.

curl -X POST https://wireflow.ai/api/v1/media/upload-url \
  -H "Authorization: Bearer $WIREFLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"contentType":"video/mp4","bytes":18874368,"filename":"broll.mp4"}'
{
  "uploadUrl": "https://....r2.cloudflarestorage.com/...?X-Amz-Signature=...",
  "method": "PUT",
  "headers": {
    "Content-Type": "video/mp4",
    "Content-Length": "18874368",
    "Cache-Control": "public, max-age=31536000, immutable"
  },
  "mediaId": "a1b2c3d4-....mp4",
  "url": "https://cdn.wireflow.ai/uploads/api/42/a1b2c3d4-....mp4",
  "contentType": "video/mp4",
  "bytes": 18874368,
  "expiresAt": "2026-08-18T12:15:00.000Z"
}

2. PUT the bytes to uploadUrl with exactly the headers you were given. The content type and the byte length are part of the signature, so a mismatch is rejected by storage with SignatureDoesNotMatch. The URL is valid for 15 minutes.

curl -X PUT "$UPLOAD_URL" \
  -H "Content-Type: video/mp4" \
  -H "Content-Length: 18874368" \
  --data-binary @broll.mp4

3. Finalize.

curl -X POST https://wireflow.ai/api/v1/media/upload-url/complete \
  -H "Authorization: Bearer $WIREFLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"mediaId":"a1b2c3d4-....mp4"}'

This verifies the object landed, checks its size, confirms an image is really a decodable image, and returns the standard response above. It is safe to retry.

If it answers unreadable_image, the bytes you PUT were not the image type you asked for. The object is discarded — mint a new upload url and send the real file.

Metadata on the presigned path

Images get their width and height read from the file. Video and audio return null for width, height and duration unless you send them yourself on the /complete call — we will not download a 25MB file just to measure it. Sent values are bounded and are only ever used to fill a gap.

Accepted types

Images jpg, png, gif, webp, avif · video mp4, webm, mov · audio mp3, wav, m4a.

Anything else is refused with unsupported_type.

Errors

Code Meaning
unsupported_type The content type is not in the list above
file_too_large Over the limit for the door you used
invalid_bytes bytes is missing or is not a positive whole number
invalid_media_id The mediaId is not one we minted
upload_not_found Unknown mediaId, the PUT never happened, or the 24h window lapsed
unreadable_image The bytes are not a decodable image of the type you declared
empty_file Zero bytes

© 2026 Wireflow. All rights reserved.

Media Uploads | Wireflow Docs