# ListObjectsV2

ListObjectsV2 lists the objects in a bucket, sorted by key, up to 1,000 per request. `prefix`
narrows the list to keys starting with some text, `delimiter` groups keys into folder-like
`CommonPrefixes`, and `continuation-token` fetches the next page. It is the listing the AWS SDKs
and the AWS CLI use by default - `aws s3 ls` calls it too. Every version and delete marker is
what [ListObjectVersions](https://docs.strg.com/s3-api/ListObjectVersions) shows, and [ListObjects](https://docs.strg.com/s3-api/ListObjects)
is the older form of this operation.

**Info: Good to know on STRG**

- An access key limited to an object prefix sends that prefix, or a longer one, as `prefix`. A
  listing without it answers `403 AccessDenied`.
- Each bucket is reached through its own regional endpoint, so a bucket in another region
  answers `404 NoSuchBucket` rather than a redirect.
- With `fetch-owner=true`, each object's `Owner` names your STRG account.
- Listings are always signed. An unsigned listing answers `403 AccessDenied`, even for a bucket
  whose *Bucket Protection* is off - anonymous visitors read its objects through
  [GetObject](https://docs.strg.com/s3-api/GetObject) instead.
- `x-amz-expected-bucket-owner` is welcome and has nothing to check: a key only ever reaches
  buckets of its own account.

## When you'd use it

- **Browsing a folder.** A file manager, a sync tool or the AWS CLI shows one "folder" at a time
  by listing with `prefix=photos/2024/` and `delimiter=/`: the objects in it come back as
  `Contents`, the folders below it as `CommonPrefixes`.
- **Walking a whole bucket.** A nightly backup or an audit script reads every key, page by page,
  following `NextContinuationToken` until `IsTruncated` is `false` - the SDKs' paginators do
  this for you.
- **Checking what is already there.** A sync tool compares each key's `Size`, `ETag` and
  `LastModified` with its local copy, so only what changed is uploaded again.
- **An agent finding its own files.** An agent keeping its memory in a bucket, with a key limited
  to the prefix `agent/`, lists with `prefix=agent/` to see what it stored earlier.

## Request

```http
GET /?list-type=2&prefix=photos%2F2024%2F&delimiter=%2F&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?list-type=2&...` on `s3.eu-central-1.strg.com`, which is
what 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`). For a key limited to certain buckets, the
bucket needs to be one of them. For a key limited to an object prefix, the request's `prefix`
starts with that prefix - a key limited to `reports/` can list with `prefix=reports/` or
`prefix=reports/2026/`, and a listing with no `prefix` or with `prefix=re` answers
`403 AccessDenied`. [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. |
| `list-type` | String | Yes | Always `2`. Any other value is answered with `501 NotImplemented`. |
| `prefix` | String | No | Only keys starting with this text are listed. For an access key limited to an object prefix, it is required and at least as long as the key's own prefix. |
| `delimiter` | String | No | Keys containing this string after the `prefix` are rolled up into one `CommonPrefixes` entry per distinct part, up to and including the delimiter - with `/`, one entry per "folder". Each entry counts as one key towards `max-keys`. |
| `max-keys` | Integer | No | The most keys and common prefixes to return in one page. Default and maximum 1,000. |
| `continuation-token` | String | No | The `NextContinuationToken` of the previous page, which fetches the next one. It is an opaque string, best passed along exactly as received. |
| `start-after` | String | No | Lists only keys that sort after this key. |
| `fetch-owner` | Boolean | No | `true` adds an `Owner` to each object. The owner is always your STRG account. |
| `encoding-type` | String | No | `url` returns `Key`, `Prefix`, `Delimiter` and `StartAfter` URL-encoded, for keys containing characters XML 1.0 cannot carry. |

Any query parameter not listed here - apart from a presigned URL's `X-Amz-*` parameters and the
JavaScript SDK's `x-id` - is answered with `501 NotImplemented`, so a client knows straight away.

### 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-optional-object-attributes` | String | No | Has nothing to add: objects are never archived, so there is no `RestoreStatus`. |

### Request body

None.

## Response

A successful request answers `200 OK` with a `ListBucketResult` XML document.

### Response headers

| Name | Description |
| --- | --- |
| `x-amz-request-id` | The request ID, handy for a support request. |
| `x-amz-request-charged` | Never sent, as there are no requester charges. |

### Response body

| Element | Description |
| --- | --- |
| `Name` | The bucket name. |
| `Prefix` | The `prefix` the request sent. |
| `Delimiter` | The `delimiter` the request sent, if any. |
| `StartAfter` | The `start-after` the request sent, if any. |
| `ContinuationToken` | The `continuation-token` the request sent, if any. |
| `MaxKeys` | The page size in effect. |
| `KeyCount` | How many keys and common prefixes this page holds. Never more than `MaxKeys`. |
| `IsTruncated` | `true` if there are more results, which `NextContinuationToken` fetches. |
| `NextContinuationToken` | Sent when `IsTruncated` is `true`, to pass as `continuation-token` in the next request. |
| `EncodingType` | `url`, when the request sent `encoding-type=url`. |
| `Contents` | One per object: `Key`, `LastModified`, `ETag`, `Size`, `StorageClass` and, with `fetch-owner=true`, `Owner`. |
| `Contents` / `Owner` | `ID` and `DisplayName` both carry your STRG account ID. |
| `CommonPrefixes` | One per rolled-up group when the request sent a `delimiter`, each with its `Prefix`. |

Multipart uploads that were started but never completed are not part of this list -
[ListMultipartUploads](https://docs.strg.com/s3-api/ListMultipartUploads) shows those.

```xml
<?xml version="1.0" encoding="UTF-8"?>
<ListBucketResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
  <Name>my-bucket</Name>
  <Prefix>photos/2024/</Prefix>
  <Delimiter>/</Delimiter>
  <MaxKeys>100</MaxKeys>
  <KeyCount>3</KeyCount>
  <IsTruncated>false</IsTruncated>
  <Contents>
    <Key>photos/2024/cat.jpg</Key>
    <LastModified>2026-09-20T09:14:02.000Z</LastModified>
    <ETag>"85f30635602dc09bd85957a6e82a2c21"</ETag>
    <Size>482133</Size>
    <StorageClass>STANDARD</StorageClass>
  </Contents>
  <Contents>
    <Key>photos/2024/dog.jpg</Key>
    <LastModified>2026-09-21T16:40:55.000Z</LastModified>
    <ETag>"3729bf569924c845dfe90bfe6281a9ff-2"</ETag>
    <Size>104857600</Size>
    <StorageClass>STANDARD</StorageClass>
  </Contents>
  <CommonPrefixes>
    <Prefix>photos/2024/holiday/</Prefix>
  </CommonPrefixes>
</ListBucketResult>
```

## Errors

These are the errors this request can return, and the situation behind each one.

| Code | HTTP status | When |
| --- | --- | --- |
| `NoSuchBucket` | 404 | The bucket does not exist or belongs to another account, 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 **List objects**, the bucket is outside the key's buckets, `prefix` does not start with the key's object prefix, the account is not active, the request is unsigned, an `x-amz-*` header is not signed, or a presigned URL has expired. |
| `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 | `list-type` is not `2`, or the request carries a query parameter this operation does not use. |
| `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 |
| --- | --- | --- |
| Access key limited to a prefix | IAM policy conditions decide | The request sets `prefix` to the key's prefix or a longer one; without it, `403 AccessDenied` |
| Bucket in another region | `301` redirect with `x-amz-bucket-region` | `404 NoSuchBucket`. A bucket is reached through its own regional endpoint. |
| `Owner` with `fetch-owner=true` | The bucket owner's canonical ID | Your STRG account ID, in both `ID` and `DisplayName` |
| Unsigned requests to public buckets | Accepted where the bucket allows public listing | `403 AccessDenied`: listings are always signed, and anonymous visitors read objects with [GetObject](https://docs.strg.com/s3-api/GetObject) |
| `x-amz-expected-bucket-owner` | Checked against the bucket owner | Welcome, with nothing to check |
| `x-amz-optional-object-attributes`, `x-amz-request-payer` | Add `RestoreStatus`; acknowledge requester charges | Nothing to add: objects are never archived, and there are no requester charges |

## Examples

### Listing a folder

**AWS CLI**

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

The AWS CLI follows `NextContinuationToken` for you and prints every page as one result.
`--max-items 100` stops it after 100 keys.

**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.list_objects_v2(
    Bucket="my-bucket",
    Prefix="photos/2024/",
    Delimiter="/",
)

for folder in response.get("CommonPrefixes", []):
    print("folder:", folder["Prefix"])
for obj in response.get("Contents", []):
    print(obj["Key"], obj["Size"])
```

**JavaScript (AWS SDK v3)**

```javascript
import { ListObjectsV2Command, 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 ListObjectsV2Command({
    Bucket: "my-bucket",
    Prefix: "photos/2024/",
    Delimiter: "/",
  }),
);

for (const folder of response.CommonPrefixes ?? []) console.log("folder:", folder.Prefix);
for (const object of response.Contents ?? []) console.log(object.Key, object.Size);
```

### Listing every object, page by page

A bucket with more than 1,000 objects takes several requests. The SDKs' paginators follow
`NextContinuationToken` until `IsTruncated` is `false`, so the code only sees one page after
another.

**AWS CLI**

```bash
aws s3api list-objects-v2 \
  --bucket my-bucket \
  --query 'Contents[].[Key, Size]' \
  --output text \
  --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_objects_v2")
for page in paginator.paginate(Bucket="my-bucket"):
    for obj in page.get("Contents", []):
        print(obj["Key"], obj["Size"])
```

**JavaScript (AWS SDK v3)**

```javascript
import { S3Client, paginateListObjectsV2 } 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 paginateListObjectsV2({ client }, { Bucket: "my-bucket" })) {
  for (const object of page.Contents ?? []) console.log(object.Key, object.Size);
}
```

### Listing with a key limited to a prefix

An access key limited to the object prefix `reports/` passes it, or a longer one, as the prefix.
The same command without `--prefix` answers `AccessDenied`.

**AWS CLI**

```bash
aws s3api list-objects-v2 \
  --bucket my-bucket \
  --prefix reports/ \
  --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_objects_v2")
for page in paginator.paginate(Bucket="my-bucket", Prefix="reports/"):
    for obj in page.get("Contents", []):
        print(obj["Key"])
```

**JavaScript (AWS SDK v3)**

```javascript
import { S3Client, paginateListObjectsV2 } 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 paginateListObjectsV2({ client }, { Bucket: "my-bucket", Prefix: "reports/" })) {
  for (const object of page.Contents ?? []) console.log(object.Key);
}
```

## Related

- [ListObjectVersions](https://docs.strg.com/s3-api/ListObjectVersions) - every version and delete marker
- [ListObjects](https://docs.strg.com/s3-api/ListObjects) - the older listing
- [ListMultipartUploads](https://docs.strg.com/s3-api/ListMultipartUploads) - uploads started but not completed
- [HeadObject](https://docs.strg.com/s3-api/HeadObject) and [GetObject](https://docs.strg.com/s3-api/GetObject)
- [Access keys](https://docs.strg.com/access-keys) - bucket and prefix limits
- [S3 API overview](https://docs.strg.com/s3-api/overview#differences-from-amazon-s3) - every difference from Amazon S3
