# S3-compatible gateway

Use aws, rclone, Cyberduck, boto3 or any AWS SDK with Side Space. A space is a bucket, a path is a key, every put is a version, and S3 versioning shows the whole history.

Tools that speak S3 can read and write spaces directly. Make a token with `--s3` to get a key pair:

```sh
sidespace token create rclone --scope files:write --space "Film A" --s3
# aws_access_key_id     = SSAK7QF2M4XKD9WZ3T0B
# aws_secret_access_key = ssp_…   (also an ordinary token)
```

The endpoint is port `8070` on your server (in the production setup, Caddy serves it over HTTPS with the same certificate). Use path-style addressing; any region works, for example `us-east-1`.

```sh tab=aws
aws configure set aws_access_key_id SSAK7QF2M4XKD9WZ3T0B
aws configure set aws_secret_access_key ssp_…
aws --endpoint-url https://side.example.com:8070 s3 ls
aws --endpoint-url https://side.example.com:8070 s3 cp ./Ep01 s3://film-a/Ep01 --recursive
```

```ini tab=rclone
# ~/.config/rclone/rclone.conf
[side]
type = s3
provider = Other
access_key_id = SSAK7QF2M4XKD9WZ3T0B
secret_access_key = ssp_…
endpoint = https://side.example.com:8070
force_path_style = true

# rclone copy side:film-a/Ep01 ./Ep01
```

```python tab=boto3
import boto3

s3 = boto3.client("s3", endpoint_url="https://side.example.com:8070",
                  aws_access_key_id="SSAK7QF2M4XKD9WZ3T0B", aws_secret_access_key="ssp_…",
                  region_name="us-east-1")
s3.upload_file("A001_C003.mov", "film-a", "Ep01/Shots/A001_C003.mov")
url = s3.generate_presigned_url("get_object", Params={"Bucket": "film-a", "Key": "Ep01/notes.md"}, ExpiresIn=3600)
```

## Buckets and keys

- A space's bucket name is its name in lower case with anything other than letters and digits turned into `-` (`Film A` → `film-a`). If two spaces would clash, or the name is too short, the space id is used (`spc_01…` written `spc-01…`).
- Keys are paths in the space.
- With a key whose token has the `admin` scope, CreateBucket makes a space named like the bucket. Send `x-sidespace-fork-source: <bucket>[/<folder>]` with it to make a [fork](/docs/forks) instead. DeleteBucket deletes a space only if it never held anything, as S3 deletes only empty buckets; otherwise it answers `BucketNotEmpty`, and the space is deleted in Side Space.

## What a write does

A put is a normal save: it makes a version and triggers notifications, webhooks and pipelines. A delete goes to the trash for 30 days. `CopyObject` within a space and its forks copies no bytes.

## Versions and the past

Every bucket answers versioning `Enabled`, because every save in Side Space is a version. Tools that know S3 versions (rclone `--s3-versions`, backup tools, boto3) see the whole history.

```sh
aws --endpoint-url https://side.example.com:8070 s3api list-object-versions --bucket film-a --prefix Ep01/
aws --endpoint-url https://side.example.com:8070 s3api get-object --bucket film-a --key Ep01/script.md \
  --version-id ver_01JD… old.md
# Bring an old version back: copy it onto its own key
aws --endpoint-url https://side.example.com:8070 s3api copy-object --bucket film-a --key Ep01/script.md \
  --copy-source "film-a/Ep01/script.md?versionId=ver_01JD…"
```

- S3 version ids are Side Space version ids (`ver_…`). PutObject, CopyObject and CompleteMultipartUpload answer `x-amz-version-id`.
- ListObjectVersions lists every version of every file, newest first per key, with key and version-id markers for paging and delimiter prefixes.
- GetObject, HeadObject and the source of CopyObject take `versionId`.
- A file in the trash shows as a delete marker with the id `del.<node id>`. Deleting that marker restores the file, as in S3. DeleteObject on a key leaves a marker: the file goes to the trash.
- To see a bucket as it was, send `x-sidespace-as-of` (a time, `-2h` or a journal seq; as a query parameter for presigned URLs). Lists and reads then answer from that moment. See [History and rewind](/docs/history).
- Versions stay until the file is deleted for good, by its owner in Side Space. Deleting a single version answers `MethodNotAllowed`, and suspending versioning answers `InvalidArgument`.

## What works

ListBuckets, HeadBucket, CreateBucket, DeleteBucket, ListObjects v1 and v2, ListObjectVersions, GetBucketVersioning (prefix, delimiter, paging), GetObject and HeadObject (with Range), PutObject, CopyObject, DeleteObject, DeleteObjects, multipart uploads (Create, UploadPart, Complete, Abort, ListParts, ListMultipartUploads; no 5 MB minimum part size), presigned URLs (SigV4 and SigV2) and aws-chunked streaming uploads.

Not supported: bucket policies, ACLs, object tags, suspending versioning and deleting single versions. ETags are content hashes, not MD5.

Permissions come from the token: `files:read` reads, `files:write` writes, and a token limited to some spaces sees only their buckets. Revoking the token disables the key at once.
