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

Upload A File

result = volume.files.upload_file(
    "./model.bin",
    "/models/model.bin",
    resumable=True,
    checksum="auto",
    max_concurrency=4,
)
print(result.bytes_uploaded, result.target_path)
nullspace volume upload team-data ./model.bin /models/model.bin
# 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

Upload A Directory

result = volume.files.upload_dir(
    "./datasets",
    "/datasets",
    conflict="merge",
    ignore_patterns=["*.tmp"],
    max_concurrency=4,
)
print(result.file_count)
nullspace volume upload team-data ./datasets /datasets \
  --conflict merge \
  --exclude '*.tmp' \
  --concurrency 4
# 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}"
Directory uploads support conflict=, ignore_patterns=, and max_concurrency=.

Dispatch By Source Type

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

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")
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
# 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}"
File uploads support resumable transfer options such as --resumable, --checksum, --spool-to-disk, and --concurrency.

Upload From Stdin

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

nullspace volume upload team-data ./datasets /datasets --dry-run --json
Dry runs preview the effective upload plan without sending data.

Signed Upload URL

grant = volume.files.upload_url("/models/model.bin")
print(grant.url, grant.method, grant.headers)
nullspace volume upload-url team-data /models/model.bin
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"}'
Use signed URLs when a browser, CI job, or another service should upload bytes directly.