oxen push uses for
large files: you hash the file, upload it in pieces, and ask the server to put those
pieces back together. Because each piece is its own request, the file’s size stops
mattering — only the size of one chunk does.
It also does something the single-request upload can’t: stage the assembled file
directly into a workspace, so it’s ready to commit — or to read
back — without ever committing it.
The three steps
All three take your API key as a bearer token:
The version id is the file’s hash
{version_id} is not an id you invent. It is the XXH3-128 hash of the file’s complete
contents, written as lowercase hexadecimal with leading zeros stripped — the format
Rust’s {:x} produces for a u128.
This matters because step 3 re-hashes the bytes it assembled and compares them to
{version_id}. Get the hash wrong and the upload is rejected; get it right and you have
a guarantee that what the server stored is exactly what you sent.
For files large enough that you don’t want them in memory, hash incrementally — every
XXH3 implementation supports feeding it the file in pieces. Reuse the same pieces you’re
about to upload and you read the file only once.
Step 1: Announce the upload
Tell the server what’s coming.dst_dir is optional and only used in step 3.
Request
200 means go ahead and upload chunks. A rejection means this content already exists
in the version store — file contents are addressed by their hash, so there is nothing
left to upload and you can skip to step 3.
Step 2: Upload the chunks
Send the file in slices. The body is the raw bytes of that slice — not multipart, not JSON — andoffset is the slice’s byte position in the complete file.
Request
Keep each chunk at or below 10 MiB, the server’s transfer segment size. Below that,
pick a size that fits whatever limit sits between you and the server — a chunk still has
to survive one ordinary HTTP request.
Step 3: Reassemble
Ask the server to join the chunks into the version file. It verifies the count, then verifies the hash.Request
Staging without committing
workspace_id is what makes this more than an upload. Provide it and the assembled file
is staged into that workspace at dst_dir/file_name — it shows up in
GET /workspaces/{workspace_id}/changes, it can be read back through
GET /workspaces/{workspace_id}/files/{path}, and it becomes part of the next commit
you make from that workspace.
Leave workspace_id out and the bytes simply live in the version store, addressed by
their hash, for you to reference later.
This means a large file can be uploaded, inspected, and even discarded without ever
entering the repository’s history.
Errors
Complete example
Uploads a file in 4 MB chunks and stages it into a workspace.Python
When to use this
Reach for chunked upload when any of these is true:- The file is larger than one request can carry. The most common reason. Serverless platforms in particular cap request bodies at a few megabytes.
- You want the upload to be resumable. Chunks are independent, so a failure costs you one chunk, not the whole file.
- You want parallelism. Offsets make the order irrelevant.
- You want the file staged but not committed.
workspace_idputs it in a workspace, where you can read it back or throw it away without touching history.