The S3 door — your existing S3 client, governed
Relata serves a real S3-compatible API. Your existing boto3 / aws CLI / rclone / MinIO client works unchanged — point it at Relata's S3 port and use the bearer token as the access key (or set up SigV4). Every PutObject lands as a governed S3Object row with PROV-O provenance, bi-temporal history, and Cedar ACL — so you can write via S3 and read the same data back over SQL, with the audit chain recording every object event.
Why this matters: MinIO/S3 give you blob storage. Relata's S3 door gives you blob storage plus provenance, bi-temporal versioning, cell-level ACL, and cross-protocol reads — your SQL analytics, your graph traversal, and your hybrid search all see the same objects.
Connect
The door auto-enables when RELATA_BEARER_TOKEN is set (see Compatibility & Doors).
RELATA_BEARER_TOKEN=change-me relata serve
# S3 door auto-starts on port 9191import boto3
from botocore.config import Config
s3 = boto3.client(
"s3",
endpoint_url="http://127.0.0.1:9191",
aws_access_key_id="change-me", # = RELATA_BEARER_TOKEN
aws_secret_access_key="unused", # SigV4 secret defaults to the bearer token
config=Config(signature_version="s3v4", s3={"addressing_style": "path"}),
)The SigV4 secret defaults to RELATA_BEARER_TOKEN unless you set RELATA_S3_SECRET_KEY to a dedicated secret. When a secret is configured, the door requires verified SigV4 and rejects plaintext bearer auth (set RELATA_S3_ALLOW_PLAINTEXT=true only for dev — cleartext credentials are forgeable).
Supported operations
| Operation | Notes |
|---|---|
ListBuckets | governed — only buckets the caller's ACL permits |
CreateBucket / HeadBucket / DeleteBucket | bucket must be empty to delete |
ListObjectsV2 | with prefix + delimiter |
PutObject / GetObject / DeleteObject / HeadObject | bodies ≥ RELATA_S3_BLOB_THRESHOLD_MB (default 4 MiB) spill to content-addressed blob store |
ListObjectVersions | bi-temporal versioning for free — see below |
| Multipart upload | parts in-memory only (lost on restart) |
ETag is SHA-256.
Bi-temporal object versioning
Because every S3Object is a governed bi-temporal row, S3 object versioning comes for free — no separate versioning flag, no separate store. PUT creates a new row version (the prior one's valid_to closes); DELETE is a delete-marker; history is AS OF-queryable.
# List every version of an object (the bi-temporal history)
curl 'http://127.0.0.1:9191/cases/exhibit-1.txt?versions' \
-H 'Authorization: Bearer <token>'
# Fetch a specific version by its system_from_ns
curl 'http://127.0.0.1:9191/cases/exhibit-1.txt?versionId=1735490000000000000' \
-H 'Authorization: Bearer <token>'Honest caveat:
ListObjectVersionsis not wired for cluster fan-out yet (s3_server.rsfollow-up). OnRELATA_PROFILE=clusterit returns a clear typed error rather than silently returning partial results. Use it onfree/servertoday; cluster support lands in a future release.
Cross-protocol — read your S3 objects over SQL
The killer feature: the same objects you wrote via boto3 are queryable over SQL, joinable to your typed rows, and searchable via hybrid search:
-- What's in the 'cases' bucket?
SELECT key, size, content_hash, system_from
FROM S3Object
WHERE bucket = 'cases'
ORDER BY system_from DESC LIMIT 20;
-- Join objects to typed investigation rows
SELECT s.key, s.size, p.name AS suspect
FROM S3Object s
JOIN Person p ON s.metadata->>'case_id' = p.case_id
WHERE s.bucket = 'cases';
-- Object version history at a point in time
SELECT key, size FROM S3Object
WHERE bucket = 'cases' AND key = 'exhibit-1.txt'
AS OF '2026-01-15T00:00:00';Governed identically: ACL, purpose, tenant isolation, and the audit chain apply on every S3 read/write just as they do on SQL.
Event notifications
Wire S3 events to downstream pipelines (SOAR, Lambda, dgrep) via RELATA_S3_NOTIFY_URL:
RELATA_S3_NOTIFY_URL=https://soar.example.com/relata-s3-events relata serveAfter each mutating S3 operation, a JSON payload {"event":"<put|delete>","bucket":"…","key":"…"} is POSTed fire-and-forget — errors are logged and discarded (non-blocking, won't slow the write path).
Tips & takeaways
- Use a dedicated SigV4 secret in production.
RELATA_S3_SECRET_KEY=$(openssl rand -hex 32)keeps your S3 door credential separate from the master bearer token — blast-radius containment if the S3 key leaks. - Tune
RELATA_S3_BLOB_THRESHOLD_MBfor your workload. Small default (4 MiB) inlines objects for fast SQL reads; raise it for media-heavy workloads to keep the row store lean. - Versioning is always on. Unlike AWS S3 where you opt in per bucket, every Relata S3 object is bi-temporal from the first
PUT—?versionsworks everywhere. - Join objects to your graph. Metadata you attach at
PUT(s3.put_object(Metadata={"case_id": "case-7"})) is queryable in SQL — link exhibits to suspects without a separate join table. - Audit the S3 door by principal. Per-door ACL (
s3-clientprincipal) means every S3 write is forensically attributable — see Per-Door ACL.
See also
- Compatibility & Doors — full port + credential table
- Deploying Protocol Doors — Docker/K8s wiring for the S3 port
- Bi-temporal queries —
AS OFoverS3Objectversions - Per-Door ACL — the
s3-clientCedar principal - Backup & Restore — the object store is also the backup backend