Summary
container.delete_all_items_by_partition_key(...) returns 401 Unauthorized ("the expected payload is not built as per the protocol") when the container's id contains a URL-unsafe character (e.g. a space). The root cause is an encoding mismatch between path construction and auth signing in _base.py: the URL is URL-encoded but the resource link used to compute the auth signature is not.
Reproduces on a real production Cosmos account (https://*.documents.azure.com), not just PPE/staging.
Repro
import os, uuid
from azure.cosmos import CosmosClient, PartitionKey
client = CosmosClient(os.environ["COSMOS_ENDPOINT"], os.environ["COSMOS_KEY"])
db = client.create_database("repro_" + uuid.uuid4().hex[:8])
# container id contains a space
cid = "spaced id " + uuid.uuid4().hex[:8]
c = db.create_container(id=cid, partition_key=PartitionKey(path="/pk"))
c.upsert_item({"id": "i1", "pk": "p1"})
c.delete_all_items_by_partition_key("p1") # -> 401 Unauthorized
Output:
azure.cosmos.exceptions.CosmosHttpResponseError: (Unauthorized) The input authorization
token can't serve the request. The wrong key is being used or the expected payload is
not built as per the protocol.
Server used the following payload to sign:
'post
colls
dbs/<db>/colls/spaced%20id%20<uuid>/operations/partitionkeydelete
<date>
'
Note the %20 in the resource link the server used. The SDK signed with a raw space.
Root cause
In azure/cosmos/_cosmos_client_connection.py (sync) and azure/cosmos/aio/_cosmos_client_connection_async.py (async) DeleteAllItemsByPartitionKey:
path = base.GetPathFromLink(collection_link) # URL-encoded (%20)
path = '{}{}/{}'.format(path, "operations", "partitionkeydelete")
collection_id = base.GetResourceIdOrFullNameFromLink(collection_link) # NOT URL-encoded (raw space)
headers = base.GetHeaders(self, ..., "post", path, collection_id, "partitionkey", ...)
GetPathFromLink (_base.py:608) calls urllib_quote → URL has %20.
GetResourceIdOrFullNameFromLink (_base.py:553) only trims slashes → raw space.
auth.__get_authorization_token_using_master_key signs the raw form.
The server reconstructs the canonical signing string from the request URL (encoded) and the two strings disagree → (Unauthorized).
Why it surfaced now
The pipeline test test_delete_all_items_by_partition_key uses an id with a space:
id='test_delete_all_items_by_partition_key ' + str(uuid.uuid4())
Until PR #46568 (commit 7e47f0a194, May 22, 2026) the test had this guard:
# enable the test only for the emulator
if "localhost" not in self.host and "127.0.0.1" not in self.host:
return
That PR removed the guard. The emulator was tolerant of the encoding mismatch; real Cosmos isn't. First failing run: pipeline build 222650271 (sync + async test variants both fail, identical signature).
Scope
Verified on a real account that the following ops on the same spaced-id container do work: create_container, container.read(), upsert_item, read_item, database.delete_container. The gateway evidently normalizes URL encoding for standard resource URLs but not for sub-operation paths like …/operations/partitionkeydelete. So today the user-visible impact is delete_all_items_by_partition_key only, but the SDK-side encoding mismatch is general and could bite any future sub-operation that follows the same pattern.
Suggested fix
Either:
-
(Preferred, smallest blast radius) Make GetResourceIdOrFullNameFromLink URL-encode name-based links to match the URL path the server sees:
if IsNameBased(resource_link):
return urllib_quote(TrimBeginningAndEndingSlashes(resource_link), safe="/")
This keeps SDK signing aligned with the URL the gateway receives.
-
Validate ids at the public API surface and reject names containing URL-encoding-significant characters. Backwards-incompatible.
Regression test
Add (sync + async) coverage that creates a container with an id containing a space and runs delete_all_items_by_partition_key, alongside read/upsert/read-item to lock in the encoding contract across all container-scoped sub-operations.
Repro environment
- Account:
https://*.documents.azure.com (public production endpoint, master-key auth)
- SDK:
Azure/azure-sdk-for-python main @ commit ~ab7e36298b (also reproduces on local checkout)
- Pipeline that surfaced it:
CosmosDB-SDK-SQL-Python-Signoff build 222650271
Summary
container.delete_all_items_by_partition_key(...)returns 401 Unauthorized ("the expected payload is not built as per the protocol") when the container'sidcontains a URL-unsafe character (e.g. a space). The root cause is an encoding mismatch between path construction and auth signing in_base.py: the URL is URL-encoded but the resource link used to compute the auth signature is not.Reproduces on a real production Cosmos account (
https://*.documents.azure.com), not just PPE/staging.Repro
Output:
Note the
%20in the resource link the server used. The SDK signed with a raw space.Root cause
In
azure/cosmos/_cosmos_client_connection.py(sync) andazure/cosmos/aio/_cosmos_client_connection_async.py(async)DeleteAllItemsByPartitionKey:GetPathFromLink(_base.py:608) callsurllib_quote→ URL has%20.GetResourceIdOrFullNameFromLink(_base.py:553) only trims slashes → raw space.auth.__get_authorization_token_using_master_keysigns the raw form.The server reconstructs the canonical signing string from the request URL (encoded) and the two strings disagree →
(Unauthorized).Why it surfaced now
The pipeline test
test_delete_all_items_by_partition_keyuses an id with a space:Until PR #46568 (commit
7e47f0a194, May 22, 2026) the test had this guard:That PR removed the guard. The emulator was tolerant of the encoding mismatch; real Cosmos isn't. First failing run: pipeline build
222650271(sync + async test variants both fail, identical signature).Scope
Verified on a real account that the following ops on the same spaced-id container do work:
create_container,container.read(),upsert_item,read_item,database.delete_container. The gateway evidently normalizes URL encoding for standard resource URLs but not for sub-operation paths like…/operations/partitionkeydelete. So today the user-visible impact isdelete_all_items_by_partition_keyonly, but the SDK-side encoding mismatch is general and could bite any future sub-operation that follows the same pattern.Suggested fix
Either:
(Preferred, smallest blast radius) Make
GetResourceIdOrFullNameFromLinkURL-encode name-based links to match the URL path the server sees:This keeps SDK signing aligned with the URL the gateway receives.
Validate ids at the public API surface and reject names containing URL-encoding-significant characters. Backwards-incompatible.
Regression test
Add (sync + async) coverage that creates a container with an id containing a space and runs
delete_all_items_by_partition_key, alongside read/upsert/read-item to lock in the encoding contract across all container-scoped sub-operations.Repro environment
https://*.documents.azure.com(public production endpoint, master-key auth)Azure/azure-sdk-for-pythonmain@ commit ~ab7e36298b(also reproduces on local checkout)CosmosDB-SDK-SQL-Python-Signoffbuild222650271