Brain by AIStack

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_key

Permissions

API Key PermissionS3 Read OpsS3 Write Ops
readListBuckets, ListObjectsV2, GetObject, HeadObject, HeadBucketNo
writeAll read opsPutObject, DeleteObject, DeleteObjects, CopyObject
adminAll read opsAll write ops

AWS CLI

Configure credentials

aws configure --profile brain
# Access Key ID: rak_xxxxxxxx
# Secret Access Key: rag_your_api_key
# Region: auto

Upload a document

aws s3 cp README.md s3://acme/docs/README.md \
  --endpoint-url https://brain.aistack.run/api/s3 \
  --profile brain

Sync a directory

aws s3 sync ./my-docs/ s3://acme/docs/ \
  --endpoint-url https://brain.aistack.run/api/s3 \
  --profile brain

List documents

aws s3 ls s3://acme/docs/ \
  --endpoint-url https://brain.aistack.run/api/s3 \
  --profile brain

Delete a document

aws s3 rm s3://acme/docs/old-file.md \
  --endpoint-url https://brain.aistack.run/api/s3 \
  --profile brain

Copy a document

aws s3 cp s3://acme/docs/guide.md s3://acme/archive/guide.md \
  --endpoint-url https://brain.aistack.run/api/s3 \
  --profile brain

Python (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 OperationMethodPathDescription
ListBucketsGET //api/s3/List your organization (single bucket)
HeadBucketHEAD /<bucket>/api/s3/acmeCheck bucket exists
ListObjectsV2GET /<bucket>/api/s3/acme?prefix=docs/List documents with optional prefix/delimiter
GetObjectGET /<bucket>/<key>/api/s3/acme/docs/guide.mdDownload document content
HeadObjectHEAD /<bucket>/<key>/api/s3/acme/docs/guide.mdGet document metadata
PutObjectPUT /<bucket>/<key>/api/s3/acme/docs/guide.mdUpload/replace a document
DeleteObjectDELETE /<bucket>/<key>/api/s3/acme/docs/old.mdDelete a document
DeleteObjectsPOST /<bucket>?delete/api/s3/acme?deleteBatch delete (XML body)
CopyObjectPUT /<bucket>/<key>With x-amz-copy-source headerCopy a document

ListObjectsV2 Parameters

ParameterDescription
prefixFilter results to keys starting with this prefix (e.g., docs/)
delimiterGroup keys by delimiter (e.g., / for folder-like listing)
max-keysMaximum number of results (default: 1000)
continuation-tokenResume 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_modules are 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 CodeHTTP StatusDescription
AccessDenied403Invalid credentials or insufficient permissions
NoSuchBucket404Organization slug not found
NoSuchKey404Document not found
EntityTooLarge400File exceeds 10 MB limit
InvalidArgument400Invalid request parameter
MalformedXML400Invalid XML in batch delete request
NotImplemented501Unsupported 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.

On this page