Upload from a browser
Open /upload and select an upload-enabled PyPI index. Enter its upload token, then choose one .whl or .tar.gz
file. The page reports transfer progress and the server's rejection rule beside the filename. The token remains in the
page's password field and request memory until reload; peryx writes no browser storage or URL parameter.
The selected index needs a hosted upload target whose token grants write access to the package. A hosted index receives
the file itself. A writable virtual index receives the file through its configured upload layer, under the same route
used by twine upload and uv publish. A cached index and a virtual index without an upload target do not appear in
the selector.
Peryx sends the browser's File through FormData. The browser streams that file to the existing legacy upload
endpoint; the page does not read the artifact into WebAssembly memory. Peryx derives the legacy form identity with its
package-filename parser, then runs the multipart staging and package validation used for twine and uv. Both paths use
the same metadata transaction and blob commit.
The index's max_file_size_bytes policy applies while the multipart body streams. A rejected or cancelled transfer
removes its unpublished stage, and any failure before the commit leaves no package release in the index. A storage
failure produces a bounded message in the page; it does not return a temporary path or storage error detail to the
operator's browser.
CSRF check
The page sends X-Peryx-CSRF with the browser's current origin. Peryx requires that value to match the Origin header
and target host, and rejects a request whose Sec-Fetch-Site header is not same-origin. A cross-origin HTML form
cannot set the custom header. Standard publishing clients send no browser Origin header, so their upload requests keep
the existing authentication behavior.
Do not place the web UI behind a proxy that rewrites Host away from the public origin. Preserve the incoming host so
peryx can compare it with the browser's origin. A rejected check returns browser upload rejected and publishes
nothing.
Cancellation
Cancel aborts the active XMLHttpRequest. If the server is still reading the multipart body, the read fails and peryx
deletes the staged bytes. The page disables the button after it receives the server response because the commit has
finished by then.
Differences from twine and uv
The browser page handles one wheel or modern .tar.gz source distribution per request. Use twine or uv for release
automation and multiple files; those clients also support .zip source distributions and uploaded attestations. They
provide the legacy form identity and optional digests themselves. The browser path derives the identity from the
filename and lets peryx compute the content digest during staging. Both paths finish in the same validator and
transaction.