Files Block

Enterprise cloud file storage with global CDN delivery
The Files Block provides a complete file management solution built on AWS S3 and CloudFront CDN. Upload files of any size with chunked uploads, transform images on-the-fly, generate presigned URLs for secure sharing, and serve files globally with sub-50ms latency.
Key Features
- AWS S3 Storage - Industry-leading object storage with 99.999999999% durability
- CloudFront CDN - 200+ edge locations for sub-50ms global delivery
- Chunked Uploads - Upload files up to 5GB with progress tracking and resume
- Image Transformations - Resize, crop, format conversion (WebP, AVIF) on-the-fly
- Presigned URLs - Time-limited secure access to private files
- Access Control - Public, private, and authenticated access modes
- Folder Organization - Virtual folders, metadata tagging, and full-text search
- Virus Scanning - Automatic malware detection on uploads
API Endpoint
| Service | URL |
|---|---|
| Files | https://files.api.us.23blocks.com |
Environment Routing: Use the same URL for all environments. Your API key determines the environment:
pk_test_*/sk_test_*→ Routes to Stagingpk_live_*/sk_live_*→ Routes to Production
Quick Start
Installation
npm install @23blocks/sdk
Basic Usage
import { create23BlocksClient } from '@23blocks/sdk';
const client = create23BlocksClient({
urls: { files: 'https://files.api.us.23blocks.com' },
apiKey: 'your-api-key',
});
// Upload a file
const file = await client.files.upload({
file: fileBlob,
folder: 'uploads/images',
public: true
});
// Get file URL with transformations
const thumbnailUrl = client.files.getUrl(file.id, {
width: 200,
height: 200,
format: 'webp'
});
// Generate presigned URL (expires in 1 hour)
const presignedUrl = await client.files.getPresignedUrl(file.id, {
expiresIn: 3600
});
Authentication
Required Headers
All API requests require authentication:
Authorization: Bearer [JWT_TOKEN]
X-API-Key: [COMPANY_API_KEY]
Content-Type: application/json
For file uploads, use multipart/form-data:
Content-Type: multipart/form-data
API Reference
Upload File
Upload a single file to cloud storage.
curl -X POST https://files.api.us.23blocks.com/files \
-H "Authorization: Bearer your-jwt-token" \
-H "X-API-Key: your-api-key" \
-F "file=@/path/to/image.jpg" \
-F "folder=uploads/images" \
-F "public=true" \
-F "metadata[alt]=Profile photo" \
-F "metadata[category]=avatars"
Response:
{
"data": {
"id": "file_abc123",
"type": "file",
"attributes": {
"unique_id": "file_abc123",
"filename": "image.jpg",
"original_filename": "profile-photo.jpg",
"content_type": "image/jpeg",
"size": 245678,
"folder": "uploads/images",
"public": true,
"url": "https://cdn.23blocks.com/company/uploads/images/image.jpg",
"metadata": {
"alt": "Profile photo",
"category": "avatars"
},
"dimensions": {
"width": 1920,
"height": 1080
},
"created_at": "2024-01-15T10:30:00Z"
}
}
}
Chunked Upload (Large Files)
For files larger than 100MB, use chunked uploads:
Step 1: Initialize Upload
curl -X POST https://files.api.us.23blocks.com/files/multipart/init \
-H "Authorization: Bearer your-jwt-token" \
-H "X-API-Key: your-api-key" \
-H "Content-Type: application/json" \
-d '{
"filename": "large-video.mp4",
"content_type": "video/mp4",
"size": 524288000,
"folder": "videos",
"parts": 50
}'
Response:
{
"data": {
"id": "upload_xyz789",
"type": "multipart_upload",
"attributes": {
"upload_id": "upload_xyz789",
"part_urls": [
"https://s3.amazonaws.com/...?partNumber=1&uploadId=...",
"https://s3.amazonaws.com/...?partNumber=2&uploadId=..."
],
"expires_at": "2024-01-15T11:30:00Z"
}
}
}
Step 2: Upload Each Part
curl -X PUT "https://s3.amazonaws.com/...?partNumber=1&uploadId=..." \
--data-binary @chunk1.bin
Step 3: Complete Upload
curl -X POST https://files.api.us.23blocks.com/files/multipart/complete \
-H "Authorization: Bearer your-jwt-token" \
-H "X-API-Key: your-api-key" \
-H "Content-Type: application/json" \
-d '{
"upload_id": "upload_xyz789",
"parts": [
{"part_number": 1, "etag": "abc123"},
{"part_number": 2, "etag": "def456"}
]
}'
Get File
Retrieve file metadata:
curl -X GET https://files.api.us.23blocks.com/files/file_abc123 \
-H "Authorization: Bearer your-jwt-token" \
-H "X-API-Key: your-api-key"
List Files
List files in a folder:
curl -X GET "https://files.api.us.23blocks.com/files?folder=uploads/images&limit=50&offset=0" \
-H "Authorization: Bearer your-jwt-token" \
-H "X-API-Key: your-api-key"
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
folder | string | Filter by folder path |
content_type | string | Filter by MIME type (e.g., image/*) |
search | string | Full-text search in filenames and metadata |
limit | integer | Max results (default: 20, max: 100) |
offset | integer | Pagination offset |
sort | string | Sort field (created_at, size, filename) |
order | string | Sort order (asc, desc) |
Delete File
curl -X DELETE https://files.api.us.23blocks.com/files/file_abc123 \
-H "Authorization: Bearer your-jwt-token" \
-H "X-API-Key: your-api-key"
Update File Metadata
curl -X PATCH https://files.api.us.23blocks.com/files/file_abc123 \
-H "Authorization: Bearer your-jwt-token" \
-H "X-API-Key: your-api-key" \
-H "Content-Type: application/json" \
-d '{
"metadata": {
"alt": "Updated description",
"tags": ["profile", "user"]
},
"public": false
}'
Presigned URLs
Generate temporary URLs for private file access:
curl -X POST https://files.api.us.23blocks.com/files/file_abc123/presign \
-H "Authorization: Bearer your-jwt-token" \
-H "X-API-Key: your-api-key" \
-H "Content-Type: application/json" \
-d '{
"expires_in": 3600,
"disposition": "inline"
}'
Response:
{
"data": {
"url": "https://cdn.23blocks.com/company/private/file.pdf?X-Amz-Signature=...",
"expires_at": "2024-01-15T11:30:00Z"
}
}
Parameters:
| Parameter | Type | Description |
|---|---|---|
expires_in | integer | Seconds until URL expires (max: 604800 = 7 days) |
disposition | string | inline (view in browser) or attachment (force download) |
filename | string | Override download filename |
Image Transformations
Transform images on-the-fly by adding parameters to the URL:
https://cdn.23blocks.com/company/images/photo.jpg?w=300&h=200&f=webp&q=80
Transform Parameters
| Parameter | Description | Example |
|---|---|---|
w / width | Width in pixels | w=300 |
h / height | Height in pixels | h=200 |
f / format | Output format (webp, avif, png, jpg) | f=webp |
q / quality | Quality 1-100 (default: 85) | q=80 |
fit | Resize mode: cover, contain, fill, inside, outside | fit=cover |
position | Crop position: top, right, bottom, left, center | position=center |
blur | Gaussian blur 0.3-1000 | blur=5 |
sharpen | Sharpen filter | sharpen=1 |
grayscale | Convert to grayscale | grayscale=true |
rotate | Rotate degrees (90, 180, 270) | rotate=90 |
flip | Flip image (h = horizontal, v = vertical) | flip=h |
Transform Examples
Thumbnail (200x200, cropped to center, WebP):
https://cdn.23blocks.com/.../image.jpg?w=200&h=200&fit=cover&f=webp
Responsive hero image (max 1200px wide):
https://cdn.23blocks.com/.../image.jpg?w=1200&q=85&f=webp
Profile avatar (circular crop):
https://cdn.23blocks.com/.../image.jpg?w=100&h=100&fit=cover&position=center
SDK Transform Example
// Generate transformed image URL
const thumbnailUrl = client.files.getUrl('file_abc123', {
width: 200,
height: 200,
fit: 'cover',
format: 'webp',
quality: 80
});
// Returns: https://cdn.23blocks.com/.../image.jpg?w=200&h=200&fit=cover&f=webp&q=80
Folder Operations
Create Folder
curl -X POST https://files.api.us.23blocks.com/folders \
-H "Authorization: Bearer your-jwt-token" \
-H "X-API-Key: your-api-key" \
-H "Content-Type: application/json" \
-d '{
"path": "uploads/2024/january",
"metadata": {
"description": "January 2024 uploads"
}
}'
List Folders
curl -X GET https://files.api.us.23blocks.com/folders?parent=uploads \
-H "Authorization: Bearer your-jwt-token" \
-H "X-API-Key: your-api-key"
Delete Folder
curl -X DELETE https://files.api.us.23blocks.com/folders/uploads%2F2024%2Fjanuary \
-H "Authorization: Bearer your-jwt-token" \
-H "X-API-Key: your-api-key"
Note: Deleting a folder also deletes all files within it.
Access Control
Visibility Levels
| Level | Description |
|---|---|
public | Anyone can access via CDN URL |
private | Requires presigned URL or authentication |
authenticated | Requires valid JWT token |
Set File Visibility
curl -X PATCH https://files.api.us.23blocks.com/files/file_abc123 \
-H "Authorization: Bearer your-jwt-token" \
-H "X-API-Key: your-api-key" \
-H "Content-Type: application/json" \
-d '{"public": false}'
Data Types
File
| Field | Type | Description |
|---|---|---|
unique_id | string | Unique identifier |
filename | string | Storage filename (may be sanitized) |
original_filename | string | Original upload filename |
content_type | string | MIME type |
size | integer | File size in bytes |
folder | string | Folder path |
public | boolean | Public access enabled |
url | string | CDN URL (for public files) |
metadata | object | Custom metadata key-value pairs |
dimensions | object | Width/height for images |
created_at | string | ISO 8601 timestamp |
updated_at | string | ISO 8601 timestamp |
Folder
| Field | Type | Description |
|---|---|---|
path | string | Full folder path |
name | string | Folder name |
parent | string | Parent folder path |
file_count | integer | Number of files |
total_size | integer | Total size in bytes |
Error Handling
The API uses JSON:API error format:
{
"errors": [
{
"status": "413",
"source": "Files Service",
"code": "20001",
"title": "File Too Large",
"detail": "File exceeds maximum size of 5GB"
}
]
}
Common Error Codes
| Status | Code | Description |
|---|---|---|
| 400 | 20001 | Invalid file format |
| 400 | 20002 | Invalid transform parameters |
| 401 | 20003 | Authentication required |
| 403 | 20004 | Access denied to file |
| 404 | 20005 | File not found |
| 409 | 20006 | File already exists |
| 413 | 20007 | File too large |
| 415 | 20008 | Unsupported media type |
| 422 | 20009 | Virus detected in upload |
| 429 | 20010 | Rate limit exceeded |
| 507 | 20011 | Storage quota exceeded |
Rate Limiting
| Endpoint Type | Limit |
|---|---|
| Upload | 100 requests/minute/API key |
| Download | 10,000 requests/minute/API key |
| Transformations | 5,000 requests/minute/API key |
| Presigned URLs | 500 requests/minute/API key |
Rate limit headers in responses:
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 1642248000
Storage & Bandwidth
Limits by Plan
| Plan | Storage | Bandwidth/month | Max File Size |
|---|---|---|---|
| Free | 5 GB | 10 GB | 100 MB |
| Starter | 50 GB | 100 GB | 1 GB |
| Pro | 500 GB | 1 TB | 5 GB |
| Enterprise | Unlimited | Custom | 5 GB |
Check Usage
curl -X GET https://files.api.us.23blocks.com/usage \
-H "Authorization: Bearer your-jwt-token" \
-H "X-API-Key: your-api-key"
Response:
{
"data": {
"storage_used": 1073741824,
"storage_limit": 5368709120,
"bandwidth_used": 2147483648,
"bandwidth_limit": 10737418240,
"file_count": 1234
}
}
SDK Examples
TypeScript/JavaScript
import { create23BlocksClient } from '@23blocks/sdk';
const client = create23BlocksClient({
urls: { files: 'https://files.api.us.23blocks.com' },
apiKey: process.env.BLOCKS_API_KEY,
});
// Upload with progress tracking
const file = await client.files.upload({
file: fileInput,
folder: 'uploads/documents',
public: false,
metadata: {
uploadedBy: 'user123',
category: 'contracts'
},
onProgress: (progress) => {
console.log(`Upload: ${progress}%`);
}
});
// List files in folder
const files = await client.files.list({
folder: 'uploads/documents',
contentType: 'application/pdf',
limit: 20
});
// Get file with transforms
const imageUrl = client.files.getUrl(file.id, {
width: 800,
format: 'webp',
quality: 85
});
// Generate presigned URL
const downloadUrl = await client.files.getPresignedUrl(file.id, {
expiresIn: 3600,
disposition: 'attachment',
filename: 'contract.pdf'
});
// Delete file
await client.files.delete(file.id);
React Component Example
import { useFiles, useUpload } from '@23blocks/react';
function FileUploader() {
const { upload, progress, isUploading, error } = useUpload();
const { files, isLoading, refetch } = useFiles({ folder: 'uploads' });
const handleUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (!file) return;
await upload({
file,
folder: 'uploads',
public: true
});
refetch(); // Refresh file list
};
return (
<div>
<input type="file" onChange={handleUpload} disabled={isUploading} />
{isUploading && <progress value={progress} max="100" />}
{error && <p className="error">{error.message}</p>}
<ul>
{files.map(file => (
<li key={file.id}>
{file.filename} ({file.size} bytes)
</li>
))}
</ul>
</div>
);
}
Node.js Server Example
import { create23BlocksClient } from '@23blocks/sdk';
import express from 'express';
import multer from 'multer';
const app = express();
const upload = multer({ storage: multer.memoryStorage() });
const client = create23BlocksClient({
urls: { files: 'https://files.api.us.23blocks.com' },
apiKey: process.env.BLOCKS_API_KEY,
});
// Proxy upload endpoint
app.post('/upload', upload.single('file'), async (req, res) => {
try {
const file = await client.files.upload({
file: req.file.buffer,
filename: req.file.originalname,
contentType: req.file.mimetype,
folder: 'user-uploads',
public: false
});
res.json({ fileId: file.id, url: file.url });
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// Generate signed URL for download
app.get('/download/:fileId', async (req, res) => {
const url = await client.files.getPresignedUrl(req.params.fileId, {
expiresIn: 300, // 5 minutes
disposition: 'attachment'
});
res.redirect(url);
});
Webhooks
Subscribe to file events:
curl -X POST https://files.api.us.23blocks.com/webhooks \
-H "Authorization: Bearer your-jwt-token" \
-H "X-API-Key: your-api-key" \
-H "Content-Type: application/json" \
-d '{
"url": "https://yourapp.com/webhooks/files",
"events": ["file.uploaded", "file.deleted", "file.scanned"],
"secret": "your-webhook-secret"
}'
Webhook Events
| Event | Description |
|---|---|
file.uploaded | File upload completed |
file.deleted | File was deleted |
file.scanned | Virus scan completed |
file.processed | Image/video processing completed |
folder.created | New folder created |
folder.deleted | Folder deleted |
Webhook Payload
{
"event": "file.uploaded",
"timestamp": "2024-01-15T10:30:00Z",
"data": {
"file": {
"id": "file_abc123",
"filename": "document.pdf",
"size": 1048576,
"folder": "uploads"
}
},
"signature": "sha256=..."
}
Best Practices
1. Use Chunked Uploads for Large Files
For files over 100MB, always use multipart uploads to ensure reliable transfers and enable resume capability.
2. Leverage CDN Caching
Set appropriate cache headers and use versioned filenames for static assets to maximize CDN cache hit rates.
3. Optimize Images
Use image transformations to serve appropriately sized images. Request WebP format with Accept header negotiation.
4. Use Presigned URLs for Private Content
Generate short-lived presigned URLs (5-60 minutes) for secure file access instead of exposing private storage URLs.
5. Implement Virus Scanning Callbacks
Listen to the file.scanned webhook event before making uploaded files available to users.
Related Resources
- Marketing Page - Feature overview
- API Reference - Interactive API documentation
- Platform Status - Service uptime monitoring