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

# Upload data

> Upload files, directories, stdin streams, and resumable transfers into persistent volumes.

Upload directly to volumes when data should persist independently of any single
machine. Destinations are absolute volume-internal paths rooted at `/`.

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

  ```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",
      conflict="merge",
      ignore_patterns=["*.tmp"],
      max_concurrency=4,
  )
  print(result.file_count)
  ```

  ```bash CLI theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
  nullspace volume upload team-data ./datasets /datasets \
    --conflict merge \
    --exclude '*.tmp' \
    --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.
  # 1. Create the session (kind directory_tar).
  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"}'

  # 2. PUT each part (here a single part 0) to the returned upload id.
  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

  # 3. Complete to extract and publish the archive.
  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=`.

## Dispatch By Source Type

```python theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
volume.files.upload("./model.bin", "/models/model.bin")
volume.files.upload("./datasets", "/datasets")
```

`upload()` dispatches to `upload_file()` or `upload_dir()` based on the local
source type. Use the explicit helpers when you need file-only or directory-only
options.

## Resumable Uploads

<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 \
    --resumable always \
    --checksum auto \
    --concurrency 4

  nullspace volume upload team-data ./large.bin /models/large.bin --resume upload_123
  ```

  ```bash HTTP API theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
  # Resumable file uploads use the same volume upload-session primitives.
  # 1. Create the session (kind file, source restartable_file).
  curl -X POST "${NULLSPACE_API_URL}/v1/volumes/vol_123/uploads" \
    -H "Authorization: Bearer ${NULLSPACE_API_KEY}" \
    -H "Content-Type: application/json" \
    -d '{"kind": "file", "source_kind": "restartable_file", "target_path": "/models/large.bin", "content_length": 1048576, "conflict": "merge", "checksum_algorithm": "sha256"}'

  # 2. Upload each part; parts may be replayed and sent out of order.
  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

  # 3. Poll status to resume after an interruption, then complete.
  curl -X GET "${NULLSPACE_API_URL}/v1/volumes/vol_123/uploads/upload_123" \
    -H "Authorization: Bearer ${NULLSPACE_API_KEY}"
  curl -X POST "${NULLSPACE_API_URL}/v1/volumes/vol_123/uploads/upload_123/complete" \
    -H "Authorization: Bearer ${NULLSPACE_API_KEY}"
  ```
</CodeGroup>

File uploads support resumable transfer options such as `--resumable`,
`--checksum`, `--spool-to-disk`, and `--concurrency`.

## Upload From Stdin

```bash theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
tar -czf - ./dataset | nullspace volume upload team-data - /archives/dataset.tar.gz
```

Stdin uploads require a concrete destination file path, not a directory suffix.
Resumable stdin uploads require local spooling with `--spool-to-disk auto` or
`always`.

## Dry Run

```bash theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
nullspace volume upload team-data ./datasets /datasets --dry-run --json
```

Dry runs preview the effective upload plan without sending data.

## Signed Upload URL

<CodeGroup>
  ```python Python SDK theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
  grant = volume.files.upload_url("/models/model.bin")
  print(grant.url, grant.method, grant.headers)
  ```

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

  ```bash HTTP API theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
  curl -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"}'
  ```
</CodeGroup>

Use signed URLs when a browser, CI job, or another service should upload bytes
directly.

## Related

* [Download data](./download-data)
* [Read & write](./read-write)
* [CLI volume commands](../cli/manage-volumes)
