# ListObjectVersions

ListObjectVersions lists the versions and delete markers of the objects in a bucket, up to 1,000
a page, optionally narrowed by prefix and grouped by delimiter. Every STRG bucket keeps
versions of its objects, so an overwrite or a delete never loses the earlier data - and this
listing is where those earlier versions, and the delete markers that hide deleted objects, can be
seen. For the current objects alone, [ListObjectsV2](https://docs.strg.com/s3-api/ListObjectsV2) is the simpler choice.

**Info: Good to know on STRG**

- An access key limited to a key prefix names that prefix, or a longer one, as `prefix`; a
  listing without it answers `403 AccessDenied`.
- `Owner` carries your STRG account ID, in both `ID` and `DisplayName`.
- `x-amz-expected-bucket-owner` is welcome and has nothing to check: a key only ever reaches
  buckets of its own account.
- `x-amz-optional-object-attributes` and `x-amz-request-payer` have nothing to do here: there is
  one storage class and no request fees.
- Every listing is signed, even of a public bucket: an unsigned one answers `403 AccessDenied`.

Every version and delete marker in the listing is billed as stored data until it is permanently
deleted or a [lifecycle policy](https://docs.strg.com/lifecycle-policies) removes it.

## When you'd use it

- **Recovering a deleted file.** Someone removed `photos/2024/cat.jpg` by mistake. The listing
  shows its delete marker on top and the earlier version underneath; deleting the marker with
  [DeleteObject](https://docs.strg.com/s3-api/DeleteObject) brings the photo back exactly as it was.
- **Going back to an earlier version.** A config file was overwritten with a broken one. The
  listing gives each version's `VersionId` and `LastModified`, and
  [GetObject](https://docs.strg.com/s3-api/GetObject) with the right `versionId` reads the good one.
- **Seeing where storage goes.** Old versions are billed as stored data, so a periodic report -
  or an agent asked why a bucket is larger than its current files - adds up the `Size` of the
  versions that are not latest, before deciding on a
  [lifecycle rule](https://docs.strg.com/lifecycle-policies#keeping-30-days-of-old-versions) or permanent deletes.
- **Emptying a bucket before deleting it.** A bucket that still holds versions or delete markers
  cannot be deleted, so a tear-down script lists them all and removes each by version ID with
  [DeleteObjects](https://docs.strg.com/s3-api/DeleteObjects).

## Request

```http
GET /?versions&prefix=photos/&delimiter=/&max-keys=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?versions` 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

The access key needs **List objects** (`objects:list`). A key limited to certain buckets lists
those buckets only, and any other answers `403 AccessDenied`. A key limited to a key prefix sends
`prefix` set to that prefix or a longer one. [Access keys](https://docs.strg.com/access-keys#object-prefix) has
the details.

### URI parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `Bucket` | String | Yes | The bucket to list. |
| `versions` | Flag | Yes | Selects the versions listing. Sent with no value. |
| `prefix` | String | No | Lists only keys that begin with this string. For an access key limited to a key prefix, it is that prefix or a longer one. |
| `delimiter` | String | No | Groups keys that share the part between `prefix` and the first occurrence of the delimiter into one `CommonPrefixes` entry. Each group counts as one result against `max-keys`. |
| `max-keys` | Integer | No | The most versions, delete markers and common prefixes to return. Default and maximum 1,000. |
| `key-marker` | String | No | The key the listing starts after. A follow-up request sends the previous page's `NextKeyMarker` here. |
| `version-id-marker` | String | No | The version of `key-marker` the listing starts after. A follow-up request sends the previous page's `NextVersionIdMarker` here. |
| `encoding-type` | String | No | `url` returns key names URL-encoded, for keys that contain characters XML 1.0 cannot carry. |

A query parameter other than these is answered `501 NotImplemented`, so a client knows straight
away; presigning's `X-Amz-*` parameters and the JavaScript SDK's `x-id` are always welcome.

### 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-optional-object-attributes` | String | No | Not applicable: there is one storage class and nothing is archived, so there is no `RestoreStatus` to add. |
| `x-amz-request-payer` | String | No | Not applicable: STRG has no Requester Pays buckets and charges no request fees. |

### Request body

None.

## Response

A successful request answers `200 OK` with the listing as XML.

### Response headers

| Name | Description |
| --- | --- |
| `x-amz-request-id` | The request ID, handy for a support request. |

`x-amz-request-charged` is never returned.

### Response body

A `ListVersionsResult` document, with `Version` and `DeleteMarker` entries listed together.

| Element | Description |
| --- | --- |
| `Name` | The bucket name. |
| `Prefix` | The `prefix` sent. |
| `Delimiter` | The `delimiter` sent. |
| `MaxKeys` | The `max-keys` in effect. |
| `EncodingType` | `url` when the request sent `encoding-type=url`. `Key`, `Prefix`, `Delimiter`, `KeyMarker` and `NextKeyMarker` are then URL-encoded. |
| `KeyMarker` | The `key-marker` sent. |
| `VersionIdMarker` | The `version-id-marker` sent. |
| `IsTruncated` | `true` if more results remain. |
| `NextKeyMarker` | When truncated, the value for `key-marker` on the next page. |
| `NextVersionIdMarker` | When truncated, the value for `version-id-marker` on the next page. |
| `Version` | One object version: `Key`, `VersionId`, `IsLatest`, `LastModified`, `ETag`, `Size`, `StorageClass` (always `STANDARD`) and `Owner`. |
| `DeleteMarker` | One delete marker: `Key`, `VersionId`, `IsLatest`, `LastModified` and `Owner`. A key whose latest entry is a delete marker appears deleted to `GetObject` and to `ListObjectsV2`. |
| `CommonPrefixes` | One entry per group of keys rolled up by `delimiter`, each with a `Prefix`. |
| `Owner` | Inside `Version` and `DeleteMarker`. `ID` and `DisplayName` both carry your STRG account ID. |

Version IDs are opaque strings, and work exactly as returned.

```xml
<?xml version="1.0" encoding="UTF-8"?>
<ListVersionsResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
  <Name>my-bucket</Name>
  <Prefix>photos/</Prefix>
  <KeyMarker></KeyMarker>
  <VersionIdMarker></VersionIdMarker>
  <MaxKeys>100</MaxKeys>
  <Delimiter>/</Delimiter>
  <IsTruncated>false</IsTruncated>
  <DeleteMarker>
    <Key>photos/2024/cat.jpg</Key>
    <VersionId>EXAMPLE-VERSION-ID-3</VersionId>
    <IsLatest>true</IsLatest>
    <LastModified>2026-09-20T10:15:00.000Z</LastModified>
    <Owner>
      <ID>EXAMPLE-ACCOUNT-ID</ID>
      <DisplayName>EXAMPLE-ACCOUNT-ID</DisplayName>
    </Owner>
  </DeleteMarker>
  <Version>
    <Key>photos/2024/cat.jpg</Key>
    <VersionId>EXAMPLE-VERSION-ID-2</VersionId>
    <IsLatest>false</IsLatest>
    <LastModified>2026-09-12T08:30:00.000Z</LastModified>
    <ETag>"85f30635602dc09bd85957a6e82a2c21"</ETag>
    <Size>48213</Size>
    <StorageClass>STANDARD</StorageClass>
    <Owner>
      <ID>EXAMPLE-ACCOUNT-ID</ID>
      <DisplayName>EXAMPLE-ACCOUNT-ID</DisplayName>
    </Owner>
  </Version>
  <Version>
    <Key>photos/2024/cat.jpg</Key>
    <VersionId>EXAMPLE-VERSION-ID-1</VersionId>
    <IsLatest>false</IsLatest>
    <LastModified>2026-09-01T17:02:00.000Z</LastModified>
    <ETag>"0b5f4d7e3c2a19f86e1d4c7b2a9f3e10"</ETag>
    <Size>45102</Size>
    <StorageClass>STANDARD</StorageClass>
    <Owner>
      <ID>EXAMPLE-ACCOUNT-ID</ID>
      <DisplayName>EXAMPLE-ACCOUNT-ID</DisplayName>
    </Owner>
  </Version>
  <CommonPrefixes>
    <Prefix>photos/2025/</Prefix>
  </CommonPrefixes>
</ListVersionsResult>
```

## Errors

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

| Code | HTTP status | When |
| --- | --- | --- |
| `NoSuchBucket` | 404 | The bucket does not exist, belongs to another account, 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 **List objects**, the bucket is outside its buckets, `prefix` does not start with its key prefix, or the account is not active. Also every unsigned request. |
| `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. |
| `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 other than those listed under [URI parameters](https://docs.strg.com/s3-api/ListObjectVersions#uri-parameters) (presigning's `X-Amz-*` parameters and the JavaScript SDK's `x-id` aside). |
| `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 |
| --- | --- | --- |
| Versioning | Only buckets with versioning enabled or suspended hold more than one version | Always on. Overwrites and deletes keep earlier versions, so there is always something to go back to - and every version is billed as stored data until it is deleted |
| Bucket in another region | `301` redirect with `x-amz-bucket-region` | `404 NoSuchBucket`. A bucket is reached through its own regional endpoint |
| Nothing stored in the endpoint's region yet | Any valid key reaches the endpoint | `404 NoSuchBucket` |
| Access keys limited to a key prefix | - | The listing names the key's prefix, or a longer one, as `prefix`; without it, `403 AccessDenied` |
| `Owner` | Your AWS canonical user ID | Your STRG account ID, in both `ID` and `DisplayName` |
| `StorageClass` | Any storage class | Always `STANDARD` |
| `x-amz-optional-object-attributes` | Returns `RestoreStatus` | Not applicable: nothing is archived |
| `x-amz-request-payer`, `x-amz-request-charged` | Requester Pays | Not applicable: there are no request fees |
| `x-amz-expected-bucket-owner` | Checked against the bucket owner | Welcome, with nothing to check |
| Unsigned requests to public buckets | Answered where the bucket allows public listing | `403 AccessDenied`: a listing is always signed |

## Examples

Listing every version and delete marker under a prefix. The AWS CLI and the boto3 paginator
follow `NextKeyMarker` and `NextVersionIdMarker` on their own:

**AWS CLI**

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

The versions of one object only, as a table:

```bash
aws s3api list-object-versions \
  --bucket my-bucket \
  --prefix photos/2024/cat.jpg \
  --query "Versions[?Key=='photos/2024/cat.jpg'].[VersionId,IsLatest,LastModified,Size]" \
  --output table \
  --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_object_versions")
for page in paginator.paginate(Bucket="my-bucket", Prefix="photos/2024/"):
    for version in page.get("Versions", []):
        latest = "latest" if version["IsLatest"] else ""
        print(version["Key"], version["VersionId"], version["Size"], latest)
    for marker in page.get("DeleteMarkers", []):
        latest = "latest" if marker["IsLatest"] else ""
        print(marker["Key"], marker["VersionId"], "delete marker", latest)
```

**JavaScript (AWS SDK v3)**

```javascript
import { ListObjectVersionsCommand, 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 VersionIdMarker;
do {
  const page = await client.send(
    new ListObjectVersionsCommand({
      Bucket: "my-bucket",
      Prefix: "photos/2024/",
      KeyMarker,
      VersionIdMarker,
    }),
  );
  for (const version of page.Versions ?? []) {
    console.log(version.Key, version.VersionId, version.Size, version.IsLatest ? "latest" : "");
  }
  for (const marker of page.DeleteMarkers ?? []) {
    console.log(marker.Key, marker.VersionId, "delete marker", marker.IsLatest ? "latest" : "");
  }
  KeyMarker = page.IsTruncated ? page.NextKeyMarker : undefined;
  VersionIdMarker = page.IsTruncated ? page.NextVersionIdMarker : undefined;
} while (KeyMarker !== undefined);
```

A deleted object comes back when its latest delete marker is deleted with
[DeleteObject](https://docs.strg.com/s3-api/DeleteObject) and that marker's `versionId`. An earlier version is read by
passing its `versionId` to [GetObject](https://docs.strg.com/s3-api/GetObject).

## Related

- [ListObjectsV2](https://docs.strg.com/s3-api/ListObjectsV2) - the current objects only
- [ListObjects](https://docs.strg.com/s3-api/ListObjects)
- [GetBucketVersioning](https://docs.strg.com/s3-api/GetBucketVersioning)
- [DeleteObject](https://docs.strg.com/s3-api/DeleteObject) and [DeleteObjects](https://docs.strg.com/s3-api/DeleteObjects)
- [GetObject](https://docs.strg.com/s3-api/GetObject) and [HeadObject](https://docs.strg.com/s3-api/HeadObject)
- [Lifecycle policies](https://docs.strg.com/lifecycle-policies) - removing old versions 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
