# ListObjects

ListObjects returns up to 1,000 of the objects in a bucket per request, in ascending order of
their keys. A prefix narrows the listing to one part of the bucket, a delimiter groups keys into
folder-like common prefixes, and a marker pages through the rest. It lists the current version
of each object, so deleted objects and older versions stay out of it -
[ListObjectVersions](https://docs.strg.com/s3-api/ListObjectVersions) shows those.

**Note: Deprecated**

ListObjects is the original version of the listing API, and Amazon S3 deprecated it in favour of
[ListObjectsV2](https://docs.strg.com/s3-api/ListObjectsV2), which is the better choice for new code. STRG answers
ListObjects as well, so older tools and scripts keep working.

**Info: Good to know on STRG**

- Each bucket is reached through its own regional endpoint. A bucket in another region answers
  `404 NoSuchBucket` rather than a redirect.
- A listing is always signed: an unsigned one is answered `403 AccessDenied`, even for a bucket
  whose *Bucket Protection* is off.
- An access key limited to an object prefix lists by naming that prefix, or a longer one, as
  `prefix`.
- `Owner` names your STRG account.
- A query parameter ListObjects does not define is answered with `501 NotImplemented`, so a
  client knows straight away.
- `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

- **Older backup and sync tools.** Backup software and file-system mounts such as s3fs that were
  written before ListObjectsV2 existed use ListObjects to find out what a bucket already holds.
  They work against STRG as they are.
- **Browsing a bucket like folders.** With `prefix=photos/` and `delimiter=/`, a script sees the
  objects directly inside `photos/` and each subfolder as one entry, much as a file manager shows
  a directory.
- **Scripts written for Amazon S3 years ago.** A nightly job that lists a prefix to decide what to
  copy or clean up keeps running unchanged once it points at a STRG endpoint.

## Request

```http
GET /?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?prefix=photos/&delimiter=/&max-keys=100` 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; any other answers `403 AccessDenied`. A key limited to an object
prefix lists by setting `prefix` to that prefix or a longer one, and a listing without it is
answered `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. |

### Query parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `prefix` | String | No | Lists only keys that begin with this string. Needed - as the key's object prefix or a longer one - when the access key is limited to an object prefix. |
| `delimiter` | String | No | Groups keys that contain this string after the prefix into one `CommonPrefixes` entry each, up to the first occurrence of the delimiter. Usually `/`. |
| `marker` | String | No | Starts the listing after this key. It can be any key, whether or not it exists. |
| `max-keys` | Integer | No | The most keys and common prefixes to return. At most 1,000, which is also the default. A response may hold fewer. |
| `encoding-type` | String | No | `url` has keys and prefixes in the response URL-encoded - useful for keys holding characters XML 1.0 cannot carry. |

Any other query parameter, apart from those of a presigned URL, is answered with
`501 NotImplemented`.

### 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: STRG has a single storage class and no archived objects, so there is no restore status to return. |

### 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, handy for a support request. |
| `x-amz-id-2` | The same request ID. |
| `x-amz-request-charged` | Not returned, since STRG has no Requester Pays buckets. |

### Response body

A `ListBucketResult` element holding:

| Element | Description |
| --- | --- |
| `Name` | The bucket name. |
| `Prefix` | The `prefix` the request sent. |
| `Marker` | The `marker` the request sent. |
| `NextMarker` | When the listing is truncated and the request sent a `delimiter`: the value for `marker` on the next page. Without a `delimiter`, the last `Key` in `Contents` serves the same purpose. |
| `MaxKeys` | The page size in effect. |
| `Delimiter` | The `delimiter` the request sent. |
| `IsTruncated` | `true` if more keys match than this response holds. |
| `EncodingType` | `url` when the request asked for URL-encoded keys. |
| `Contents` | One per object, described below. |
| `CommonPrefixes` | One per group of keys rolled up by `delimiter`, each with a `Prefix`. Each counts as one key against `max-keys`. |

Each `Contents` element holds:

| Element | Description |
| --- | --- |
| `Key` | The object's key. |
| `LastModified` | When the object was stored. |
| `ETag` | The object's entity tag. |
| `Size` | The object's size in bytes. |
| `StorageClass` | Always `STANDARD`. |
| `Owner` | The object's owner. `ID` and `DisplayName` both carry your STRG account ID. |
| `RestoreStatus` | Never returned, as objects are never archived. |

```xml
<?xml version="1.0" encoding="UTF-8"?>
<ListBucketResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
  <Name>my-bucket</Name>
  <Prefix>photos/</Prefix>
  <Marker></Marker>
  <NextMarker>photos/2024/</NextMarker>
  <MaxKeys>2</MaxKeys>
  <Delimiter>/</Delimiter>
  <IsTruncated>true</IsTruncated>
  <Contents>
    <Key>photos/cover.jpg</Key>
    <LastModified>2026-09-20T08:15:02.000Z</LastModified>
    <ETag>"85f30635602dc09bd85957a6e82a2c21"</ETag>
    <Size>184312</Size>
    <Owner>
      <ID>your-account-id</ID>
      <DisplayName>your-account-id</DisplayName>
    </Owner>
    <StorageClass>STANDARD</StorageClass>
  </Contents>
  <CommonPrefixes>
    <Prefix>photos/2024/</Prefix>
  </CommonPrefixes>
</ListBucketResult>
```

## 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 another account, it is in a region other than the endpoint's, or the account has no storage in this endpoint's region yet. |
| `AccessDenied` | 403 | The access key lacks **List objects**, the bucket is outside the key's buckets, or `prefix` does not start with the key's object prefix; the request is unsigned, an `x-amz-*` header is not signed, or a presigned URL has expired; or the account 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 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 ListObjects does not use (presigning's `X-Amz-*` parameters 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. |
| `InternalError` | 500 | An unexpected 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` with `x-amz-bucket-region`, which the SDKs follow | `404 NoSuchBucket`. A bucket is reached through its own regional endpoint, so a client configured with the bucket's region gets its answer first time |
| Bucket owned by another account | `403 AccessDenied` | `404 NoSuchBucket` |
| Anonymous listing | Allowed where the bucket permits it | Listings are always signed; an unsigned one is `403 AccessDenied`. [Anonymous reads](https://docs.strg.com/s3-api/overview#anonymous-reads) covers what a public bucket serves |
| Access keys limited to an object prefix | - | `prefix` starts with the key's object prefix, and a listing outside it is `403 AccessDenied` |
| Unknown query parameters | Ignored | `501 NotImplemented`, so a client knows straight away |
| `Owner` | Your AWS canonical user ID | Your STRG account ID, in both `ID` and `DisplayName` |
| Storage classes | Several | `STANDARD` for every object; no `RestoreStatus` |
| `x-amz-expected-bucket-owner` | Checked against the bucket owner | Welcome, with nothing to check |

## Examples

### Listing a folder

This lists the objects directly under `photos/`, with each subfolder rolled up into a common
prefix.

**AWS CLI**

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

The AWS CLI follows the marker on its own and prints every page as one result.

**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(Bucket="my-bucket", Prefix="photos/", 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 { ListObjectsCommand, 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 ListObjectsCommand({ Bucket: "my-bucket", Prefix: "photos/", Delimiter: "/" }),
);

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

### Paging through every object

Each request returns at most 1,000 keys. While `IsTruncated` is `true`, the next request sets
`marker` to `NextMarker` - or, when there is none, to the last key received.

**AWS CLI**

```bash
aws s3api list-objects \
  --bucket my-bucket \
  --prefix photos/ \
  --query "Contents[].Key" \
  --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",
)

marker = ""
while True:
    response = s3.list_objects(Bucket="my-bucket", Prefix="photos/", Marker=marker)
    contents = response.get("Contents", [])
    for obj in contents:
        print(obj["Key"])
    if not response.get("IsTruncated"):
        break
    marker = response.get("NextMarker") or contents[-1]["Key"]
```

**JavaScript (AWS SDK v3)**

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

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

let marker;
while (true) {
  const response = await client.send(
    new ListObjectsCommand({ Bucket: "my-bucket", Prefix: "photos/", Marker: marker }),
  );
  const contents = response.Contents ?? [];
  for (const object of contents) {
    console.log(object.Key);
  }
  if (!response.IsTruncated) break;
  marker = response.NextMarker ?? contents.at(-1).Key;
}
```

## Related

- [ListObjectsV2](https://docs.strg.com/s3-api/ListObjectsV2) - the current listing API, the better choice for new code
- [ListObjectVersions](https://docs.strg.com/s3-api/ListObjectVersions) - every version and delete marker
- [ListMultipartUploads](https://docs.strg.com/s3-api/ListMultipartUploads)
- [HeadObject](https://docs.strg.com/s3-api/HeadObject), [GetObject](https://docs.strg.com/s3-api/GetObject)
- [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
