Storage
Storage
Object storage backed by Cloudflare R2. Upload and download files up to 100 MB. Objects are organized into buckets scoped per project. Supports signed URLs for direct client-side uploads and downloads.
Virtual buckets: SaaSignal uses a single R2 bucket under the hood. Your "buckets" are logical namespaces stored in D1. Objects are keyed by
{projectId}/{bucketId}/{objectKey}.Create Bucket
GET
json — 201 Created
{
"id": "sb_01JNXS...",
"name": "assets",
"visibility": "private",
"created_at": "2026-03-09T12:00:00Z"
}
typescript
await ss.infra.storage.createBucket('assets', { visibility: 'private' })
- Open Dashboard and select your project
- Go to Storage in the sidebar
- Click Create Bucket
- Enter a name and choose visibility, then click Create
Body field
Type
Description
name requiredstring
Bucket name (1–63 chars, lowercase alphanumeric and hyphens)
visibilitystring
Bucket visibility:
private or public. Default private.Error responses
400 Invalid body (validation error)
401 Unauthorized
402 Insufficient Tokens
409 Bucket name already exists
429 Rate Limited
List Buckets
GET
json — 200 OK
{
"buckets": [
{ "id": "sb_01JNXS...", "name": "assets", "visibility": "private", "object_count": 42, "total_size": 10485760, "created_at": "2026-03-09T12:00:00Z" }
]
}
typescript
const { buckets } = await ss.infra.storage.listBuckets()
- Open Dashboard and select your project
- Go to Storage in the sidebar
- All buckets are listed with their object count and total size
Error responses
401 Unauthorized
429 Rate Limited
Get Bucket
GET
json — 200 OK
{
"id": "sb_01JNXS...",
"name": "assets",
"visibility": "private",
"object_count": 42,
"total_size": 10485760,
"created_at": "2026-03-09T12:00:00Z"
}
typescript
const bucket = await ss.infra.storage.getBucket('sb_...')
- Open Dashboard and select your project
- Go to Storage and click on a bucket to view its details
Path param
Type
Description
bucket_id requiredstring
Bucket ID
Error responses
401 Unauthorized
404 Bucket not found
429 Rate Limited
Delete Bucket
GET
typescript
await ss.infra.storage.deleteBucket('sb_...')
- Open Dashboard and select your project
- Go to Storage and click on the bucket
- Click Delete Bucket and confirm
Path param
Type
Description
bucket_id requiredstring
Bucket ID to delete
Error responses
401 Unauthorized
404 Bucket not found
409 Bucket is not empty
429 Rate Limited
Upload Object
GET
json — 201 Created
{
"key": "images/logo.png",
"size": 24576,
"content_type": "image/png",
"etag": ""d41d8cd98f00b204e9800998ecf8427e"",
"uploaded_at": "2026-03-09T12:00:00Z"
}
typescript
await ss.infra.storage.putObject('sb_...', 'images/logo.png', file, { contentType: 'image/png' })
- Open Dashboard and select your project
- Go to Storage and open a bucket
- Click Upload and select a file
- Optionally set the object key (defaults to filename)
Path param
Type
Description
bucket_id requiredstring
Bucket ID
key requiredstring
Object key (path within the bucket, e.g.
images/logo.png)Body field
Type
Description
body requiredbinary
File content (max 100 MB)
Query param
Type
Description
content_typestring
MIME type of the object
Error responses
400 Invalid key or file too large
401 Unauthorized
402 Insufficient Tokens
404 Bucket not found
429 Rate Limited
List Objects
GET
json — 200 OK
{
"objects": [
{ "key": "images/logo.png", "size": 24576, "content_type": "image/png", "etag": ""d41d8..."", "uploaded_at": "2026-03-09T12:00:00Z" }
],
"next_cursor": null,
"truncated": false
}
typescript
const { objects } = await ss.infra.storage.listObjects('sb_...', { prefix: 'images/' })
- Open Dashboard and select your project
- Go to Storage and open a bucket
- Browse objects or filter by prefix using the search bar
Path param
Type
Description
bucket_id requiredstring
Bucket ID
Query param
Type
Description
prefixstring
Filter objects by key prefix
cursorstring
Pagination cursor from a previous
next_cursor responselimitinteger
Max objects to return per page. Range 1–1000, default
100.Error responses
401 Unauthorized
404 Bucket not found
429 Rate Limited
Download Object
GET
typescript
const response = await ss.infra.storage.getObject('sb_...', 'images/logo.png')
- Open Dashboard and select your project
- Go to Storage and open a bucket
- Click on an object, then click Download
Path param
Type
Description
bucket_id requiredstring
Bucket ID
key requiredstring
Object key to download
Error responses
401 Unauthorized
404 Object not found
429 Rate Limited
Head Object
GET
json — 200 OK
{
"key": "images/logo.png",
"size": 24576,
"content_type": "image/png",
"etag": ""d41d8cd98f00b204e9800998ecf8427e"",
"uploaded_at": "2026-03-09T12:00:00Z"
}
typescript
const meta = await ss.infra.storage.headObject('sb_...', 'images/logo.png')
- Open Dashboard and select your project
- Go to Storage, open a bucket, and click on an object to view its metadata
Path param
Type
Description
bucket_id requiredstring
Bucket ID
key requiredstring
Object key
Error responses
401 Unauthorized
404 Object not found
429 Rate Limited
Delete Object
GET
typescript
await ss.infra.storage.deleteObject('sb_...', 'images/logo.png')
- Open Dashboard and select your project
- Go to Storage, open a bucket, and click on the object
- Click Delete and confirm
Path param
Type
Description
bucket_id requiredstring
Bucket ID
key requiredstring
Object key to delete
Error responses
401 Unauthorized
404 Object not found
429 Rate Limited
Generate Signed URL
GET
json — 200 OK
{
"url": "https://storage.saasignal.saastemly.com/sb_.../images/logo.png?X-Amz-Signature=...",
"expires_at": "2026-03-09T13:00:00Z"
}
typescript
const { url } = await ss.infra.storage.signedUrl('sb_...', 'images/logo.png', { method: 'PUT', expires_in: 3600 })
- Open Dashboard and select your project
- Go to Storage, open a bucket, and click on an object
- Click Signed URL, configure method and expiry, then copy the URL
Path param
Type
Description
bucket_id requiredstring
Bucket ID
key requiredstring
Object key
Body field
Type
Description
methodstring
HTTP method the signed URL permits:
GET or PUT. Default GET.expires_ininteger
URL validity in seconds. Range 1–86400, default
3600 (1 hour).Error responses
401 Unauthorized
404 Bucket not found
429 Rate Limited
Consume Signed Download URL
GET
/infra/storage/signed/:token
typescript
const response = await ss.infra.storage.downloadSignedUrl(signedUrl.url)
- Generate a signed download URL from Storage
- Paste the resulting URL into a new tab to validate public download access, or call the underlying route from API Console
Path param
Type
Description
token requiredstring
Signed URL token
Query param
Type
Description
expires requiredstring
Signed expiry timestamp embedded in the URL
p requiredstring
Signed project payload
b requiredstring
Signed bucket payload
k requiredstring
Signed object key payload
m requiredstring
Signed HTTP method. Must be
GET.Error responses
403 Signed URL is invalid or expired
404 Object not found
429 Rate Limited
Consume Signed Upload URL
PUT
/infra/storage/signed/:token
typescript
await ss.infra.storage.uploadSignedUrl(signedUrl.url, file, { contentType: 'image/png' })
- Generate a signed upload URL from Storage
- Use API Console in raw mode to send file bytes through the public signed upload route
- Or hand the signed URL to any browser client that should upload directly without exposing your API key
Path param
Type
Description
token requiredstring
Signed URL token
Query param
Type
Description
expires requiredstring
Signed expiry timestamp embedded in the URL
p requiredstring
Signed project payload
b requiredstring
Signed bucket payload
k requiredstring
Signed object key payload
m requiredstring
Signed HTTP method. Must be
PUT.ctstring
Optional content type embedded in the signed URL
Body field
Type
Description
body requiredbinary
Raw file bytes to upload
Error responses
403 Signed URL is invalid or expired
429 Rate Limited