# AbortMultipartUpload

AbortMultipartUpload cancels a multipart upload started with
[CreateMultipartUpload](https://docs.strg.com/s3-api/CreateMultipartUpload). The parts uploaded so far are deleted,
and from then on the upload ID no longer works for [UploadPart](https://docs.strg.com/s3-api/UploadPart) or
[CompleteMultipartUpload](https://docs.strg.com/s3-api/CompleteMultipartUpload). It is how an upload that is not going
to be finished gets tidied away.

**Info: Good to know on STRG**

- `x-amz-expected-bucket-owner` is welcome and has nothing to check: a key only ever reaches
  buckets of its own account.
- `x-amz-if-match-initiated-time` is answered with `501 NotImplemented`, and the upload stays
  exactly as it was, so a condition meant to protect an upload never ends up cancelling it.

**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 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. [ListMultipartUploads](https://docs.strg.com/s3-api/ListMultipartUploads) shows any uploads left behind.

## When you'd use it

- **A user cancels an upload.** A web or desktop app uploading a large video in parts can
  abort the upload when the person presses *Cancel*, so the parts sent so far do not linger.
- **An upload fails partway.** A backup tool whose part keeps failing, or whose source file
  changed mid-upload, can abort and start again cleanly rather than leave the old parts behind.
- **A periodic clean-up.** A maintenance script can list the bucket's unfinished uploads with
  [ListMultipartUploads](https://docs.strg.com/s3-api/ListMultipartUploads) and abort the ones it no longer needs -
  the second example below does exactly that.

## Request

```http
DELETE /photos/2024/cat.jpg?uploadId=UploadId 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 `DELETE /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`). For a key limited to certain buckets
or to a key prefix, the bucket and the object key are 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. |
| `uploadId` | String | Yes | The upload ID that CreateMultipartUpload returned. |

### 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-if-match-initiated-time` | Timestamp | No | STRG answers `501 NotImplemented`, and the upload is not aborted. |

### Request body

None.

## Response

A successful abort answers `204 No Content` with no 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

None.

## Errors

These are the errors an abort 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. |
| `NotImplemented` | 501 | The request carries `x-amz-if-match-initiated-time`. Nothing is aborted. |
| `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. |
| `x-amz-expected-bucket-owner` | Checked against the bucket owner | Welcome, with nothing to check |
| `x-amz-if-match-initiated-time` | Supported on directory buckets | `501 NotImplemented`, and the upload is not aborted. The same request without the header aborts it. |

## Examples

Cancelling an upload started for `photos/2024/cat.jpg`, with its upload ID in `UPLOAD_ID`:

**AWS CLI**

```bash
aws s3api abort-multipart-upload \
  --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",
)

s3.abort_multipart_upload(
    Bucket="my-bucket",
    Key="photos/2024/cat.jpg",
    UploadId=os.environ["UPLOAD_ID"],
)
```

**JavaScript (AWS SDK v3)**

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

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

await client.send(
  new AbortMultipartUploadCommand({
    Bucket: "my-bucket",
    Key: "photos/2024/cat.jpg",
    UploadId: process.env.UPLOAD_ID,
  }),
);
```

Cancelling every unfinished upload in a bucket, which is a handy clean-up after a batch job:

**AWS CLI**

```bash
aws s3api list-multipart-uploads \
  --bucket my-bucket \
  --endpoint-url https://s3.eu-central-1.strg.com \
  --region eu-central-1 \
  --query 'Uploads[].[Key,UploadId]' \
  --output text |
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")
for page in paginator.paginate(Bucket="my-bucket"):
    for upload in page.get("Uploads", []):
        s3.abort_multipart_upload(
            Bucket="my-bucket",
            Key=upload["Key"],
            UploadId=upload["UploadId"],
        )
```

**JavaScript (AWS SDK v3)**

```javascript
import {
  AbortMultipartUploadCommand,
  S3Client,
  paginateListMultipartUploads,
} 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 paginateListMultipartUploads({ client }, { Bucket: "my-bucket" })) {
  for (const upload of page.Uploads ?? []) {
    await client.send(
      new AbortMultipartUploadCommand({
        Bucket: "my-bucket",
        Key: upload.Key,
        UploadId: upload.UploadId,
      }),
    );
  }
}
```

## 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)
- [ListParts](https://docs.strg.com/s3-api/ListParts)
- [ListMultipartUploads](https://docs.strg.com/s3-api/ListMultipartUploads)
- [Lifecycle policies](https://docs.strg.com/lifecycle-policies)
- [Access keys](https://docs.strg.com/access-keys)
- [S3 API overview](https://docs.strg.com/s3-api/overview)
