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

# Upload input files

> Stage a file once, then pass its reference to any workflow that takes a file input.

Workflows can declare parameters of `type: file`. To submit a task
against one, upload the file first, then pass the returned
`file_key` as the parameter value. The path runs through S3 with a
presigned `PUT` — uploads don't touch the API server's bandwidth.

## The flow

Three calls in order:

1. `POST /api/v1/r/files/presign` — describe the file. Returns
   `file_key`, `upload_url`, and `upload_headers`.
2. `PUT <upload_url>` — upload the file body directly to S3,
   sending every header in `upload_headers` verbatim.
3. `POST /api/v1/r/workflows/{workflowId}/run` (or
   `/r/workflows/slug/{workflowSlug}/run`) with the `file_key` as
   the value for the file parameter.

## Request a presigned URL

```http theme={null}
POST /api/v1/r/files/presign HTTP/1.1
Authorization: Bearer sk-...
Content-Type: application/json

{
  "file_name": "photo.png",
  "file_size": 1048576,
  "content_type": "image/png"
}
```

Response:

```json theme={null}
{
  "file_key": "inputs/.../photo.png",
  "upload_url": "https://s3.example.com/bucket/key?X-Amz-Signature=...",
  "upload_headers": {
    "Content-Length": "1048576",
    "If-None-Match": "*"
  }
}
```

| Field            | Notes                                                                    |
| ---------------- | ------------------------------------------------------------------------ |
| `file_key`       | Opaque reference. Pass this as the value for any `type: file` parameter. |
| `upload_url`     | S3 `PUT` URL with the signature in the query string.                     |
| `upload_headers` | Headers that **must** be sent verbatim on the `PUT`. Signed in.          |

## Upload to S3

Send the file body as the `PUT` request body with all of
`upload_headers` attached. The signature enforces them — if you skip
`Content-Length` or `If-None-Match`, S3 returns `403`. If a file
already exists at the key, S3 returns `412` (overwrite prevented).

```bash theme={null}
curl -X PUT \
  -H "Content-Length: 1048576" \
  -H "If-None-Match: *" \
  --upload-file photo.png \
  "<upload_url>"
```

The `Content-Length` value must match the `file_size` you sent at
step 1 exactly.

## Submit the workflow

Pass `file_key` as the parameter value:

```http theme={null}
POST /api/v1/r/workflows/slug/img2img/run HTTP/1.1
Authorization: Bearer sk-...
Content-Type: application/json

{
  "parameters": {
    "input_image": "inputs/.../photo.png",
    "prompt": "enhance"
  }
}
```

The agent fetches the file from S3 when the task runs.

## End-to-end examples

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

  API_KEY = "sk-..."
  BASE = "https://core.cyberun.cloud/api/v1/r"
  HEADERS = {"Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json"}

  # 1. Presign
  presign = requests.post(
      f"{BASE}/files/presign",
      headers=HEADERS,
      json={
          "file_name": "photo.png",
          "file_size": 1048576,
          "content_type": "image/png",
      },
  ).json()

  # 2. PUT to S3 — pass back every header the presign returned
  with open("photo.png", "rb") as f:
      requests.put(presign["upload_url"], data=f, headers=presign["upload_headers"])

  # 3. Run a workflow that takes a file parameter
  resp = requests.post(
      f"{BASE}/workflows/slug/img2img/run",
      headers=HEADERS,
      json={"parameters": {"input_image": presign["file_key"], "prompt": "enhance"}},
  )
  print(resp.json()["task_id"])
  ```

  ```bash curl theme={null}
  # 1. Presign
  PRESIGN=$(curl -s -X POST \
    -H "Authorization: Bearer sk-..." \
    -H "Content-Type: application/json" \
    -d '{"file_name":"photo.png","file_size":1048576,"content_type":"image/png"}' \
    https://core.cyberun.cloud/api/v1/r/files/presign)

  FILE_KEY=$(echo "$PRESIGN" | jq -r '.file_key')
  UPLOAD_URL=$(echo "$PRESIGN" | jq -r '.upload_url')

  # 2. PUT — fan upload_headers into -H args
  HEADERS_ARGS=()
  while IFS= read -r h; do HEADERS_ARGS+=(-H "$h"); done < <(
    echo "$PRESIGN" | jq -r '.upload_headers | to_entries[] | "\(.key): \(.value)"'
  )
  curl -X PUT "${HEADERS_ARGS[@]}" --upload-file photo.png "$UPLOAD_URL"

  # 3. Run workflow
  curl -s -X POST \
    -H "Authorization: Bearer sk-..." \
    -H "Content-Type: application/json" \
    -d "{\"parameters\":{\"input_image\":\"$FILE_KEY\",\"prompt\":\"enhance\"}}" \
    https://core.cyberun.cloud/api/v1/r/workflows/slug/img2img/run
  ```

  ```ts Node.js theme={null}
  const API_KEY = process.env.CYBERUN_KEY!;
  const BASE = 'https://core.cyberun.cloud/api/v1/r';

  // 1. Presign
  const presign = await fetch(`${BASE}/files/presign`, {
  	method: 'POST',
  	headers: {
  		Authorization: `Bearer ${API_KEY}`,
  		'Content-Type': 'application/json'
  	},
  	body: JSON.stringify({
  		file_name: 'photo.png',
  		file_size: 1048576,
  		content_type: 'image/png'
  	})
  }).then((r) => r.json());

  // 2. PUT to S3 with the signed headers
  const fileBody = await Bun.file('photo.png').arrayBuffer();
  await fetch(presign.upload_url, {
  	method: 'PUT',
  	headers: presign.upload_headers,
  	body: fileBody
  });

  // 3. Run workflow
  const run = await fetch(`${BASE}/workflows/slug/img2img/run`, {
  	method: 'POST',
  	headers: {
  		Authorization: `Bearer ${API_KEY}`,
  		'Content-Type': 'application/json'
  	},
  	body: JSON.stringify({
  		parameters: { input_image: presign.file_key, prompt: 'enhance' }
  	})
  }).then((r) => r.json());

  console.log(run.task_id);
  ```
</CodeGroup>

## Retention and limits

* **Input files** uploaded via `/r/files/presign` are kept for **1
  day** then removed by S3 lifecycle policy. Submit the task within
  that window.
* **Task outputs** are retained for **7 days**. After that the
  `download_url` you receive from `GET /r/tasks/{taskId}/result`
  stops working.
* Default per-file size limit is **500 MB**. Larger limits are a
  deployment configuration. For files near or above this limit use
  the multipart upload endpoints (`/uploads/multipart/...`) instead
  — they presign each part separately.

## Multipart for large files

For files that exceed the single-part limit, use the multipart flow:

1. `POST /api/v1/uploads/multipart/initiate` — start an upload, get an upload ID.
2. `POST /api/v1/uploads/multipart/presign-parts` — request signed URLs for each part.
3. `PUT` each part to its signed URL.
4. `POST /api/v1/uploads/multipart/complete` — finalize, get the same `file_key` shape back.

The single-part flow is simpler — use multipart only when you need it.

The multipart endpoints (`initiate`, `presign-parts`, `complete`)
accept a user session token only — they reject `sk-` integration
credentials and `dk-` device credentials with `401`. Single-part
`/r/files/presign` accepts all three. If you call the API with an
`sk-` or `dk-` credential, use the single-part presign flow.

## Related

* [API reference](/api-reference/introduction) — `/r/files/presign`
  and `/uploads/multipart/*` endpoint specs.
* [Stream task events (SSE)](/api-reference/streaming-events) —
  watch the run after submission.
* [Generate an API key](/cloud/guides/api-key) — issue an `sk-`
  credential.
