Skip to main content

Storage API

File storage operations using R2 with $0 egress.

All Storage endpoints use the base path /api/storage/:bucket, where :bucket is the bucket name defined in your edgebase.config.ts.

Authentication

All endpoints require a Bearer Token (Authorization: Bearer <accessToken>) unless a signed URL is used for access.


Upload File

POST /api/storage/:bucket/upload

Upload a file using multipart/form-data.

Auth: Bearer Token required

Form FieldTypeRequiredDescription
fileFileYesThe file to upload
keystringYesStorage path (key) for the file
metadataJSON stringNoCustom metadata as a JSON string
curl -X POST https://your-project.edgebase.dev/api/storage/avatars/upload \
-H "Authorization: Bearer <accessToken>" \
-F "file=@photo.jpg" \
-F "key=profile/photo.jpg" \
-F 'metadata={"alt":"Profile photo"}'

Response 201

{
"key": "profile/photo.jpg",
"size": 123456,
"httpMetadata": { "contentType": "image/jpeg" },
"customMetadata": { "alt": "Profile photo" },
"uploaded": "2026-01-01T00:00:00.000Z"
}
ErrorStatusDescription
Missing file400No file provided in the request
Missing key400Storage key not specified
Unauthorized401Invalid or missing token
Rule denied403Bucket write rule rejected the request
File too large413File exceeds the configured size limit

Download File

GET /api/storage/:bucket/:key

Download a file by its key. The response is the raw file binary with the appropriate Content-Type header set automatically based on the stored httpMetadata.

Auth: Bearer Token required (unless bucket read rule allows public access)

Path ParameterDescription
bucketBucket name
keyFile key (path)

Response: File binary with auto-detected Content-Type.

ErrorStatusDescription
Not found404File does not exist
Unauthorized401Invalid or missing token
Rule denied403Bucket read rule rejected the request

Check File Exists (HEAD)

HEAD /api/storage/:bucket/:key

Check if a file exists without downloading the content. Returns only HTTP headers with no response body.

Auth: Bearer Token required (unless bucket read rule allows public access)

Service Key Scope: storage:bucket:{bucketName}:read

Path ParameterDescription
bucketBucket name
keyFile key (path)

Response 200 — file exists. Includes the following headers:

HeaderDescription
Content-TypeMIME type of the file
Content-LengthFile size in bytes
ETagR2 ETag
Last-ModifiedLast modification timestamp

Response 404 — file does not exist.

ErrorStatusDescription
Not found404File does not exist
Unauthorized401Invalid or missing token
Rule denied403Bucket read rule rejected the request

List Files

GET /api/storage/:bucket

List files in a bucket with optional prefix filtering and cursor-based pagination.

Auth: Bearer Token required

Query ParameterTypeDefaultDescription
prefixstringFilter files by key prefix
limitnumberMaximum number of results to return
cursorstringPagination cursor from a previous response

Response 200

{
"objects": [
{ "key": "profile/photo.jpg", "size": 123456, "uploaded": "2026-01-01T00:00:00.000Z" },
{ "key": "profile/banner.png", "size": 654321, "uploaded": "2026-01-02T00:00:00.000Z" }
],
"truncated": false,
"cursor": null
}
FieldTypeDescription
objectsarrayArray of file objects with key, size, and uploaded
truncatedbooleantrue if more results are available
cursorstring | nullCursor to pass for the next page, null if no more results

Delete File

DELETE /api/storage/:bucket/:key

Delete a file by its key.

Auth: Bearer Token required

Path ParameterDescription
bucketBucket name
keyFile key (path) to delete

Response 200

{ "ok": true }
ErrorStatusDescription
Not found404File does not exist
Rule denied403Bucket delete rule rejected the request

Batch Delete

POST /api/storage/:bucket/delete-batch

Delete multiple files in a single request.

Auth: Bearer Token required

Request Body

FieldTypeRequiredDescription
keysstring[]YesArray of file keys to delete (max 100)
{
"keys": ["file1.jpg", "folder/file2.png"]
}

Response 200

{
"deleted": ["file1.jpg"],
"failed": [{ "key": "folder/file2.png", "error": "Not found" }]
}
FieldTypeDescription
deletedstring[]Array of successfully deleted file keys
failedarrayArray of objects with key and error for files that could not be deleted
  • Maximum 100 keys per request
  • Each key is individually checked against delete access rules
  • Non-existent files are reported in the failed array
ErrorStatusDescription
Too many keys400More than 100 keys provided
Unauthorized401Invalid or missing token
Rule denied403Bucket delete rule rejected the request for a key

Get File Metadata

GET /api/storage/:bucket/:key/metadata

Retrieve file metadata without downloading the file body.

Auth: Bearer Token required

Path ParameterDescription
bucketBucket name
keyFile key (path)

Response 200

{
"key": "profile/photo.jpg",
"size": 123456,
"httpMetadata": { "contentType": "image/jpeg" },
"customMetadata": { "alt": "Profile photo" },
"uploaded": "2026-01-01T00:00:00.000Z"
}

Update File Metadata

PATCH /api/storage/:bucket/:key/metadata

Update the custom metadata of an existing file. Provide key-value pairs to set or overwrite.

Auth: Bearer Token required

Path ParameterDescription
bucketBucket name
keyFile key (path)

Request Body: Key-value pairs of custom metadata.

{
"alt": "Updated description",
"tags": "profile,avatar"
}

Response 200

{
"key": "profile/photo.jpg",
"size": 123456,
"httpMetadata": { "contentType": "image/jpeg" },
"customMetadata": { "alt": "Updated description", "tags": "profile,avatar" },
"uploaded": "2026-01-01T00:00:00.000Z"
}

Generate Signed Download URL

POST /api/storage/:bucket/signed-url

Generate a temporary signed URL for downloading a file. The URL provides public access without authentication for a limited time.

Auth: Bearer Token required

Request BodyTypeRequiredDescription
keystringYesFile key to generate the URL for
expiresInnumberNoExpiration time in seconds
{
"key": "profile/photo.jpg",
"expiresIn": 3600
}

Response 200

{
"url": "https://your-bucket.r2.cloudflarestorage.com/profile/photo.jpg?X-Amz-Signature=..."
}

Batch Signed URLs

POST /api/storage/:bucket/signed-urls

Generate multiple signed download URLs in a single request.

Auth: Bearer Token required

Service Key Scope: storage:bucket:{bucketName}:read

Request Body

FieldTypeRequiredDescription
keysstring[]YesArray of file keys (max 100)
expiresInstringNoExpiration duration (e.g. "1h", "30m")
{
"keys": ["photo1.jpg", "photo2.jpg"],
"expiresIn": "1h"
}

Response 200

{
"urls": [
{
"key": "photo1.jpg",
"url": "/api/storage/my-bucket/photo1.jpg?token=...",
"expiresAt": "2024-01-01T01:00:00Z"
}
]
}
FieldTypeDescription
urlsarrayArray of objects with key, url, and expiresAt
  • Maximum 100 keys per request
  • Non-existent files are silently skipped (no error)
  • Subject to read access rules
  • Token format: HMAC-SHA256 signed

Generate Signed Upload URL

POST /api/storage/:bucket/signed-upload-url

Generate a signed URL that allows the client to upload a file directly to R2, bypassing the EdgeBase server. Useful for large files or reducing server load.

Auth: Bearer Token required

Request BodyTypeRequiredDescription
keystringYesTarget storage key for the upload
{
"key": "videos/intro.mp4"
}

Response 200

{
"url": "https://your-bucket.r2.cloudflarestorage.com/videos/intro.mp4?X-Amz-Signature=..."
}

The client can then PUT the file directly to the returned URL:

curl -X PUT "<signed-upload-url>" \
-H "Content-Type: video/mp4" \
--data-binary @intro.mp4

Multipart Upload

For large files, use multipart upload to split the file into parts and upload them individually. This supports resumable uploads and parallel part uploads.

MethodEndpointDescription
POST/api/storage/:bucket/multipart/createStart a new multipart upload
POST/api/storage/:bucket/multipart/upload-partUpload a single part
POST/api/storage/:bucket/multipart/completeComplete the multipart upload
POST/api/storage/:bucket/multipart/abortAbort the multipart upload
GET/api/storage/:bucket/uploads/:uploadId/partsList uploaded parts (for resuming)

Auth: Bearer Token required for all multipart endpoints.

Create Multipart Upload

POST /api/storage/:bucket/multipart/create

Start a new multipart upload session.

Request BodyTypeRequiredDescription
keystringYesTarget storage key
{
"key": "videos/large-file.mp4"
}

Response 200

{
"uploadId": "upload_abc123",
"key": "videos/large-file.mp4"
}

Upload Part

POST /api/storage/:bucket/multipart/upload-part

Upload a single part of a multipart upload. Parts can be uploaded in parallel.

Query ParameterTypeRequiredDescription
uploadIdstringYesThe upload ID from the create step
partNumbernumberYesPart number (starting from 1)
keystringYesThe file key

The request body should be the raw binary data for this part.

Response 200

{
"partNumber": 1,
"etag": "\"abc123def456\""
}

Complete Multipart Upload

POST /api/storage/:bucket/multipart/complete

Finalize the multipart upload by assembling all uploaded parts.

Request BodyTypeRequiredDescription
uploadIdstringYesThe upload ID
keystringYesThe file key
partsarrayYesArray of { partNumber, etag } objects
{
"uploadId": "upload_abc123",
"key": "videos/large-file.mp4",
"parts": [
{ "partNumber": 1, "etag": "\"abc123def456\"" },
{ "partNumber": 2, "etag": "\"789ghi012jkl\"" }
]
}

Response 200

{
"key": "videos/large-file.mp4",
"size": 52428800,
"uploaded": "2026-01-01T00:00:00.000Z"
}

Abort Multipart Upload

POST /api/storage/:bucket/multipart/abort

Cancel an in-progress multipart upload and clean up any uploaded parts.

Request BodyTypeRequiredDescription
uploadIdstringYesThe upload ID to abort
keystringYesThe file key
{
"uploadId": "upload_abc123",
"key": "videos/large-file.mp4"
}

Response 200

{ "ok": true }

List Uploaded Parts

GET /api/storage/:bucket/uploads/:uploadId/parts

List all parts that have been uploaded for a given multipart upload. Useful for resuming an interrupted upload.

Path ParameterDescription
uploadIdThe upload ID
Query ParameterTypeRequiredDescription
keystringYesThe file key

Response 200

{
"parts": [
{ "partNumber": 1, "size": 10485760, "etag": "\"abc123def456\"" },
{ "partNumber": 2, "size": 10485760, "etag": "\"789ghi012jkl\"" }
]
}

Error Format

All Storage API errors follow the standard EdgeBase error format:

{
"code": 400,
"message": "Validation failed.",
"data": {
"key": { "code": "required", "message": "Field is required." }
}
}
HTTP StatusMeaning
400Bad request or validation failure
401Authentication required
403Access denied by bucket access rule
404File not found
413File size exceeds the configured limit
429Rate limit exceeded
500Internal server error