@@ -253,6 +253,8 @@ the url structure of a domain. For example, the root endpoint could be
253253The choice of the root endpoint is left up to the index operator.
254254
255255
256+ .. _authentication :
257+
256258Authentication for Upload 2.0 API
257259----------------------------------
258260
@@ -421,6 +423,8 @@ The successful response includes the following content:
421423 "stage" : " ..." ,
422424 "upload" : " ..." ,
423425 "session" : " ..." ,
426+ "publish" : " ..." ,
427+ "extend" : " ..." ,
424428 },
425429 "mechanisms" : [" http-post-bytes" ],
426430 "session-token" : " <token-string>" ,
@@ -481,7 +485,9 @@ Multiple Session Creation Requests
481485If a second attempt to create a session is received for the same name-version pair while a session for that
482486pair is in the ``pending ``, ``processing ``, or ``complete `` state, then a new session is *not * created.
483487Instead, the server **MUST ** respond with a ``409 Conflict `` and **MUST ** include a ``Location `` header that
484- points to the :ref: `session status URL <publishing-session-status >`.
488+ points to the :ref: `session status URL <publishing-session-status >`. Subsequent session creation requests
489+ **MUST ** be performed with the same credentials as the in-progress session, otherwise a ``403 Forbidden `` is
490+ returned.
485491
486492For sessions in the ``error `` or ``canceled `` state, a new session is created with same ``201 Created ``
487493response and payload, except that the :ref: `publishing session status URL <publishing-session-status >`,
@@ -495,12 +501,16 @@ Publishing Session Links
495501For the ``links `` key in the success JSON, the following sub-keys are valid:
496502
497503``session ``
498- The endpoint where actions for this session can be performed,
499- including :ref: `publishing this session <publishing-session-completion >`,
500- :ref: `canceling and discarding the session <publishing-session-cancellation >`,
501- :ref: `querying the current session status <publishing-session-status >`,
502- and :ref: `requesting an extension of the session lifetime <publishing-session-extension >`
503- (*if * the server supports it).
504+ The endpoint where the session resource can be accessed for
505+ :ref: `querying the current session status <publishing-session-status >` (via ``GET ``)
506+ and :ref: `canceling and discarding the session <publishing-session-cancellation >` (via ``DELETE ``).
507+
508+ ``publish ``
509+ The endpoint for :ref: `publishing this session <publishing-session-completion >` (via ``POST ``).
510+
511+ ``extend ``
512+ The endpoint for :ref: `requesting an extension of the session lifetime <publishing-session-extension >`
513+ (via ``POST ``). If the server does not support session extensions, this key **MUST ** be omitted.
504514
505515``upload ``
506516 The endpoint session clients will use to initiate a :ref: `file upload session <file-upload-session >`
@@ -550,7 +560,7 @@ Complete a Publishing Session
550560~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
551561
552562To complete a session and publish the files that have been included in it, a client issues a
553- ``POST `` request to the ``session `` :ref: `link <publishing-session-links >`
563+ ``POST `` request to the ``publish `` :ref: `link <publishing-session-links >`
554564given in the :ref: `session creation response body <publishing-session-response >`.
555565
556566The request looks like:
@@ -560,8 +570,7 @@ The request looks like:
560570 {
561571 "meta" : {
562572 "api-version" : " 2.0"
563- },
564- "action" : " publish" ,
573+ }
565574 }
566575
567576
@@ -614,7 +623,8 @@ Publishing Session Extension
614623
615624Servers **MAY ** allow clients to extend sessions, but the overall lifetime and number of extensions
616625allowed is left to the server. To extend a session, a client issues a ``POST `` request to the
617- :ref: `links.session <publishing-session-links >` URL (same as above, also the ``Location `` header).
626+ :ref: `links.extend <publishing-session-links >` URL. If the server does not support session extensions,
627+ the ``links.extend `` key will not be present in the response.
618628
619629The request looks like:
620630
@@ -624,7 +634,6 @@ The request looks like:
624634 "meta" : {
625635 "api-version" : " 2.0"
626636 },
627- "action" : " extend" ,
628637 "extend-for" : 3600
629638 }
630639
@@ -756,7 +765,9 @@ The successful response includes the following:
756765 "api-version" : " 2.0"
757766 },
758767 "links" : {
759- "file-upload-session" : " ..."
768+ "file-upload-session" : " ..." ,
769+ "complete" : " ..." ,
770+ "extend" : " ..."
760771 },
761772 "status" : " pending" ,
762773 "expires-at" : " 2025-08-01T13:00:00Z" ,
@@ -801,30 +812,35 @@ File Upload Session Links
801812For the ``links `` key in the response payload, the following sub-keys are valid:
802813
803814``file-upload-session ``
804- The endpoint where actions for this file-upload-session can be performed. including :ref: `completing a
805- file upload session <file-upload-session-completion>`, :ref: `canceling and discarding the file upload
806- session <file-upload-session-cancellation>`, :ref: `querying the current file upload session status
807- <file-upload-session-status>`, and :ref: `requesting an extension of the file upload session lifetime
808- <file-upload-session-extension>` (*if * the server supports it).
815+ The endpoint where the file upload session resource can be accessed for
816+ :ref: `querying the current file upload session status <file-upload-session-status >` (via ``GET ``)
817+ and :ref: `canceling and discarding the file upload session <file-upload-session-cancellation >` (via ``DELETE ``).
818+
819+ ``complete ``
820+ The endpoint for :ref: `completing a file upload session <file-upload-session-completion >` (via ``POST ``).
821+
822+ ``extend ``
823+ The endpoint for :ref: `requesting an extension of the file upload session lifetime
824+ <file-upload-session-extension>` (via ``POST ``). If the server does not support file upload session
825+ extensions, this key **MUST ** be omitted.
809826
810827.. _file-upload-session-completion :
811828
812829Complete a File Upload Session
813830~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
814831
815832To complete a file upload session, which indicates that the file upload mechanism has been executed
816- and did not produce an error, a client issues a ``POST `` to the ``file-upload-session `` link in the
817- file upload session creation response body.
833+ and did not produce an error, a client issues a ``POST `` to the ``complete `` :ref: ` link
834+ <file-upload-session-links>` in the file upload session creation response body.
818835
819- The requests looks like:
836+ The request looks like:
820837
821838.. code-block :: json
822839
823840 {
824841 "meta" : {
825842 "api-version" : " 2.0"
826- },
827- "action" : " complete" ,
843+ }
828844 }
829845
830846 If the server is able to immediately complete the file upload session, it may do so and return a ``201
@@ -887,8 +903,9 @@ File Upload Session Extension
887903
888904Servers **MAY ** allow clients to extend file upload sessions, but the overall lifetime and number of
889905extensions allowed is left to the server. To extend a file upload session, a client issues a ``POST `` request
890- to the ``links.file-upload-session `` URL from the :ref: `file upload session creation response
891- <file-upload-session-response>`.
906+ to the ``extend `` :ref: `link <file-upload-session-links >` from the :ref: `file upload session creation response
907+ <file-upload-session-response>`. If the server does not support file upload session extensions,
908+ the ``links.extend `` key will not be present in the response.
892909
893910The request looks like:
894911
@@ -898,7 +915,6 @@ The request looks like:
898915 "meta" : {
899916 "api-version" : " 2.0"
900917 },
901- "action" : " extend" ,
902918 "extend-for" : 3600
903919 }
904920
@@ -1035,6 +1051,200 @@ If a server intends to precisely match the behavior of another server's implemen
10351051with that implementation's file upload mechanism name.
10361052
10371053
1054+ .. _client-recommendations :
1055+
1056+ Recommendations for Client Implementers
1057+ =======================================
1058+
1059+ This section is non-normative and provides guidance for client tool authors
1060+ implementing the Upload 2.0 protocol. These recommendations are suggestions
1061+ based on the expected usage patterns of the protocol; client authors are free
1062+ to implement alternative approaches that best suit their users' needs.
1063+
1064+ General Workflow
1065+ ----------------
1066+
1067+ A typical upload workflow using the Upload 2.0 protocol follows these steps:
1068+
1069+ 1. Create a :ref: `publishing session <publishing-session-create >` for the project name and version.
1070+ 2. For each artifact (sdist, wheels), :ref: `create a file upload session <file-upload-session >`,
1071+ execute the negotiated upload mechanism, and :ref: `complete the file upload session
1072+ <file-upload-session-completion>`.
1073+ 3. Optionally, if the index supports :ref: `stage previews <staged-preview >`, use the ``links.stage ``
1074+ URL to test the release before publishing.
1075+ 4. :ref: `Publish the session <publishing-session-completion >` to make the release public,
1076+ or :ref: `cancel it <publishing-session-cancellation >` if issues are discovered.
1077+
1078+ Clients **SHOULD ** handle failures gracefully at each step. If an error occurs during file upload,
1079+ the client should :ref: `cancel the file upload session <file-upload-session-cancellation >`. If an
1080+ unrecoverable error occurs at any point, the client should :ref: `cancel the publishing session
1081+ <publishing-session-cancellation>` to clean up server-side resources.
1082+
1083+ Parallel Uploads
1084+ ~~~~~~~~~~~~~~~~
1085+
1086+ Clients **MAY ** upload multiple files in parallel by creating and executing multiple file upload
1087+ sessions concurrently within the same publishing session. This can significantly improve upload
1088+ times for releases with many wheel variants. However, clients should be prepared for servers that
1089+ do not support parallel uploads and may return ``409 Conflict `` if parallel uploads are attempted.
1090+
1091+ Session Management
1092+ ~~~~~~~~~~~~~~~~~~
1093+
1094+ Clients should monitor the ``expires-at `` timestamp in session responses. For long-running uploads
1095+ (e.g., large files on slow connections), clients may need to :ref: `request session extensions
1096+ <publishing-session-extension>` if the ``links.extend `` endpoint is available. If the server does
1097+ not support extensions (indicated by the absence of ``links.extend ``), clients should warn users
1098+ when uploads may exceed the session lifetime.
1099+
1100+ Suggested Command-Line Interfaces
1101+ ---------------------------------
1102+
1103+ The following examples illustrate how existing tools might expose the Upload 2.0 protocol to users.
1104+ These are suggestions only; actual implementations may vary.
1105+
1106+ twine
1107+ ~~~~~
1108+
1109+ `twine <https://twine.readthedocs.io/ >`__ currently provides a simple ``twine upload dist/* ``
1110+ command. The Upload 2.0 protocol could be exposed through additional options:
1111+
1112+ ``twine upload dist/* ``
1113+ Maintains backward compatibility. Uses the Upload 2.0 protocol if available, falling back to
1114+ the legacy protocol if not. Creates a session, uploads all files, and publishes immediately.
1115+
1116+ ``twine upload --stage dist/* ``
1117+ Uses the Upload 2.0 protocol to create a session and upload files, but does not publish.
1118+ This is useful even when the index does not support stage preview URLs, as it still provides
1119+ the atomic release semantics of Upload 2.0. If the index supports stage previews, prints the
1120+ ``links.stage `` URL for testing. Prints a session identifier that can be used with subsequent
1121+ commands. This session identifier is local to the client and is mapped internally to the
1122+ in-progress server session.
1123+
1124+ ``twine publish <session-id> ``
1125+ Publishes a previously staged session.
1126+
1127+ ``twine cancel <session-id> ``
1128+ Cancels a staged session and discards all uploaded files.
1129+
1130+ ``twine status <session-id> ``
1131+ Queries and displays the current status of a session.
1132+
1133+ uv
1134+ ~~
1135+
1136+ `uv <https://docs.astral.sh/uv/ >`__ could provide similar functionality with additional integration:
1137+
1138+ ``uv publish dist/* ``
1139+ Creates a session, uploads all files, and publishes. May leverage parallel uploads for
1140+ faster publishing of multiple wheels.
1141+
1142+ ``uv publish --stage dist/* ``
1143+ Uploads without publishing. Like twine, this is valuable even without stage preview support.
1144+
1145+ ``uv publish --test-install dist/* ``
1146+ If the index supports stage previews, uploads files, installs the package from the stage URL
1147+ into a temporary virtual environment, optionally runs a smoke test command, and only publishes
1148+ if successful. This provides an integrated "upload, test, publish" workflow.
1149+
1150+ GitHub Actions
1151+ ~~~~~~~~~~~~~~
1152+
1153+ The `pypa/gh-action-pypi-publish <https://github.com/pypa/gh-action-pypi-publish >`__ action could
1154+ leverage staged releases to enable powerful CI/CD workflows. A multi-job workflow might look like:
1155+
1156+ .. code-block :: yaml
1157+
1158+ jobs :
1159+ upload :
1160+ runs-on : ubuntu-latest
1161+ outputs :
1162+ stage-url : ${{ steps.upload.outputs.stage-url }}
1163+ session-id : ${{ steps.upload.outputs.session-id }}
1164+ steps :
1165+ - uses : actions/download-artifact@v4
1166+ with :
1167+ name : dist
1168+ path : dist/
1169+ - id : upload
1170+ uses : pypa/gh-action-pypi-publish@v2
1171+ with :
1172+ stage-only : true # Upload but don't publish
1173+
1174+ test :
1175+ needs : upload
1176+ runs-on : ubuntu-latest
1177+ steps :
1178+ - uses : actions/setup-python@v5
1179+ - name : Test staged release
1180+ run : |
1181+ pip install --extra-index-url "${{ needs.upload.outputs.stage-url }}" my-package
1182+ python -c "import my_package; my_package.smoke_test()"
1183+
1184+ publish :
1185+ needs : [upload, test]
1186+ runs-on : ubuntu-latest
1187+ steps :
1188+ - uses : pypa/gh-action-pypi-publish@v2
1189+ with :
1190+ publish-session : ${{ needs.upload.outputs.session-id }}
1191+
1192+ This pattern allows the actual PyPI artifacts to be tested in a realistic installation scenario
1193+ before being published. If the test job fails, the workflow can include a cleanup job to cancel
1194+ the session:
1195+
1196+ .. code-block :: yaml
1197+
1198+ cancel-on-failure :
1199+ needs : [upload, test]
1200+ if : failure()
1201+ runs-on : ubuntu-latest
1202+ steps :
1203+ - uses : pypa/gh-action-pypi-publish@v2
1204+ with :
1205+ cancel-session : ${{ needs.upload.outputs.session-id }}
1206+
1207+ Even when the index does not support stage preview URLs, the staged upload pattern is still
1208+ valuable as it ensures atomic releases: either all artifacts are published together, or none are.
1209+
1210+ Error Handling
1211+ --------------
1212+
1213+ Clients should implement robust error handling for the multi-step upload process:
1214+
1215+ **File upload failures **: If a file upload fails (network error, validation error, etc.), the
1216+ client should :ref: `cancel that file upload session <file-upload-session-cancellation >` before
1217+ retrying. The client may then create a new file upload session for the same filename.
1218+
1219+ **Partial upload recovery **: If some files have been successfully uploaded but others fail, the
1220+ client has options:
1221+
1222+ - Cancel the entire publishing session and start over.
1223+ - Cancel only the failed file upload sessions and retry those files.
1224+ - If using ``--stage `` mode, leave the session open for manual intervention.
1225+
1226+ **Session expiration **: If a session expires during upload, the client must create a new publishing
1227+ session and re-upload all files. Clients should monitor ``expires-at `` and warn users proactively.
1228+
1229+ **Publishing failures **: If the publish request fails, the session remains in its current state.
1230+ The client can query the session status to determine the cause and retry the publish operation.
1231+
1232+ **Graceful cancellation **: When a user interrupts an upload (e.g., Ctrl+C), clients should attempt
1233+ to cancel the publishing session to avoid leaving orphaned sessions on the server.
1234+
1235+ Legacy API Fallback
1236+ -------------------
1237+
1238+ During the transition period, clients **SHOULD ** support both the Upload 2.0 and legacy protocols.
1239+ A suggested approach:
1240+
1241+ 1. Attempt to use Upload 2.0 by checking for the 2.0 endpoint or using content negotiation.
1242+ 2. If the server does not support Upload 2.0 (e.g., returns ``404 `` or ``406 ``), fall back to the
1243+ legacy protocol.
1244+ 3. Provide a command-line option to force a specific protocol version if needed for debugging or
1245+ compatibility.
1246+
1247+
10381248FAQ
10391249===
10401250
@@ -1105,7 +1315,16 @@ as experience is gained operating Upload 2.0.
11051315 Change History
11061316==============
11071317
1108- * `06-Dec-2025 <TBD >`__
1318+ * `26-Jan-2026 <TBD >`__
1319+
1320+ * Session actions now use dedicated endpoint links instead of an ``action `` key in request bodies.
1321+ Publishing sessions add ``links.publish `` and ``links.extend ``; file upload sessions add
1322+ ``links.complete `` and ``links.extend ``. The ``links.session `` and ``links.file-upload-session ``
1323+ endpoints are now used only for ``GET `` (status) and ``DELETE `` (cancel) operations.
1324+ * Add non-normative :ref: `Recommendations for Client Implementers <client-recommendations >` section
1325+ with suggested UX patterns for tools like twine, uv, and GitHub Actions.
1326+
1327+ * `06-Dec-2025 <https://discuss.python.org/t/pep-694-pypi-upload-api-2-0-round-2/101483/35 >`__
11091328
11101329 * Error responses conform to the :rfc: `9457 ` format.
11111330
0 commit comments