Skip to main content

Using S3 Clients

FoundryDB Files speaks the S3 API with virtual-host bucket addressing. Any S3-compatible client works by pointing it at the regional endpoint and supplying a scoped access key.

Before you start, retrieve your bucket coordinates from GET /file-services/{id} and mint an access key from POST /file-services/{id}/keys. See Buckets and Keys.

TLS

All connections to the Files endpoint are TLS-only. The endpoint does not accept plaintext HTTP. S3 clients default to HTTPS so no extra configuration is needed, but verify that any useSSL, secure, or equivalent option in your client is not explicitly disabled.

Addressing

Files uses virtual-host bucket addressing. The bucket name is the first label of the hostname:

https://files-f0e1d2c3.eu.files.foundrydb.com/path/to/object

When you configure an S3 client, set the endpoint to the bare regional host (https://eu.files.foundrydb.com or https://us.files.foundrydb.com). The SDK prepends the bucket name automatically. If your client has a forcePathStyle or path_style option, leave it disabled (false). Path-style addressing (https://eu.files.foundrydb.com/files-f0e1d2c3/key) is not supported.

AWS CLI

Configure the CLI with your scoped key:

export AWS_ACCESS_KEY_ID=EXAMPLEkeyid
export AWS_SECRET_ACCESS_KEY=EXAMPLEsecret
export AWS_DEFAULT_REGION=europe-1

All AWS CLI commands require --endpoint-url pointing at the regional endpoint:

# Upload a file
aws s3 cp report.pdf s3://files-f0e1d2c3/reports/q1.pdf \
--endpoint-url https://eu.files.foundrydb.com

# Download a file
aws s3 cp s3://files-f0e1d2c3/reports/q1.pdf ./q1.pdf \
--endpoint-url https://eu.files.foundrydb.com

# List objects under a prefix
aws s3 ls s3://files-f0e1d2c3/reports/ \
--endpoint-url https://eu.files.foundrydb.com

# Delete an object
aws s3 rm s3://files-f0e1d2c3/reports/q1.pdf \
--endpoint-url https://eu.files.foundrydb.com

AWS SDK for JavaScript / TypeScript

import { S3Client, PutObjectCommand, GetObjectCommand } from "@aws-sdk/client-s3";
import { readFileSync } from "fs";

const s3 = new S3Client({
region: "europe-1",
endpoint: "https://eu.files.foundrydb.com",
credentials: {
accessKeyId: process.env.S3_ACCESS_KEY_ID!,
secretAccessKey: process.env.S3_SECRET_ACCESS_KEY!,
},
// Required: virtual-host addressing is the default in AWS SDK v3 but must
// be confirmed when using a custom endpoint.
forcePathStyle: false,
});

const BUCKET = "files-f0e1d2c3";

// Upload
await s3.send(new PutObjectCommand({
Bucket: BUCKET,
Key: "uploads/photo.jpg",
Body: readFileSync("./photo.jpg"),
ContentType: "image/jpeg",
}));

// Download
const response = await s3.send(new GetObjectCommand({
Bucket: BUCKET,
Key: "uploads/photo.jpg",
}));
const body = await response.Body?.transformToByteArray();

AWS SDK for Python (boto3)

import boto3
import os

s3 = boto3.client(
"s3",
region_name="europe-1",
endpoint_url="https://eu.files.foundrydb.com",
aws_access_key_id=os.environ["S3_ACCESS_KEY_ID"],
aws_secret_access_key=os.environ["S3_SECRET_ACCESS_KEY"],
)

BUCKET = "files-f0e1d2c3"

# Upload
s3.upload_file("report.pdf", BUCKET, "reports/q1.pdf")

# Download
s3.download_file(BUCKET, "reports/q1.pdf", "q1_local.pdf")

# List objects under a prefix
response = s3.list_objects_v2(Bucket=BUCKET, Prefix="reports/")
for obj in response.get("Contents", []):
print(obj["Key"], obj["Size"])

AWS SDK for Go

package main

import (
"context"
"os"

"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/credentials"
"github.com/aws/aws-sdk-go-v2/service/s3"
)

func main() {
cfg, err := config.LoadDefaultConfig(context.Background(),
config.WithRegion("europe-1"),
config.WithCredentialsProvider(credentials.NewStaticCredentialsProvider(
os.Getenv("S3_ACCESS_KEY_ID"),
os.Getenv("S3_SECRET_ACCESS_KEY"),
"",
)),
)
if err != nil {
panic(err)
}

client := s3.NewFromConfig(cfg, func(o *s3.Options) {
o.BaseEndpoint = aws.String("https://eu.files.foundrydb.com")
o.UsePathStyle = false // virtual-host addressing; leave false
})

bucket := "files-f0e1d2c3"

// Upload
f, _ := os.Open("report.pdf")
defer f.Close()
_, err = client.PutObject(context.Background(), &s3.PutObjectInput{
Bucket: aws.String(bucket),
Key: aws.String("reports/q1.pdf"),
Body: f,
ContentType: aws.String("application/pdf"),
})
if err != nil {
panic(err)
}

// Download
result, err := client.GetObject(context.Background(), &s3.GetObjectInput{
Bucket: aws.String(bucket),
Key: aws.String("reports/q1.pdf"),
})
if err != nil {
panic(err)
}
defer result.Body.Close()
}

Presigned URLs

Presigned URLs let a browser or third-party upload or download a single object directly, without credentials. The URL is signed by the platform and expires after a configurable duration. The signed request runs directly against the object storage endpoint and is not proxied through the platform.

Every issued URL is recorded in the platform audit log with the object key, method, expiry, and actor, even though the signed request itself is not observable by the platform after issuance.

Issue a Presigned PUT (browser upload)

curl -u admin:password -X POST \
https://api.foundrydb.com/file-services/{id}/presign \
-H "Content-Type: application/json" \
-d '{
"method": "PUT",
"key": "uploads/avatar.png",
"content_type": "image/png",
"expires_seconds": 300
}'

Response:

{
"url": "https://files-f0e1d2c3.eu.files.foundrydb.com/uploads/avatar.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-...",
"method": "PUT",
"expires_at": "2026-06-22T15:35:00Z"
}

The browser uploads directly to that URL with the matching Content-Type header:

curl -X PUT \
-H "Content-Type: image/png" \
--data-binary @avatar.png \
"https://files-f0e1d2c3.eu.files.foundrydb.com/uploads/avatar.png?X-Amz-Algorithm=..."

Issue a Presigned GET (time-limited download)

curl -u admin:password -X POST \
https://api.foundrydb.com/file-services/{id}/presign \
-H "Content-Type: application/json" \
-d '{
"method": "GET",
"key": "reports/q1.pdf",
"expires_seconds": 3600
}'

Presign Request Fields

FieldRequiredDescription
methodYesGET, PUT, HEAD, or DELETE (uppercase)
keyYesObject key, up to 1024 characters. Must not contain ..
expires_secondsNoURL lifetime in seconds. Default 900 (15 minutes), maximum 604800 (7 days)
content_typeNoWhen set on a PUT, the upload must send the matching Content-Type header

PUT presigning returns 403 while the service is over its hard storage quota. GET presigning is always available.

Console Object Listing

The platform exposes a paginated object listing for console use. It does not replace S3 client access for bulk operations.

# List objects, optionally filtered by prefix
curl -u admin:password \
"https://api.foundrydb.com/file-services/{id}/objects?prefix=reports/&max=50"

# Continue from a cursor
curl -u admin:password \
"https://api.foundrydb.com/file-services/{id}/objects?prefix=reports/&cursor=NEXT_CURSOR"

Delete one object via the console endpoint:

curl -u admin:password -X DELETE \
"https://api.foundrydb.com/file-services/{id}/objects?key=reports/old.pdf"

S3 API Surface

The following S3 operations are supported on the Files endpoint. Operations not listed here are not available.

OperationMethodNotes
PutObjectPUTMultipart upload supported
GetObjectGETRange requests supported
DeleteObjectDELETE
DeleteObjectsPOSTBatch delete, up to 1000 keys per request
HeadObjectHEADReturns metadata without the body
ListObjectsV2GETVirtual-host addressing; prefix and delimiter filtering
CreateMultipartUploadPOSTInitiate a multipart upload
UploadPartPUTIndividual part upload
CompleteMultipartUploadPOSTAssemble parts into a final object
AbortMultipartUploadDELETECancel an in-progress multipart upload

Bucket-level operations (create, delete, ACL) are managed by the platform. Clients do not need to call CreateBucket; the bucket already exists when the service reaches Running.

What's Next

  • Attach to Apps: skip manual credential management by attaching the bucket to a hosted app