# ListParts

ListParts shows the parts uploaded so far for a multipart upload started with
[CreateMultipartUpload](https://docs.strg.com/s3-api/CreateMultipartUpload), in part-number order, each with its size
and `ETag`. Those `ETag`s are exactly what [CompleteMultipartUpload](https://docs.strg.com/s3-api/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.

**Info: Good to know on STRG**

- 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.
- `Owner` and `Initiator` both name your STRG account.
- `x-amz-expected-bucket-owner` is welcome and has nothing to check: a key only ever reaches
  buckets of its own account.
- A query parameter other than `uploadId`, `max-parts` and `part-number-marker` gets a clear
  answer straight away: STRG responds `501 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 `ETag`s to finish an upload.** A backup job that uploads parts from several
  workers at once does not need to pass every `ETag` back 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](https://docs.strg.com/s3-api/ListMultipartUploads) can look at its parts - how many, how large,
  how recent - before deciding whether to finish it or
  [abort it](https://docs.strg.com/s3-api/AbortMultipartUpload).

## Request

```http
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](https://docs.strg.com/s3-api/overview#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](https://docs.strg.com/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](https://docs.strg.com/s3-api/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](https://docs.strg.com/s3-api/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
<?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**

```bash
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
```

**Python (boto3)**

```python
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"])
```

**JavaScript (AWS SDK v3)**

```javascript
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**

```bash
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
```

**Python (boto3)**

```python
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"])
```

**JavaScript (AWS SDK v3)**

```javascript
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);
}
```

## Related

- [CreateMultipartUpload](https://docs.strg.com/s3-api/CreateMultipartUpload)
- [UploadPart](https://docs.strg.com/s3-api/UploadPart)
- [UploadPartCopy](https://docs.strg.com/s3-api/UploadPartCopy)
- [CompleteMultipartUpload](https://docs.strg.com/s3-api/CompleteMultipartUpload)
- [AbortMultipartUpload](https://docs.strg.com/s3-api/AbortMultipartUpload)
- [ListMultipartUploads](https://docs.strg.com/s3-api/ListMultipartUploads)
- [Access keys](https://docs.strg.com/access-keys)
- [S3 API overview](https://docs.strg.com/s3-api/overview)
