Storage
ZendBX Storage is an enterprise-grade object storage system backed by Backblaze B2. Files are organized into buckets per project with a clean, slug-based API that never exposes internal UUIDs.
Architecture V3
Storage V3 is built as a first-class platform service following these core principles:
- Every API is project-scoped
- Public APIs use human-readable identifiers (slugs), never internal UUIDs
- Business logic exists only once in service layers
- Routers are thin; services contain logic
- Provider-agnostic design supports future multi-cloud storage
All routes follow the pattern:
/p/{project-slug}/storage/buckets/{bucket-slug}/...File metadata is stored in PostgreSQL (storage_buckets and storage_objects tables). The actual file bytes live in Backblaze B2, but the architecture supports pluggable storage providers.
Architecture Layers
HTTP Router (FastAPI)
↓
Service Layer (business logic)
↓
Repository Layer (SQL)
↓
Database (PostgreSQL)
↓
Storage Provider (Backblaze B2)Resource Resolution
The SDK and REST API never require UUIDs. The backend automatically resolves:
project_slug → project_id → bucket_slug → bucket_uuid → storage_providerThis means you can reference resources naturally:
const bucket = db.storage.bucket('avatars'); // Not: bucket('550e8400-e29b-41d4-a716...')Buckets
A bucket is a named container for files. Create buckets in the Dashboard → Storage → Buckets, or programmatically:
// via SDK
await db.storage.createBucket('avatars', {
description: 'User profile pictures',
isPublic: false,
});const { data: buckets } = await db.storage.listBuckets();My Avatars becomes my-avatars. Use slugs in all SDK calls. UUIDs are supported internally for backward compatibility but should not be used in new code.Public vs Private Buckets
Private buckets (default) — files require a signed URL or authenticated download.
Public buckets — files are accessible via a direct preview URL without authentication.
Upload
const bucket = db.storage.bucket('avatars');
// Browser: upload a File object
const file = event.target.files[0];
const { data, error } = await bucket.upload(file, 'user-123.png');
// Node.js: upload a Buffer
const buffer = fs.readFileSync('./resume.pdf');
const { data, error } = await bucket.upload(buffer, 'resume.pdf', {
contentType: 'application/pdf',
});| Parameter | Type | Required | Description |
|---|---|---|---|
| file | File | Blob | ArrayBuffer | Uint8Array | required | The file to upload. |
| filename | string | optional | Override the filename. Defaults to file.name for File objects. |
| options.contentType | string | optional | MIME type. Auto-detected for File objects. |
List Files
const { data: files } = await bucket.list();
// data: StorageObject[]
// With options
const { data: files } = await bucket.list({
search: 'resume',
sortBy: 'created_at',
sortDir: 'desc',
});Download
const response = await bucket.download('file-uuid');
const blob = await response.blob();
const url = URL.createObjectURL(blob);Signed URLs
Generate temporary URLs for private files. Use these to share files with specific users or serve them in <img> tags.
// Generate a temporary URL (expires in 1 hour)
const { data } = await bucket.createSignedUrl('file-uuid', '1h');
console.log(data.url);
// Available expiry values: '5m', '15m', '1h', '24h', '7d'Delete
// Delete single file
await bucket.delete('file-uuid');
// Delete multiple files
await bucket.bulkDelete(['uuid-1', 'uuid-2', 'uuid-3']);Preview URL
// Get inline preview URL (public buckets only)
const url = bucket.getPreviewUrl('file-uuid');REST API Examples
curl -X POST https://api.zendbx.in/p/my-project/storage/buckets/avatars/upload \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "apikey: YOUR_ANON_KEY" \
-F "file=@/path/to/photo.png"curl https://api.zendbx.in/p/my-project/storage/buckets/avatars/files \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "apikey: YOUR_ANON_KEY"curl -X POST https://api.zendbx.in/p/my-project/storage/files/{file-id}/signed-url \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "apikey: YOUR_ANON_KEY" \
-H "Content-Type: application/json" \
-d '{"expiry":"1h"}'Security & Permissions
Every storage operation validates:
- Project ownership and access
- Bucket existence and permissions
- User authorization (JWT or API key)
- Row-level security policies (if configured)
Storage Provider Abstraction
ZendBX Storage V3 uses a provider-agnostic architecture. Currently supports Backblaze B2, with planned support for:
- AWS S3
- Cloudflare R2
- MinIO (self-hosted)
- Azure Blob Storage
- Google Cloud Storage
Changing storage providers requires zero application code changes — only backend configuration.
Monitoring & Metrics
Storage V3 tracks:
- Upload/download counts
- Storage usage per project and bucket
- Transfer bandwidth
- Failed upload attempts
- Operation duration (latency)
- Provider availability
View these metrics in Dashboard → Analytics → Storage.
Future Features
The V3 architecture is designed to support:
- Access Control Lists (ACLs)
- Object versioning
- Lifecycle rules (auto-delete old files)
- CDN integration
- Multipart uploads for large files
- Multi-region replication
- Image transformations (resize, compress)
Migrating from Legacy API
The legacy /api/storage endpoint is still supported but deprecated. New projects should use /p/{project-slug}/storage.

