> ## 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.

# Volume Transfers

> Upload and download files or directories directly to persistent volumes.

Volume transfer destinations and sources use absolute volume-internal paths
rooted at `/`. They do not use machine `cwd` or machine user semantics.

## Upload a file

<CodeGroup>
  ```python Python SDK theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
  result = volume.files.upload_file(
      "./model.bin",
      "/models/model.bin",
      resumable=True,
      checksum="auto",
      max_concurrency=4,
  )
  print(result.bytes_uploaded, result.target_path)
  ```

  ```bash CLI theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
  nullspace volume upload team-data ./model.bin /models/model.bin --resumable always --concurrency 4
  ```

  ```bash HTTP API theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
  # 1. Mint a signed direct upload URL for the destination path.
  URL=$(curl -fsS -X POST "${NULLSPACE_API_URL}/v1/volumes/vol_123/files/upload-url" \
    -H "Authorization: Bearer ${NULLSPACE_API_KEY}" \
    -H "Content-Type: application/json" \
    -d '{"path": "/models/model.bin"}' | jq -r .url)

  # 2. Stream the file bytes to the signed URL with PUT.
  curl -X PUT "${URL}" \
    -H "Content-Type: application/octet-stream" \
    --data-binary @./model.bin
  ```
</CodeGroup>

## Upload a directory

<CodeGroup>
  ```python Python SDK theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
  result = volume.files.upload_dir("./datasets", "/datasets")
  print(result.file_count)
  ```

  ```bash CLI theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
  nullspace volume upload team-data ./models /models --concurrency 4
  ```

  ```bash HTTP API theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
  # Directory uploads stream a tar archive through a resumable volume upload session.
  tar -czf datasets.tar.gz ./datasets
  curl -X POST "${NULLSPACE_API_URL}/v1/volumes/vol_123/uploads" \
    -H "Authorization: Bearer ${NULLSPACE_API_KEY}" \
    -H "Content-Type: application/json" \
    -d '{"kind": "directory_tar", "source_kind": "directory_archive", "target_path": "/datasets", "content_length": '"$(wc -c < datasets.tar.gz)"', "conflict": "merge", "archive_format": "tar", "checksum_algorithm": "sha256"}'

  curl -X PUT "${NULLSPACE_API_URL}/v1/volumes/vol_123/uploads/upload_123/parts/0" \
    -H "Authorization: Bearer ${NULLSPACE_API_KEY}" \
    -H "Content-Type: application/octet-stream" \
    --data-binary @datasets.tar.gz

  curl -X POST "${NULLSPACE_API_URL}/v1/volumes/vol_123/uploads/upload_123/complete" \
    -H "Authorization: Bearer ${NULLSPACE_API_KEY}"
  ```
</CodeGroup>

Directory uploads support `conflict=`, `ignore_patterns=`, and
`max_concurrency=`. File uploads support resumable transfer options such as
`resumable`, `checksum`, and `spool_to_disk`. `volume.files.upload()` dispatches
to the file or directory helper based on the local source type.

## Resume upload

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

  try:
      volume.files.upload_file("./large.bin", "/models/large.bin", resumable=True)
  except FileUploadError as exc:
      if exc.upload_id is None:
          raise
      volume.files.resume_upload(exc.upload_id, "./large.bin")
  ```

  ```bash CLI theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
  nullspace volume upload team-data ./large.bin /models/large.bin --resume upload_...
  ```

  ```bash HTTP API theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
  # Resume by polling the upload session and re-PUTting any missing parts,
  # then completing. Parts may be safely replayed.
  curl -X GET "${NULLSPACE_API_URL}/v1/volumes/vol_123/uploads/upload_123" \
    -H "Authorization: Bearer ${NULLSPACE_API_KEY}"

  curl -X PUT "${NULLSPACE_API_URL}/v1/volumes/vol_123/uploads/upload_123/parts/0" \
    -H "Authorization: Bearer ${NULLSPACE_API_KEY}" \
    -H "Content-Type: application/octet-stream" \
    --data-binary @./large.bin

  curl -X POST "${NULLSPACE_API_URL}/v1/volumes/vol_123/uploads/upload_123/complete" \
    -H "Authorization: Bearer ${NULLSPACE_API_KEY}"
  ```
</CodeGroup>

## Download

<CodeGroup>
  ```python Python SDK theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
  volume.files.download_file("/models/model.bin", "./model.bin")
  volume.files.download_dir("/datasets", "./datasets-copy", overwrite=True)
  volume.files.download_dir("/datasets", "./datasets.tar", archive=True, overwrite=True)
  print(volume.files.download_url("/models/model.bin"))
  ```

  ```bash CLI theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
  nullspace volume download team-data /models/model.bin ./model.bin
  nullspace volume download team-data /datasets ./datasets-copy
  nullspace volume download team-data /datasets ./datasets.tar --archive --force
  nullspace volume download-url team-data /datasets --archive-format tar
  ```

  ```bash HTTP API theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
  # File download: mint a download URL, then GET the bytes.
  FILE_URL=$(curl -fsS -X POST "${NULLSPACE_API_URL}/v1/volumes/vol_123/files/download-url" \
    -H "Authorization: Bearer ${NULLSPACE_API_KEY}" \
    -H "Content-Type: application/json" \
    -d '{"path": "/models/model.bin"}' | jq -r .url)
  curl -fsS "${FILE_URL}" -o ./model.bin

  # Directory download: request a tar archive URL, then GET and extract.
  ARCHIVE_URL=$(curl -fsS -X POST "${NULLSPACE_API_URL}/v1/volumes/vol_123/files/download-url" \
    -H "Authorization: Bearer ${NULLSPACE_API_KEY}" \
    -H "Content-Type: application/json" \
    -d '{"path": "/datasets", "archive_format": "tar"}' | jq -r .url)
  curl -fsS "${ARCHIVE_URL}" -o ./datasets.tar
  mkdir -p ./datasets-copy && tar -xf ./datasets.tar -C ./datasets-copy
  ```
</CodeGroup>

Downloads never overwrite existing local paths unless `overwrite=True` is set
(CLI `--force` maps to SDK `overwrite=True`). Directory downloads fetch a tar
archive internally; by default the SDK extracts it, and `archive=True` keeps
the tar file instead.

## Related

* [Direct volume files](./files)
* [Uploads](../filesystem/uploads)
* [API Reference](../api-reference)
