S3-compatible API
Every storage zone speaks the S3 API, so existing S3 SDKs and tools work against it without modification. There is no separate S3 credential to generate — the zone itself is the bucket, and its name and password are the key pair.
Finding your credentials
In the dashboard, open Storage, select your zone, and click Storage access. The S3-compatible API section holds everything below. The values map like this:
| S3 concept | Your value |
|---|---|
| Access Key ID | Storage zone name |
| Secret Access Key | Storage zone password |
| Bucket | Storage zone name (same as the access key) |
| Region | Your zone's region code, lowercase |
The zone password is a real credential — it grants full read and write access to the zone. For anything that only needs to read, use the read-only secret access key shown alongside it instead. Both can be rotated from the same panel, and rotating the password rotates the S3 secret key, because they are the same credential.
Endpoints
The endpoint is region-specific and must match the region your zone was created in. Note that the S3 hostname is not the same as the FTP hostname for the same region.
| Region | Code | S3 endpoint |
|---|---|---|
| Frankfurt, DE | DE | https://de-s3.storage.bunnycdn.com |
| London, UK | UK | https://uk-s3.storage.bunnycdn.com |
| Stockholm, SE | SE | https://se-s3.storage.bunnycdn.com |
| New York, US | NY | https://ny-s3.storage.bunnycdn.com |
| Los Angeles, US | LA | https://la-s3.storage.bunnycdn.com |
| Singapore, SG | SG | https://sg-s3.storage.bunnycdn.com |
| Sydney, AU | SYD | https://syd-s3.storage.bunnycdn.com |
| São Paulo, BR | BR | Not available |
| Johannesburg, ZA | JH | https://jh-s3.storage.bunnycdn.com |
S3 access is not available in São Paulo. Zones in that region can still be reached over FTP and the native Storage API — see Storage API & FTP. If you need S3 access specifically, create your zone in another region.
Addressing style
Both path-style (https://{endpoint}/{bucket}/{key}) and virtual-hosted-style addressing work. Most SDKs default to virtual-hosted-style and need path-style enabled explicitly; the examples below do that, and it is the safer default if you hit unexpected NoSuchBucket errors.
AWS CLI
Configure a named profile, then pass the endpoint on each call:
aws configure --profile zerobuffer
# AWS Access Key ID: your-storage-zone-name
# AWS Secret Access Key: your-storage-zone-password
# Default region name: de
# Default output format: json# List objects
aws s3 ls s3://your-storage-zone-name/ \
--profile zerobuffer \
--endpoint-url https://de-s3.storage.bunnycdn.com
# Upload a file
aws s3 cp ./video.mp4 s3://your-storage-zone-name/media/video.mp4 \
--profile zerobuffer \
--endpoint-url https://de-s3.storage.bunnycdn.com
# Sync a directory
aws s3 sync ./dist s3://your-storage-zone-name/assets \
--profile zerobuffer \
--endpoint-url https://de-s3.storage.bunnycdn.comNode.js
Using @aws-sdk/client-s3 (v3):
import { S3Client, PutObjectCommand, ListObjectsV2Command } from "@aws-sdk/client-s3";
import { readFile } from "node:fs/promises";
const s3 = new S3Client({
region: "de",
endpoint: "https://de-s3.storage.bunnycdn.com",
forcePathStyle: true,
credentials: {
accessKeyId: process.env.ZEROBUFFER_ZONE_NAME,
secretAccessKey: process.env.ZEROBUFFER_ZONE_PASSWORD,
},
});
// Upload
await s3.send(new PutObjectCommand({
Bucket: process.env.ZEROBUFFER_ZONE_NAME,
Key: "media/video.mp4",
Body: await readFile("./video.mp4"),
ContentType: "video/mp4",
}));
// List
const { Contents = [] } = await s3.send(new ListObjectsV2Command({
Bucket: process.env.ZEROBUFFER_ZONE_NAME,
Prefix: "media/",
}));
console.log(Contents.map((o) => o.Key));Python
Using boto3:
import os
import boto3
from botocore.config import Config
zone = os.environ["ZEROBUFFER_ZONE_NAME"]
s3 = boto3.client(
"s3",
region_name="de",
endpoint_url="https://de-s3.storage.bunnycdn.com",
aws_access_key_id=zone,
aws_secret_access_key=os.environ["ZEROBUFFER_ZONE_PASSWORD"],
config=Config(s3={"addressing_style": "path"}),
)
# Upload
s3.upload_file("video.mp4", zone, "media/video.mp4")
# List
for obj in s3.list_objects_v2(Bucket=zone, Prefix="media/").get("Contents", []):
print(obj["Key"], obj["Size"])rclone
[zerobuffer]
type = s3
provider = Other
access_key_id = your-storage-zone-name
secret_access_key = your-storage-zone-password
endpoint = https://de-s3.storage.bunnycdn.com
region = de
force_path_style = truerclone ls zerobuffer:your-storage-zone-name
rclone copy ./dist zerobuffer:your-storage-zone-name/assets --progressServing what you upload
Files uploaded over S3 are served by the zone's pull zone like any other object — the S3 endpoint is for writing and managing files, not for delivering them to end users. Use your CDN hostname for public traffic, which you will find on the zone's page in the dashboard.
Unsupported features
The implementation covers the core object operations. These S3 features are not supported, and calls to them will fail:
- ACLs and object tagging
- Server-side encryption (SSE, SSE-C)
- Batch delete
- Object versioning
- Custom object metadata
- CORS configuration via the S3 API
Multi-part upload, ranged reads, and the standard get/put/list/delete operations all work normally.
Troubleshooting
| Error | Cause |
|---|---|
SignatureDoesNotMatch | Secret key is wrong, or the region in your client does not match the endpoint hostname |
NoSuchBucket | Bucket name does not match the zone name, or the zone lives in a different region than the endpoint you called |
AccessDenied | Using the read-only secret key for a write, or the credential has been rotated |
| TLS / certificate errors | Check you are using the endpoint exactly as listed above; custom hostnames are not valid S3 endpoints |