> ## Documentation Index
> Fetch the complete documentation index at: https://docs.zerogen.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Uploads and URL inputs

> Send images, video, and audio as generation inputs, by upload or by URL.

Every generation input is an asset in your organization's Library before a
model sees it, so ZeroGen can check its real type and duration, which pricing
depends on. You can give an input in either of two ways:

* **Asset ID.** Upload the file first, then send its asset ID. Uploads take
  images, video, and audio of up to 5 GiB.
* **URL.** Send an `https` URL, and ZeroGen imports it into your Library before
  it quotes the request. URLs take smaller images and video.

```json theme={null}
{
  "model": "openai/gpt-image-2/edit",
  "prompt": "Turn the sky into a sunset",
  "inputs": [
    { "role": "source", "asset": "9e8d7c6b-5a4f-4e3d-8c2b-1a0f9e8d7c6b" },
    { "role": "reference", "url": "https://example.com/sunset-palette.png" }
  ]
}
```

Each input has a `role` and exactly one of `asset` or `url`. The model's
request schema lists the roles it takes and the media each accepts; for
details, see [Models](https://docs.zerogen.com/models.md#read-the-request-schema).

## Upload a file

Uploading takes three requests. Each needs the `uploads:create` scope.

**1. Start the upload**

Send the file's name, media type, and size to `POST /v1/uploads`:

```bash theme={null}
curl https://api.zerogen.com/v1/uploads \
  -H "Authorization: Bearer $ZEROGEN_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 8a7b6c5d-4e3f-4a2b-9c1d-0e9f8a7b6c5d" \
  -d '{
    "filename": "storyboard.png",
    "contentType": "image/png",
    "sizeBytes": 2483011,
    "sha256": "3a5f0c1e9b7d2a4c6e8f0b1d3a5c7e9f1b3d5a7c9e1f3b5d7a9c1e3f5b7d9a1c"
  }'
```

| Field | Description |
| - | - |
| `filename` | Required. The file's name. |
| `contentType` | Required. The file's media type, from the list in [Supported files](#supported-files). |
| `sizeBytes` | Required. The file's exact size in bytes, up to 5 GiB. |
| `sha256` | Optional. The file's SHA-256 digest in lowercase hex. With it, an identical file already in the project is reused. |
| `projectId` | Optional. The project to upload into. Defaults to your organization's default project. |
| `folderId` | Optional. A folder in that project to add the asset to. |

The response is `201` with `status: "upload_required"`, an `upload` target,
and a `Location` header for the upload:

```json theme={null}
{
  "id": "6d5c4b3a-2f1e-4d0c-9b8a-7f6e5d4c3b2a",
  "projectId": "5a4b3c2d-1e0f-4a9b-8c7d-6e5f4a3b2c1d",
  "status": "upload_required",
  "assetId": null,
  "upload": {
    "method": "PUT",
    "url": "https://storage.example.com/...",
    "headers": { "Content-Type": "image/png" },
    "expiresAt": "2026-10-21T17:19:05.000Z"
  }
}
```

If you sent `sha256` and the project already has an identical file, the
response is `200` with `status: "ready"` and that file's `assetId`, and
you can skip the next two steps.

**2. Send the bytes**

`PUT` the file's exact bytes to `upload.url` with every header in
`upload.headers`, before `expiresAt`. The target is valid for 15 minutes.
Don't send your API key to it.

```bash theme={null}
curl -X PUT "$UPLOAD_URL" \
  -H "Content-Type: image/png" \
  --data-binary @storyboard.png
```

**3. Complete the upload**

Complete the upload by its `id`, with no request body:

```bash theme={null}
curl -X POST https://api.zerogen.com/v1/uploads/6d5c4b3a-2f1e-4d0c-9b8a-7f6e5d4c3b2a/complete \
  -H "Authorization: Bearer $ZEROGEN_API_KEY"
```

ZeroGen verifies the stored file and returns `status: "ready"` with the
`assetId` to send as an input. If the project already has an identical
file, `assetId` is that file's. Completing again returns the same asset.

`GET /v1/uploads/{id}` returns an upload as it stands, without its `upload`
target. You can read only uploads you started.

When you name a `folderId`, a `ready` response from `POST /v1/uploads` or
`POST /v1/uploads/{id}/complete` carries a `folder` object that reports whether
the asset was filed there: `filed` is `true` or `false`, and `error` explains a
refusal. An `upload_required` response has no `folder` object yet. The asset
stays usable either way.

## Use a URL as an input

Send `url` in place of `asset`:

```json theme={null}
{ "role": "reference", "url": "https://example.com/sunset-palette.png" }
```

ZeroGen's servers download the URL, check the file, and save it to your Library
as an ordinary upload in the generation's project, before quoting the request.
The model provider never receives your URL, and ZeroGen doesn't store it, so
you can send a signed URL. Importing the same file again reuses its asset, and
an imported asset stays in your Library until someone deletes it.

A URL input has these limits:

* The URL uses `https` on port 443 and a domain name, and is at most 2,048
  characters long.
* The file is a PNG, JPEG, or WebP image of up to 50 MiB, or an MP4 or MOV
  video of up to 100 MiB. ZeroGen checks the type from the file's bytes, not
  from its name or headers.
* Each download must finish within 30 seconds.
* Audio inputs can't be URLs. Upload audio first.

ZeroGen imports a request's URLs one at a time, and downloads a URL repeated
across inputs once. Quotes import URL inputs too.

A request with a URL input needs the `uploads:create` scope in addition to
`generations:create`. Without it, the request fails with
`403 permission_denied` before ZeroGen downloads anything.

If ZeroGen can't use a URL, the request fails with `400 input_import_failed`,
`param` names the input, such as `inputs[1].url`, and nothing is charged. The
error doesn't say whether a host exists or refused the connection. Your
organization's URL imports also have [rate limits](https://docs.zerogen.com/rate-limits.md#url-imports).

## Supported files

Uploads accept the following media types:

| Kind | Media types |
| - | - |
| Image | `image/png`, `image/jpeg`, `image/webp`, `image/gif`, `image/avif`, `image/bmp`, `image/tiff` |
| Video | `video/mp4`, `video/quicktime`, `video/webm`, `video/x-matroska`, `video/x-msvideo` |
| Audio | `audio/mpeg`, `audio/wav`, `audio/aac`, `audio/mp4`, `audio/ogg`, `audio/flac`, `audio/aiff` |

Each model accepts a subset. Its request schema's `x-roles` lists the media
types for each role, and `urlAccept` lists the ones a URL input can be.

## Test keys

A test key uploads and imports only into its
[API sandbox project](https://docs.zerogen.com/test-keys.md#the-api-sandbox). A `projectId` for any other
project returns `404 not_found`.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.