Use write() for in-memory strings and bytes. Use upload_file() for local files, upload_dir() for directories, and upload() when you want the SDK to dispatch by source type. upload() accepts local file paths, directories, file-like objects, and stdin style streams. Directory-specific options such as conflict and ignore_patterns belong on upload_dir(); resumable options such as resumable, checksum, and spool_to_disk belong on upload_file().

Write many small files

machine.files.write_files([
    ("/workspace/app.py", "print('hello')\n"),
    ("/workspace/README.md", "# Demo\n"),
])
nullspace machine file write-files mch_... '[["/workspace/app.py","print(\"hello\")\n"],["/workspace/README.md","# Demo\n"]]'
# batch-write is a multipart request: a JSON `manifest` part plus one part per file field.
printf "print('hello')\n" > app.py
printf '# Demo\n' > README.md
curl -X POST "${NULLSPACE_API_URL}/v1/machines/mch_123/files/batch-write" \
  -H "Authorization: Bearer ${NULLSPACE_API_KEY}" \
  -F 'manifest={"files":[{"path":"/workspace/app.py","field":"file0"},{"path":"/workspace/README.md","field":"file1"}]}' \
  -F "file0=@app.py" \
  -F "file1=@README.md"

Upload one file

result = machine.files.upload_file("./dist/app.tar.gz", "/workspace/app.tar.gz")
print(result.transport, result.target_path, result.bytes_uploaded)
nullspace machine upload mch_... ./dist/app.tar.gz /workspace/app.tar.gz
# stream from stdin with "-" as the source:
cat ./dist/app.tar.gz | nullspace machine upload mch_... - /workspace/app.tar.gz
# 1. Mint a signed upload URL for the destination path.
URL=$(curl -fsS -X POST "${NULLSPACE_API_URL}/v1/machines/mch_123/files/upload-url" \
  -H "Authorization: Bearer ${NULLSPACE_API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{"path": "/workspace/app.tar.gz"}' | 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 @./dist/app.tar.gz
Upload destinations must be valid machine paths. Do not upload into /workspace/.nullspace, which is reserved for runtime metadata and resumable transfer state.

Upload a directory

result = machine.files.upload_dir(
    "./src",
    "/workspace/src",
    conflict="merge",
    ignore_patterns=["*.pyc", "!pkg/__init__.py"],
)
print(result.file_count, result.target_path)
nullspace machine upload mch_... ./src /workspace/src --exclude '*.pyc'
# Directory uploads stream a tar archive through a resumable upload session.
# 1. Create the session (kind directory_tar).
tar -czf src.tar.gz ./src
curl -X POST "${NULLSPACE_API_URL}/v1/machines/mch_123/uploads" \
  -H "Authorization: Bearer ${NULLSPACE_API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{"kind": "directory_tar", "source_kind": "directory_archive", "target_path": "/workspace/src", "content_length": '"$(wc -c < src.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/machines/mch_123/uploads/upload_123/parts/0" \
  -H "Authorization: Bearer ${NULLSPACE_API_KEY}" \
  -H "Content-Type: application/octet-stream" \
  --data-binary @src.tar.gz

# 3. Complete to extract and publish the archive.
curl -X POST "${NULLSPACE_API_URL}/v1/machines/mch_123/uploads/upload_123/complete" \
  -H "Authorization: Bearer ${NULLSPACE_API_KEY}"
Directory uploads honor .nullspaceignore from the source root and append any explicit ignore_patterns after it. Conflict policies control how existing destination files are handled; use "merge" for normal sync-style uploads and choose stricter policies when automation must fail on pre-existing paths.

Resumable upload and progress

from nullspace import FileUploadError

def on_progress(event) -> None:
    print(event.phase, event.bytes_completed, event.bytes_total, event.transport)

try:
    result = machine.files.upload_file(
        "./large-model.bin",
        "/data/model.bin",
        resumable=True,
        checksum="auto",
        spool_to_disk="auto",
        max_concurrency=4,
        progress=on_progress,
    )
except FileUploadError as exc:
    if exc.upload_id is None:
        raise
    result = machine.files.resume_upload(
        exc.upload_id,
        "./large-model.bin",
        progress=on_progress,
    )

print(result.upload_id, result.bytes_uploaded)
nullspace machine upload mch_... ./large.bin /data/large.bin \
  --resumable always --checksum auto --concurrency 4
# resume an interrupted transfer by upload id:
nullspace machine upload mch_... ./large.bin /data/large.bin --resume upload_...
# Resumable file uploads use the same upload-session primitives.
# 1. Create the session (kind file, source restartable_file).
curl -X POST "${NULLSPACE_API_URL}/v1/machines/mch_123/uploads" \
  -H "Authorization: Bearer ${NULLSPACE_API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{"kind": "file", "source_kind": "restartable_file", "target_path": "/data/model.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/machines/mch_123/uploads/upload_123/parts/0" \
  -H "Authorization: Bearer ${NULLSPACE_API_KEY}" \
  -H "Content-Type: application/octet-stream" \
  --data-binary @./large-model.bin

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

Signed upload URL

grant = machine.files.upload_url("/workspace/input.bin")
print(grant.url, grant.method, grant.headers)
Use a signed upload URL when a browser or another service should upload bytes directly. Use machine.upload_url(path) as a convenience wrapper around machine.files.upload_url(path).url. The signed upload grant includes the URL plus request metadata such as the HTTP method and required headers. Forward those fields to the client performing the direct upload.