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

# An actor's files

> Keep files in storage volumes, and give them to the runner jobs and off-chain functions an actor starts

This page shows you how to give an actor files to work from, by setting up a volume and building an actor that answers from a document in it.

An actor's files live in **volumes**: named, durable storage that you create and own. Actor code itself never opens a file. The work an actor starts does that for it: an [off-chain function](https://docs.cowboy.inc/build/where-your-code-runs.md#off-chain-functions) or a runner job gets the volumes the actor names, mounted as ordinary folders, while it runs.

## Public and private volumes

| | Public | Private |
| - | - | - |
| Who can read it | Anyone | You, and the actors and accounts you allow |
| Stored | As is | Encrypted before upload |
| Good for | Web pages, public datasets, shared docs | Documents, notes, anything personal |

You pick public or private when you create a volume, and can't change it. Either way, a volume pays for its own storage from money you put in it. [Your keys and your data](https://docs.cowboy.inc/hire/keys-and-data.md) says who can see what.

## 1. Create a volume and add a file

First connect your wallet to storage, once. It asks for a passphrase to protect the storage key it creates:

```bash theme={"system"}
cowboy cbfs auth setup --rpc-url https://rpc.cloud.cowboy.inc
```

```text Output theme={"system"}
wallet:    0x2858…423c
cbfs key:  0x0220…2e
expires:   1796377375268
rpc:       https://rpc.cloud.cowboy.inc
```

Then create the volume and upload a file:

```bash theme={"system"}
mkdir faq && echo 'Refunds take five days.' > faq/refunds.md
cowboy cbfs volume create --name handbook --public
cowboy cbfs put handbook faq/refunds.md faq/refunds.md
cowboy cbfs ls handbook
```

```text Output theme={"system"}
Created volume 'handbook'
  Volume ID:  75c1…efd2
  Visibility: Public
  Erasure:    3+2
  Nodes:      5
  Escrow:     0.369725 CBY escrow, about 127271 days of rent at 1 MiB stored; …
faq/refunds.md (24 B) -> handbook
PATH                                                   SIZE HASH
faq/refunds.md                                         24 B ec172694cbb26986
```

`volume create` creates the volume and pays for its first stretch of storage from your wallet, and `put <volume> <local file> <path in the volume>` uploads a file. `cowboy cbfs cat handbook faq/refunds.md` prints one file back.

When the volume needs more storage, `cowboy cbfs volume deposit handbook --amount 1` adds 1 CBY.

Leave out `--public` to create a private volume. An actor can use your private volume only after you allow it:

```bash theme={"system"}
cowboy cbfs volume allow handbook --actor <actor address> --mode ro
```

```text Output theme={"system"}
Granted mount on 'handbook' for:
  actor 0x2858…c20c
  Allowlist now holds 1 grant(s)
```

## 2. Read it from an off-chain function

An off-chain function is your own Python, run in a container, and `cowboy.toml` declares the volumes it gets. Runner jobs can take volumes too, with `volumes=` ([reference](https://docs.cowboy.inc/developers/sdk-reference.md#agent)), but this page uses a function.

```bash theme={"system"}
cowboy init reader --dir library --offchain
cd library
rm offchain/primes.py offchain/tests/test_primes.py tests/test_reader.py
```

```text Output theme={"system"}
✓ Project initialized.
→ Preparing Python environment
✓ Python environment ready · .venv (0.1s)
→ Preparing off-chain environment
✓ Off-chain environment ready · .cowboy/local/offchain-env (0.2s)

Yeehaw! Project ready: /path/to/library
```

The last line removes the starter function and tests you won't use.

Replace `cowboy.toml` with the following, using your wallet's address as `owner`:

```toml theme={"system"}
schema_version = 2

[offchain]

[[offchain.volumes]]
name = "handbook"
owner = "0x4444444444444444444444444444444444444444"
files = ["faq/refunds.md"]
```

`name` and `owner` say which volume: the volume called `handbook` that this address owns. A volume declared this way is mounted read-only. `files` lists the files `cowboy run` downloads for a local run, and doesn't limit what the deployed function can read.

Write the function in `offchain/handbook.py`:

```python theme={"system"}
import os
from pathlib import Path


def read_page(path: str) -> str:
    if not isinstance(path, str) or ".." in path.split("/"):
        raise ValueError("send a path inside the handbook")
    root = Path(os.environ.get("COWBOY_VOLUME_ROOT", "/mnt/volumes"))
    return (root / "handbook" / path).read_text(encoding="utf-8")
```

Each volume appears as a folder with the volume's name, under `/mnt/volumes`. A local run sets `COWBOY_VOLUME_ROOT` to a folder of its own instead.

Export the function in `offchain/__init__.py`:

```python theme={"system"}
from .handbook import read_page as read_page
```

Replace `actors/reader/main.py` with the actor that calls it:

```python theme={"system"}
from cowboy_sdk import Actor, OffchainError, public

from offchain import read_page


class ReaderActor(Actor):
    @public
    async def page(self, path: object) -> str:
        if not isinstance(path, str) or not path.endswith(".md"):
            raise ValueError("send the path of a .md page")
        try:
            text = await read_page(path)
        except OffchainError as failure:
            return "unavailable: " + str(failure)
        self.storage["last_read"] = path
        return text
```

```bash theme={"system"}
cowboy check
```

```text Output theme={"system"}
✓ Checked reader
  …
Cost per off-chain call:
  Resource tier: small-ephemeral (default)
```

`cowboy check` also prints how one call is priced. The actor's balance pays for each call.

## 3. Test it

Test the function with a folder standing in for the volume, in `offchain/tests/test_handbook.py`:

```python theme={"system"}
from pathlib import Path

import pytest

from offchain.handbook import read_page


def test_reads_a_page_from_the_volume(
    tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    page = tmp_path / "handbook" / "faq" / "refunds.md"
    page.parent.mkdir(parents=True)
    page.write_text("Refunds take five days.", encoding="utf-8")
    monkeypatch.setenv("COWBOY_VOLUME_ROOT", str(tmp_path))
    assert read_page("faq/refunds.md") == "Refunds take five days."


def test_refuses_a_path_outside_the_volume() -> None:
    with pytest.raises(ValueError):
        read_page("../secrets.txt")
```

```bash theme={"system"}
cowboy test
```

```text Output theme={"system"}
offchain/tests/test_handbook.py ..                                       [100%]
============================== 2 passed in 0.02s ===============================
```

Then run the actor with the function's answer stubbed, so no volume is needed:

```bash theme={"system"}
cowboy run --handler page --payload-json '"faq/refunds.md"' \
  --stub 'read_page="Refunds take five days."'
```

```text Output theme={"system"}
0x7752…2e (text "wRefunds take five days." or CBOR "Refunds take five days.")
```

The `CBOR` reading is the handler's answer. Without `--stub`, `cowboy run` downloads the file from the real volume, so it needs a network.

## Next

* [Call models, the web and tools](https://docs.cowboy.inc/developers/submitting-jobs.md)
* [Your keys and your data](https://docs.cowboy.inc/hire/keys-and-data.md)


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