ListParts
ListParts shows the parts uploaded so far for a multipart upload started with
CreateMultipartUpload, in part-number order, each with its size
and ETag. Those ETags are exactly what CompleteMultipartUpload
asks for, so ListParts is the natural way to see how far an upload has got and to gather what
finishing it needs. A response holds up to 1,000 parts, and part-number-marker carries on to
the next page.
- The permission is Write objects, the same one that uploads the parts, so a key that can upload can also check its progress. List objects on its own does not cover it.
OwnerandInitiatorboth name your STRG account.x-amz-expected-bucket-owneris welcome and has nothing to check: a key only ever reaches buckets of its own account.- A query parameter other than
uploadId,max-partsandpart-number-markergets a clear answer straight away: STRG responds501 NotImplemented.
When you'd use it
- Resuming an interrupted upload. A desktop app sending a long video loses its connection halfway through. When it comes back, ListParts tells it which parts are already stored, so it sends only the rest and then completes the upload.
- Collecting the
ETags to finish an upload. A backup job that uploads parts from several workers at once does not need to pass everyETagback to one place: the process that completes the upload can read them all with ListParts. - Checking an upload left behind. A clean-up script that finds an unfinished upload with ListMultipartUploads can look at its parts - how many, how large, how recent - before deciding whether to finish it or abort it.
Request
GET /photos/2024/cat.jpg?uploadId=UploadId&max-parts=MaxParts&part-number-marker=PartNumberMarker HTTP/1.1
Host: my-bucket.s3.eu-central-1.strg.com
Authorization: AWS4-HMAC-SHA256 Credential=...
x-amz-date: 20260926T120000Z
x-amz-content-sha256: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
The path-style form is GET /my-bucket/photos/2024/cat.jpg?uploadId=UploadId on
s3.eu-central-1.strg.com - Addressing explains both.
Permissions
The access key needs Write objects (objects:write), the permission that uploads the parts,
rather than List objects. For a key limited to certain buckets or to a key prefix, the bucket
and the object key need to be within them. Access keys has the details.
URI parameters
| Name | Type | Required | Description |
|---|---|---|---|
Bucket | String | Yes | The bucket the upload was started in. |
Key | String | Yes | The object key the upload was started for. A key with a . or .. path segment (a/../b) cannot travel safely in a URL, and is answered 400 InvalidURI. |
uploadId | String | Yes | The upload ID that CreateMultipartUpload returned. Its presence is what makes this request ListParts rather than GetObject. |
max-parts | Integer | No | The most parts to return. Up to 1,000, which is also the default. |
part-number-marker | Integer | No | Lists only the parts with a higher part number. The previous response's NextPartNumberMarker goes here. |
Any other query parameter is answered 501 NotImplemented.
Request headers
| Name | Type | Required | Description |
|---|---|---|---|
x-amz-expected-bucket-owner | String | No | Welcome, with nothing to check: an access key only ever reaches its own account's buckets. |
x-amz-request-payer | String | No | Has no effect: STRG has no Requester Pays buckets. |
x-amz-server-side-encryption-customer-algorithm | String | No | AES256, for an upload started with a customer-provided encryption key (SSE-C). Accepted. |
x-amz-server-side-encryption-customer-key | String | No | The Base64 SSE-C key. Accepted. |
x-amz-server-side-encryption-customer-key-MD5 | String | No | Base64 MD5 of the SSE-C key. Accepted. |
Request body
None.
Response
Response headers
| Name | Description |
|---|---|
x-amz-request-id | The request ID, useful when asking support about a request. |
x-amz-request-charged is never returned. x-amz-abort-date and x-amz-abort-rule-id are not
something to count on for whether a lifecycle rule will abort the upload; the bucket's rules,
read with GetBucketLifecycleConfiguration, are the
dependable answer.
Response body
| Element | Description |
|---|---|
ListPartsResult | The root element. |
Bucket | The bucket name, as you use it. |
Key | The object key. |
UploadId | The upload ID. |
PartNumberMarker | The part-number-marker of the request, 0 when none was sent. |
NextPartNumberMarker | The last part number in this response - the part-number-marker for the next page. |
MaxParts | The most parts this response could hold. |
IsTruncated | true if more parts follow, in which case the next page starts after NextPartNumberMarker. |
Part | One per part (Parts in the SDKs). Zero or more. |
Part / PartNumber | The part number, 1-10,000. |
Part / LastModified | When the part was uploaded. |
Part / ETag | The part's entity tag, as UploadPart returned it - the value CompleteMultipartUpload takes for this part. |
Part / Size | The part's size in bytes. |
Part / ChecksumCRC32, ChecksumCRC32C, ChecksumCRC64NVME, ChecksumSHA1, ChecksumSHA256 | The part's checksum, when one was stored for it. |
Initiator | Who started the upload. ID and DisplayName both carry your STRG account ID. |
Owner | The owner of the object to be created. ID and DisplayName both carry your STRG account ID. |
StorageClass | Always STANDARD. |
ChecksumAlgorithm | The checksum algorithm of the upload, when it has one. |
ChecksumType | COMPOSITE or FULL_OBJECT, when the upload has a checksum. |
<?xml version="1.0" encoding="UTF-8"?>
<ListPartsResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<Bucket>my-bucket</Bucket>
<Key>photos/2024/cat.jpg</Key>
<UploadId>example-upload-id</UploadId>
<PartNumberMarker>0</PartNumberMarker>
<NextPartNumberMarker>2</NextPartNumberMarker>
<MaxParts>1000</MaxParts>
<IsTruncated>false</IsTruncated>
<Part>
<ETag>"85f30635602dc09bd85957a6e82a2c21"</ETag>
<LastModified>2026-09-26T12:00:05.000Z</LastModified>
<PartNumber>1</PartNumber>
<Size>67108864</Size>
</Part>
<Part>
<ETag>"1b2cf535f27731c974343645a3985328"</ETag>
<LastModified>2026-09-26T12:00:09.000Z</LastModified>
<PartNumber>2</PartNumber>
<Size>1048576</Size>
</Part>
<Initiator>
<ID>your-account-id</ID>
<DisplayName>your-account-id</DisplayName>
</Initiator>
<Owner>
<ID>your-account-id</ID>
<DisplayName>your-account-id</DisplayName>
</Owner>
<StorageClass>STANDARD</StorageClass>
</ListPartsResult>
Errors
These are the errors ListParts can return, and the situation behind each one.
| Code | HTTP status | When |
|---|---|---|
NoSuchUpload | 404 | The upload ID does not exist, or the upload was already completed or aborted. |
NoSuchBucket | 404 | The bucket does not exist, it is in a region other than the endpoint's, or the account has no storage in this endpoint's region. |
AccessDenied | 403 | The access key lacks Write objects, or the bucket or key is outside its buckets or prefix. |
InvalidAccessKeyId | 403 | The access key does not exist, has expired or been revoked - or it was created moments ago and is not usable in this endpoint's region yet, in which case the same request works shortly afterwards. |
SignatureDoesNotMatch | 403 | The request signature does not match. |
RequestTimeTooSkewed | 403 | The request time is more than 15 minutes from the server's. |
AuthorizationHeaderMalformed | 400 | The signature's credential scope names a region other than the endpoint's. |
InvalidURI | 400 | The key contains a . or .. path segment, such as a/../b. |
NotImplemented | 501 | The request carries a query parameter this operation does not take. |
SlowDown | 503 | Too many requests. The Retry-After header says when a retry is welcome; the AWS SDKs retry on their own. |
ServiceUnavailable | 503 | A temporary failure; the same request can be retried. |
Differences from Amazon S3
The table lists each place where STRG behaves differently, and what that means for a client.
| Aspect | Amazon S3 | STRG |
|---|---|---|
| Permission | s3:ListMultipartUploadParts | Write objects (objects:write), the permission that uploads the parts. List objects on its own does not cover it |
Owner, Initiator | Your AWS canonical user ID, or an IAM user ARN | Your STRG account ID, in both ID and DisplayName |
| Bucket in another region | 301 redirect with x-amz-bucket-region | 404 NoSuchBucket. A bucket is reached through its own regional endpoint. |
x-amz-expected-bucket-owner | Checked against the bucket owner | Welcome, with nothing to check |
| Unknown query parameters | Ignored | 501 NotImplemented, so a client knows straight away |
Keys with . or .. segments | Allowed | 400 InvalidURI |
Examples
Listing every part of an upload means following NextPartNumberMarker until IsTruncated is
false - and the AWS CLI and the SDK paginators do that paging for you:
- AWS CLI
- Python (boto3)
- JavaScript (AWS SDK v3)
aws s3api list-parts \
--bucket my-bucket \
--key photos/2024/cat.jpg \
--upload-id "$UPLOAD_ID" \
--endpoint-url https://s3.eu-central-1.strg.com \
--region eu-central-1
import os
import boto3
s3 = boto3.client(
"s3",
endpoint_url="https://s3.eu-central-1.strg.com",
region_name="eu-central-1",
)
paginator = s3.get_paginator("list_parts")
for page in paginator.paginate(
Bucket="my-bucket",
Key="photos/2024/cat.jpg",
UploadId=os.environ["UPLOAD_ID"],
):
for part in page.get("Parts", []):
print(part["PartNumber"], part["Size"], part["ETag"])
import { S3Client, paginateListParts } from "@aws-sdk/client-s3";
const client = new S3Client({
endpoint: "https://s3.eu-central-1.strg.com",
region: "eu-central-1",
});
for await (const page of paginateListParts(
{ client },
{ Bucket: "my-bucket", Key: "photos/2024/cat.jpg", UploadId: process.env.UPLOAD_ID },
)) {
for (const part of page.Parts ?? []) {
console.log(part.PartNumber, part.Size, part.ETag);
}
}
One page of at most 100 parts, starting after part 200:
- AWS CLI
- Python (boto3)
- JavaScript (AWS SDK v3)
aws s3api list-parts \
--bucket my-bucket \
--key photos/2024/cat.jpg \
--upload-id "$UPLOAD_ID" \
--max-parts 100 \
--part-number-marker 200 \
--no-paginate \
--endpoint-url https://s3.eu-central-1.strg.com \
--region eu-central-1
import os
import boto3
s3 = boto3.client(
"s3",
endpoint_url="https://s3.eu-central-1.strg.com",
region_name="eu-central-1",
)
response = s3.list_parts(
Bucket="my-bucket",
Key="photos/2024/cat.jpg",
UploadId=os.environ["UPLOAD_ID"],
MaxParts=100,
PartNumberMarker=200,
)
for part in response.get("Parts", []):
print(part["PartNumber"], part["ETag"])
if response["IsTruncated"]:
print("next page starts after part", response["NextPartNumberMarker"])
import { ListPartsCommand, S3Client } from "@aws-sdk/client-s3";
const client = new S3Client({
endpoint: "https://s3.eu-central-1.strg.com",
region: "eu-central-1",
});
const response = await client.send(
new ListPartsCommand({
Bucket: "my-bucket",
Key: "photos/2024/cat.jpg",
UploadId: process.env.UPLOAD_ID,
MaxParts: 100,
PartNumberMarker: "200",
}),
);
for (const part of response.Parts ?? []) {
console.log(part.PartNumber, part.ETag);
}
if (response.IsTruncated) {
console.log("next page starts after part", response.NextPartNumberMarker);
}