> ## Documentation Index
> Fetch the complete documentation index at: https://docs.sevalla.com/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> These are the docs for Sevalla, a platform for deploying applications, databases, static sites, object storage and load balancers. The dashboard is at https://app.sevalla.com.
> Bold text in steps is an exact dashboard label.
> To act on a Sevalla account, prefer the Sevalla MCP server (https://mcp.sevalla.com/mcp, see https://docs.sevalla.com/ai-agents/overview.md), then the Sevalla CLI (https://docs.sevalla.com/developer-tools/cli.md), then the REST API (https://api-docs.sevalla.com, OpenAPI spec at https://api.sevalla.com/v3/openapi.json).
> Every page index is at https://docs.sevalla.com/llms.txt.

# Connect with the S3 API

> Connect an application, the AWS CLI or any S3 tool to a bucket with its endpoint, bucket name, region auto and access keys, with JavaScript, Python and CLI examples.

Every bucket speaks the S3 API, so any S3 client can read and write it: the AWS SDKs, the AWS CLI, `rclone`, or your framework's S3 storage library. A client needs five values: the endpoint, the bucket name, the region `auto`, the access key and the secret key.

## Connection values

Find them on the bucket's **Settings** tab: in the sidebar, click **Object storage**, click the bucket, then click **Settings**.

| S3 setting | Value | Where it is |
| - | - | - |
| Endpoint (`endpoint`, `endpoint_url`, `--endpoint-url`) | `https://<account-id>.r2.cloudflarestorage.com` | **Details** > **Endpoint** |
| Bucket | The bucket name, such as `product-images-x7k2m` | **Details** > **Bucket name** |
| Region | `auto` | Always `auto`. Clients that only accept AWS region names can use `us-east-1`, which R2 treats as `auto`. |
| Access key ID | A 32-character key | **Access keys** > **Access key** |
| Secret access key | A 64-character key | **Access keys** > **Secret key** |

The keys are hidden until you click the eye icon; the copy icon copies them without showing them.

<Warning>
  Use the **Bucket name**, never the display name. A bucket shown as **Database backups** has a bucket name like `database-backups-q8w2e`, and only that name works in S3 requests.
</Warning>

<Frame>
  <img className="block dark:hidden" src="https://assets.sevalla.com/screenshots/light/object-storage-settings-details.png" alt="The bucket's Details and Access keys sections: display name, location, bucket name, ID, endpoint, and the masked access and secret keys" width="1216" />

  <img className="hidden dark:block" src="https://assets.sevalla.com/screenshots/dark/object-storage-settings-details.png" alt="The bucket's Details and Access keys sections: display name, location, bucket name, ID, endpoint, and the masked access and secret keys" width="1216" />
</Frame>

## What the keys can do

The keys read, write, list and delete objects in this one bucket. They can't list, create or delete buckets, and they can't change bucket settings such as CORS rules or public access: do that on the bucket's [Settings](/object-storage/settings) tab or with the [Sevalla API](/developer-tools/api). Always name the bucket in requests: a command that lists all buckets, such as `aws s3 ls` on its own, fails.

## Store the values as environment variables

Keep the values out of your code. For a Sevalla application, add them on the application's **Environment variables** tab, then deploy the application so its processes get them. See [Environment variables](/applications/environment-variables).

```bash .env theme={null}
S3_ENDPOINT=https://<account-id>.r2.cloudflarestorage.com
S3_BUCKET=product-images-x7k2m
S3_ACCESS_KEY_ID=<access-key>
S3_SECRET_ACCESS_KEY=<secret-key>
```

Replace each value with the one from the bucket's **Settings** tab. The names are your choice; the examples on this page read these four.

## Connect with the AWS SDK for JavaScript

Install the S3 client, the multipart upload helper and the URL presigner (Node.js 20 or later):

```bash theme={null}
npm install @aws-sdk/client-s3 @aws-sdk/lib-storage @aws-sdk/s3-request-presigner
```

This script uploads, lists, downloads and deletes a file, and creates a link to it:

```javascript s3.mjs expandable theme={null}
import { createReadStream } from "node:fs";
import { writeFile } from "node:fs/promises";
import {
  S3Client,
  ListObjectsV2Command,
  GetObjectCommand,
  DeleteObjectCommand,
} from "@aws-sdk/client-s3";
import { Upload } from "@aws-sdk/lib-storage";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";

const s3 = new S3Client({
  region: "auto",
  endpoint: process.env.S3_ENDPOINT,
  credentials: {
    accessKeyId: process.env.S3_ACCESS_KEY_ID,
    secretAccessKey: process.env.S3_SECRET_ACCESS_KEY,
  },
  requestChecksumCalculation: "WHEN_REQUIRED",
  responseChecksumValidation: "WHEN_REQUIRED",
});
const Bucket = process.env.S3_BUCKET;

// Upload a file. Upload splits large files into parts on its own.
await new Upload({
  client: s3,
  params: {
    Bucket,
    Key: "images/logo.png",
    Body: createReadStream("logo.png"),
    ContentType: "image/png",
  },
}).done();

// List the files in a folder.
const { Contents = [] } = await s3.send(
  new ListObjectsV2Command({ Bucket, Prefix: "images/" })
);
for (const object of Contents) {
  console.log(object.Key, object.Size);
}

// Download a file.
const { Body } = await s3.send(
  new GetObjectCommand({ Bucket, Key: "images/logo.png" })
);
await writeFile("logo-copy.png", await Body.transformToByteArray());

// Create a link that works for one hour.
const url = await getSignedUrl(
  s3,
  new GetObjectCommand({ Bucket, Key: "images/logo.png" }),
  { expiresIn: 3600 }
);
console.log(url);

// Delete the file.
await s3.send(new DeleteObjectCommand({ Bucket, Key: "images/logo.png" }));
```

The two `WHEN_REQUIRED` options stop the SDK from adding checksums that only some operations need: without them, it signs the checksum of an empty body into [presigned upload URLs](#share-files-with-presigned-urls), and R2 rejects the real upload. Run the script with the four environment variables set and a `logo.png` next to it: `node s3.mjs`. For a complete Node.js application, see [Use object storage from Node.js](/guides/javascript/object-storage).

## Connect with Python (boto3)

Install boto3:

```bash theme={null}
pip install boto3
```

This script does the same as the JavaScript one:

```python s3.py expandable theme={null}
import os

import boto3
from botocore.config import Config

s3 = boto3.client(
    "s3",
    endpoint_url=os.environ["S3_ENDPOINT"],
    aws_access_key_id=os.environ["S3_ACCESS_KEY_ID"],
    aws_secret_access_key=os.environ["S3_SECRET_ACCESS_KEY"],
    region_name="auto",
    config=Config(signature_version="s3v4"),
)
bucket = os.environ["S3_BUCKET"]

# Upload a file. upload_file splits large files into parts on its own.
s3.upload_file(
    "logo.png", bucket, "images/logo.png", ExtraArgs={"ContentType": "image/png"}
)

# List the files in a folder, page by page.
paginator = s3.get_paginator("list_objects_v2")
for page in paginator.paginate(Bucket=bucket, Prefix="images/"):
    for obj in page.get("Contents", []):
        print(obj["Key"], obj["Size"])

# Download a file.
s3.download_file(bucket, "images/logo.png", "logo-copy.png")

# Create a link that works for one hour.
url = s3.generate_presigned_url(
    "get_object",
    Params={"Bucket": bucket, "Key": "images/logo.png"},
    ExpiresIn=3600,
)
print(url)

# Delete the file.
s3.delete_object(Bucket=bucket, Key="images/logo.png")
```

Run it with `python s3.py`. For Django and FastAPI, see [Store Django media in object storage](/guides/python/django/object-storage) and [Use object storage from FastAPI](/guides/python/fastapi/object-storage).

## Connect with the AWS CLI

Save the bucket's values in a profile named `sevalla`. AWS CLI 2.13 and later read `endpoint_url` from the profile; with an older version, add `--endpoint-url https://<account-id>.r2.cloudflarestorage.com` to every command instead.

```bash theme={null}
aws configure set aws_access_key_id <access-key> --profile sevalla
aws configure set aws_secret_access_key <secret-key> --profile sevalla
aws configure set region auto --profile sevalla
aws configure set endpoint_url https://<account-id>.r2.cloudflarestorage.com --profile sevalla
```

Then use `s3://<bucket-name>/` paths with `--profile sevalla`:

```bash theme={null}
# List the files in a folder
aws s3 ls s3://product-images-x7k2m/images/ --profile sevalla

# Upload a file
aws s3 cp logo.png s3://product-images-x7k2m/images/logo.png --profile sevalla

# Upload a folder, skipping files that haven't changed
aws s3 sync ./public s3://product-images-x7k2m/assets/ --profile sevalla

# Download a file
aws s3 cp s3://product-images-x7k2m/images/logo.png logo-copy.png --profile sevalla

# Download a whole folder
aws s3 cp s3://product-images-x7k2m/images/ ./images --recursive --profile sevalla

# Create a link that works for one hour
aws s3 presign s3://product-images-x7k2m/images/logo.png --expires-in 3600 --profile sevalla

# Delete a file
aws s3 rm s3://product-images-x7k2m/images/logo.png --profile sevalla
```

The AWS CLI uploads large files in parts on its own. The S3 API operations R2 supports are listed in Cloudflare's [S3 API compatibility](https://developers.cloudflare.com/r2/api/s3/api/) reference.

## Share files with presigned URLs

A presigned URL gives anyone who has it access to one object, without the keys, until it expires. Every example on this page creates one that works for an hour (`3600` seconds); the longest allowed is 7 days (`604800` seconds). Use presigned URLs for private files, such as invoices or user uploads, and [public access](#public-urls) for files anyone may read.

Presigned URLs also let browsers upload straight to the bucket. Create one for `PutObject` on your server, with the `s3` client and `Bucket` from the [JavaScript example](#connect-with-the-aws-sdk-for-javascript):

```javascript theme={null}
import { PutObjectCommand } from "@aws-sdk/client-s3";

const uploadUrl = await getSignedUrl(
  s3,
  new PutObjectCommand({ Bucket, Key: "uploads/avatar.png", ContentType: "image/png" }),
  { expiresIn: 600 }
);
```

The browser then sends the file with `fetch(uploadUrl, { method: "PUT", body: file, headers: { "Content-Type": "image/png" } })`. For that request to pass the browser's checks, the bucket needs a [CORS rule](/object-storage/settings#cors-policy) that allows `PUT` from your site's origin, such as `https://shop.example.com`, and the `Content-Type` header.

## Public URLs

With [public access](/object-storage/settings#enable-public-access) on, every object has a public URL on the bucket's public domain:

```text theme={null}
https://<bucket-name>.sevalla.storage/<key>
```

For example, `https://product-images-x7k2m.sevalla.storage/images/logo.png`. Anyone can read objects this way, without keys, and Cloudflare's CDN caches them. After you replace a file, [purge the cache](/object-storage/settings#purge-the-cdn-cache) if visitors still get the old version. The public domain is read-only: uploads and deletions always go through the S3 endpoint with the keys.

## Update your clients after you roll the keys

When you [roll the keys](/object-storage/settings#roll-the-access-keys), the bucket gets a new access key and secret key. The old ones keep working only until the grace period you picked ends. Before then, update the environment variables in every application that uses the bucket, deploy those applications, and update your CLI profile. Database backup targets that use the bucket get the new keys without any action from you.

## Troubleshoot connections

| Symptom | Cause | Fix |
| - | - | - |
| Requests fail because the bucket doesn't exist or access is denied | The client uses the display name, or a typo in the bucket name | Use the **Bucket name** from **Settings**, such as `product-images-x7k2m`. |
| Every request fails with an authentication or signature error | The keys were rolled and the old ones expired, or a key was copied with extra characters | Copy both keys again with the copy icons on **Settings**, update the client, and deploy. |
| Listing all buckets fails, but requests with a bucket name work | The keys work for one bucket only | Name the bucket in every request, such as `aws s3 ls s3://product-images-x7k2m/`. |
| The client asks for a region or rejects the one you set | No region set, or an AWS-only region check | Set the region to `auto`, or to `us-east-1` if the client accepts only AWS region names. |
| The browser blocks requests with a CORS error | The bucket has no CORS rule for your site's origin, method or header | Add a [CORS rule](/object-storage/settings#cors-policy) for that origin, such as `https://shop.example.com`. |
| A file at the public URL is out of date | Cloudflare's CDN still has the old version | [Purge the CDN cache](/object-storage/settings#purge-the-cdn-cache). |


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.