Skip to content

Files & uploads

Many generations take file inputs: reference images, inpainting masks, poses, depth maps, or audio. You upload a file once, then pass its file_id into generation requests.

Uploads go directly to storage with a resumable upload. You request a signed URL, then upload the bytes yourself.

  1. Request an upload URL

    Terminal window
    curl -X POST https://api.app.layer.ai/api/v2/workspaces/$WORKSPACE_ID/files/upload-url \
    -H "Authorization: Bearer $LAYER_TOKEN" \
    -H "Content-Type: application/json" \
    -d '{ "content_type": "image/png" }'
    # → { "file_id": "…", "upload_url": "https://storage.googleapis.com/…", "content_type": "image/png", "source_type": "asset" }
  2. Start a resumable session by POSTing to upload_url with the x-goog-resumable: start header; the response’s Location is your session URI.

  3. Upload the bytes with a PUT to the session URI.

Type Content types Max size
Image image/png, image/jpeg, image/webp 64 MB
Video video/mp4 100 MB
Audio audio/wav, audio/mpeg 64 MB
Archive application/zip 100 MB

Pass the returned file_id into a generation request. Reference images and other guidance go in guidance_files; an inpainting mask goes in mask (request it with source_type: "mask", and it must be an image):

{
"model_id": "MODEL_ID",
"parameters": {
"prompt": "replace the sky with aurora",
"guidance_files": [{ "file_id": "FILE_ID" }],
"mask": { "file_id": "MASK_FILE_ID" }
}
}

Layer resolves each file_id to the stored object internally: you don’t pass URLs.

Instead of describing where an edit goes, you can mark it on the image. Add annotations to an init_image or reference_image guidance file: a list of numbered marks, each with a note saying what to do there. On the image to edit a mark says what to change; on a reference it says what to use from it (“this hat”), or with REMOVE, what to leave out. Points run from (0, 0) at the top left to (1, 1) at the bottom right of the image: the file as uploaded or, for a video or a 3D model, the frame taken from it (video_to_image / mesh_to_image), after any crop or rotation: marks are the last edit on the image, placed on it as it is framed.

{
"guidance_files": [
{
"file_id": "FILE_ID",
"type": "init_image",
"annotations": {
"items": [
{ "id": "lantern", "kind": "BOX", "points": [{ "x": 0.16, "y": 0.08 }, { "x": 0.32, "y": 0.36 }], "note": "make the lantern glow green" },
{ "id": "bottle", "kind": "ELLIPSE", "intent": "REMOVE", "points": [{ "x": 0.66, "y": 0.13 }, { "x": 0.79, "y": 0.34 }] },
{ "id": "apples", "kind": "BOX", "points": [{ "x": 0.3, "y": 0.36 }, { "x": 0.55, "y": 0.64 }] },
{ "id": "move", "kind": "ARROW", "intent": "MOVE", "source_id": "apples", "points": [{ "x": 0.42, "y": 0.5 }, { "x": 0.12, "y": 0.8 }] },
{ "id": "barrel", "kind": "PIN", "points": [{ "x": 0.1, "y": 0.82 }], "note": "add a small wooden barrel" }
]
}
}
]
}
Kind Points
BOX, ELLIPSE Two opposite corners of the region
ARROW Tail, then head
FREEHAND The path, 2 to 512 points
PIN One point

A mark’s intent is EDIT (the default), REMOVE, or MOVE: an arrow that moves what the box or ellipse named by source_id encloses to where it points. A run takes up to 32 marks. Leave color unset: a colour that stands out against the image is chosen and stored with the run, so you can show the marks as they were sent.

How a model reads the marks is in its capabilities, under visual_annotations:

  • RENDERED: the model is sent your images unchanged, plus a copy of each marked image with the numbered marks drawn on it, and the prompt gets a numbered list of your notes for each. Each copy takes one of the model’s reference image slots.
  • NATIVE_BOXES: the model takes boxes as input, on the init_image only. Only BOX marks and arrows that move a box can be sent; any other mark, or marks on a reference, is refused.
  • Absent: the model does not read marks. They are left out, and the run reports the adjustment.

There is no download endpoint. Generated outputs are returned as URLs in the run result (see Async jobs); fetch those directly.