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

# Read & write

> Read, write, batch-write, move, remove, chmod, search, replace, and watch volume files.

Direct volume paths are absolute volume-internal paths rooted at `/`. Relative
paths are rejected, `..` cannot escape the volume root, and there is no machine
`cwd` or `user` context for direct volume operations.

## Read

<CodeGroup>
  ```python Python SDK theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
  from nullspace import Volume

  volume = Volume.from_name("team-data", create_if_missing=True)

  text = volume.files.read("/datasets/report.txt")
  raw = volume.files.read("/datasets/report.bin", format="bytes")
  ```

  ```bash CLI theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
  nullspace volume read team-data /datasets/report.txt
  nullspace volume read team-data /datasets/report.bin --encoding base64
  nullspace volume cat team-data /datasets/report.txt
  ```

  ```bash HTTP API theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
  curl -X POST "${NULLSPACE_API_URL}/v1/volumes/vol_123/files/read" \
    -H "Authorization: Bearer ${NULLSPACE_API_KEY}" \
    -H "Content-Type: application/json" \
    -d '{"path": "/datasets/report.txt"}'

  curl -X POST "${NULLSPACE_API_URL}/v1/volumes/vol_123/files/read" \
    -H "Authorization: Bearer ${NULLSPACE_API_KEY}" \
    -H "Content-Type: application/json" \
    -d '{"path": "/datasets/report.bin", "encoding": "base64"}'
  ```
</CodeGroup>

Use `cat` for UTF-8 text. Use `read --encoding base64` for binary data.

## Write

<CodeGroup>
  ```python Python SDK theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
  volume.files.make_dir("/datasets")
  volume.files.write("/datasets/report.txt", "ready\n")
  volume.files.write("/datasets/blob.bin", b"\x00\x01")
  ```

  ```bash CLI theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
  printf "ready\n" | nullspace volume write team-data /datasets/report.txt
  printf 'AAE=' | nullspace volume write team-data /datasets/blob.bin --encoding base64
  ```

  ```bash HTTP API theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
  curl -X POST "${NULLSPACE_API_URL}/v1/volumes/vol_123/files/mkdir" \
    -H "Authorization: Bearer ${NULLSPACE_API_KEY}" \
    -H "Content-Type: application/json" \
    -d '{"path": "/datasets"}'

  curl -X POST "${NULLSPACE_API_URL}/v1/volumes/vol_123/files/write" \
    -H "Authorization: Bearer ${NULLSPACE_API_KEY}" \
    -H "Content-Type: application/json" \
    -d '{"path": "/datasets/report.txt", "content": "ready\n"}'

  curl -X POST "${NULLSPACE_API_URL}/v1/volumes/vol_123/files/write" \
    -H "Authorization: Bearer ${NULLSPACE_API_KEY}" \
    -H "Content-Type: application/json" \
    -d '{"path": "/datasets/blob.bin", "content": "AAE=", "encoding": "base64"}'
  ```
</CodeGroup>

## Batch Write

<CodeGroup>
  ```python Python SDK theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
  from nullspace import BatchWriteError

  try:
      volume.files.write_files([
          ("/datasets/a.txt", "a\n"),
          ("/datasets/b.txt", "b\n"),
      ])
  except BatchWriteError as exc:
      print(exc.successes, exc.failures)
  ```

  ```bash CLI theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
  nullspace volume write-files team-data '[["/a.txt","a\n"],["/b.txt","b\n"]]'
  nullspace volume write-files team-data @files.json
  ```
</CodeGroup>

There is no dedicated volume batch-write REST route; over HTTP, issue one
`POST /v1/volumes/{id}/files/write` per file.

`write_files()` can partially succeed before raising `BatchWriteError`. Inspect
`successes` and `failures` before retrying.

## Move, Remove, And Permissions

<CodeGroup>
  ```python Python SDK theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
  volume.files.rename("/datasets/report.txt", "/datasets/report-old.txt")
  volume.files.set_permissions("/datasets/report-old.txt", "0644")
  volume.files.remove("/datasets/report-old.txt")
  ```

  ```bash CLI theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
  nullspace volume mv team-data /datasets/report.txt /datasets/report-old.txt
  nullspace volume chmod team-data /datasets/report-old.txt 0644
  nullspace volume rm team-data /datasets/report-old.txt
  ```

  ```bash HTTP API theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
  curl -X POST "${NULLSPACE_API_URL}/v1/volumes/vol_123/files/rename" \
    -H "Authorization: Bearer ${NULLSPACE_API_KEY}" \
    -H "Content-Type: application/json" \
    -d '{"old_path": "/datasets/report.txt", "new_path": "/datasets/report-old.txt"}'

  curl -X POST "${NULLSPACE_API_URL}/v1/volumes/vol_123/files/permissions" \
    -H "Authorization: Bearer ${NULLSPACE_API_KEY}" \
    -H "Content-Type: application/json" \
    -d '{"path": "/datasets/report-old.txt", "permissions": "0644"}'

  curl -X POST "${NULLSPACE_API_URL}/v1/volumes/vol_123/files/remove" \
    -H "Authorization: Bearer ${NULLSPACE_API_KEY}" \
    -H "Content-Type: application/json" \
    -d '{"path": "/datasets/report-old.txt"}'
  ```
</CodeGroup>

## Search And Replace

<CodeGroup>
  ```python Python SDK theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
  matches = volume.files.find_files("/datasets", "ready")
  paths = volume.files.search_files("/datasets", "*.txt")
  result = volume.files.replace_in_files(paths, "ready", "done", dry_run=True)
  print(result.dry_run, result.replacements, result.files_modified)
  ```

  ```bash CLI theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
  nullspace volume find team-data /datasets "ready"
  nullspace volume search team-data /datasets "*.txt"
  nullspace volume replace team-data "ready" "done" /datasets/report.txt --dry-run
  ```

  ```bash HTTP API theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
  curl -X POST "${NULLSPACE_API_URL}/v1/volumes/vol_123/files/find" \
    -H "Authorization: Bearer ${NULLSPACE_API_KEY}" \
    -H "Content-Type: application/json" \
    -d '{"path": "/datasets", "pattern": "ready"}'

  curl -X POST "${NULLSPACE_API_URL}/v1/volumes/vol_123/files/search" \
    -H "Authorization: Bearer ${NULLSPACE_API_KEY}" \
    -H "Content-Type: application/json" \
    -d '{"path": "/datasets", "pattern": "*.txt"}'

  curl -X POST "${NULLSPACE_API_URL}/v1/volumes/vol_123/files/replace" \
    -H "Authorization: Bearer ${NULLSPACE_API_KEY}" \
    -H "Content-Type: application/json" \
    -d '{"files": ["/datasets/report.txt"], "pattern": "ready", "replacement": "done", "dry_run": true}'
  ```
</CodeGroup>

`find_files()` searches file contents. `search_files()` matches file names.
`VolumeReplaceResult` includes replacement counts and matched files, so dry
runs can show planned changes before writing.

## Watch

<CodeGroup>
  ```python Python SDK theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
  def on_event(event):
      print(event.type, event.path)

  with volume.files.watch_dir("/", on_event=on_event, recursive=True):
      volume.files.write("/datasets/changed.txt", "changed\n")
  ```

  ```bash CLI theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
  nullspace volume watch team-data /datasets --recursive --timeout 30
  ```
</CodeGroup>

Volume watch is not a plain REST call: it streams change events over the volume
data plane rather than a single request/response endpoint, so there is no
`curl` equivalent.

Direct watches are opened against a concrete volume ID. If you look up a volume
by name, the SDK resolves it first and then watches that ID. In multi-host
deployments, mutations through another data-plane host may require a resync;
clients should treat `resync_required` as a prompt to list or stat the watched
tree again.

## Async

```python theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
from nullspace import AsyncVolume

volume = await AsyncVolume.from_name("team-data", create_if_missing=True)
await volume.files.write("/hello.txt", "persistent\n")
print(await volume.files.read("/hello.txt"))
```

## Related

* [File & directory metadata](./file-directory-metadata)
* [Upload data](./upload-data)
* [CLI volume commands](../cli/manage-volumes)
