# ListMultipartUploads

ListMultipartUploads lists the multipart uploads in a bucket that were started with
[CreateMultipartUpload](https://docs.strg.com/s3-api/CreateMultipartUpload) and have not yet been completed with
[CompleteMultipartUpload](https://docs.strg.com/s3-api/CompleteMultipartUpload) or aborted with
[AbortMultipartUpload](https://docs.strg.com/s3-api/AbortMultipartUpload). It is how uploads left behind by an
interrupted client come to light, and [ListParts](https://docs.strg.com/s3-api/ListParts) then shows the parts of any
one of them.

**Info: Good to know on STRG**

- An access key limited to a key prefix sends `prefix`, set to that prefix or a longer one, so
  the listing stays inside what the key may see.
- `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 bucket is reached through its own region's endpoint; from another region, STRG answers
  `404 NoSuchBucket` rather than a redirect.

**Danger: Unfinished uploads are billed**

The parts of an upload that is never completed or aborted are billed as stored data, for as
long as they are kept. Aborting each listed upload that will not be completed stops that, and a
lifecycle rule can abort incomplete multipart uploads automatically after a number of days -
[Lifecycle policies](https://docs.strg.com/lifecycle-policies#cleaning-up-abandoned-multipart-uploads) has the
recipe.

## When you'd use it

- **A periodic clean-up.** A maintenance script can list a bucket's unfinished uploads, for
  instance after a nightly backup, and abort the ones nobody is going to finish - the second
  example below does exactly that.
- **Picking up an interrupted upload.** A desktop app that crashed halfway through a large video
  upload can find the upload again by its key, check which parts arrived with
  [ListParts](https://docs.strg.com/s3-api/ListParts), and carry on from there rather than start over.
- **Understanding stored data.** Someone looking at what a bucket holds can see which uploads are
  still open under a prefix, and since when, before deciding what to keep.

## Request

```http
GET /?uploads&prefix=photos/&max-uploads=100 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?uploads` on `s3.eu-central-1.strg.com` -
[Addressing](https://docs.strg.com/s3-api/overview#addressing) explains both.

### Permissions

The access key needs **List objects** (`objects:list`). A key limited to certain buckets sees
just those. A key limited to a key prefix sends a `prefix` that starts with that prefix; without
one, or with one outside the key's prefix, STRG answers `403 AccessDenied`.
[Access keys](https://docs.strg.com/access-keys) has the details.

### URI parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `Bucket` | String | Yes | The bucket whose unfinished uploads are listed. |
| `uploads` | - | Yes | Selects this operation. Sent without a value. |
| `prefix` | String | No | Only uploads whose key starts with this prefix are listed. Required, and held to the key's prefix, for an access key limited to a key prefix. |
| `delimiter` | String | No | Keys that share the part between `prefix` and the first occurrence of this character are grouped into one `CommonPrefixes` entry, like a folder. |
| `max-uploads` | Integer | No | The most uploads to return, 1 to 1,000. The default and the maximum is 1,000. |
| `key-marker` | String | No | The listing starts after this key. For the next page, this is the previous page's `NextKeyMarker`. |
| `upload-id-marker` | String | No | Together with `key-marker`, the listing starts after this upload of that key. For the next page, this is the previous page's `NextUploadIdMarker`. Has no effect without `key-marker`. |
| `encoding-type` | String | No | `url` has keys, prefixes, markers and the delimiter URL-encoded in the response - handy for keys with characters XML cannot carry. |

For any other query parameter, apart from those of a presigned URL, STRG answers
`501 NotImplemented`, so a client knows straight away that it has no effect here.

### 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. |

### Request body

None.

## Response

A successful request answers `200 OK` with an XML body.

### 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.

### Response body

The root element is `ListMultipartUploadsResult`.

| Element | Description |
| --- | --- |
| `Bucket` | The bucket name. |
| `KeyMarker` | The `key-marker` you sent. |
| `UploadIdMarker` | The `upload-id-marker` you sent. |
| `NextKeyMarker` | When `IsTruncated` is `true`, the value to send as `key-marker` for the next page. |
| `NextUploadIdMarker` | When `IsTruncated` is `true`, the value to send as `upload-id-marker` for the next page. |
| `Prefix` | The `prefix` you sent. |
| `Delimiter` | The `delimiter` you sent. Absent if you sent none. |
| `MaxUploads` | The most uploads this page could hold. |
| `IsTruncated` | `true` if more uploads match than this page holds. |
| `EncodingType` | `url` if you sent `encoding-type=url`. |
| `Upload` | One per upload (`Uploads` in the SDKs). Zero or more. |
| `Upload` / `Key` | The object key the upload was started for. |
| `Upload` / `UploadId` | The upload ID, which [UploadPart](https://docs.strg.com/s3-api/UploadPart), [ListParts](https://docs.strg.com/s3-api/ListParts), [CompleteMultipartUpload](https://docs.strg.com/s3-api/CompleteMultipartUpload) and [AbortMultipartUpload](https://docs.strg.com/s3-api/AbortMultipartUpload) take. It is an opaque string, best passed on exactly as received. |
| `Upload` / `Initiated` | When the upload was started. |
| `Upload` / `StorageClass` | Always `STANDARD`: STRG has a single storage class. |
| `Upload` / `Owner` | `ID` and `DisplayName` both carry your STRG account ID. |
| `Upload` / `Initiator` | `ID` and `DisplayName` both carry your STRG account ID, whichever access key started the upload. |
| `Upload` / `ChecksumAlgorithm` | The checksum algorithm the upload was started with, where one is returned. |
| `Upload` / `ChecksumType` | The checksum type of the upload, where one is returned. |
| `CommonPrefixes` / `Prefix` | With `delimiter`, one entry per group of keys. The uploads in a group are not listed individually. |

```xml
<?xml version="1.0" encoding="UTF-8"?>
<ListMultipartUploadsResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
  <Bucket>my-bucket</Bucket>
  <KeyMarker></KeyMarker>
  <UploadIdMarker></UploadIdMarker>
  <NextKeyMarker>photos/2024/cat.jpg</NextKeyMarker>
  <NextUploadIdMarker>example-upload-id-2</NextUploadIdMarker>
  <Prefix>photos/</Prefix>
  <MaxUploads>2</MaxUploads>
  <IsTruncated>true</IsTruncated>
  <Upload>
    <Key>photos/2024/beach.mp4</Key>
    <UploadId>example-upload-id-1</UploadId>
    <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>
    <Initiated>2026-09-26T11:02:14.000Z</Initiated>
  </Upload>
  <Upload>
    <Key>photos/2024/cat.jpg</Key>
    <UploadId>example-upload-id-2</UploadId>
    <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>
    <Initiated>2026-09-26T11:40:52.000Z</Initiated>
  </Upload>
</ListMultipartUploadsResult>
```

## Errors

These are the errors a listing can return, and the situation behind each one.

| Code | HTTP status | When |
| --- | --- | --- |
| `NoSuchBucket` | 404 | The bucket does not exist, belongs to someone else, or is in a region other than the endpoint's - including when the account has no storage in this endpoint's region yet. |
| `AccessDenied` | 403 | The access key lacks **List objects**, the bucket is outside its buckets, the key is limited to a key prefix and `prefix` is missing or outside it, or the account the key belongs to is not active. |
| `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. |
| `InvalidBucketName` | 400 | The bucket name is not a valid STRG bucket name - names are 3-58 characters. |
| `NotImplemented` | 501 | The request carries a query parameter this operation does not accept. |
| `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 |
| --- | --- | --- |
| Bucket in another region | `301` redirect with `x-amz-bucket-region` | `404 NoSuchBucket`. A bucket is reached through its own regional endpoint |
| Access key limited to a key prefix | Governed by IAM policy conditions | The key sends a `prefix` starting with its own; without one, STRG answers `403 AccessDenied` |
| `Owner`, `Initiator` | The AWS account or IAM principal | Your STRG account ID, in both |
| `StorageClass` | The class the upload was started with | Always `STANDARD` |
| `x-amz-expected-bucket-owner` | Checked against the bucket owner | Welcome, with nothing to check |

## Examples

Listing the unfinished uploads under `photos/`. The AWS CLI and boto3's paginator follow
`NextKeyMarker` and `NextUploadIdMarker` on their own:

**AWS CLI**

```bash
aws s3api list-multipart-uploads \
  --bucket my-bucket \
  --prefix photos/ \
  --endpoint-url https://s3.eu-central-1.strg.com \
  --region eu-central-1
```

**Python (boto3)**

```python
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_multipart_uploads")
for page in paginator.paginate(Bucket="my-bucket", Prefix="photos/"):
    for upload in page.get("Uploads", []):
        print(upload["Key"], upload["UploadId"], upload["Initiated"])
```

**JavaScript (AWS SDK v3)**

```javascript
import { ListMultipartUploadsCommand, S3Client } from "@aws-sdk/client-s3";

const client = new S3Client({
  endpoint: "https://s3.eu-central-1.strg.com",
  region: "eu-central-1",
});

let KeyMarker;
let UploadIdMarker;
do {
  const page = await client.send(
    new ListMultipartUploadsCommand({
      Bucket: "my-bucket",
      Prefix: "photos/",
      KeyMarker,
      UploadIdMarker,
    }),
  );
  for (const upload of page.Uploads ?? []) {
    console.log(upload.Key, upload.UploadId, upload.Initiated);
  }
  KeyMarker = page.IsTruncated ? page.NextKeyMarker : undefined;
  UploadIdMarker = page.IsTruncated ? page.NextUploadIdMarker : undefined;
} while (KeyMarker !== undefined);
```

### Aborting every unfinished upload under a prefix

Uploads that are no longer needed are billed until they are aborted. This example lists them
and aborts each one, so the key needs **Write objects** (`objects:write`) as well as
**List objects**:

**AWS CLI**

```bash
aws s3api list-multipart-uploads \
  --bucket my-bucket \
  --prefix photos/ \
  --query 'Uploads[].[Key, UploadId]' \
  --output text \
  --endpoint-url https://s3.eu-central-1.strg.com \
  --region eu-central-1 |
while IFS=$'\t' read -r key upload_id; do
  [ "$key" = "None" ] && continue
  aws s3api abort-multipart-upload \
    --bucket my-bucket \
    --key "$key" \
    --upload-id "$upload_id" \
    --endpoint-url https://s3.eu-central-1.strg.com \
    --region eu-central-1
done
```

**Python (boto3)**

```python
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_multipart_uploads")
uploads = [
    upload
    for page in paginator.paginate(Bucket="my-bucket", Prefix="photos/")
    for upload in page.get("Uploads", [])
]
for upload in uploads:
    s3.abort_multipart_upload(
        Bucket="my-bucket", Key=upload["Key"], UploadId=upload["UploadId"]
    )
    print("aborted", upload["Key"])
```

**JavaScript (AWS SDK v3)**

```javascript
import {
  AbortMultipartUploadCommand,
  ListMultipartUploadsCommand,
  S3Client,
} from "@aws-sdk/client-s3";

const client = new S3Client({
  endpoint: "https://s3.eu-central-1.strg.com",
  region: "eu-central-1",
});

const uploads = [];
let KeyMarker;
let UploadIdMarker;
do {
  const page = await client.send(
    new ListMultipartUploadsCommand({
      Bucket: "my-bucket",
      Prefix: "photos/",
      KeyMarker,
      UploadIdMarker,
    }),
  );
  uploads.push(...(page.Uploads ?? []));
  KeyMarker = page.IsTruncated ? page.NextKeyMarker : undefined;
  UploadIdMarker = page.IsTruncated ? page.NextUploadIdMarker : undefined;
} while (KeyMarker !== undefined);

for (const { Key, UploadId } of uploads) {
  await client.send(new AbortMultipartUploadCommand({ Bucket: "my-bucket", Key, UploadId }));
  console.log("aborted", Key);
}
```

## 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)
- [ListParts](https://docs.strg.com/s3-api/ListParts) - the parts of one upload
- [CompleteMultipartUpload](https://docs.strg.com/s3-api/CompleteMultipartUpload),
  [AbortMultipartUpload](https://docs.strg.com/s3-api/AbortMultipartUpload)
- [Lifecycle policies](https://docs.strg.com/lifecycle-policies#cleaning-up-abandoned-multipart-uploads) - aborting incomplete uploads automatically
- [Access keys](https://docs.strg.com/access-keys)
- [S3 API overview](https://docs.strg.com/s3-api/overview#differences-from-amazon-s3) - every difference from Amazon S3
