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

# Files and downloads

Write a file to the session's machine and pass its path to `DOM.setFileInputFiles`. List, read and delete what the browser downloaded.

These routes talk to the session's machine, not the browser, so they work while a CDP client is connected. Files live in the session's memory and end with the session, so there is nothing to clean up and nothing to recover afterwards. The routes add no credit charge and count under the [drive throttle](https://docs.tilion.dev/account/rate-limits.md#request-throttles).

To fetch one URL through the page instead, with the session's cookies, use [`POST /v1/session/{id}/files/fetch`](https://docs.tilion.dev/sessions/driving).

## PUT /v1/session/\{session\_id}/files/\{path}

The raw request body is the file, sent as a stream with `curl -T` or any chunked or sized body. The limit is 200 MB by default, and the route is exempt from the 4 MB body cap of other routes.

`path` is relative to the upload folder and may include subdirectories. An existing file is replaced. A file appears only after its upload completes.

```bash theme={"dark"}
curl -T report.pdf -H "Authorization: Bearer ph_live_..." \
  https://back.tilion.dev/v1/session/sess_3d245e56b86d4bc8ae32095b644117d4/files/report.pdf
```

Response (201):

```json theme={"dark"}
{"session_id": "sess_3d245e56b86d4bc8ae32095b644117d4", "path": "/tmp/tilion-uploads/report.pdf", "size": 104857600}
```

## Attach an upload to a file input

Pass the response's `path` to the CDP command `DOM.setFileInputFiles`:

```json theme={"dark"}
{"id": 7, "method": "DOM.setFileInputFiles", "params": {"files": ["/tmp/tilion-uploads/report.pdf"], "nodeId": 42}, "sessionId": "<target session>"}
```

`nodeId` is the file input's node, from `DOM.querySelector`. The [guide](https://docs.tilion.dev/guides/files-and-downloads.md) has a Playwright example.

## GET /v1/session/\{session\_id}/files

List uploads:

```json theme={"dark"}
{
  "session_id": "sess_3d245e56b86d4bc8ae32095b644117d4",
  "dir": "/tmp/tilion-uploads",
  "files": [
    {"name": "report.pdf", "path": "/tmp/tilion-uploads/report.pdf", "size": 104857600, "modified": 1792420179, "in_progress": false}
  ],
  "truncated": false
}
```

`modified` is in epoch seconds. `truncated` is true when the folder holds more than the list returns.

## DELETE /v1/session/\{session\_id}/files/\{path}

Delete one file, or a directory with everything in it. `DELETE /v1/session/{id}/files` empties the folder. A delete answers `{"session_id": "...", "deleted": 1}`, where `deleted` counts the files removed.

## GET /v1/session/\{session\_id}/downloads

List the browser's download folder, including downloads started by a click, with or without a connected client:

```json theme={"dark"}
{
  "session_id": "sess_3d245e56b86d4bc8ae32095b644117d4",
  "dir": "/tmp/home/Downloads",
  "files": [
    {"name": "invoice.pdf", "size": 48213, "modified": 1792420179, "in_progress": false},
    {"name": "export.csv.crdownload", "size": 1048576, "modified": 1792420180, "in_progress": true}
  ],
  "truncated": false
}
```

`in_progress` is true while Chrome writes the file as `.crdownload`. Poll until the final name appears.

## GET /v1/session/\{session\_id}/downloads/\{name}

Stream one finished file, up to 1 GB. A `.crdownload` file answers 409 `in_progress`.

```bash theme={"dark"}
curl -o invoice.pdf -H "Authorization: Bearer ph_live_..." \
  https://back.tilion.dev/v1/session/sess_3d245e56b86d4bc8ae32095b644117d4/downloads/invoice.pdf
```

Reading a download counts the bytes sent as your egress, at the datacenter rate whichever exit the session uses. A client that disconnects pays for what it received. A spent quota refuses the read with its usual answer. Uploads are not charged.

## DELETE /v1/session/\{session\_id}/downloads/\{name}

Delete one file or directory. `DELETE /v1/session/{id}/downloads` empties the folder. Deleting the file of a running download makes that download fail in the browser.

## Playwright downloads

Playwright sends its own `Browser.setDownloadBehavior` on connect, to a `playwright-artifacts` folder, whichever system your client runs on. Those downloads appear in the same list as `playwright-downloads/<folder>/<GUID>`. Read and delete them by that full name. Encode each part of a name when you put it in a URL:

```bash theme={"dark"}
curl -o invoice.pdf -H "Authorization: Bearer ph_live_..." \
  "https://back.tilion.dev/v1/session/$SID/downloads/playwright-downloads/$FOLDER/$GUID"
```

Playwright's GUID is the only name these files have. Playwright's `suggested_filename` gives the site's name for the file. Deleting `playwright-downloads/<folder>` empties that folder and keeps it, and emptying the download folder clears these files too.

## What is not listed

Your own `Browser.setDownloadBehavior` takes precedence while the client that sent it stays connected: Chrome writes where you said, and `behavior: "deny"` stops downloads. When that client disconnects, Chrome returns to the session's folder.

A `downloadPath` outside `/tmp` names a folder on your client's machine, which the session's machine does not have. Tilion points it at the session's folder, so the download still lands and is listed. A path under `/tmp` stays as you sent it, and those files are not listed unless the folder is the session's own or Playwright's.

To use your own behavior and the list together, send:

```json theme={"dark"}
{"behavior": "allow", "downloadPath": "<dir from the list response>", "eventsEnabled": true}
```

## Errors

These routes also answer 402 and 429 like every drive call.

| Status | `error` | When |
| - | - | - |
| 400 | `bad_path` | `..`, an absolute path outside the folder, or a symlink on the way. |
| 400 | `interrupted` | The upload was cut short. |
| 404 | `not_found` | No such file. |
| 408 | `stalled` | No bytes for 120 seconds. |
| 409 | `not_a_file` | The name is a directory. |
| 409 | `too_many` | 512 files in a folder. |
| 409 | `in_progress` | The browser is still writing this download. |
| 409 | `unavailable` | The session cannot serve files, for example one created before this feature. |
| 413 | `too_large` | Over the upload or download limit. |
| 429 | `busy` (`Retry-After: 5`) | 4 uploads are already running on this session. |
| 501 | `unsupported` | The session's machine does not support these routes. |
| 502 | `guest_unreachable`, `bad_guest`, `truncated`, `io_error` | The transfer failed between the API and the session's machine. |
| 507 | `no_space` | The session's memory or disk is short. |


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