# DeleteObject

DeleteObject deletes an object. Every STRG bucket keeps versions of its objects, so a
delete comes in two kinds, and which one happens depends on whether the request names a
version:

| Request | Effect | Permission |
| --- | --- | --- |
| No `versionId` | Writes a delete marker, which becomes the object's current version. The object disappears from listings and reads, and every earlier version is kept | *Write objects* |
| With `versionId` | Permanently deletes that version, or removes that delete marker | *Delete object versions* |

For many objects at once, [DeleteObjects](https://docs.strg.com/s3-api/DeleteObjects) takes up to a thousand in one
request.

**Info: Good to know on STRG**

- Versioning is always on, so a delete without `versionId` never removes data: an object
  deleted by mistake comes back when its delete marker is deleted.
- A permanent delete needs *Delete object versions*, rather than *Write objects*.
- Conditional deletes (`If-Match`, `x-amz-if-match-last-modified-time`, `x-amz-if-match-size`)
  and `x-amz-mfa` are answered with `501 NotImplemented`, and nothing is deleted.
- `x-amz-expected-bucket-owner` is welcome and has nothing to check: a key only ever reaches
  buckets of its own account.

**Danger: Deleted objects are still billed**

A delete marker hides an object; it does not remove it. Every earlier version stays billed as
stored data until it is deleted by `versionId` or a
[lifecycle policy](https://docs.strg.com/lifecycle-policies) removes it. A permanent delete cannot be undone.

## When you'd use it

- **An app removing what a user deleted.** A photo-sharing app deletes `photos/2024/cat.jpg`
  when its owner removes it. The delete marker hides it at once, and the earlier version is
  still there if the user changes their mind.
- **Recovering from a mistake.** A script that deleted the wrong file can list its versions
  with [ListObjectVersions](https://docs.strg.com/s3-api/ListObjectVersions) and delete the delete marker by its
  version ID - the object is back exactly as it was.
- **Freeing storage for good.** A clean-up job that has to make data truly gone - a customer's
  export after its download window, or an old build artifact - deletes each version by
  `versionId`, so it no longer counts as stored data.
- **An agent tidying its workspace.** An agent keeping scratch files in a bucket deletes the
  ones it no longer needs, the same way any S3 client does.

## Request

```http
DELETE /photos/2024/cat.jpg 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` on `s3.eu-central-1.strg.com`,
which a bucket name containing a dot needs - [Addressing](https://docs.strg.com/s3-api/overview#addressing) explains
both.

### Permissions

Which permission the access key needs depends on the request:

| Request | Permission |
| --- | --- |
| Without `versionId` | **Write objects** (`objects:write`) |
| With `versionId` | **Delete object versions** (`objects:delete-versions`). *Write objects* alone is not enough, and is not needed |
| With `x-amz-bypass-governance-retention: true` | **Bypass governance retention** (`object-retention:bypass-governance`), in addition to the above |

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 holding the object. |
| `Key` | String | Yes | The object key. A key containing a `.` or `..` path segment (`a/../b`) cannot be put in a URL, so [DeleteObjects](https://docs.strg.com/s3-api/DeleteObjects), which carries the key in the request body, is the way to delete it. |
| `versionId` | String | No | The version to delete permanently. Naming a delete marker's version ID removes the marker, and the previous version becomes current again. Without it, a delete marker is written. |

Any other query parameter is answered with `501 NotImplemented`.

### Request headers

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `x-amz-bypass-governance-retention` | Boolean | No | `true` permanently deletes a version locked in governance mode, with *Bypass governance retention*. It has no effect on compliance mode or a legal hold. [Object Lock](https://docs.strg.com/object-locks#bypassing-governance-retention) has the details. |
| `x-amz-mfa` | String | No | STRG has no MFA Delete, and answers `501 NotImplemented`. |
| `If-Match` | String | No | STRG answers `501 NotImplemented`, and nothing is deleted. |
| `x-amz-if-match-last-modified-time` | Timestamp | No | STRG answers `501 NotImplemented`, and nothing is deleted. |
| `x-amz-if-match-size` | Long | No | STRG answers `501 NotImplemented`, and nothing is deleted. |
| `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 delete answers `204 No Content` with no body.

### Response headers

| Name | Description |
| --- | --- |
| `x-amz-delete-marker` | `true` when the request wrote a delete marker, or when the version it permanently deleted was a delete marker. |
| `x-amz-version-id` | Without `versionId`: the version ID of the new delete marker. With `versionId`: the version that was deleted. Version IDs are opaque strings. |
| `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 a delete can return, and the situation behind each one.

| Code | HTTP status | When |
| --- | --- | --- |
| `NotImplemented` | 501 | The request carries `x-amz-mfa`, `If-Match`, `x-amz-if-match-last-modified-time`, `x-amz-if-match-size`, or a query parameter other than `versionId`. Nothing is deleted. |
| `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 the permission the request needs (see [Permissions](https://docs.strg.com/s3-api/DeleteObject#permissions)), 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 yet in a region where the account already stores data, in which case the same request works shortly afterwards. |
| `InvalidURI` | 400 | The key contains a `.` or `..` path segment. [DeleteObjects](https://docs.strg.com/s3-api/DeleteObjects) can delete it. |
| `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. |
| `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. |

A version under retention or a legal hold stays until its lock ends, and a permanent delete
of it does not go through - except governance-mode retention, with
`x-amz-bypass-governance-retention: true`. [Object Lock](https://docs.strg.com/object-locks) explains both
modes.

## Differences from Amazon S3

The table lists each place where STRG behaves differently, and what that means for a client.

| Aspect | Amazon S3 | STRG |
| --- | --- | --- |
| Versioning | Depends on the bucket: off, enabled or suspended | Always on. A delete without `versionId` writes a delete marker and removes nothing, so deleting the marker undoes it |
| Permissions | `s3:DeleteObject`, plus `s3:DeleteObjectVersion` for a version | *Write objects* without `versionId`; *Delete object versions* **instead** with one |
| Conditional deletes | `If-Match` supported; size and time conditions on directory buckets | `501 NotImplemented`, and nothing is deleted |
| MFA Delete | `x-amz-mfa` | `501 NotImplemented` |
| `x-amz-expected-bucket-owner` | Checked against the bucket owner | Welcome, with nothing to check |
| Bucket in another region | `301` redirect with `x-amz-bucket-region` | `404 NoSuchBucket`. A bucket is reached through its own regional endpoint |
| Keys with `.` or `..` segments | Allowed | `400 InvalidURI`. [DeleteObjects](https://docs.strg.com/s3-api/DeleteObjects) deletes such a key |

## Examples

Deleting `photos/2024/cat.jpg`, which writes a delete marker:

**AWS CLI**

```bash
aws s3api delete-object \
  --bucket my-bucket \
  --key photos/2024/cat.jpg \
  --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",
)

response = s3.delete_object(Bucket="my-bucket", Key="photos/2024/cat.jpg")
print(response.get("DeleteMarker"), response.get("VersionId"))
```

**JavaScript (AWS SDK v3)**

```javascript
import { DeleteObjectCommand, 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 DeleteObjectCommand({
    Bucket: "my-bucket",
    Key: "photos/2024/cat.jpg",
  }),
);
console.log(response.DeleteMarker, response.VersionId);
```

Permanently deleting one version, with its version ID - from
[ListObjectVersions](https://docs.strg.com/s3-api/ListObjectVersions) - in `VERSION_ID`. Naming a delete marker's
version ID restores the object:

**AWS CLI**

```bash
aws s3api delete-object \
  --bucket my-bucket \
  --key photos/2024/cat.jpg \
  --version-id "$VERSION_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.delete_object(
    Bucket="my-bucket",
    Key="photos/2024/cat.jpg",
    VersionId=os.environ["VERSION_ID"],
)
```

**JavaScript (AWS SDK v3)**

```javascript
import { DeleteObjectCommand, 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 DeleteObjectCommand({
    Bucket: "my-bucket",
    Key: "photos/2024/cat.jpg",
    VersionId: process.env.VERSION_ID,
  }),
);
```

A version locked in governance mode is deleted the same way with `--bypass-governance-retention`
added (`BypassGovernanceRetention=True` in boto3, `BypassGovernanceRetention: true` in the
JavaScript SDK). The access key then needs *Bypass governance retention* as well as *Delete
object versions*.

## Related

- [DeleteObjects](https://docs.strg.com/s3-api/DeleteObjects)
- [ListObjectVersions](https://docs.strg.com/s3-api/ListObjectVersions)
- [GetBucketVersioning](https://docs.strg.com/s3-api/GetBucketVersioning)
- [PutObject](https://docs.strg.com/s3-api/PutObject)
- [Object Lock](https://docs.strg.com/object-locks)
- [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)
