Skip to main content

Files Block

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

ServiceURL
Fileshttps://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 Staging
  • pk_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:

ParameterTypeDescription
folderstringFilter by folder path
content_typestringFilter by MIME type (e.g., image/*)
searchstringFull-text search in filenames and metadata
limitintegerMax results (default: 20, max: 100)
offsetintegerPagination offset
sortstringSort field (created_at, size, filename)
orderstringSort 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:

ParameterTypeDescription
expires_inintegerSeconds until URL expires (max: 604800 = 7 days)
dispositionstringinline (view in browser) or attachment (force download)
filenamestringOverride 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

ParameterDescriptionExample
w / widthWidth in pixelsw=300
h / heightHeight in pixelsh=200
f / formatOutput format (webp, avif, png, jpg)f=webp
q / qualityQuality 1-100 (default: 85)q=80
fitResize mode: cover, contain, fill, inside, outsidefit=cover
positionCrop position: top, right, bottom, left, centerposition=center
blurGaussian blur 0.3-1000blur=5
sharpenSharpen filtersharpen=1
grayscaleConvert to grayscalegrayscale=true
rotateRotate degrees (90, 180, 270)rotate=90
flipFlip 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

LevelDescription
publicAnyone can access via CDN URL
privateRequires presigned URL or authentication
authenticatedRequires 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

FieldTypeDescription
unique_idstringUnique identifier
filenamestringStorage filename (may be sanitized)
original_filenamestringOriginal upload filename
content_typestringMIME type
sizeintegerFile size in bytes
folderstringFolder path
publicbooleanPublic access enabled
urlstringCDN URL (for public files)
metadataobjectCustom metadata key-value pairs
dimensionsobjectWidth/height for images
created_atstringISO 8601 timestamp
updated_atstringISO 8601 timestamp

Folder

FieldTypeDescription
pathstringFull folder path
namestringFolder name
parentstringParent folder path
file_countintegerNumber of files
total_sizeintegerTotal 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

StatusCodeDescription
40020001Invalid file format
40020002Invalid transform parameters
40120003Authentication required
40320004Access denied to file
40420005File not found
40920006File already exists
41320007File too large
41520008Unsupported media type
42220009Virus detected in upload
42920010Rate limit exceeded
50720011Storage quota exceeded

Rate Limiting

Endpoint TypeLimit
Upload100 requests/minute/API key
Download10,000 requests/minute/API key
Transformations5,000 requests/minute/API key
Presigned URLs500 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

PlanStorageBandwidth/monthMax File Size
Free5 GB10 GB100 MB
Starter50 GB100 GB1 GB
Pro500 GB1 TB5 GB
EnterpriseUnlimitedCustom5 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

EventDescription
file.uploadedFile upload completed
file.deletedFile was deleted
file.scannedVirus scan completed
file.processedImage/video processing completed
folder.createdNew folder created
folder.deletedFolder 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.