This guide shows how to use the S3 Orchestrator from common S3 clients and SDKs. The orchestrator is a standard S3-compatible endpoint — any tool that speaks the S3 protocol will work.
Prerequisites
You need four pieces of information from your orchestrator admin:
Setting
Example
Endpoint URL
http://s3-orchestrator.service.consul:9000
Bucket name
app1-files
Access Key ID
AKID_APP1_WRITER
Secret Access Key
wJalrXUtnFEMI/K7MDENG+bPxRfi...
Your credentials are tied to a specific bucket. You can only access the bucket your credentials are authorized for.
AWS CLI
Setup
Create a named profile so your orchestrator credentials don’t conflict with other AWS configurations:
# List top-level "directories"s3o s3 ls s3://app1-files/
# List everything under a prefixs3o s3 ls s3://app1-files/path/to/ --recursive
# Detailed listing with the s3api commands3o s3api list-objects-v2 --bucket app1-files --prefix "photos/"
Delete a file
s3o s3 rm s3://app1-files/path/to/myfile.txt
# Delete all files under a prefix (uses batch DeleteObjects internally)s3o s3 rm s3://app1-files/old-backups/ --recursive
# From a files3.upload_file("myfile.txt", "app1-files", "path/to/myfile.txt")
# From bytess3.put_object(
Bucket="app1-files",
Key="path/to/data.json",
Body=b'{"key": "value"}',
ContentType="application/json",
Metadata={"project": "acme", "env": "prod"},
)
Download
# To a files3.download_file("app1-files", "path/to/myfile.txt", "myfile.txt")
# To memoryresponse = s3.get_object(Bucket="app1-files", Key="path/to/data.json")
data = response["Body"].read()
List objects
response = s3.list_objects_v2(Bucket="app1-files", Prefix="photos/")
for obj in response.get("Contents", []):
print(f"{obj['Key']} ({obj['Size']} bytes)")
# Paginate large listingspaginator = s3.get_paginator("list_objects_v2")
for page in paginator.paginate(Bucket="app1-files", Prefix="photos/"):
for obj in page.get("Contents", []):
print(obj["Key"])
The orchestrator honors the If-None-Match: * header on PutObject and CompleteMultipartUpload to give clients opt-in conflict detection. When the header is set and an object already exists at the target key, the request fails with 412 Precondition Failed and the upload bytes are not stored.
The check is best-effort: a small race window exists between the existence check and the metadata commit, so two simultaneous writers each sending If-None-Match: * can both succeed in rare cases. This matches AWS S3’s documented behavior — the precondition is a strong signal but not a hard guarantee under contention.
Only the * form is honored on writes. A specific etag value in If-None-Match is ignored on PUT and the upload proceeds as a normal overwrite.
curl -X PUT -H 'If-None-Match: *' --data-binary @new.txt \
http://s3-orchestrator.service.consul:9000/app1-files/new.txt
A 412 Precondition Failed response can be retried with a different key or by intentionally omitting the header to overwrite.
Presigned URLs
Presigned URLs let you generate a time-limited URL that grants temporary access to an object without requiring the requester to have credentials. Any AWS SDK presign client works (Go, Python boto3, Java, JavaScript, etc.).
Generating a presigned URL
AWS CLI:
# Generate a presigned GET URL valid for 5 minutes (300 seconds)s3o s3 presign s3://app1-files/path/to/myfile.txt --expires-in 300
The presigned URL can be used with any HTTP client — no AWS credentials or SDK required:
curl -o myfile.txt "THE_PRESIGNED_URL"
Notes
Presigned URLs use the same access_key_id and secret_access_key as normal requests. No additional configuration is needed.
Maximum expiry is 7 days (604800 seconds). The server rejects URLs with a longer expiry.
Presigned URLs work for GET, PUT, DELETE, and HEAD operations.
For security recommendations (TLS, expiry values), see the Security Hardening guide.
Request Tracing
Every response from the orchestrator includes an X-Amz-Request-Id header with a unique ID for that request. When reporting issues to your admin, include this ID so they can look up the full request trace in the audit logs.
You can also supply your own correlation ID by sending an X-Request-Id header with your request. The orchestrator will use your ID instead of generating one and return it in the response.
# Check the request ID in a responses3o s3api put-object --bucket app1-files --key test.txt --body test.txt 2>&1 | grep -i request-id
# Supply your own correlation IDcurl -H "X-Request-Id: my-trace-123"\
http://s3-orchestrator.service.consul:9000/app1-files/test.txt
# Response header: X-Amz-Request-Id: my-trace-123
Limitations
The orchestrator implements a practical subset of the S3 API. A few things to be aware of:
Same-bucket copies only — CopyObject requires source and destination to be in the same bucket.
No bucket management — Buckets are configured server-side. CreateBucket, DeleteBucket, and ListBuckets are not supported.
No ACLs or policies — Access control is handled entirely through the credential-to-bucket mapping in the server config.
No object versioning — Each key holds exactly one object. Uploading to an existing key overwrites it. Concurrent PUTs to the same key are last-writer-wins, matching native S3 semantics. The orchestrator’s per-key advisory lock keeps location metadata consistent across replicas, and bytes from the losing writer are enqueued for backend cleanup. Clients that need conflict detection should send If-None-Match: * (see Conditional Writes).
Max object size — Configurable server-side (default: 5 GB). For larger objects, use multipart upload (most clients do this automatically).
Multipart upload timeout — Incomplete multipart uploads are automatically cleaned up after 24 hours.
Range reads — GET requests with a Range header are supported and return 206 Partial Content.