# S3 API compatibility

> Which S3 operations work with a read only key, a read and write key or the dashboard, and how checksums, multipart and presigned uploads behave.

Source: https://www.coritan.com/docs/object-storage/s3-compatibility/

Object Storage answers the Amazon S3 API at each region's endpoint. These tables list what an S3 client can do with each kind of access key, how the request features behave, and what the dashboard does without a key. An operation the page does not list may or may not work, so try it on a test bucket before you rely on it.

## Signing and addressing

| What | Answer |
| --- | --- |
| Signature | AWS Signature Version 4, in the `Authorization` header or in the query string of a presigned URL. |
| Region in the signature | Any name, such as `fra`, `us-east-1` or `auto`. |
| Host in the signature | The address you connect to, exactly as the dashboard gives it, port included when it has one, such as `s3.fra.coritan.com:7337`. In virtual-hosted style, the bucket's own hostname. A request signed for another host or port answers `403` `SignatureDoesNotMatch` ([Endpoints, ports and addressing styles](/docs/object-storage/endpoints/#ports)). |
| Addressing | Path style in every region, and virtual-hosted style where the dashboard offers it ([Endpoints, ports and addressing styles](/docs/object-storage/endpoints/#addressing-styles)). [Connect an S3 client](/docs/object-storage/connect-an-s3-client/#the-settings) shows the setting for each tool. |
| `GetBucketLocation` | Answers with no location. Clients read that as `us-east-1`, which signs as well as any other name. |
| Temporary credentials (`AssumeRole`) | Not available at the endpoint: an access key that calls it gets `403` `AccessDenied`. Create temporary credentials in the dashboard or with the API ([Create temporary credentials](/docs/object-storage/temporary-credentials/)), and send the session token with them. |

## What each key can do {#key-modes}

A key is **Read only** or **Read and write**, and works on one bucket or on all your buckets ([What a key can do](/docs/object-storage/access-keys/#what-a-key-can-do)). A request outside its permissions or its scope answers `403` `AccessDenied`.

Temporary credentials do what a key of the same permissions does with objects, and nothing with a bucket's settings: they cannot read them either ([Create temporary credentials](/docs/object-storage/temporary-credentials/#what-temporary-credentials-can-do)).

| Operation | Read only | Read and write |
| --- | --- | --- |
| `ListBuckets` | Yes | Yes |
| `HeadBucket`, `GetBucketLocation` | Yes | Yes |
| `ListObjects`, `ListObjectsV2`, `ListObjectVersions` | Yes | Yes |
| `GetObject`, `HeadObject`, `GetObjectAttributes` | Yes | Yes |
| `GetObjectTagging` | Yes | Yes |
| `ListMultipartUploads`, `ListParts` | Yes | Yes |
| `GetBucketVersioning`, `GetBucketCors`, `GetBucketLifecycleConfiguration` | Yes | Yes |
| `PutObject`, `CopyObject`, `DeleteObject`, `DeleteObjects` | No | Yes |
| `RenameObject` | No | Yes |
| `PutObjectTagging`, `DeleteObjectTagging` | No | Yes |
| `GetBucketTagging`, `PutBucketTagging` | No | Yes |
| `CreateMultipartUpload`, `UploadPart`, `UploadPartCopy`, `CompleteMultipartUpload`, `AbortMultipartUpload` | No | Yes |
| `PutBucketVersioning` | No | No, read [Bucket settings](#bucket-settings) |
| `PutBucketCors`, `DeleteBucketCors`, `PutBucketLifecycleConfiguration`, `DeleteBucketLifecycle` | No | No, read [Bucket settings](#bucket-settings) |
| `CreateBucket`, `DeleteBucket` | No | No |
| `GetObjectAcl`, `PutObjectAcl`, `GetBucketAcl`, `PutBucketAcl` | No | No |
| `PutBucketPolicy` | No | No |
| `GetBucketEncryption`, `PutBucketEncryption`, `DeleteBucketEncryption` | No | No, read [Bucket settings](#bucket-settings) |
| `PutObjectLockConfiguration`, `PutObjectRetention`, `PutObjectLegalHold` | No | No |
| `PutBucketNotificationConfiguration`, `PutBucketReplication`, `PutBucketWebsite`, `PutBucketLogging` | No | No |
| `SelectObjectContent`, `RestoreObject` | No | No |

`ListBuckets` lists only the buckets in the key's scope, so a key limited to one bucket lists that bucket alone. Such a key gets `403` `AccessDenied` from every other bucket, also as the source of a `CopyObject`.

`DeleteObjects` with a read only key answers `200` and deletes nothing. Each key comes back under `Errors` with the code `AccessDenied`, so read `Errors` as well as the status.

`SelectObjectContent` and `RestoreObject` answer `405` `MethodNotAllowed` with any key.

## Bucket settings {#bucket-settings}

Create and delete buckets, and change their settings, in the dashboard or through the Coritan API ([Create and delete buckets](/docs/object-storage/buckets/)). No key can.

A request signed with your key that changes a bucket or object setting answers `403` `AccessDenied` with the message `Change this setting in the dashboard or the Object Storage API.` The settings are versioning, CORS, lifecycle rules, Object Lock, retention, legal holds, policies, ACLs, encryption, website, notifications, replication and logging. Change them here instead:

- Versioning and Object Lock: [Versioning](/docs/object-storage/versioning/) and [Object Lock](/docs/object-storage/object-lock/).
- CORS: [CORS rules](/docs/object-storage/cors/).
- Lifecycle rules: [Lifecycle rules](/docs/object-storage/lifecycle-rules/).
- Encryption: [Encrypt the objects in a bucket](/docs/object-storage/encryption/). No key can read the encryption setting either.

Reading a setting works with any key, as the table above shows. Object tags are yours to write with a read and write key.

## Conditional requests

| Header | Request | Answer |
| --- | --- | --- |
| `If-None-Match: *` | `PutObject` | Writes only when no object has the key. Otherwise `412` `PreconditionFailed`. |
| `If-Match: <ETag>` | `PutObject` | Writes only over the object with that ETag. Another ETag, or no object, answers `412`. |
| `If-None-Match: <ETag>` | `GetObject` | The current ETag answers `304 Not Modified`. |
| `If-Match: <ETag>` | `GetObject` | An ETag that is no longer current answers `412`. |
| `x-amz-copy-source-if-match` | `CopyObject` | An ETag that is no longer current answers `412`. |
| `If-Match: <ETag>` | `DeleteObject` | An ETag that is no longer current answers `412`. |
| `Range: bytes=0-1023` | `GetObject` | Returns those bytes, with `Content-Range`. |

## Checksums

| What | Answer |
| --- | --- |
| Algorithms | CRC32, CRC32C, CRC64NVME, SHA-1 and SHA-256 in the `x-amz-checksum-*` headers, and `Content-MD5`. |
| A value that does not match the body | `400` `BadDigest`. |
| Reading it back | `HeadObject` with `x-amz-checksum-mode: ENABLED` (`ChecksumMode="ENABLED"` in boto3) returns the checksum stored with the object. |
| Multipart uploads | Send a checksum with each part. `CompleteMultipartUpload` answers a checksum of the part checksums that ends in `-<number of parts>`, with `ChecksumType` set to `COMPOSITE`. |

## Encryption with your own key (SSE-C)

Upload with your own 256-bit key in the `x-amz-server-side-encryption-customer-*` headers, and every read of the object needs the same key. In boto3, pass `SSECustomerAlgorithm="AES256"` and `SSECustomerKey` to `put_object`, `get_object` and `head_object`.

| Request | Answer |
| --- | --- |
| `PutObject` with the key | `200`, with `SSECustomerAlgorithm` `AES256` in the answer. Needs a read and write key. |
| `GetObject` or `HeadObject` with the same key | `200`. A read only key works too. |
| `GetObject` without the key | `400` `InvalidArgument`. |
| `GetObject` with another key | `403` `AccessDenied`. |

> [!WARNING]
> Keep the key. Without it, the object cannot be read back.

Default encryption for a whole bucket is separate: [Encrypt the objects in a bucket](/docs/object-storage/encryption/). A `PutObject` that asks for `x-amz-server-side-encryption: AES256` works once default encryption is available, and can answer `500` `InternalError` until then.

## Multipart uploads

| What | Answer |
| --- | --- |
| Part numbers | 1–10,000. `10001` answers `400` `InvalidPart`. |
| Part size | At least 5 MiB for every part except the last. A smaller one answers `400` `EntityTooSmall` when you complete the upload. |
| `UploadPartCopy` | Copies a part from an object in a bucket the key can read. |
| Unfinished uploads | `ListMultipartUploads` lists them. `AbortMultipartUpload` removes one with its parts, and `ListParts` then answers `404` `NoSuchUpload`. |

## Presigned URLs

| What | Answer |
| --- | --- |
| Lifetime of a URL signed with your key | Up to 7 days (`X-Amz-Expires=604800`). Longer answers `400` `AuthorizationQueryParametersError`. |
| After it expires | `403` `AccessDenied`. |
| What it allows | What the key allows. A read only key signs `GET` URLs that work, and its `PUT` URLs answer `403` `AccessDenied`. |
| Content type | A `PUT` URL signed with a `Content-Type` takes only an upload with that type. Another type answers `403` `SignatureDoesNotMatch`. |
| `response-content-disposition` | Sets the `Content-Disposition` header on the answer. |
| URLs from the dashboard and the Coritan API | 60–3600 seconds ([Presigned URLs](/docs/object-storage/limits/#presigned-urls)). |

## Browser uploads with a POST form

An HTML form can upload a file straight to a bucket with a POST policy that you sign with a read and write key, such as one from boto3's `generate_presigned_post`. The form posts to the bucket's URL, such as `https://s3.fra.coritan.com:7337/u7-assets`.

| The form | Answer |
| --- | --- |
| Meets every condition in the policy | `204 No Content`, and the object is stored. |
| Sends a file larger than `content-length-range` allows | `400` `EntityTooLarge`. |
| Names a key the policy does not allow | `403` `AccessDenied`. |
| Carries a policy signed with a read only key | `403` `AccessDenied`. |
| Has no signature fields | `400` `AuthorizationQueryParametersError`. |

## Objects and ACLs

ACLs are off on every bucket. An upload with `x-amz-acl: public-read` or an `x-amz-grant-*` header succeeds, and the object stays private: a request without a signature still answers `403`. To share one object, send a [presigned URL](/docs/object-storage/objects/#share-a-file-with-a-presigned-url).

| What | Answer |
| --- | --- |
| Metadata | `x-amz-meta-*` headers, `Cache-Control` and `Content-Disposition` are stored with the object and returned when it is read. |
| Storage class | One class. A `StorageClass` you send, such as `STANDARD_IA`, is kept as a label that `HeadObject` reports, and the object is billed like any other ([How Object Storage is billed](/docs/object-storage/usage-and-billing/)). |
| `RenameObject` | Renames an object within its bucket: a `PUT` to the new key with `?renameObject` and the old one in `x-amz-rename-source`, or `rename_object` in boto3. The old key then answers `404`. |

## In the dashboard

The object browser on a bucket's page uses none of your keys, because we sign each of its requests. It lists objects, uploads files with one request for each file, downloads them, creates folders, and deletes objects and folders ([Upload, download and delete objects](/docs/object-storage/objects/)). Everything else on this page needs an S3 client and a key ([Connect an S3 client](/docs/object-storage/connect-an-s3-client/)).

## Related

- [Connect an S3 client](/docs/object-storage/connect-an-s3-client/)
- [Create and revoke access keys](/docs/object-storage/access-keys/)
- [Object Storage limits](/docs/object-storage/limits/)
- [Troubleshoot Object Storage](/docs/object-storage/troubleshooting/)
