# S3 API overview

STRG speaks the Amazon S3 REST API, so the tools you may already know - the AWS CLI, the AWS
SDKs and other S3 clients - work with your buckets as they are. All they need is a STRG
endpoint and a STRG [access key](https://docs.strg.com/access-keys) to sign with. This is the API for an
application storing its uploads, a nightly backup job, or an agent keeping its files in a bucket.

This page gathers what every operation has in common: where to send requests, how to sign them,
and the handful of places where STRG behaves differently from Amazon S3. Each operation has a
page of its own, listed under [Supported operations](https://docs.strg.com/s3-api/overview#supported-operations).

## Regions and endpoints

Every bucket lives in one region, picked when the bucket is created. Each region has its own
endpoint, and an endpoint answers for the buckets in its own region.

| Region | Name | Endpoint (path-style) | Virtual-hosted form |
| --- | --- | --- | --- |
| `us-east-1` | US East | `https://s3.us-east-1.strg.com` | `https://<bucket>.s3.us-east-1.strg.com` |
| `eu-central-1` | EU Central | `https://s3.eu-central-1.strg.com` | `https://<bucket>.s3.eu-central-1.strg.com` |

A client is configured with both the endpoint and its matching region, because the region is part
of the signature. A request signed for a region other than the endpoint's answers
`400 AuthorizationHeaderMalformed` (`400 AuthorizationQueryParametersError` for a presigned URL),
and the error message names the region the endpoint expects.

## Addressing

S3 has two ways of naming a bucket in a URL, and STRG understands both.

| Style | Request for `photos/2024/cat.jpg` in `my-bucket` |
| --- | --- |
| Path-style | `https://s3.eu-central-1.strg.com/my-bucket/photos/2024/cat.jpg` |
| Virtual-hosted | `https://my-bucket.s3.eu-central-1.strg.com/photos/2024/cat.jpg` |

**Warning: Bucket names with a dot**

A bucket name containing a dot (`my.bucket`) works with **path-style addressing**. In
virtual-hosted form the TLS handshake fails before the request is even sent. For such a bucket,
the client setting is `forcePathStyle: true` (JavaScript), `addressing_style: "path"` (boto3) or
`s3.addressing_style = path` (AWS CLI).

## Client configuration

The AWS CLI and the SDKs read your credentials from the environment: `AWS_ACCESS_KEY_ID` holds
your access key's ID and `AWS_SECRET_ACCESS_KEY` its secret. Each example below lists the objects
in `my-bucket` in `eu-central-1`, once per addressing style.

### Path-style

**AWS CLI**

```bash
aws configure set default.s3.addressing_style path

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

**Python (boto3)**

```python
import boto3
from botocore.config import Config

s3 = boto3.client(
    "s3",
    endpoint_url="https://s3.eu-central-1.strg.com",
    region_name="eu-central-1",
    config=Config(s3={"addressing_style": "path"}),
)

for obj in s3.list_objects_v2(Bucket="my-bucket").get("Contents", []):
    print(obj["Key"], obj["Size"])
```

**JavaScript (AWS SDK v3)**

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

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

const { Contents = [] } = await s3.send(new ListObjectsV2Command({ Bucket: "my-bucket" }));
for (const obj of Contents) console.log(obj.Key, obj.Size);
```

### Virtual-hosted

**AWS CLI**

```bash
aws configure set default.s3.addressing_style virtual

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

**Python (boto3)**

```python
import boto3
from botocore.config import Config

s3 = boto3.client(
    "s3",
    endpoint_url="https://s3.eu-central-1.strg.com",
    region_name="eu-central-1",
    config=Config(s3={"addressing_style": "virtual"}),
)

for obj in s3.list_objects_v2(Bucket="my-bucket").get("Contents", []):
    print(obj["Key"], obj["Size"])
```

**JavaScript (AWS SDK v3)**

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

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

const { Contents = [] } = await s3.send(new ListObjectsV2Command({ Bucket: "my-bucket" }));
for (const obj of Contents) console.log(obj.Key, obj.Size);
```

The SDKs' defaults - request checksums and `aws-chunked` streaming uploads included - work as they
are, with nothing to switch off.

## Authentication

Every signed request uses AWS Signature Version 4 (`AWS4-HMAC-SHA256`), with an access key created
on the *Access Keys* page of the dashboard, through the MCP server or through the
[REST API](https://docs.strg.com/rest-api/overview). [Access keys](https://docs.strg.com/access-keys) describes the permissions a
key can carry.

The table shows each way of signing and how STRG handles it.

| | |
| --- | --- |
| Header signing | The `Authorization` header |
| Presigned URLs | Query-string signing, `X-Amz-Expires` from 1 second to 7 days (604,800 seconds) |
| Streaming uploads | `aws-chunked` bodies (`STREAMING-AWS4-HMAC-SHA256-PAYLOAD`, `STREAMING-AWS4-HMAC-SHA256-PAYLOAD-TRAILER`, `STREAMING-UNSIGNED-PAYLOAD-TRAILER`) on `PutObject` and `UploadPart`, with every chunk verified. Trailing checksums: CRC32, CRC32C, CRC64NVME, SHA-1 and SHA-256. A presigned URL carries its body in one piece instead: `aws-chunked` in a presigned URL answers `400 InvalidRequest` |
| `UNSIGNED-PAYLOAD` | Accepted |
| Clock skew | Up to 15 minutes between `X-Amz-Date` and the server's clock; beyond that, `403 RequestTimeTooSkewed` |
| `x-amz-*` headers | **Every one is part of the signature**. A request carrying an unsigned `x-amz-*` header answers `403 AccessDenied` |
| Unsigned (anonymous) requests | Welcome for `GetObject` and `HeadObject` on a bucket whose *Bucket Protection* is off - [Anonymous reads](https://docs.strg.com/s3-api/overview#anonymous-reads) has the details. Every other unsigned request, listing included, answers `403 AccessDenied` |

STRG keeps to Signature Version 4 and long-lived access keys, so a few other mechanisms get a
clear answer instead:

| Mechanism | Answer |
| --- | --- |
| Signature Version 2 (`AWS AKID:signature`) | `400 InvalidRequest` |
| SigV4A (`AWS4-ECDSA-P256-SHA256`) | `400 InvalidRequest` |
| Session tokens (`x-amz-security-token`, `X-Amz-Security-Token`) and S3 Express sessions (`x-amz-s3session-token`) | `400 InvalidToken` - STRG has no temporary credentials, so there is no token to send |
| Browser `POST` uploads with a policy document | `501 NotImplemented` |

A revoked access key, or a change to the account behind one, takes effect within 60 seconds.

### Anonymous reads

A bucket whose *Bucket Protection* is off is public: anyone can read its objects without signing,
which suits a website's images or files meant to be shared by link. The read goes to the bucket's
own regional endpoint, path-style or virtual-hosted:

```bash
curl https://my-bucket.s3.eu-central-1.strg.com/photos/2024/cat.jpg
```

Here is how each kind of anonymous request is answered:

| Request | Answer |
| --- | --- |
| `GET` or `HEAD` of an object in a public bucket | The object, as for a signed request. `Range`, `versionId` and conditional headers work |
| Listing a public bucket, or any other operation | `403 AccessDenied` |
| A `response-*` query parameter (`response-content-type`, ...) | `400 InvalidRequest`, as on Amazon S3 |
| A bucket with *Bucket Protection* on | `403 AccessDenied` |
| A bucket that does not exist, or is in another region | `404 NoSuchBucket` |

A bucket created a moment ago can answer `404 NoSuchBucket` to anonymous requests for a few
seconds, until every endpoint has heard about it.

Anonymous reads can be answered from a cache, which makes popular public files quick to fetch. That
happens for an object stored with a `Cache-Control` that lets a shared cache keep it
(`public, max-age=60`, for example); an object without one is never cached. A cached answer is
kept for its `max-age` (or `s-maxage`) or **5 minutes, whichever is shorter**, and the reader is
still sent the object's own `Cache-Control`. The cache is never cleared early, so for up to
5 minutes after an object is overwritten or deleted, *Bucket Protection* is turned on, or the
bucket's [CORS rules](https://docs.strg.com/cors-rules) change, anonymous readers can still get the previous
answer. Signed requests never use the cache.

### CORS

A browser asks permission with a CORS preflight (`OPTIONS`) before a web page talks to your bucket
directly. STRG answers it on the bucket's regional endpoint from the bucket's
[CORS rules](https://docs.strg.com/cors-rules), as Amazon S3 does, and a preflight the rules do not allow answers
`403 AccessForbidden`.

## `403 AccountProblem`

`CreateBucket` answers `403 AccountProblem` while your account has no verified card on file: a card
comes before the first bucket, and *Billing* in the dashboard, `https://app.strg.com/billing`, is
where it is added. [A card is required](https://docs.strg.com/billing#a-card-is-required) explains why. Once the
card is there, the same request succeeds.

## Differences from Amazon S3

STRG behaves like Amazon S3 almost everywhere. Where it differs, it is usually because there is
less to configure - and the table lists each place, so nothing comes as a surprise.

| Area | Amazon S3 | STRG |
| --- | --- | --- |
| Bucket in another region | `301` with `x-amz-bucket-region`, which the SDKs follow | `404 NoSuchBucket`. Each endpoint answers for its own region, and the SDKs do not retry, so a client set to another region hears that a bucket does not exist even while the dashboard shows it. Setting the client to the bucket's region is all it takes |
| Nothing stored in the endpoint's region yet | Any valid key reaches the endpoint | The plain truth: `404 NoSuchBucket` for a bucket operation, an empty `ListBuckets` result, and `501 NotImplemented` for an operation STRG does not offer. `CreateBucket` creates the bucket |
| A key created moments ago | Works at once | In a region where you already store data, a brand-new key can take a moment to arrive: until it does, every operation but `CreateBucket` answers `403 InvalidAccessKeyId`, and a retry shortly afterwards succeeds |
| `CreateBucket` | Creates in any region | Creates the bucket in the endpoint's region - any region, your first bucket there included. A `LocationConstraint` naming another region answers `400 InvalidLocationConstraint`. A first bucket also sets up storage in the region, which takes a little longer - up to about 90 seconds - and from then on the key that signed the request works in that region too. A creation still running at that point answers `503 SlowDown` with `Retry-After`; the SDKs retry automatically, and the retry joins the same creation rather than starting another |
| Object Lock at creation | `x-amz-bucket-object-lock-enabled: true` on `CreateBucket` | Two steps: the bucket is created, then [PutObjectLockConfiguration](https://docs.strg.com/s3-api/PutObjectLockConfiguration) enables [Object Lock](https://docs.strg.com/object-locks). `x-amz-bucket-object-lock-enabled: true` on `CreateBucket` answers `501 NotImplemented` |
| Deleting and re-creating a bucket | A policy naming the bucket keeps applying | A bucket re-created under a deleted bucket's name is a new bucket, so an access key limited to the old one does not reach it. A new key for the new bucket takes only a moment to create |
| Bucket names | 3-63 characters | **3-58 characters**, with the same rules otherwise. Names are globally unique across STRG |
| `GetBucketLocation` | The region | The STRG region label, and an empty `LocationConstraint` for `us-east-1`, as S3 answers it |
| `ListBuckets` | Every bucket | The buckets in the endpoint's region, all in one answer - it is never paginated - and without `BucketRegion`. A key limited to specific buckets lists them when it holds the *List all bucket names* permission; without it, `ListBuckets` answers `403 AccessDenied` |
| `HeadBucket` | `403` for a bucket you cannot access | `404` for a bucket that is not yours, exactly as for one that does not exist |
| Multipart parts | 5 MiB to 5 GiB, 1-10,000 parts | 5,000,000 bytes (except the last part) to 5 GiB, part numbers 1-10,000 |
| Incomplete multipart uploads | Billed | Billed as stored data until completed or aborted. A [lifecycle policy](https://docs.strg.com/lifecycle-policies) can abort them for you |
| Versioning | Off until enabled; can be suspended | **Always on**, so an earlier version is always there to go back to. A delete writes a delete marker, and every version is billed as stored data until it is deleted or a [lifecycle policy](https://docs.strg.com/lifecycle-policies) removes it. `PutBucketVersioning` with `Suspended` answers `501 NotImplemented` |
| Storage classes | Several | `STANDARD`, for every object - nothing to choose between |
| Encryption | SSE-S3, SSE-KMS, SSE-C | Server-side encryption with STRG-managed keys (`AES256`) as a bucket default, and SSE-C per request. SSE-KMS answers `501 NotImplemented`. `GetBucketEncryption` answers `AES256` even for a bucket never given a default |
| ACLs | Grants and canned ACLs | One canned ACL per bucket, `private` or `public-read`. An object shares its bucket's ACL, so `PutObjectAcl` succeeds when it names the bucket's own and answers `501 NotImplemented` otherwise. Grant headers and XML grant bodies answer `501 NotImplemented` |
| Tagging | Object and bucket tags | No tags: object and bucket tag sets are always empty, so tools that read them still work, and `x-amz-tagging` on an upload or copy answers `501 NotImplemented` |
| Conditional writes | `If-None-Match` / `If-Match` on `PutObject`, `CompleteMultipartUpload` and `CopyObject` | `501 NotImplemented`, so a client knows straight away |
| Bucket policies, public access block, ownership controls, notifications, acceleration | Configurable | Nothing to configure. The read operations answer as AWS does for a bucket never configured, so tools that probe them work as usual |
| `x-amz-expected-bucket-owner`, `x-amz-source-expected-bucket-owner` | Checked | Welcome, with nothing to check: a key only ever reaches buckets of its own account |
| Object keys with `.` or `..` path segments | Stored and read as sent | A key with a `.` or `..` segment (`a/../b`, `a/./b`), percent-encoded as `%2e` or not, cannot travel safely in a URL: the request answers `400 InvalidURI`, and nothing is stored or read under any name. [DeleteObjects](https://docs.strg.com/s3-api/DeleteObjects), which carries the key in the request body, removes an existing one |
| Keys limited to a prefix | - | A listing passes the key's prefix as `prefix` (or a longer one); any other listing answers `403 AccessDenied` |
| Streaming a body without a length | Allowed in some cases | `Transfer-Encoding: chunked` without `Content-Length` answers `411 MissingContentLength`. `aws-chunked` uploads work as usual |
| Throttling | `503 SlowDown` | `503 SlowDown` with `Retry-After`, which the SDKs retry automatically |

Each operation's page has the differences particular to it.

## Supported operations

### Buckets

| Operation | |
| --- | --- |
| [ListBuckets](https://docs.strg.com/s3-api/ListBuckets) | Lists the buckets in the endpoint's region |
| [HeadBucket](https://docs.strg.com/s3-api/HeadBucket) | Tells whether a bucket exists and you can reach it |
| [GetBucketLocation](https://docs.strg.com/s3-api/GetBucketLocation) | Answers a bucket's region |
| [CreateBucket](https://docs.strg.com/s3-api/CreateBucket) | Creates a bucket in the endpoint's region |
| [DeleteBucket](https://docs.strg.com/s3-api/DeleteBucket) | Deletes an empty bucket |

### Bucket configuration

| Operation | |
| --- | --- |
| [GetBucketAcl](https://docs.strg.com/s3-api/GetBucketAcl), [PutBucketAcl](https://docs.strg.com/s3-api/PutBucketAcl) | `private` or `public-read` |
| [GetBucketCors](https://docs.strg.com/s3-api/GetBucketCors), [PutBucketCors](https://docs.strg.com/s3-api/PutBucketCors), [DeleteBucketCors](https://docs.strg.com/s3-api/DeleteBucketCors) | [CORS rules](https://docs.strg.com/cors-rules) |
| [GetBucketLifecycleConfiguration](https://docs.strg.com/s3-api/GetBucketLifecycleConfiguration), [PutBucketLifecycleConfiguration](https://docs.strg.com/s3-api/PutBucketLifecycleConfiguration), [DeleteBucketLifecycle](https://docs.strg.com/s3-api/DeleteBucketLifecycle) | [Lifecycle policies](https://docs.strg.com/lifecycle-policies) |
| [GetBucketLifecycle](https://docs.strg.com/s3-api/GetBucketLifecycle), [PutBucketLifecycle](https://docs.strg.com/s3-api/PutBucketLifecycle) | The older, deprecated forms of the above, still answered |
| [GetBucketEncryption](https://docs.strg.com/s3-api/GetBucketEncryption), [PutBucketEncryption](https://docs.strg.com/s3-api/PutBucketEncryption) | Default encryption, `AES256` |
| [GetBucketVersioning](https://docs.strg.com/s3-api/GetBucketVersioning), [PutBucketVersioning](https://docs.strg.com/s3-api/PutBucketVersioning) | Always `Enabled` |
| [GetBucketTagging](https://docs.strg.com/s3-api/GetBucketTagging), [DeleteBucketTagging](https://docs.strg.com/s3-api/DeleteBucketTagging) | Empty tag sets, for tools that read them |
| [GetBucketAccelerateConfiguration](https://docs.strg.com/s3-api/GetBucketAccelerateConfiguration), [GetBucketNotification](https://docs.strg.com/s3-api/GetBucketNotification), [GetBucketNotificationConfiguration](https://docs.strg.com/s3-api/GetBucketNotificationConfiguration), [GetBucketOwnershipControls](https://docs.strg.com/s3-api/GetBucketOwnershipControls), [GetBucketPolicy](https://docs.strg.com/s3-api/GetBucketPolicy), [GetBucketRequestPayment](https://docs.strg.com/s3-api/GetBucketRequestPayment), [GetPublicAccessBlock](https://docs.strg.com/s3-api/GetPublicAccessBlock) | The never-configured answer, for tools that probe them |

### Objects

| Operation | |
| --- | --- |
| [PutObject](https://docs.strg.com/s3-api/PutObject) | Uploads an object of up to 5 GiB |
| [GetObject](https://docs.strg.com/s3-api/GetObject), [HeadObject](https://docs.strg.com/s3-api/HeadObject) | Reads an object, or its metadata |
| [CopyObject](https://docs.strg.com/s3-api/CopyObject) | Copies an object |
| [DeleteObject](https://docs.strg.com/s3-api/DeleteObject), [DeleteObjects](https://docs.strg.com/s3-api/DeleteObjects) | Write delete markers, or delete versions permanently |
| [ListObjectsV2](https://docs.strg.com/s3-api/ListObjectsV2), [ListObjects](https://docs.strg.com/s3-api/ListObjects), [ListObjectVersions](https://docs.strg.com/s3-api/ListObjectVersions) | List objects, or every version and delete marker |
| [GetObjectAcl](https://docs.strg.com/s3-api/GetObjectAcl), [PutObjectAcl](https://docs.strg.com/s3-api/PutObjectAcl) | The bucket's ACL |
| [GetObjectTagging](https://docs.strg.com/s3-api/GetObjectTagging), [DeleteObjectTagging](https://docs.strg.com/s3-api/DeleteObjectTagging) | Empty tag sets, for tools that read them |

### Multipart uploads

Multipart uploads carry a large object in parts, each retried on its own if a connection drops.

| Operation | |
| --- | --- |
| [CreateMultipartUpload](https://docs.strg.com/s3-api/CreateMultipartUpload) | Starts an upload |
| [UploadPart](https://docs.strg.com/s3-api/UploadPart), [UploadPartCopy](https://docs.strg.com/s3-api/UploadPartCopy) | Uploads a part, or copies one from an existing object |
| [CompleteMultipartUpload](https://docs.strg.com/s3-api/CompleteMultipartUpload), [AbortMultipartUpload](https://docs.strg.com/s3-api/AbortMultipartUpload) | Finish or discard an upload |
| [ListParts](https://docs.strg.com/s3-api/ListParts), [ListMultipartUploads](https://docs.strg.com/s3-api/ListMultipartUploads) | Show the uploads in progress and their parts |

### Object Lock

| Operation | |
| --- | --- |
| [GetObjectLockConfiguration](https://docs.strg.com/s3-api/GetObjectLockConfiguration), [PutObjectLockConfiguration](https://docs.strg.com/s3-api/PutObjectLockConfiguration) | A bucket's [Object Lock](https://docs.strg.com/object-locks) setting and default retention |
| [GetObjectRetention](https://docs.strg.com/s3-api/GetObjectRetention), [PutObjectRetention](https://docs.strg.com/s3-api/PutObjectRetention) | An object version's retention |
| [GetObjectLegalHold](https://docs.strg.com/s3-api/GetObjectLegalHold), [PutObjectLegalHold](https://docs.strg.com/s3-api/PutObjectLegalHold) | An object version's legal hold |

**Danger: Object Lock is permanent**

Enabling Object Lock on a bucket is permanent: it can never be switched off again. Locked object
versions stay billed as stored data until their lock expires. [Object Lock](https://docs.strg.com/object-locks)
explains both before a `PutObjectLockConfiguration` call makes them true for a bucket.

### Other S3 operations

The rest of the S3 API answers `501 NotImplemented`, so a client knows straight away - as does a
request with a query parameter none of the operations above accepts:

`CreateBucketMetadataConfiguration`, `CreateBucketMetadataTableConfiguration`,
`CreateSession`, `DeleteBucketAnalyticsConfiguration`, `DeleteBucketEncryption`,
`DeleteBucketIntelligentTieringConfiguration`, `DeleteBucketInventoryConfiguration`,
`DeleteBucketMetadataConfiguration`, `DeleteBucketMetadataTableConfiguration`,
`DeleteBucketMetricsConfiguration`, `DeleteBucketOwnershipControls`, `DeleteBucketPolicy`,
`DeleteBucketReplication`, `DeleteBucketWebsite`, `DeleteObjectAnnotation`,
`DeletePublicAccessBlock`, `GetBucketAbac`, `GetBucketAnalyticsConfiguration`,
`GetBucketIntelligentTieringConfiguration`, `GetBucketInventoryConfiguration`, `GetBucketLogging`,
`GetBucketMetadataConfiguration`, `GetBucketMetadataTableConfiguration`,
`GetBucketMetricsConfiguration`, `GetBucketPolicyStatus`, `GetBucketReplication`,
`GetBucketWebsite`, `GetObjectAnnotation`, `GetObjectAttributes`, `GetObjectTorrent`,
`ListBucketAnalyticsConfigurations`, `ListBucketIntelligentTieringConfigurations`,
`ListBucketInventoryConfigurations`, `ListBucketMetricsConfigurations`, `ListDirectoryBuckets`,
`ListObjectAnnotations`, `PutBucketAbac`, `PutBucketAccelerateConfiguration`,
`PutBucketAnalyticsConfiguration`, `PutBucketIntelligentTieringConfiguration`,
`PutBucketInventoryConfiguration`, `PutBucketLogging`, `PutBucketMetricsConfiguration`,
`PutBucketNotification`, `PutBucketNotificationConfiguration`, `PutBucketOwnershipControls`,
`PutBucketPolicy`, `PutBucketReplication`, `PutBucketRequestPayment`, `PutBucketTagging`,
`PutBucketWebsite`, `PutObjectAnnotation`, `PutObjectTagging`, `PutPublicAccessBlock`,
`RenameObject`, `RestoreObject`, `SelectObjectContent`,
`UpdateBucketMetadataAnnotationTableConfiguration`,
`UpdateBucketMetadataInventoryTableConfiguration`,
`UpdateBucketMetadataJournalTableConfiguration`, `UpdateObjectEncryption`,
`WriteGetObjectResponse`.
