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

# Files

> Upload input files once, attach them read-only to any session, show them to the agent in a message, and download the deliverables sessions save.

Files are your organization's file library. It holds two kinds of file:

* **Uploads.** Files you add, such as a data set, a spec, or a rubric. Attach an upload to any number of sessions, read-only.
* **Session outputs.** Deliverables an agent saves in a session. They're kept with the session, and you can download them.

The TypeScript samples assume the `rl` client from [Client setup](/managed-agents/api#client-setup). Uploading and downloading bytes are plain HTTP calls, so those samples use cURL.

## Before you begin

* Listing and downloading files needs read permission. Uploading needs create, attaching to or detaching from a session needs update, and deleting needs delete. Organization developers and admins have all of them. See [Organizations and roles](/managed-agents/organizations-and-roles).
* Attaching files needs an environment with a sandbox, which every environment has by default.

## How files reach a session

```mermaid theme={"theme":"css-variables"}
flowchart LR
  upload["Upload a file"] --> library["Files library"]
  library -->|"attach at start or while running"| inputs["/workspace/.managed-agents/files"]
  library -->|"name it in a message"| message["Image or document in the message"]
  agent["Agent saves a deliverable"] --> outputs["/workspace/.managed-agents/outputs"]
  outputs -->|"kept after each turn"| library
```

An attached file is copied into the sandbox under `/workspace/.managed-agents/files`, read-only. The agent knows it's there and can read it with `read_file` or from a shell. Each attachment takes the file's content as it is when you attach it, so later changes to the library don't change a running session.

## Upload a file

<Tabs>
  <Tab title="Console">
    1. In the sidebar, click **Files**, then click **Upload file**.
    2. Choose one or more files.
    3. Optionally set **Expires** to **1 day**, **7 days**, **30 days**, or **90 days**. The default is **Never**.
    4. Optionally describe the files in **Purpose**. It's stored with each file.
    5. Click **Upload**.
  </Tab>

  <Tab title="cURL">
    ```bash theme={"theme":"css-variables"}
    curl -X POST 'https://api.recursion.labelbox.com/managed-agents/v1/files' \
      -H "Authorization: Bearer $RECURSION_API_KEY" \
      -F 'file=@signups.csv;type=text/csv' \
      -F 'metadata={"purpose":"Q3 signup analysis"}'
    ```
  </Tab>
</Tabs>

The response is `201` with the file:

```json theme={"theme":"css-variables"}
{
  "type": "file",
  "file_id": "9f8b1c2d-3e4f-5a6b-7c8d-9e0f1a2b3c4d",
  "filename": "signups.csv",
  "media_type": "text/csv",
  "byte_size": 48213,
  "sha256": "7d3b6e2a8c5f0d1b9e7a3c6f2d8b4e0a1c7f5d934f1c9a0e7b2d5836c1e04a9f",
  "source": "upload",
  "downloadable": true,
  "metadata": { "purpose": "Q3 signup analysis" },
  "organization_id": "c0ffee00-1234-4abc-9def-001122334455",
  "created_at": "2026-09-25T10:15:03Z"
}
```

Send the bytes as the `file` part of a `multipart/form-data` body. The part's file name becomes `filename`, and its `Content-Type` becomes `media_type`; when the part has no type, the type is detected from the file name and then the bytes. Optional parts:

* `metadata`: a JSON object, stored with the file and returned unchanged. A value that doesn't parse is refused.
* `expires_in_seconds`: from `3600` (an hour) to `7776000` (90 days). After that the file can't be attached or downloaded. Leave it out for a file that doesn't expire. You can't change it later.

## List and find files

<Tabs>
  <Tab title="Console">
    **Files** lists every file with its **Name**, **Type**, **Size**, **Added**, and **Expires**. Search by name, media type, or id, and use **Filter by source** to show **All files**, **Uploads**, or **Session outputs**.
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={"theme":"css-variables"}
    const { files, next_page_token } = await rl.managedAgents.listFiles({
      source: 'upload',
      limit: 100,
    });
    ```
  </Tab>

  <Tab title="cURL">
    ```bash theme={"theme":"css-variables"}
    curl 'https://api.recursion.labelbox.com/managed-agents/v1/files?source=upload&limit=100' \
      -H "Authorization: Bearer $RECURSION_API_KEY"
    ```
  </Tab>
</Tabs>

Files come back newest first, 200 per page by default and up to 1,000, with `next_page_token` while more follow. Pass it back as `page_token` with the same filters.

| Query              | Returns                                                                                |
| ------------------ | -------------------------------------------------------------------------------------- |
| `source`           | `upload` or `session_output`.                                                          |
| `scope_session_id` | The outputs of one session.                                                            |
| `file_ids`         | Up to 100 specific files, in one page. Can't be combined with `limit` or `page_token`. |

An expired file stays listed, with `expires_at` in the past, until it's removed after a grace period. To show only files you can still attach, skip files whose `expires_at` has passed. `getFile` returns one file's metadata.

## Attach files when a session starts

Name files in `resources` when you start the session. They're in the sandbox before the agent's first step.

<Tabs>
  <Tab title="Console">
    1. In the sidebar, click **Sessions**, then click **Launch session**.
    2. Choose the agent and environment, and write the opening message.
    3. Under **Files**, add each file and, optionally, the path it should have.
    4. Click **Launch session**.
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={"theme":"css-variables"}
    const session = await rl.managedAgents.startSession({
      'Idempotency-Key': 'signups-summary-2026-09-25',
      body: {
        agent_id: '5f0c2a1e-8b7d-4c3a-9e21-6d4f0b9a7c55',
        environment_id: '0d5e8a2c-6f13-4b97-a4e0-3c7f9b1d5e62',
        message: 'Summarize the weekly trend in data/signups.csv.',
        resources: [
          { type: 'file', file_id: '9f8b1c2d-3e4f-5a6b-7c8d-9e0f1a2b3c4d', relative_path: 'data/signups.csv' },
        ],
      },
    });
    ```
  </Tab>

  <Tab title="cURL">
    ```bash theme={"theme":"css-variables"}
    curl -X POST 'https://api.recursion.labelbox.com/managed-agents/v1/sessions' \
      -H "Authorization: Bearer $RECURSION_API_KEY" \
      -H 'Content-Type: application/json' \
      -H 'Idempotency-Key: signups-summary-2026-09-25' \
      -d '{
        "agent_id": "5f0c2a1e-8b7d-4c3a-9e21-6d4f0b9a7c55",
        "environment_id": "0d5e8a2c-6f13-4b97-a4e0-3c7f9b1d5e62",
        "message": "Summarize the weekly trend in data/signups.csv.",
        "resources": [
          { "type": "file", "file_id": "9f8b1c2d-3e4f-5a6b-7c8d-9e0f1a2b3c4d", "relative_path": "data/signups.csv" }
        ]
      }'
    ```
  </Tab>
</Tabs>

`relative_path` places the file under the files directory; it defaults to the file's own name. It must be unique in the session and can't contain `.` or `..` segments. The start is all-or-nothing: one unknown, expired, or out-of-organization `file_id` refuses the whole request with `404`, and no session is created.

## Attach and detach files while a session runs

<Tabs>
  <Tab title="Console">
    1. Open the session and choose the **Files** tab in the side panel.
    2. Click **Attach files**, choose the files, and click **Attach**.
    3. To detach one, open its row's menu and choose to detach it.
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={"theme":"css-variables"}
    const { resources } = await rl.managedAgents.addSessionResources({
      session_id: 'e3a91f5c-7d24-4b68-9c10-2f8e6b4d7a53',
      body: {
        resources: [{ type: 'file', file_id: '9f8b1c2d-3e4f-5a6b-7c8d-9e0f1a2b3c4d' }],
      },
    });
    await rl.managedAgents.deleteSessionResource({
      session_id: 'e3a91f5c-7d24-4b68-9c10-2f8e6b4d7a53',
      resource_id: resources[0].resource_id,
    });
    ```
  </Tab>

  <Tab title="cURL">
    ```bash theme={"theme":"css-variables"}
    curl -X POST 'https://api.recursion.labelbox.com/managed-agents/v1/sessions/e3a91f5c-7d24-4b68-9c10-2f8e6b4d7a53/resources' \
      -H "Authorization: Bearer $RECURSION_API_KEY" \
      -H 'Content-Type: application/json' \
      -d '{"resources": [{"type": "file", "file_id": "9f8b1c2d-3e4f-5a6b-7c8d-9e0f1a2b3c4d"}]}'
    curl -X DELETE 'https://api.recursion.labelbox.com/managed-agents/v1/sessions/e3a91f5c-7d24-4b68-9c10-2f8e6b4d7a53/resources/4a7c2e91-6b35-4d08-9f1e-3c8a5b7d2e64' \
      -H "Authorization: Bearer $RECURSION_API_KEY"
    ```
  </Tab>
</Tabs>

Attach returns `201` with only the resources it added:

```json theme={"theme":"css-variables"}
{
  "resources": [
    {
      "type": "file",
      "resource_id": "4a7c2e91-6b35-4d08-9f1e-3c8a5b7d2e64",
      "file_id": "9f8b1c2d-3e4f-5a6b-7c8d-9e0f1a2b3c4d",
      "filename": "signups.csv",
      "media_type": "text/csv",
      "byte_size": 48213,
      "sha256": "7d3b6e2a8c5f0d1b9e7a3c6f2d8b4e0a1c7f5d934f1c9a0e7b2d5836c1e04a9f",
      "relative_path": "signups.csv",
      "created_at": "2026-09-25T10:21:40Z"
    }
  ]
}
```

**What success means:** the file is placed in the sandbox on the session's next tool call, and the agent can see it from its next turn. `mount_path` appears on the resource once it's in place. Read it with `listSessionResources` or `getSessionResource`.

* Attach is all-or-nothing. An unknown file refuses the batch with `404`. A path that collides with another attachment, or nests inside one, refuses it with `409`.
* Attach to the root session of a multi-agent tree. A subagent or teammate session answers `409`; everyone in the tree shares the root's sandbox.
* You can attach to a finished root session. Its next message resumes it with the file already in place.
* Detach stops listing the file. A copy already in the sandbox stays, because the agent may be reading it.

## Show a file to the agent in a message

To have the agent look at an image or read a short document as part of a message, name the file in the message instead of attaching it. The file is read when the message is accepted.

<Tabs>
  <Tab title="Console">
    In the session's message box, click **Attach from Files**, choose an image or a text document, then send the message.
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={"theme":"css-variables"}
    await rl.managedAgents.sendSessionEvents({
      session_id: 'e3a91f5c-7d24-4b68-9c10-2f8e6b4d7a53',
      body: {
        events: [
          {
            type: 'user.message',
            content: [
              { type: 'text', text: 'Does this chart match the numbers in the CSV?' },
              { type: 'image', source: { type: 'file', file_id: '2b6e9d41-8c37-4f05-a1d2-7e9c3b5f8a60' } },
            ],
          },
        ],
      },
    });
    ```
  </Tab>

  <Tab title="cURL">
    ```bash theme={"theme":"css-variables"}
    curl -X POST 'https://api.recursion.labelbox.com/managed-agents/v1/sessions/e3a91f5c-7d24-4b68-9c10-2f8e6b4d7a53/events' \
      -H "Authorization: Bearer $RECURSION_API_KEY" \
      -H 'Content-Type: application/json' \
      -d '{"events": [{"type": "user.message", "content": [
        {"type": "text", "text": "Does this chart match the numbers in the CSV?"},
        {"type": "image", "source": {"type": "file", "file_id": "2b6e9d41-8c37-4f05-a1d2-7e9c3b5f8a60"}}
      ]}]}'
    ```
  </Tab>
</Tabs>

Use `"type": "image"` for a PNG, JPEG, or WebP image the model sees, and `"type": "document"` for a text file, which is placed in the message as text. To send an image that isn't in your library, see [Send an image](/managed-agents/sessions#send-an-image).

## Download session deliverables

Every deliverable an agent saves in `/workspace/.managed-agents/outputs` is kept as a session output after each turn. See [Deliverables and artifacts](/managed-agents/artifacts) for what's kept and when.

<Tabs>
  <Tab title="Console">
    Open the session and choose the **Files** tab in the side panel. Outputs are listed with their paths. Click one to preview it, or download it. **Files** in the sidebar also lists every session's outputs under **Session outputs**, with **Open session** on each.
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={"theme":"css-variables"}
    const { files } = await rl.managedAgents.listFiles({
      source: 'session_output',
      scope_session_id: 'e3a91f5c-7d24-4b68-9c10-2f8e6b4d7a53',
    });
    ```
  </Tab>

  <Tab title="cURL">
    ```bash theme={"theme":"css-variables"}
    curl 'https://api.recursion.labelbox.com/managed-agents/v1/files?source=session_output&scope_session_id=e3a91f5c-7d24-4b68-9c10-2f8e6b4d7a53' \
      -H "Authorization: Bearer $RECURSION_API_KEY"
    curl 'https://api.recursion.labelbox.com/managed-agents/v1/files/5c1e8a3f-2d97-4b60-8e14-a6f3c9d2b7e05/content' \
      -H "Authorization: Bearer $RECURSION_API_KEY" \
      -o risks.md
    ```
  </Tab>
</Tabs>

A session output has `source: "session_output"`, `scope_session_id` set to the root session, and its path under the outputs directory in `metadata.path`:

```json theme={"theme":"css-variables"}
{
  "type": "file",
  "file_id": "5c1e8a3f-2d97-4b60-8e14-a6f3c9d2b7e05",
  "filename": "risks.md",
  "media_type": "text/markdown",
  "byte_size": 3120,
  "sha256": "c1e04a9f7d3b6e2a8c5f0d1b9e7a3c6f2d8b4e0a1c7f5d934f1c9a0e7b2d5836",
  "source": "session_output",
  "downloadable": true,
  "scope_session_id": "e3a91f5c-7d24-4b68-9c10-2f8e6b4d7a53",
  "metadata": { "path": "reports/risks.md" },
  "organization_id": "c0ffee00-1234-4abc-9def-001122334455",
  "created_at": "2026-09-25T10:42:18Z"
}
```

`getFileContent` returns the bytes with the file's media type and a `Content-Disposition` that names it. It works the same for uploads and outputs. Subagent and teammate deliverables are listed under the root session. Compare the download with `sha256` to check it.

## Delete a file

<Tabs>
  <Tab title="Console">
    Open the file's row menu in **Files**, choose **Delete file**, and confirm.
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={"theme":"css-variables"}
    await rl.managedAgents.deleteFile({ file_id: '9f8b1c2d-3e4f-5a6b-7c8d-9e0f1a2b3c4d' });
    ```
  </Tab>

  <Tab title="cURL">
    ```bash theme={"theme":"css-variables"}
    curl -X DELETE 'https://api.recursion.labelbox.com/managed-agents/v1/files/9f8b1c2d-3e4f-5a6b-7c8d-9e0f1a2b3c4d' \
      -H "Authorization: Bearer $RECURSION_API_KEY"
    ```
  </Tab>
</Tabs>

```json theme={"theme":"css-variables"}
{ "deleted": true }
```

The file stops being listed at once, and new attachments and downloads answer `404`. Its stored content is removed after a grace period. Until then, a session that attached it before the delete still receives it on later turns and resumes; after that, the session continues without it and records a warning. To stop a running session using a file, detach it as well.

## What can go wrong

| Code or symptom                                             | Cause                                                                                                                                         | Fix                                                                                                     |
| ----------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- |
| `400 invalid_request` on upload                             | The `file` part is missing, `metadata` isn't a JSON object, or `expires_in_seconds` is out of range.                                          | Send one `file` part, valid JSON metadata, and an expiry from 3600 to 7776000.                          |
| `400 invalid_request` on `file`, or `413 payload_too_large` | The file is over 64 MiB.                                                                                                                      | Split it, or have the agent fetch it from its source.                                                   |
| `413 file_quota_exceeded`                                   | The upload would take your organization's live files past its storage quota. `details` has `quota_bytes`, `used_bytes`, and `incoming_bytes`. | Delete files you no longer need, or set expiries on uploads.                                            |
| `404 not_found` on `file_id`                                | The file doesn't exist in your organization, was deleted, or has expired.                                                                     | Use a live file id from `listFiles`.                                                                    |
| `409 conflict` attaching                                    | A `relative_path` collides with or nests inside another attachment, or the session isn't the root of its tree.                                | Choose another path, or attach to the root session.                                                     |
| `400 invalid_request` on a message's `source.file_id`       | The file isn't an image on an image block, or is binary or too large on a document block.                                                     | Send PNG, JPEG, or WebP images, and text documents up to 512 KiB.                                       |
| The agent can't find an attached file                       | It was attached mid-turn.                                                                                                                     | It's in place on the next tool call and visible from the next turn. Check `mount_path` on the resource. |
| A deliverable isn't in the outputs                          | The agent saved it somewhere else, or it broke a capture rule.                                                                                | See [Deliverables and artifacts](/managed-agents/artifacts#what-can-go-wrong).                          |

For every error code, see [Errors](/managed-agents/errors).

## Limits

| Limit                              | Value                           |
| ---------------------------------- | ------------------------------- |
| One upload                         | 64 MiB                          |
| Live files per organization        | 10 GiB in total                 |
| File expiry                        | 1 hour to 90 days, or none      |
| Files attached in one request      | 500                             |
| Files listed per page              | 1 to 1,000, default 200         |
| `file_ids` lookup                  | 100 ids                         |
| Documents in one message           | 512 KiB each, 1 MiB per message |
| Library files named in one message | 100 blocks and 32 MiB           |

See [Limits](/managed-agents/limits) for deliverable capture limits and every other limit.

## Next steps

<CardGroup cols={2}>
  <Card title="Deliverables and artifacts" href="/managed-agents/artifacts">
    Ask for named deliverables and see how they're kept.
  </Card>

  <Card title="Sessions" href="/managed-agents/sessions">
    Start sessions, send messages, and attach images.
  </Card>

  <Card title="Outcomes" href="/managed-agents/outcomes">
    Grade sessions against a rubric, including one from a file.
  </Card>

  <Card title="Automations" href="/managed-agents/automations">
    Mount the same files into every automated run.
  </Card>
</CardGroup>
