S3-Compatible API
Use standard S3 clients (AWS CLI, boto3, AWS SDK) to manage documents in Brain
S3-Compatible API
Brain exposes an S3-compatible API so you can manage documents using tools you already know: the AWS CLI, boto3, the AWS SDK for JavaScript, or any S3-compatible client.
Documents uploaded via S3 are automatically indexed for search, just like the REST API.
How It Works
Your organization maps to an S3 bucket, and each project maps to the first path segment of the object key. The remaining path becomes the document filename.
s3://acme/docs/api/auth.md
^^^^ ^^^^ ^^^^^^^^^^^
| | +-- filename: "api/auth.md"
| +-- project slug: "docs"
+-- org slug (bucket): "acme"Endpoint
https://brain.aistack.run/api/s3/Configure your S3 client to use this as the endpoint URL with path-style addressing.
Authentication
The S3 API supports two authentication methods:
AWS SigV4 (standard S3 clients)
When you create an API key in the dashboard, you receive:
- Access Key ID — starts with
rak_(shown on the API Keys page) - Secret Access Key — the API key itself (
rag_..., shown once at creation)
Configure your S3 client with these credentials. Use any value for the region (e.g., auto).
Bearer Token (simple HTTP clients)
For direct HTTP requests, you can use a Bearer token instead:
Authorization: Bearer rag_your_api_keyPermissions
| API Key Permission | S3 Read Ops | S3 Write Ops |
|---|---|---|
read | ListBuckets, ListObjectsV2, GetObject, HeadObject, HeadBucket | No |
write | All read ops | PutObject, DeleteObject, DeleteObjects, CopyObject |
admin | All read ops | All write ops |
AWS CLI
Configure credentials
aws configure --profile brain
# Access Key ID: rak_xxxxxxxx
# Secret Access Key: rag_your_api_key
# Region: autoUpload a document
aws s3 cp README.md s3://acme/docs/README.md \
--endpoint-url https://brain.aistack.run/api/s3 \
--profile brainSync a directory
aws s3 sync ./my-docs/ s3://acme/docs/ \
--endpoint-url https://brain.aistack.run/api/s3 \
--profile brainList documents
aws s3 ls s3://acme/docs/ \
--endpoint-url https://brain.aistack.run/api/s3 \
--profile brainDelete a document
aws s3 rm s3://acme/docs/old-file.md \
--endpoint-url https://brain.aistack.run/api/s3 \
--profile brainCopy a document
aws s3 cp s3://acme/docs/guide.md s3://acme/archive/guide.md \
--endpoint-url https://brain.aistack.run/api/s3 \
--profile brainPython (boto3)
import boto3
s3 = boto3.client(
"s3",
endpoint_url="https://brain.aistack.run/api/s3",
aws_access_key_id="rak_xxxxxxxx",
aws_secret_access_key="rag_your_api_key",
region_name="auto",
)
# Upload a file
s3.upload_file("README.md", "acme", "docs/README.md")
# Upload from string
s3.put_object(
Bucket="acme",
Key="docs/guide.md",
Body=b"# Guide\n\nThis is a guide.",
ContentType="text/markdown",
)
# List documents in a project
response = s3.list_objects_v2(Bucket="acme", Prefix="docs/")
for obj in response.get("Contents", []):
print(f"{obj['Key']} ({obj['Size']} bytes)")
# Download a document
response = s3.get_object(Bucket="acme", Key="docs/guide.md")
content = response["Body"].read().decode("utf-8")
print(content)
# Delete a document
s3.delete_object(Bucket="acme", Key="docs/old-file.md")
# Batch delete
s3.delete_objects(
Bucket="acme",
Delete={
"Objects": [
{"Key": "docs/a.md"},
{"Key": "docs/b.md"},
]
},
)JavaScript / TypeScript (AWS SDK v3)
import {
S3Client,
PutObjectCommand,
ListObjectsV2Command,
GetObjectCommand,
DeleteObjectCommand,
} from "@aws-sdk/client-s3";
const s3 = new S3Client({
endpoint: "https://brain.aistack.run/api/s3",
region: "auto",
credentials: {
accessKeyId: "rak_xxxxxxxx",
secretAccessKey: "rag_your_api_key",
},
forcePathStyle: true,
});
// Upload a document
await s3.send(
new PutObjectCommand({
Bucket: "acme",
Key: "docs/guide.md",
Body: "# Guide\n\nThis is a guide.",
ContentType: "text/markdown",
}),
);
// List documents
const list = await s3.send(
new ListObjectsV2Command({
Bucket: "acme",
Prefix: "docs/",
Delimiter: "/",
}),
);
for (const obj of list.Contents ?? []) {
console.log(`${obj.Key} (${obj.Size} bytes)`);
}
// Get a document
const get = await s3.send(
new GetObjectCommand({ Bucket: "acme", Key: "docs/guide.md" }),
);
const body = await get.Body?.transformToString();
console.log(body);
// Delete a document
await s3.send(
new DeleteObjectCommand({ Bucket: "acme", Key: "docs/old-file.md" }),
);Supported Operations
| S3 Operation | Method | Path | Description |
|---|---|---|---|
| ListBuckets | GET / | /api/s3/ | List your organization (single bucket) |
| HeadBucket | HEAD /<bucket> | /api/s3/acme | Check bucket exists |
| ListObjectsV2 | GET /<bucket> | /api/s3/acme?prefix=docs/ | List documents with optional prefix/delimiter |
| GetObject | GET /<bucket>/<key> | /api/s3/acme/docs/guide.md | Download document content |
| HeadObject | HEAD /<bucket>/<key> | /api/s3/acme/docs/guide.md | Get document metadata |
| PutObject | PUT /<bucket>/<key> | /api/s3/acme/docs/guide.md | Upload/replace a document |
| DeleteObject | DELETE /<bucket>/<key> | /api/s3/acme/docs/old.md | Delete a document |
| DeleteObjects | POST /<bucket>?delete | /api/s3/acme?delete | Batch delete (XML body) |
| CopyObject | PUT /<bucket>/<key> | With x-amz-copy-source header | Copy a document |
ListObjectsV2 Parameters
| Parameter | Description |
|---|---|
prefix | Filter results to keys starting with this prefix (e.g., docs/) |
delimiter | Group keys by delimiter (e.g., / for folder-like listing) |
max-keys | Maximum number of results (default: 1000) |
continuation-token | Resume pagination from a previous response |
Upload Behavior
- Maximum file size: 10 MB
- Upsert semantics: If a document with the same filename already exists in the project, it is replaced (old content, chunks, and R2 object are deleted)
- Auto-indexing: Uploaded documents are automatically queued for chunking and embedding
- Upload filters: Project-level ignore/allow patterns are applied (e.g.,
node_modulesare rejected) - File type validation: Only Docling-supported file types are indexed
Error Responses
Errors follow standard S3 XML format:
<?xml version="1.0" encoding="UTF-8"?>
<Error>
<Code>NoSuchKey</Code>
<Message>The specified key does not exist.</Message>
<Resource>docs/missing.md</Resource>
<RequestId>abc123</RequestId>
</Error>| Error Code | HTTP Status | Description |
|---|---|---|
AccessDenied | 403 | Invalid credentials or insufficient permissions |
NoSuchBucket | 404 | Organization slug not found |
NoSuchKey | 404 | Document not found |
EntityTooLarge | 400 | File exceeds 10 MB limit |
InvalidArgument | 400 | Invalid request parameter |
MalformedXML | 400 | Invalid XML in batch delete request |
NotImplemented | 501 | Unsupported S3 operation |
Limitations
- Single bucket per key: Each API key is scoped to one organization. ListBuckets returns only that organization.
- No multipart upload: Files must be uploaded in a single PUT (10 MB limit).
- No presigned URLs: Not yet supported.
- No bucket creation/deletion: Buckets map to organizations and are managed via the dashboard.
- Path-style only: Virtual-hosted-style bucket addressing is not supported.