Files API
Overview
The Files API handles file upload, download, and management in Cloudillo. It supports four file types: BLOB (binary files), CRDT (collaborative documents), RTDB (real-time databases), and FLDR (folders).
File Types
Type
Description
Use Cases
BLOB
Binary files (images, PDFs, etc.)
Photos, documents, attachments
CRDT
Collaborative documents
Rich text, spreadsheets, diagrams
RTDB
Real-time databases
Structured data, forms, todos
FLDR
Folders
Organize files hierarchically
File Status
Code
Status
Description
A
Active
File is available
P
Pending
File is being processed
D
Deleted
File is in trash
Image Variants
For BLOB images, Cloudillo automatically generates 5 variants:
Variant
Code
Max Dimension
Quality
Format
Thumbnail
tn
150px
Medium
JPEG/WebP
Icon
ic
64px
Medium
JPEG/WebP
SD
sd
640px
Medium
JPEG/WebP
HD
hd
1920px
High
JPEG/WebP
Original
orig
Original
Original
Original
Automatic format selection:
Modern browsers: WebP or AVIF
Fallback: JPEG or PNG
Endpoints
List Files
List all files accessible to the user. Uses cursor-based pagination.
Authentication: Optional (visibility-based access control)
Query Parameters:
Filtering:
fileTp - Filter by file type (BLOB, CRDT, RTDB, FLDR)
contentType - Filter by MIME type
tags - Filter by tags (comma-separated)
parentId - Filter by parent folder (for hierarchical listing)
status - Filter by status (A, P, D)
Pagination:
limit - Max results (default: 20)
cursor - Opaque cursor for next page
Sorting:
sort - Sort field: created, modified, name, recent
sortDir - Sort direction: asc or desc
Example:
const api = cloudillo .createApiClient ()
// List all images
const images = await api .files .list ({
fileTp : 'BLOB' ,
contentType : 'image/*' ,
limit : 20
})
// List files in a folder
const folderContents = await api .files .list ({
parentId : 'f1~folder123' ,
sort : 'name' ,
sortDir : 'asc'
})
// List tagged files
const projectFiles = await api .files .list ({
tags : 'project-alpha,important'
})
// Cursor-based pagination
if (images .cursorPagination ? .hasMore ) {
const page2 = await api .files .list ({
fileTp : 'BLOB' ,
cursor : images.cursorPagination.nextCursor
})
}
Response:
{
"data" : [
{
"fileId" : "b1~abc123" ,
"parentId" : null ,
"status" : "A" ,
"contentType" : "image/png" ,
"fileName" : "photo.png" ,
"fileTp" : "BLOB" ,
"createdAt" : "2025-01-01T12:00:00Z" ,
"tags" : ["vacation" , "beach" ],
"visibility" : "P" ,
"owner" : {
"idTag" : "alice@example.com" ,
"name" : "Alice"
},
"userData" : {
"pinned" : false ,
"starred" : true ,
"accessedAt" : "2025-01-15T09:30:00Z"
}
}
],
"cursorPagination" : {
"nextCursor" : "eyJzIjoiY3JlYXRlZCIsInYiOjE3MzUwMDAwMDAsImlkIjoiYjF-YWJjMTIzIn0" ,
"hasMore" : true
},
"time" : "2025-01-01T12:00:00Z"
}
Create file metadata for CRDT or RTDB files . For BLOB files, use the one-step upload endpoint instead (see “Upload File (BLOB)” below).
Authentication: Required
Request Body:
{
fileTp : string // CRDT or RTDB
fileName? : string // Optional filename
tags? : string // Comma-separated tags
}
Example:
// Create CRDT document
const file = await api .files .create ({
fileTp : 'CRDT' ,
fileName : 'team-doc.crdt' ,
tags : 'collaborative,document'
})
console .log ('File ID:' , file .fileId ) // e.g., "f1~abc123"
// Now connect via WebSocket to edit the document
import * as Y from 'yjs'
const yDoc = new Y .Doc ()
const { provider } = await cloudillo .openYDoc (yDoc , file .fileId )
Response:
{
"data" : {
"fileId" : "f1~abc123" ,
"status" : "P" ,
"fileTp" : "CRDT" ,
"fileName" : "team-doc.crdt" ,
"createdAt" : 1735000000
},
"time" : 1735000000 ,
"reqId" : "req_abc123"
}
Note: For BLOB files (images, PDFs, etc.), use POST /api/files/{preset}/{file_name} instead, which creates metadata and uploads the file in a single step.
Upload File (BLOB)
POST /api/files/{preset}/{file_name}
Upload binary file data directly. This creates the file metadata and uploads the binary in a single operation.
Authentication: Required
Path Parameters:
preset (string, required) - Image processing preset (e.g., default, profile-picture, cover-photo)
file_name (string, required) - Original filename with extension
Query Parameters:
created_at (number, optional) - Unix timestamp (seconds) for when the file was created
tags (string, optional) - Comma-separated tags (e.g., vacation,beach,2025)
Content-Type: Binary content type (e.g., image/png, image/jpeg, application/pdf)
Request Body: Raw binary file data
Example:
const api = cloudillo .createApiClient ()
// Upload image file
const imageFile = document.querySelector ('input[type="file"]' ).files [0 ]
const blob = await imageFile .arrayBuffer ()
const response = await fetch ('/api/files/default/vacation-photo.jpg?tags=vacation,beach' , {
method : 'POST' ,
headers : {
'Authorization' : `Bearer ${ token } ` ,
'Content-Type' : 'image/jpeg'
},
body : blob
})
const result = await response .json ()
console .log ('File ID:' , result .data .fileId ) // e.g., "b1~abc123"
Complete upload with File object:
async function uploadFile (file : File , preset = 'default' , tags? : string ) {
const queryParams = new URLSearchParams ()
if (tags ) queryParams .set ('tags' , tags )
const url = `/api/files/ ${ preset } / ${ encodeURIComponent(file .name )}${
queryParams .toString () ? '?' + queryParams .toString () : ''
} `
const response = await fetch (url , {
method : 'POST' ,
headers : {
'Authorization' : `Bearer ${ token } ` ,
'Content-Type' : file .type
},
body : file
})
const result = await response .json ()
return result .data .fileId
}
// Usage
const fileInput = document.querySelector ('input[type="file"]' )
fileInput .addEventListener ('change' , async (e ) => {
const file = e .target .files [0 ]
const fileId = await uploadFile (file , 'default' , 'vacation,beach' )
console .log ('Uploaded:' , fileId )
// Display uploaded image
const img = document.createElement ('img' )
img .src = `/api/files/ ${ fileId } ?variant=sd`
document.body .appendChild (img )
})
Response:
{
"data" : {
"fileId" : "b1~abc123" ,
"status" : "M" ,
"fileTp" : "BLOB" ,
"contentType" : "image/jpeg" ,
"fileName" : "vacation-photo.jpg" ,
"createdAt" : 1735000000 ,
"tags" : ["vacation" , "beach" ]
},
"time" : 1735000000 ,
"reqId" : "req_abc123"
}
Download File
Download a file. Returns binary data with appropriate Content-Type.
Query Parameters:
variant - Image variant (tn, ic, sd, hd, orig)
Example:
// Direct URL usage
<img src = "/api/files/b1~abc123" />
// Get specific variant
<img src = "/api/files/b1~abc123?variant=sd" />
// Fetch with API
const response = await fetch (`/api/files/ ${ fileId } ` )
const blob = await response .blob ()
const url = URL .createObjectURL (blob )
Responsive images:
<picture >
<source srcset = "/api/files/b1~abc123?variant=hd" media = "(min-width: 1200px)" >
<source srcset = "/api/files/b1~abc123?variant=sd" media = "(min-width: 600px)" >
<img src = "/api/files/b1~abc123?variant=tn" alt = "Photo" >
</picture >
Get File Descriptor
GET /api/files/:fileId/descriptor
Get file metadata and available variants.
Example:
const descriptor = await api .files .getDescriptor ('b1~abc123' )
console .log ('File type:' , descriptor .contentType )
console .log ('Size:' , descriptor .size )
console .log ('Variants:' , descriptor .variants )
Response:
{
"data" : {
"fileId" : "b1~abc123" ,
"contentType" : "image/png" ,
"fileName" : "photo.png" ,
"size" : 1048576 ,
"createdAt" : "2025-01-01T12:00:00Z" ,
"variants" : [
{
"id" : "tn" ,
"width" : 150 ,
"height" : 100 ,
"size" : 5120 ,
"format" : "webp"
},
{
"id" : "sd" ,
"width" : 640 ,
"height" : 426 ,
"size" : 51200 ,
"format" : "webp"
},
{
"id" : "hd" ,
"width" : 1920 ,
"height" : 1280 ,
"size" : 204800 ,
"format" : "webp"
},
{
"id" : "orig" ,
"width" : 3840 ,
"height" : 2560 ,
"size" : 1048576 ,
"format" : "png"
}
]
}
}
GET /api/files/{file_id}/metadata
Get file metadata only (without variant information).
Authentication: Optional (visibility-based access control)
Path Parameters:
Response:
{
"data" : {
"fileId" : "b1~abc123" ,
"status" : "A" ,
"contentType" : "image/png" ,
"fileName" : "photo.png" ,
"fileTp" : "BLOB" ,
"createdAt" : "2025-01-01T12:00:00Z" ,
"tags" : ["vacation" , "beach" ],
"visibility" : "P" ,
"owner" : {
"idTag" : "alice@example.com" ,
"name" : "Alice"
}
},
"time" : "2025-01-01T12:00:00Z"
}
Duplicate File
POST /api/files/{file_id}/duplicate
Create a copy of a CRDT or RTDB file. The new file gets a new ID and timestamps but copies the content.
Authentication: Required
Path Parameters:
file_id - The file ID to duplicate
Request Body:
{
"fileName" : "Copy of team-doc.crdt" ,
"parentId" : "f1~folder456"
}
Field
Type
Required
Description
fileName
string
No
Name for the new file (defaults to original name)
parentId
string
No
Parent folder for the new file
Response:
{
"data" : {
"fileId" : "f1~newfile789"
},
"time" : "2025-01-01T12:00:00Z"
}
Info
File duplication is only available for CRDT and RTDB file types. BLOB files cannot be duplicated through this endpoint.
Update file metadata (tags, filename, etc.).
Authentication: Required (must be owner)
Request Body:
{
fileName? : string
tags? : string // Comma-separated
}
Example:
await api .files .update ('b1~abc123' , {
fileName : 'renamed-photo.png' ,
tags : 'vacation,beach,2025'
})
Delete File
DELETE /api/files/{file_id}
Move a file to trash (soft delete).
Authentication: Required (must have write access)
Example:
await api .files .delete ('b1~abc123' )
Response:
{
"data" : "ok" ,
"time" : "2025-01-01T12:00:00Z"
}
Restore File
POST /api/files/{file_id}/restore
Restore a file from trash.
Authentication: Required (must have write access)
Example:
await fetch ('/api/files/b1~abc123/restore' , {
method : 'POST' ,
headers : { 'Authorization' : `Bearer ${ token } ` }
})
Response:
{
"data" : "ok" ,
"time" : "2025-01-01T12:00:00Z"
}
Empty Trash
Permanently delete all files in trash.
Authentication: Required
Example:
await fetch ('/api/trash' , {
method : 'DELETE' ,
headers : { 'Authorization' : `Bearer ${ token } ` }
})
Response:
{
"data" : {
"deleted" : 15
},
"time" : "2025-01-01T12:00:00Z"
}
Update User File Data
PATCH /api/files/{file_id}/user
Update user-specific file metadata (pinned, starred). This updates only the authenticated user’s relationship with the file, not the file itself. Users can pin/star any file they have read access to.
Authentication: Required
Request:
{
"pinned" : true ,
"starred" : false
}
Response:
{
"data" : {
"pinned" : true ,
"starred" : false ,
"accessedAt" : "2025-01-01T12:00:00Z"
},
"time" : "2025-01-01T12:00:00Z"
}
Example:
// Star a file
await fetch ('/api/files/b1~abc123/user' , {
method : 'PATCH' ,
headers : {
'Authorization' : `Bearer ${ token } ` ,
'Content-Type' : 'application/json'
},
body : JSON.stringify ({ starred : true })
})
List all tags used in files owned by the authenticated user.
Authentication: Required
Response:
{
"data" : [
{
"tag" : "vacation" ,
"count" : 15
},
{
"tag" : "project-alpha" ,
"count" : 8
},
{
"tag" : "important" ,
"count" : 3
}
]
}
Example:
const response = await fetch ('/api/tags' , {
headers : {
'Authorization' : `Bearer ${ token } `
}
})
const { data : tags } = await response .json ()
// Display tags with counts
tags .forEach (({ tag , count }) => {
console .log (` ${ tag } : ${ count } files` )
})
Add Tag
PUT /api/files/:fileId/tag/:tag
Add a tag to a file.
Authentication: Required (must be owner)
Example:
await fetch (`/api/files/b1~abc123/tag/important` , {
method : 'PUT' ,
headers : {
'Authorization' : `Bearer ${ token } `
}
})
Remove Tag
DELETE /api/files/:fileId/tag/:tag
Remove a tag from a file.
Authentication: Required (must be owner)
Example:
await fetch (`/api/files/b1~abc123/tag/important` , {
method : 'DELETE' ,
headers : {
'Authorization' : `Bearer ${ token } `
}
})
Get File Variant
GET /api/files/variant/:variantId
Get a specific image variant directly.
Example:
// Variant IDs are in the format: {fileId}~{variant}
<img src = "/api/files/variant/b1~abc123~sd" />
File Identifiers
Cloudillo uses content-addressable identifiers:
Format: {prefix}{version}~{hash}
Prefixes:
b - BLOB files
f - CRDT files
r - RTDB files
Examples:
b1~abc123def - BLOB file
f1~xyz789ghi - CRDT file
r1~mno456pqr - RTDB file
Variants:
b1~abc123def~sd - SD variant of BLOB
b1~abc123def~tn - Thumbnail variant
CRDT Files
CRDT files store collaborative documents using Yjs.
Creating a CRDT file:
// 1. Create file metadata
const file = await api .files .create ({
fileTp : 'CRDT' ,
tags : 'document,collaborative'
})
// 2. Open for editing
import * as Y from 'yjs'
const yDoc = new Y .Doc ()
const { provider } = await cloudillo .openYDoc (yDoc , file .fileId )
// 3. Use shared types
const yText = yDoc .getText ('content' )
yText .insert (0 , 'Hello, CRDT!' )
See CRDT documentation for details.
RTDB Files
RTDB files store structured real-time databases.
Creating an RTDB file:
// 1. Create file metadata
const file = await api .files .create ({
fileTp : 'RTDB' ,
tags : 'database,todos'
})
// 2. Connect to database
import { RtdbClient } from '@cloudillo/rtdb'
const rtdb = new RtdbClient ({
fileId : file.fileId ,
token : cloudillo.accessToken ,
url : 'wss://server.com/ws/rtdb'
})
// 3. Use collections
const todos = rtdb .collection ('todos' )
await todos .create ({ title : 'Learn Cloudillo' , done : false })
See RTDB documentation for details.
Image Presets
Configure automatic image processing with presets:
const file = await api .files .create ({
fileTp : 'BLOB' ,
contentType : 'image/jpeg' ,
preset : 'profile-picture' // Custom preset
})
Default presets:
default - Standard 5-variant generation
profile-picture - Square crop, 400x400 max
cover-photo - 16:9 crop, 1920x1080 max
thumbnail-only - Only generate thumbnails
Tagging
Tags help organize and filter files.
Best practices:
Use lowercase tags
Use hyphens for multi-word tags (e.g., project-alpha)
Limit to 3-5 tags per file
Use namespaced tags for projects (e.g., proj:alpha, proj:beta)
Tag filtering:
// Files with ANY of these tags
const files = await api .files .list ({
tags : 'vacation,travel'
})
// Files with ALL of these tags (use multiple requests)
const vacationFiles = await api .files .list ({ tags : 'vacation' })
const summerFiles = vacationFiles .data .filter (f =>
f .tags ? .includes ('summer' )
)
Permissions
File access is controlled by:
Ownership - Owner has full access
FSHR actions - Files shared via FSHR actions grant temporary access
Public files - Files attached to public actions are publicly accessible
Audience - Files attached to actions inherit action audience permissions
Sharing a file:
// Share file with read access
await api .actions .create ({
type : 'FSHR' ,
subject : 'bob@example.com' ,
attachments : ['b1~abc123' ],
content : {
permission : 'READ' , // or 'WRITE'
message : 'Check out this photo!'
}
})
Storage Considerations
File size limits:
Free tier: 100 MB per file
Pro tier: 1 GB per file
Enterprise: Configurable
Total storage:
Free tier: 10 GB
Pro tier: 100 GB
Enterprise: Unlimited
Variant generation:
Only for image files (JPEG, PNG, WebP, AVIF, GIF)
Automatic async processing
Lanczos3 filtering for high quality
Progressive JPEG for faster loading
Best Practices
// ✅ Correct order
const metadata = await api .files .create ({ fileTp : 'BLOB' , contentType : 'image/png' })
await uploadBinary (metadata .fileId , imageBlob )
// ❌ Wrong - upload will fail without metadata
await uploadBinary ('unknown-id' , imageBlob )
2. Use Appropriate Variants
// ✅ Use thumbnails in lists
<img src = {`/api/files/ ${ fileId } ?variant=tn` } />
// ✅ Use HD for detail views
<img src = {`/api/files/ ${ fileId } ?variant=hd` } />
// ❌ Don't use original for thumbnails (wastes bandwidth)
<img src = {`/api/files/ ${ fileId } ` } width = "100" />
3. Handle Upload Errors
async function uploadWithRetry (file : File , maxRetries = 3 ) {
for (let i = 0 ; i < maxRetries ; i ++ ) {
try {
const result = await api .files .uploadBlob ('default' , file .name , file , file .type )
return result .fileId
} catch (error ) {
if (i === maxRetries - 1 ) throw error
await new Promise (r => setTimeout (r , 1000 * (i + 1 )))
}
}
}
4. Progressive Enhancement
// Show preview immediately, upload in background
function previewAndUpload (file : File ) {
// Show local preview
const reader = new FileReader ()
reader .onload = (e ) => {
setPreview (e .target .result )
}
reader .readAsDataURL (file )
// Upload in background
uploadImage (file ).then (fileId => {
setUploadedId (fileId )
})
}
5. Clean Up Unused Files
// Delete old temporary files
const oldFiles = await api .files .list ({
tags : 'temp' ,
createdBefore : Date.now () / 1000 - 86400 // 24 hours ago
})
for (const file of oldFiles .data ) {
await api .files .delete (file .fileId )
}
See Also