source_url. You have two options:
- Framelane upload — upload a local file with
POST /v1/uploads, then use the returnedsource_url. The file is measured when it lands, so video and audio elements can omitout_point. - Public URL — pass a direct, public link to the file. The render engine fetches it at render time; video and audio are probed for duration first unless you set
out_point. See Using public URLs below.
Upload flow
1. Request an upload URL
201 Created):
upload_url expires in 1 hour. uploaded: false is the normal answer here and
means what it says — a slot exists, the file has not arrived yet.
first_party_upload_url is the same session on a Framelane hostname. Prefer
upload_url when you can reach Google storage directly; use first_party_upload_url
when your network allows *.framelane.io but not storage.googleapis.com. Same
protocol, same chunks, same expiry — the only difference is which name your firewall
has to know about.
2. Upload the file bytes
PUT the file toupload_url with the same Content-Type you declared in step 1. Bytes go directly to GCS — they never pass through the Framelane API.
upload_url is a GCS resumable session: you can send it in chunks with Content-Range headers and resume after a failure. The SDKs and the console do this for you — upload_file / uploadFile send 8 MiB chunks and retry each on its own, so a dropped connection costs one chunk rather than the file.
If that PUT is refused before it leaves your machine, you are almost certainly in
a sandbox with a per-host egress allow-list — an agent’s code-execution container,
typically — and The response comes back with
storage.googleapis.com is not on it. Two ways out. If the allow-list
admits *.framelane.io (or an admin can add uploads.framelane.io, one domain), PUT
the same bytes to first_party_upload_url instead. Otherwise, for a file under
256 KB, skip both steps and send the bytes inline:"uploaded": true and a null upload_url: the file is
stored and source_url works immediately. Over 256 KB the request is rejected with
413 inline_too_large, because the payload has to be encoded into the request body —
fine for logos, crests and icons, wrong for video. MCP agents pass the same
content_base64 argument to the create_upload tool.3. Confirm it arrived (optional but cheap)
The bytes went straight to storage, so a201 in step 1 says nothing about step 2.
GET /v1/workspace/assets/{asset_id} — or the get_asset MCP tool — answers
bytes_uploaded, which is the only reliable confirmation available.
4. Use source_url in your job
Pass source_url from the upload response as source_url in any render element or task body:
out_point before extraction finishes, the request is rejected with 422 asset_not_ready and nothing is queued. Wait for the asset.ready webhook and resubmit, poll GET /v1/workspace/assets for ready assets, or set out_point yourself to skip the duration lookup entirely.
Using public URLs
A render may reference asource_url that Framelane doesn’t host. Nothing is copied: the
render engine fetches the file at render time, so the URL must be a direct, public
https link to the media file (not a share page) that supports HTTP range requests and
does not require cookies or a referer. Images and LUTs need nothing else.
A video or audio element without an explicit out_point also needs the file’s
duration. By default Framelane probes such files before rendering:
- The render is created in the
ingestingstate and is not sent to the render engine yet. - The render engine measures each public video/audio file (duration, dimensions). The same probe runs on the same fleet as renders, so the wait is the same wait a queued render has.
- Once every probed file is ready, the render automatically transitions to
queuedand proceeds.out_pointis filled in from the measured duration when omitted. - If a file can’t be probed (unreachable, not a media file, or the probe times out), the
render fails with the
ingest_failederror code. Setout_pointexplicitly to render such a file without a probe.
cdn-user.framelane.io source_url from POST /v1/uploads) are never probed this way —
uploads are measured when they land. Set ingest_external: false on a render to skip the
probe and pass URLs straight through; video and audio then require an explicit out_point.
Share-page links (a Drive or Dropbox “view” link, a YouTube URL) are not direct file
URLs and will not work. Use the service’s direct-download form, or upload the file.
Which route works where
Agents run in very different places, and the difference that matters is whether the place can reach an upload host at all. Routes: direct = PUT toupload_url or
first_party_upload_url; panel = the file picker MCP hosts render alongside
create_upload; inline = content_base64, for a file the agent produced itself
(under ~150 KB); link = POST /v1/uploads/links, a page a person opens.
The
next_step text on every create_upload result walks these in order: panel, then
upload_url / first_party_upload_url, then inline, then a link. It never asks the
agent to run a command inside its sandbox.
Supported content types
Unsupported
content_type values return 422 with an unsupported_content_type error
(details.allowed lists the accepted types).
Uploadable is not the same as renderable.
video/x-msvideo (.avi),
video/x-matroska (.mkv) and video/mpeg (.mpg) upload fine, but a video render
element rejects them with 422 — its source_url must end in .mp4, .mov or
.webm. Re-encode before referencing the file in a render..svg sources rasterize in-process on the renderer: flat fills and stroke icons
(Lucide/Feather-style) render; gradients, text and filters inside the SVG do not, so
pre-rasterize those to PNG.

