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.
{projectId}/{bucketId}/{objectKey}.Create Bucket
Create a new storage bucket for the project.
curl -X POST https://api.saasignal.saastemly.com/infra/storage/buckets \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{"name":"example"}'
{
"id": "sb_01JNXS...",
"name": "assets",
"visibility": "private",
"created_at": "2026-03-09T12:00:00Z"
}
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
name required^[a-z0-9]([a-z0-9-]*[a-z0-9])?$visibilityprivate or public. Default private.private, publicdefault privateList Buckets
List all storage buckets for the project.
curl https://api.saasignal.saastemly.com/infra/storage/buckets \
-H "Authorization: Bearer sk_live_..."
{
"buckets": [
{ "id": "sb_01JNXS...", "name": "assets", "visibility": "private", "object_count": 42, "total_size": 10485760, "created_at": "2026-03-09T12:00:00Z" }
]
}
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
Get Bucket
Get details of a storage bucket.
curl https://api.saasignal.saastemly.com/infra/storage/buckets/{bucketId} \
-H "Authorization: Bearer sk_live_..."
{
"id": "sb_01JNXS...",
"name": "assets",
"visibility": "private",
"object_count": 42,
"total_size": 10485760,
"created_at": "2026-03-09T12:00:00Z"
}
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
bucket_id requiredDelete Bucket
Delete a bucket and every object under it. Cost is conservatively prepaid for up to 10,000 objects, then refunded down to the actual delete/list work performed.
curl -X DELETE https://api.saasignal.saastemly.com/infra/storage/buckets/{bucketId} \
-H "Authorization: Bearer sk_live_..."
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
bucket_id requiredUpload Object
Upload or overwrite an object. Send raw bytes with appropriate Content-Type. Max 100 MB. Cost varies with stored bytes and prepaid retention.
curl -X PUT https://api.saasignal.saastemly.com/infra/storage/buckets/{bucketId}/objects/* \
-H "Authorization: Bearer sk_live_..."
{
"key": "images/logo.png",
"size": 24576,
"content_type": "image/png",
"etag": ""d41d8cd98f00b204e9800998ecf8427e"",
"uploaded_at": "2026-03-09T12:00:00Z"
}
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)
bucket_id requiredkey requiredimages/logo.png)body requiredcontent_typeList Objects
List objects in a bucket.
curl https://api.saasignal.saastemly.com/infra/storage/buckets/{bucketId}/objects \
-H "Authorization: Bearer sk_live_..."
{
"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
}
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
bucket_id requiredprefixcursornext_cursor responselimit100.100Download Object
Download an object by key. Returns raw bytes with appropriate Content-Type.
curl https://api.saasignal.saastemly.com/infra/storage/buckets/{bucketId}/objects/* \
-H "Authorization: Bearer sk_live_..."
{ "status": "ok" }
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
bucket_id requiredkey requiredHead Object
Get metadata for an object without downloading the body.
curl -X HEAD https://api.saasignal.saastemly.com/infra/storage/buckets/{bucketId}/objects/* \
-H "Authorization: Bearer sk_live_..."
{
"key": "images/logo.png",
"size": 24576,
"content_type": "image/png",
"etag": ""d41d8cd98f00b204e9800998ecf8427e"",
"uploaded_at": "2026-03-09T12:00:00Z"
}
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
bucket_id requiredkey requiredDelete Object
Delete an object by key.
curl -X DELETE https://api.saasignal.saastemly.com/infra/storage/buckets/{bucketId}/objects/* \
-H "Authorization: Bearer sk_live_..."
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
bucket_id requiredkey requiredGenerate Signed URL
Generate a time-limited signed URL for direct upload or download. The signed URL creation call is billed separately from the eventual upload/download it authorizes.
curl -X POST https://api.saasignal.saastemly.com/infra/storage/buckets/{bucketId}/signed-url \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{"key":"...","method":"..."}'
{
"url": "https://storage.saasignal.saastemly.com/sb_.../images/logo.png?X-Amz-Signature=...",
"expires_at": "2026-03-09T13:00:00Z"
}
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
bucket_id requiredkey required^(?!.*\.\.)[^\x00-\x1f\x7f]+$methodGET or PUT. Default GET.GET, PUTexpires_in3600 (1 hour).3600Consume Signed Download URL
Download an object using a previously issued signed URL token. This route is intentionally public and authenticated entirely by the signed URL path token and query signature. Required query parameters are embedded in the signed URL returned by POST /infra/storage/buckets/:bucketId/signed-url.
curl https://api.saasignal.saastemly.com/infra/storage/signed/{token}?expires=...&p=...&b=...&k=...&m=... \
-H "Authorization: Bearer <session_token>"
{ "status": "ok" }
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
token requiredexpires requiredp requiredb requiredk requiredm requiredGET.GETConsume Signed Upload URL
Upload an object using a previously issued signed URL token. Send raw bytes in the request body and optionally pass ct as a query parameter to lock the content type. This route is intentionally public and authenticated entirely by the signed URL path token and query signature.
curl -X PUT https://api.saasignal.saastemly.com/infra/storage/signed/{token}?expires=...&p=...&b=...&k=...&m=... \
-H "Authorization: Bearer <session_token>"
{ "status": "ok" }
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
token requiredexpires requiredp requiredb requiredk requiredm requiredPUT.PUTctbody required