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

# Uploading Files

> Upload images, audio, and video files to use as execution inputs.

## How uploading works

ModelRoute handles file uploads with zero trust. You send the raw file bytes, and ModelRoute:

* **Detects the content type** from file magic bytes (never trusts client-declared MIME types)
* **Generates a UUID filename** (original filenames are never stored or exposed)
* **Verifies the actual file size** from the uploaded bytes (declared sizes are always overridden)
* **Streams the upload** to storage without buffering the entire file in memory

You never need to tell ModelRoute what your file is. ModelRoute figures it out from the bytes.

## Step 1: Request a presigned upload URL

```bash theme={null}
curl -X POST https://api.modelroute.ai/v1/files/upload \
  -H "Authorization: Bearer sk_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "purpose": "INPUT"
  }'
```

### Request body

| Field      | Type    | Required | Description                                                         |
| ---------- | ------- | -------- | ------------------------------------------------------------------- |
| `purpose`  | string  | Yes      | `INPUT` for execution inputs, `UPLOAD` for general storage          |
| `region`   | string  | No       | Storage region: `US` (default), `EU`, or `APAC`                     |
| `ttl_days` | integer | No       | Auto-delete after N days (1-365). Uses your org default if omitted. |

### Response

```json theme={null}
{
  "file_id": "file_f47ac10b-58cc-4372-a567-0e02b2c3d479",
  "upload_url": "https://storage.modelroute.ai/uploads/file_f47ac10b...?X-Amz-Signature=...",
  "expires_at": "2026-03-20T11:30:00Z"
}
```

| Field        | Description                                   |
| ------------ | --------------------------------------------- |
| `file_id`    | The file reference to use in execution inputs |
| `upload_url` | Presigned PUT URL. Valid for 1 hour.          |
| `expires_at` | When the upload URL expires                   |

## Step 2: Upload the file

Use an HTTP PUT request to upload the raw file bytes to the presigned URL:

<CodeGroup>
  ```bash curl theme={null}
  curl -X PUT "https://storage.modelroute.ai/uploads/file_f47ac10b...?X-Amz-Signature=..." \
    --data-binary @photo.png
  ```

  ```python Python theme={null}
  import requests

  # From step 1
  upload_url = "https://storage.modelroute.ai/uploads/file_f47ac10b...?X-Amz-Signature=..."

  with open("photo.png", "rb") as f:
      response = requests.put(upload_url, data=f)
      assert response.status_code == 200
  ```

  ```typescript TypeScript theme={null}
  import fs from "fs";

  const uploadUrl = "https://storage.modelroute.ai/uploads/file_f47ac10b...?X-Amz-Signature=...";

  const response = await fetch(uploadUrl, {
    method: "PUT",
    body: fs.readFileSync("photo.png"),
  });
  ```
</CodeGroup>

<Info>
  ModelRoute detects the file type from the first bytes of the uploaded content. You do not need to set a `Content-Type` header on the PUT request. If you do set one, ModelRoute will override it with the server-detected type.
</Info>

## Step 3: Confirm the upload

After the PUT completes, confirm the upload so ModelRoute can verify the file in storage:

```bash theme={null}
curl -X POST https://api.modelroute.ai/v1/files/confirm \
  -H "Authorization: Bearer sk_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "file_id": "file_f47ac10b-58cc-4372-a567-0e02b2c3d479"
  }'
```

ModelRoute will:

* Verify the file exists in storage
* Read the actual file size from storage (not from any client-declared value)
* Detect the content type from the file bytes
* Generate a compliance clone for audit purposes
* Return the confirmed file metadata

### Confirm response

```json theme={null}
{
  "id": "file_f47ac10b-58cc-4372-a567-0e02b2c3d479",
  "status": "ACTIVE",
  "content_type": "image/png",
  "size_bytes": 2048576,
  "created_at": "2026-03-20T10:30:00Z"
}
```

## Step 4: Use the file reference

Pass the `file_id` in your execution input:

```bash theme={null}
curl -X POST https://api.modelroute.ai/v1/executions \
  -H "Authorization: Bearer sk_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "flux-1.1-pro",
    "input": {
      "prompt": "Transform this photo into an oil painting",
      "image": "file_f47ac10b-58cc-4372-a567-0e02b2c3d479"
    }
  }'
```

## Security model

ModelRoute follows a zero-trust approach to file handling:

| Concern               | How ModelRoute handles it                                                                  |
| --------------------- | ------------------------------------------------------------------------------------------ |
| Filename injection    | Original filenames are discarded. All files get UUID-based names.                          |
| Content type spoofing | MIME type is detected server-side from file magic bytes, never from client headers.        |
| Size manipulation     | Actual file size is read from storage after upload, not from client-declared values.       |
| Path traversal        | Object keys are generated server-side using UUIDs. No user input enters storage paths.     |
| Memory exhaustion     | Uploads are streamed through io.Pipe. The server never holds the full file in memory.      |
| Cross-org access      | Every file is scoped to the uploading organization. Ownership is verified on every access. |

## Error handling

| Error code                 | Cause                                                                         |
| -------------------------- | ----------------------------------------------------------------------------- |
| `UNSUPPORTED_CONTENT_TYPE` | The detected file type is not in the allow-list                               |
| `QUOTA_EXCEEDED`           | Organization storage quota reached. Delete files or request a quota increase. |
| `FILE_NOT_FOUND`           | The file reference does not exist or the upload was not completed             |
| `RATE_LIMITED`             | Too many upload requests. Retry after a short delay.                          |

## Complete upload flow example

<CodeGroup>
  ```python Python theme={null}
  import requests

  API_KEY = "sk_your_api_key_here"
  BASE = "https://api.modelroute.ai/v1"
  HEADERS = {"Authorization": f"Bearer {API_KEY}"}

  # 1. Request presigned URL
  upload_resp = requests.post(
      f"{BASE}/files/upload",
      headers=HEADERS,
      json={"purpose": "INPUT"}
  ).json()

  file_id = upload_resp["file_id"]
  upload_url = upload_resp["upload_url"]

  # 2. Upload the file (just raw bytes, no metadata needed)
  with open("photo.png", "rb") as f:
      requests.put(upload_url, data=f)

  # 3. Confirm the upload
  confirm = requests.post(
      f"{BASE}/files/confirm",
      headers=HEADERS,
      json={"file_id": file_id}
  ).json()

  print(f"Uploaded: {confirm['content_type']}, {confirm['size_bytes']} bytes")

  # 4. Use in execution
  result = requests.post(
      f"{BASE}/executions",
      headers=HEADERS,
      json={
          "model": "flux-1.1-pro",
          "input": {
              "prompt": "Make this photo look like a watercolor painting",
              "image": file_id
          }
      }
  ).json()

  print(f"Execution {result['id']}: {result['status']}")
  ```

  ```typescript TypeScript theme={null}
  import fs from "fs";

  const API_KEY = "sk_your_api_key_here";
  const BASE = "https://api.modelroute.ai/v1";
  const headers = { "Authorization": `Bearer ${API_KEY}`, "Content-Type": "application/json" };

  // 1. Request presigned URL
  const uploadResp = await fetch(`${BASE}/files/upload`, {
    method: "POST",
    headers,
    body: JSON.stringify({ purpose: "INPUT" }),
  }).then(r => r.json());

  // 2. Upload raw file bytes
  await fetch(uploadResp.upload_url, {
    method: "PUT",
    body: fs.readFileSync("photo.png"),
  });

  // 3. Confirm
  const confirm = await fetch(`${BASE}/files/confirm`, {
    method: "POST",
    headers,
    body: JSON.stringify({ file_id: uploadResp.file_id }),
  }).then(r => r.json());

  console.log(`Uploaded: ${confirm.content_type}, ${confirm.size_bytes} bytes`);

  // 4. Use in execution
  const result = await fetch(`${BASE}/executions`, {
    method: "POST",
    headers,
    body: JSON.stringify({
      model: "flux-1.1-pro",
      input: {
        prompt: "Make this photo look like a watercolor painting",
        image: uploadResp.file_id,
      },
    }),
  }).then(r => r.json());
  ```
</CodeGroup>
