PEP 694 – Upload 2.0 API for Python Package Indexes
- Author:
- Barry Warsaw <barry at python.org>, Donald Stufft <donald at stufft.io>, Ee Durbin <ee at python.org>
- PEP-Delegate:
- Dustin Ingram <di at python.org>
- Discussions-To:
- Discourse thread
- Status:
- Draft
- Type:
- Standards Track
- Topic:
- Packaging
- Created:
- 11-Jun-2022
- Post-History:
- 27-Jun-2022, 06-Jan-2025 14-Apr-2025 06-Aug-2025 27-Sep-2025
Table of Contents
- Abstract
- Rationale
- Legacy API
- Upload 2.0 API Specification
- Versioning
- Content Types
- Root Endpoint
- Authentication for Upload 2.0 API
- Errors
- Publishing Session
- File Upload Session
- Stage Previews
- File Upload Mechanisms
- FAQ
- Open Questions
- Change History
- Copyright
Abstract
This PEP proposes an extensible API for uploading files to a Python package index such as PyPI. Along with standardization, the upload API provides additional useful features such as support for:
- a publishing session, which can be used to simultaneously publish all wheels in a package release;
- “staging” a release, which can be used to test uploads before publicly publishing them, without the need for test.pypi.org;
- artifacts which can be overwritten and replaced, until a session is published;
- detailed status on the state of artifact uploads;
- new project creation without requiring the uploading of an artifact.
- a protocol to extend the supported upload mechanisms in the future without requiring a full PEP; these can be standardized and recommended for all indexes, or be index-specific;
Once this new upload API is adopted, the existing legacy API can be deprecated, however this PEP does not propose a deprecation schedule for the legacy API.
Rationale
There is currently no standardized API for uploading files to a Python package index such as PyPI. Instead, everyone has been forced to reverse engineer the existing “legacy” API.
The legacy API, while functional, leaks implementation details of the original PyPI code base, which has been faithfully replicated in the new code base and alternative implementations.
In addition, there are a number of major issues with the legacy API:
- It is fully synchronous, which forces requests to be held open both for the upload itself, and while the index processes the uploaded file to determine success or failure.
- It does not support any mechanism for parallelizing or resuming an upload. With the largest default file size on PyPI being around 1GB in size, requiring the entire upload to complete successfully means bandwidth is wasted when such uploads experience a network interruption while the request is in progress.
- The atomic unit of operation is a single file. This is problematic when a release logically includes an sdist and multiple binary wheels, leading to race conditions where consumers get different versions of the package if they are unlucky enough to require a package before their platform’s wheel has completely uploaded. If the release uploads its sdist first, this may also manifest in some consumers seeing only the sdist, triggering a local build from source.
- Status reporting is very limited. There’s no support for reporting multiple errors, warnings, deprecations, etc. Status is limited to the HTTP status code and reason phrase, of which the reason phrase has been deprecated since HTTP/2 (RFC 7540).
- Metadata for a release is submitted alongside the file. However, as this metadata is famously unreliable, most installers instead choose to download the entire file and read the metadata from there.
- There is no mechanism for allowing an index to do any sort of sanity checks before bandwidth gets expended on an upload. Many cases of invalid metadata or incorrect permissions could be checked prior to uploading files.
- There is no support for “staging” a release prior to publishing it to the index.
- Creation of new projects requires the uploading of at least one file, leading to “stub” uploads to claim a project namespace.
The new upload API proposed in this PEP provides ways to solve all of these problems, either directly or through an extensible approach, allowing servers to implement features such as resumable and parallel uploads. This upload API this PEP proposes provides better error reporting, a more robust release testing experience, and atomic and simultaneous publishing of all release artifacts.
Legacy API
The following is an overview of the legacy API. For the detailed description, consult the PyPI user guide documentation.
Endpoint
The existing upload API lives at a base URL. For PyPI, that URL is currently
https://upload.pypi.org/legacy/
. Clients performing uploads specify the API they want to call
by adding an :action
URL parameter with a value of file_upload
. [1]
The legacy API also has a protocol_version
parameter,
in theory allowing new versions of the API to be defined.
In practice this has never happened, and the value is always 1
.
Thus, the effective upload API on PyPI is:
https://upload.pypi.org/legacy/?:action=file_upload&protocol_version=1
.
Encoding
The data to be submitted is submitted as a POST
request with the content type of
multipart/form-data
. This reflects the legacy API’s historical nature, which was originally
designed not as an API, but rather as a web form on the initial PyPI implementation,
with client code written to programmatically submit that form.
Content
Roughly speaking, the metadata contained within the package is submitted as parts where the content
disposition is form-data
, and the metadata key is the name of the field. The names of these
various pieces of metadata are not documented, and they sometimes, but not always match the names
used in the METADATA
files for package artifacts.
The case rarely matches, and the form-data
to METADATA
conversion is inconsistent.
The upload artifact file itself is sent as a application/octet-stream
part with the name of
content
, and if there is a PGP signature attached, then it will be included as a
application/octet-stream
part with the name of gpg_signature
.
Authentication
Upload authentication is also not standardized.
PyPI uses HTTP Basic Authentication
with API tokens as the password
and the username __token__
.
Trusted Publishers
authenticate via OpenID Connect and receive short-lived API tokens
that are used in the same way.
Upload 2.0 API Specification
This PEP traces the root cause of most of the issues with the existing API to be roughly two things:
- The metadata is submitted alongside the file, rather than being parsed from the file itself. [2]
- It supports only a single request, using only form data, that either succeeds or fails, and all actions are atomic within that single request.
To address these issues, this PEP proposes a multi-request workflow, which at a high level involves these steps:
- Initiate a publishing session, creating a release stage.
- Initiate file upload session(s) to that stage as part of the publishing session.
- Negotiate the specific file upload mechanism to use between client and server.
- Execute the file upload mechanism for the file upload session(s) using the negotiated mechanism(s).
- Complete the file upload session(s), marking them as completed or canceled.
- Complete the publishing session, publishing or discarding the stage.
- Optionally check the status of a publishing session.
Versioning
This PEP uses the same MAJOR.MINOR
versioning system as used in PEP 691,
but it is otherwise independently versioned.
The legacy API is considered by this PEP to be version 1.0
,
but this PEP does not modify the legacy API in any way.
The API proposed in this PEP therefore has the version number 2.0
.
Both major and minor version numbers of the Upload API MUST only be changed through the PEP process. Index operators and implementers MUST NOT advertise or implement new API versions without an approved PEP. This ensures consistency across all implementations and prevents fragmentation of the ecosystem.
Content Types
Like PEP 691, this PEP proposes that all requests and responses from this upload API will have a standard content type that describes what the content is, what version of the API it represents, and what serialization format has been used.
This standard request content type applies to all requests except for requests to execute a file upload mechanism, which will be specified by the documentation for that mechanism.
The structure of the Content-Type
header for all other requests is:
application/vnd.pypi.upload.$version+$format
Since minor API version differences should never be disruptive, only the major version is included
in the content type; the version number is prefixed with a v
.
The major API version specified in the .meta.api-version
JSON key of client requests
MUST match the Content-Type
header for major version.
Unlike PEP 691, this PEP does not change the existing legacy 1.0
upload API in any way,
so servers are required to host the new API described in this PEP at a different endpoint than the
existing upload API.
Since JSON is the only defined request format defined in this PEP, all non-file-upload requests
defined in this PEP MUST include a Content-Type
header value of:
application/vnd.pypi.upload.v2+json
.
Similar to PEP 691, this PEP also standardizes on using server-driven content negotiation to
allow clients to request different versions or serialization formats,
which includes the format
part of the content type.
However, since this PEP expects the existing legacy 1.0
upload API
to exist at a different endpoint,
and this PEP currently only provides for JSON serialization,
this mechanism is not particularly useful.
Clients only have a single version and serialization they can request.
However clients SHOULD be prepared to handle content negotiation gracefully
in the case that additional formats or versions are added in the future.
Servers MUST NOT advertise support for API versions beyond those defined in approved PEPs. Any new versions or formats require standardization through a new PEP.
Unless otherwise specified, all HTTP requests and responses in this document are assumed to include the HTTP header:
Content-Type: application/vnd.pypi.upload.v2+json
Root Endpoint
All URLs described here are relative to the “root endpoint”, which may be located anywhere within
the url structure of a domain. For example, the root endpoint could be
https://upload.example.com/
, or https://example.com/upload/
.
The choice of the root endpoint is left up to the index operator.
Authentication for Upload 2.0 API
All endpoints in this specification MUST use standard HTTP authentication mechanisms as defined in RFC 7235.
Authentication follows the standard HTTP pattern:
- Servers use the
WWW-Authenticate
response header when authentication is required - Clients provide credentials via the
Authorization
request header 401 Unauthorized
indicates missing or invalid authentication403 Forbidden
indicates insufficient permissions
The specific authentication schemes (e.g., Bearer, Basic, Digest) are determined by the index operator.
Errors
Clients in general should be prepared to handle HTTP response error status codes which MAY contain payloads of the the following format:
{
"meta": {
"api-version": "2.0"
},
"message": "...",
"errors": [
{
"source": "...",
"message": "..."
}
]
}
Besides the standard meta
key, this has the following top level keys:
message
- A singular message that encapsulates all errors that may have happened on this request.
errors
- An array of specific errors, each of which contains a
source
key, which is a string that indicates what the source of the error is, and amessage
key for that specific error.
The message
and source
strings do not have any specific meaning, and are intended for human
interpretation to aid in diagnosing underlying issue.
Some responses may return more specific HTTP status codes as described in the text below.
Publishing Session
Create a Publishing Session
A release starts by creating a new publishing session. To create the session, a client submits a
POST
request to the root URL like:
{
"meta": {
"api-version": "2.0"
},
"name": "foo",
"version": "1.0",
}
The request includes the following top-level keys:
meta
(required)- Describes information about the payload itself. Currently, the only defined sub-key is
api-version
the value of which must be the string"2.0"
. Optional sub-keys can define index-specific behavior. name
(required)- The name of the project that this session is attempting to release a new version of. The name MUST conform to the standard package name format and the server MUST normalize the name.
version
(required)- The version of the project that this session is attempting to add files to. The version string MUST conform to the packaging version specification.
Upon successful session creation, the server returns a 201 Created
response. The response MUST also
include a Location
header containing the same URL as the links.session
key in the response body.
If a session is created for a project which has no previous release, then the index MAY reserve the project name before the session is published, however it MUST NOT be possible to navigate to that project using the “regular” (i.e. unstaged) access protocols, until the stage is published. If this first-release stage gets canceled, then the index SHOULD delete the project record, as if it were never uploaded.
The session is owned by the user that created it,
and all subsequent requests MUST be performed with the same credentials,
otherwise a 403 Forbidden
will be returned on those subsequent requests.
Optional Index-specific Metadata
Index can optionally define their own metadata for index-specific behavior. The metadata key MUST begin with an underscore, with the following value easily and uniquely identifying the index. For example, PyPI could allow for projects to be created in an organization account of which the publisher is a member by using the following index-specific metadata section:
{
"meta": {
"api-version": "2.0",
"_pypi.org": {
"organization": "my-main-org"
}
},
"name": "foo",
"version": "1.0",
}
This is only an example. This PEP does not define or reserve any index-specific keys or metadata; that is left up to the index to specify and document. The semantics (e.g. whether bogus keys or values result in an error or are ignored) of the index-specific metadata is also undefined here.
Response Body
The successful response includes the following content:
{
"meta": {
"api-version": "2.0"
},
"links": {
"stage": "...",
"upload": "...",
"session": "...",
},
"mechanisms": ["http-post-bytes"],
"session-token": "<token-string>",
"expires-at": "2025-08-01T12:00:00Z",
"status": "pending",
"files": {},
"notices": [
"a notice to display to the user"
]
}
Besides the meta
key, which has the same format as the request JSON, the success response has
the following keys:
links
- A dictionary mapping keys to URLs related to this session, the details of which are provided below.
mechanisms
- A list of file-upload mechanisms supported by the server, sorted in server-preferred order. At least one value is required.
session-token
- If the index supports previewing staged releases, this key will contain the unique “session token” that can be provided to installers in order to preview the staged release before it’s published. This token MUST be cryptographically unguessable. If the index does not support stage previewing, this key MUST be omitted.
expires-at
- An RFC 3339 formatted timestamp string; this string MUST represent a UTC timestamp using the
“Zulu” (i.e.
Z
) marker, and use only whole seconds (i.e. no fractional seconds). This timestamp represents when the server will expire this session, and thus all of its content, including any uploaded files and the URL links related to the session. The session SHOULD remain active until at least this time unless the client itself has canceled or published the session. Servers MAY choose to extend this expiration time, but should never move it earlier. Clients can query the session status to get the current expiration time of the session. status
- A string that contains one of
pending
,published
,error
, orcanceled
, representing the overall status of the session. files
- A mapping containing the filenames that have been uploaded to this session, to a mapping containing details about each file referenced in this session.
notices
- An optional key that points to an array of human-readable informational notices that the server wishes to communicate to the end user. These notices are specific to the overall session, not to any particular file in the session.
Multiple Session Creation Requests
If a second attempt to create a session is received for the same name-version pair while a session for that
pair is in the pending
, processing
, or complete
state, then a new session is not created.
Instead, the server MUST respond with a 409 Conflict
and MUST include a Location
header that
points to the session status URL.
For sessions in the error
or canceled
state, a new session is created with same 201 Created
response and payload, except that the publishing session status URL,
session-token
, and links.stage
values MUST be different.
Publishing Session Links
For the links
key in the success JSON, the following sub-keys are valid:
session
- The endpoint where actions for this session can be performed, including publishing this session, canceling and discarding the session, querying the current session status, and requesting an extension of the session lifetime (if the server supports it).
upload
- The endpoint session clients will use to initiate a file upload session for each file to be included in this session.
stage
- The endpoint where this staged release can be previewed prior to publishing the
session. This can be used to download and verify the not-yet-public files. This URL MUST be
cryptographically unguessable and MUST use the above
session-token
to accomplish this. Thisstage
URL should be easily calculated using thesession-token
, but the exact format of that URL is index specific. If the index does not support previewing staged releases, this key MUST be omitted.
Publishing Session Files
The files
key contains a mapping from the names of the files uploaded in this session to a
sub-mapping with the following keys:
status
- A string with valid values
pending
,processing
,complete
,error
, andcanceled
. If there was an error during upload, then clients should not assume the file is in any usable state,error
will be returned and it’s best to cancel or delete the file and start over. This action would remove the file name from thefiles
key of the session status response body. link
- The absolute URL that the client should use to reference this specific file. This URL is used to retrieve, replace, or delete the referenced file. If a preview stages are supported, this URL MUST be cryptographically unguessable, and MUST use the same publishing session token to do ensure this constraint. The exact format of the URL is left to the index, but SHOULD be documented.
notices
- An optional key with similar format and semantics as the
notices
session key, except that these notices are specific to the referenced file.
Complete a Publishing Session
To complete a session and publish the files that have been included in it, a client issues a
POST
request to the session
link
given in the session creation response body.
The request looks like:
{
"meta": {
"api-version": "2.0"
},
"action": "publish",
}
If the server is able to immediately complete the publishing session, it may do so and return a
201 Created
response. If it is unable to immediately complete the publishing session
(for instance, if it needs to do validation that may take longer than reasonable in a single HTTP
request), then it may return a 202 Accepted
response.
The server MUST include a Location
header in the response pointing back to the Publishing
Session status URL, which can be used to query the current session status. If the server
returned a 202 Accepted
, polling that URL can be used to watch for session status changes.
Publishing Session Cancellation
To cancel a publishing session, a client issues a DELETE
request to
the session
link
given in the session creation response body.
The server then marks the session as canceled, and SHOULD purge any data that was uploaded
as part of that session.
Future attempts to access that session URL or any of the publishing session URLs
MUST return a 404 Not Found
.
To prevent dangling sessions, servers may also choose to cancel timed-out sessions on their own accord. It is recommended that servers expunge their sessions after no less than a week, but each server may choose their own schedule. Servers MAY support client-directed session extensions.
Publishing Session Status
At any time, a client can query the status of a session by issuing a GET
request to the URL given in the
links.session URL (also provided in the session creation response’s Location
header).
The server will respond to this GET
request with the same publishing session creation response, that they got when they initially created the publishing session, except with
any changes to status
, expires-at
, or files
reflected.
Publishing Session Extension
Servers MAY allow clients to extend sessions, but the overall lifetime and number of extensions
allowed is left to the server. To extend a session, a client issues a POST
request to the
links.session URL (same as above, also the Location
header).
The request looks like:
{
"meta": {
"api-version": "2.0"
},
"action": "extend",
"extend-for": 3600
}
The number of seconds specified is just a suggestion to the server for the number of additional seconds to
extend the current session. For example, if the client wants to extend the current session for another hour,
extend-for
would be 3600
. Upon successful extension, the server will respond with the same
publishing session creation response body that they got when they
initially created the publishing session, except with any changes to status
, expires-at
, or files
reflected.
If the server refuses to extend the session for the requested number of seconds, it MUST still return a
success response, and the expires-at
key will simply reflect the current expiration time of the session.
Publishing Session Token
Indexes SHOULD support preview stages so that uploaded files can be live tested before publishing. E.g. a CI client could perform installation tests using pre-published wheels to ensure that their new release works as expected before they publish the release publicly.
Indexes advertise their support for staged previews by returning two key pieces of information in their response to publishing session creation. Indexes which don’t support staged previews MUST NOT include these in their responses.
The session-token
is a short token which could be used as a convenience for installation tool UX, if they
want to support staged previews via a command line switch, e.g. $TOOL install --staging $SESSION_TOKEN
.
The links.stage
key gives the full URL to the stage, which could be used in the CLI, e.g. pip
install --extra-index-url $STAGE_URL
. Both the session token and URL MUST be cryptographically
unguessable, but the algorithm for generating the token is left to the index. The stage URL MUST be
calculable from the session token, using a format documented by the index, but the exact format of the URL is
also left to the index.
File Upload Session
Create a File Upload Session
After creating a publishing session, the upload
endpoint from the response’s session links mapping is used to begin the upload of new files into that session. Clients
MUST use the provided upload
URL and MUST NOT assume there is any pattern or commonality to those
URLs from one session to the next.
To initiate a file upload, a client first sends a POST
request to the upload
URL.
The request looks like:
{
"meta": {
"api-version": "2.0"
},
"filename": "foo-1.0.tar.gz",
"size": 1000,
"hashes": {"sha256": "...", "blake2b": "..."},
"metadata": "...",
"mechanism": "http-post-bytes"
}
Besides the standard meta
key, the request JSON has the following additional keys:
filename
(required)- The name of the file being uploaded. The filename MUST conform to either the source
distribution file name specification
or the binary distribution file name convention.
Indexes SHOULD validate these file names at the time of the request, returning a
400 Bad Request
error code, as described in the Errors section when the file names do not conform. size
(required)- The size in bytes of the file being uploaded.
hashes
(required)- A mapping of hash names to hex-encoded digests. Each of these digests are the checksums of the
file being uploaded when hashed by the algorithm identified in the name.
By default, any hash algorithm available in hashlib can be used as a key for the hashes dictionary [3]. At least one secure algorithm from
hashlib.algorithms_guaranteed
MUST always be included. This PEP specifically recommendssha256
.Multiple hashes may be passed at a time, but all hashes provided MUST be valid for the file.
mechanism
(required)- The file-upload mechanisms the client intends to use for this file. This mechanism SHOULD be chosen from the list of mechanisms advertised in the publishing session creation response body. A client MAY send a mechanism that is not advertised in cases where server operators have documented a new or upcoming mechanism that is available for use on a “pre-release” basis.
metadata
(optional)- If given, this is a string value containing the file’s core metadata.
Servers MAY use the data provided in this request to do some sanity checking prior to allowing the file to be uploaded. These checks may include, but are not limited to:
- checking if the
filename
already exists in a published release; - checking if the
size
would exceed any project or file quota; - checking if the contents of the
metadata
, if provided, are valid.
If the server determines that upload should proceed, it will return a 202 Accepted
response, with the
response body below. The status of the publishing session will also
include the filename in the files
mapping. If the server cannot proceed with an upload because the
mechanism
supplied by the client is not supported it MUST return a 422 Unprocessable Content
. The
server MAY allow parallel uploads of files, but is not required to. If the server determines the upload
cannot proceed, it MUST return a 409 Conflict
.
Response Body
The successful response includes the following:
{
"meta": {
"api-version": "2.0"
},
"links": {
"file-upload-session": "..."
},
"status": "pending",
"expires-at": "2025-08-01T13:00:00Z",
"mechanism": {
"identifier": "http-post-bytes",
"file_url": "...",
"attestations_url": "..."
}
}
A Retry-After
response header MUST be present to indicate to clients when they should next poll for an
updated status.
Besides the meta
key, which has the same format as the request JSON, the success response has
the following keys:
links
- A dictionary mapping keys to URLs related to this session, the details of which are provided below.
status
- A string with valid values
pending
,processing
,complete
,error
, andcanceled
indicating the current state of the file upload session. expires-at
- An RFC 3339 formatted timestamp string representing when the server will expire this file upload
session. This string MUST represent a UTC timestamp using the “Zulu” (i.e.
Z
) marker, and use only whole seconds (i.e. no fractional seconds). The session SHOULD remain active until at least this time unless the client cancels or completes it. Servers MAY choose to extend this expiration time, but should never move it earlier. mechanism
- A mapping containing the necessary details for the supported mechanism as negotiated by the client and
server. This mapping MUST contain a key
identifier
which maps to the identifier string for the chosen file upload mechanism.
File Upload Session Links
For the links
key in the response payload, the following sub-keys are valid:
file-upload-session
- The endpoint where actions for this file-upload-session can be performed. including completing a file upload session, canceling and discarding the file upload session, querying the current file upload session status, and requesting an extension of the file upload session lifetime (if the server supports it).
Complete a File Upload Session
To complete a file upload session, which indicates that the file upload mechanism has been executed
and did not produce an error, a client issues a POST
to the file-upload-session
link in the
file upload session creation response body.
The requests looks like:
{
"meta": {
"api-version": "2.0"
},
"action": "complete",
}
If the server is able to immediately complete the file upload session, it may do so and return a 201
Created
response and set the status of the file upload session to complete
. If it is unable to
immediately complete the file upload session (for instance, if it needs to do validation that may take longer
than reasonable in a single HTTP request), then it may return a 202 Accepted
response and set the status
of the file upload session to processing
.
In either case, the server should include a Location
header pointing back to the file upload session
status URL.
Servers MUST allow clients to poll the file upload session status URL to watch for the status to change.
If the server responds with a 202 Accepted
, clients may poll the file upload session status URL to watch
for the status to change. Clients SHOULD respect the Retry-After
header value of the file upload
session status response.
Cancellation and Deletion
A client can cancel an in-progress file upload session, or delete a file that has been completely uploaded.
In both cases, the client performs this by issuing a DELETE
request to the links.file-upload-session
URL from the file upload session creation response of the file they want
to delete.
A successful deletion request MUST respond with a 204 No Content
.
Once canceled or deleted, a client MUST NOT assume that the previous file upload session resource or associated file upload mechanisms can be reused.
Replacing a Partially or Fully Uploaded File
To replace a session file, the file upload MUST have been previously completed, canceled, or deleted. It is not possible to replace a file if the upload for that file is in-progress.
To replace a session file, clients should cancel and delete the in-progress upload first. After this, the new file upload can be initiated by beginning the entire file upload sequence over again. This means providing the metadata request again to retrieve a new upload resource URL. Clients MUST NOT assume that the previous upload resource URL can be reused after deletion.
File Upload Session Status
The client can query status of the file upload session by issuing a GET
request to the
links.file-upload-session
URL from the file upload session creation response. The server responds to this request with the same payload as the file upload
session creation response, except with any changes status
and expires-at
reflected.
File Upload Session Extension
Servers MAY allow clients to extend file upload sessions, but the overall lifetime and number of
extensions allowed is left to the server. To extend a file upload session, a client issues a POST
request
to the links.file-upload-session
URL from the file upload session creation response.
The request looks like:
{
"meta": {
"api-version": "2.0"
},
"action": "extend",
"extend-for": 3600
}
The number of seconds specified is just a suggestion to the server for the number of additional seconds to
extend the current file upload session. For example, if the client wants to extend session for another hour,
extend-for
would be 3600
. Upon successful extension, the server will respond with the same file
upload session creation response body that they got when they initially
created the publishing session, except with any changes to status
or expires-at
reflected.
If the server refuses to extend the session for the requested number of seconds, it MUST still return a
success response, and the expires-at
key will simply reflect the current expiration time of the session.
Stage Previews
The ability to preview staged releases before they are published is an important feature of this PEP, enabling
an additional level of last-mile testing before the release is available to the public. Indexes MAY
provide this functionality through the URL provided in the stage
sub-key of the links key returned when the publishing session is created. The stage
URL can be passed
to installers such as pip
by setting the –extra-index-url flag to this value. Multiple
stages can even be previewed by repeating this flag with multiple values.
If supported, the index will return views that expose the staged releases to the installer tool, making them available to download and install into virtual environments built for that last-mile testing. This option allows existing installers to preview staged releases with no changes to the installer tool required. The details of this user experience are left to installer tool maintainers.
File Upload Mechanisms
Servers MUST implement required file upload mechanisms. Such mechanisms serve as a fallback if no server specific implementations exist.
Each major version of the Upload API MUST specify at least one required file upload mechanism.
New required mechanisms MUST NOT be added and existing required mechanisms MUST NOT be removed without an update to the major version. Any server-specific or experimental mechanisms added or removed MUST NOT change the major or minor version number of this specification.
Required File Upload Mechanisms
http-post-bytes
Upload API version 2.0 compliant servers MUST support the http-post-bytes
mechanism.
This mechanism MUST use the same authentication scheme as the rest of the Upload 2.0 protocol endpoints.
A client executes this mechanism by submitting a POST
request to the file_url
returned in the http-post-bytes
map of the mechanism
map of the
file upload session creation response body like:
Content-Type: application/octet-stream
<binary contents of the file to upload>
Servers MAY support uploading of digital attestations for files (see PEP 740).
This support will be indicated by inclusion of an attestations_url
key in the
http-post-bytes
map of the mechanism
map of the
file upload session creation response body.
Attestations MUST be uploaded to the attestations_url
before
file upload session completion.
To upload an attestation, a client submits a POST
request to the attestations_url
containing a JSON array of attestation objects like:
Content-Type: application/json
[{"version": 1, "verification_material": {...}, "envelope": {...}},...]
Server Specific File Upload Mechanisms
A given server MAY implement an arbitrary number of server specific mechanisms and is responsible for documenting their usage.
A server specific implementation file upload mechanism identifier has three parts:
<prefix>-<operator identifier>-<implementation identifier>
Server specific implementations MUST use vnd
as their prefix
.
The operator identifier
SHOULD clearly identify the server operator,
be unique from other well known indexes,
and contain only alphanumeric characters [a-z0-9]
.
The implementation identifier
SHOULD concisely describe the underlying implementation
and contain only alphanumeric characters [a-z0-9]
and -
.
When server operators need to make breaking changes to their upload mechanisms,
they SHOULD create a new mechanism identifier rather than modifying the existing one.
The recommended pattern is to append a version suffix like -v1
, -v2
, etc.
to the implementation identifier.
This allows clients to explicitly opt into new versions while maintaining
backward compatibility with existing clients.
For example:
File Upload Mechanism string | Server Operator | Mechanism description |
---|---|---|
vnd-pypi-s3multipart-presigned |
PyPI | S3 multipart upload via pre-signed URL |
vnd-pypi-s3multipart-presigned-v2 |
PyPI | S3 multipart upload via pre-signed URL version 2 |
vnd-pypi-http-fetch |
PyPI | File delivered by instructing server to fetch from a URL via HTTP request |
vnd-acmecorp-http-fetch |
Acme Corp | File delivered by instructing server to fetch from a URL via HTTP request |
vnd-acmecorp-postal |
Acme Corp | File delivered via postal mail |
vnd-widgetinc-stream-v1 |
Widget Inc. | Streaming upload protocol version 1 |
vnd-widgetinc-stream-v2 |
Widget Inc. | Streaming upload protocol version 2 |
vnd-madscience-quantumentanglement |
Mad Science Labs | Upload via quantum entanglement |
If a server intends to precisely match the behavior of another server’s implementation, it MAY respond with that implementation’s file upload mechanism name.
FAQ
Does this mean PyPI is planning to drop support for the existing upload API?
At this time PyPI does not have any specific plans to drop support for the existing upload API.
Unlike with PEP 691 there are significant benefits to doing so, so it is likely that support for the legacy upload API to be (responsibly) deprecated and removed at some point in the future. Such future deprecation planning is explicitly out of scope for this PEP.
Can I use the upload 2.0 API to reserve a project name?
Yes! If you’re not ready to upload files to make a release, you can still reserve a project name (assuming of course that the name doesn’t already exist).
To do this, create a new publishing session, then publish the session without uploading any files. While the version
key is required in the
JSON body of the create session request, you can simply use a placeholder version number such as
"0.0.0a0"
. The version is ignored if no artifacts are uploaded.
Generally the user that created the session will become the owner of the new project, however the index could define index-specific metadata to, for example, allow an organization of which the publisher is a member, to own the new project.
Open Questions
Extensions to the Upload 2.0 Protocol
Features such as asynchronous webhook notifications for completion of upload processing were discussed during review of this PEP. The concept of a capabilities extension for the upload protocol was discussed, which would allow implementers to advertise support for optional features such as asynchronous notifications or webhooks.
This idea was left open due to the complexity that would arise in designing such an extension protocol and ensuring that it did not cause excessive fracturing of the ecosystem as Upload 2.0 is rolled out.
Future revisions to the upload protocol should explore such extensions as experience is gained operating Upload 2.0.
Footnotes
Change History
- 23-Sep-2025
- Remove the
nonce
andgentoken()
algorithm. Indexes are now responsible for generating an cryptographically secure session token and obfuscated stage URL (but only if they support staged previews). - Clarify the semantics when multiple session creation requests are received.
- Clarify publishing session steps such as status polling and session extension.
- Require that
name
conform to the normalization rules, and include a link. - Require that
version
conform to the version specs, and include a link. - Require
filename
to conform to either the source or binary distribution file name convention, and include links. - Reference RFC 3399 instead of ISO 8601 as the timestamp spec. The RFC is a simpler format that subsets the ISO standard, and is more appropriate to our use case.
- Other protocol clarifications.
- Add optional index-specific metadata keys.
- Remove the
- 06-Aug-2025
- Add Dustin as the PEP Delegate.
- 14-Apr-2025
- Updates based on PyCon US discussions.
- Added some error return code descriptions where they were underspecified.
- Combine the canceling and deleting of upload files sections.
- Simplify the rules for replacing a staged but not yet published file.
- Add open question about deferring stage previews.
- Fix some misspellings and poorly worded text.
- 06-Jan-2025
- Resurrect and update the PEP.
- Added Barry as co-author.
- Standardize terminology on “stage” rather than “draft”.
- Proposed the root URL for PyPI to be https://upload.pypi.org/2.0
- Added an optional
nonce
key for session obfuscation. - Standardize JSON keys and made consistent with terminology.
- Added and modified several APIs, filling gaps and elaborating on details.
- Align the upload protocol with draft Internet Standard.
Copyright
This document is placed in the public domain or under the CC0-1.0-Universal license, whichever is more permissive.
Source: https://github.com/python/peps/blob/main/peps/pep-0694.rst
Last modified: 2025-09-28 21:11:14 GMT