SQLite
Serverless databases via HTTP—query, KV store, time-travel, all through HTTP.
Every file is a URL. Read, download, hash, and browse files across local storage and 60+ cloud providers—Google Drive, Dropbox, S3, OneDrive, and more—through one consistent HTTP interface.
Every Hoody container includes hoody-files, providing unified access to files wherever they live.
hoody-files transforms storage into HTTP endpoints:
?zip parameterOfficial Technical Reference:
For complete endpoint documentation with all parameters, responses, and examples:
File Reading & Downloading:
backend, base64json, simple, backend, content-typeFile Integrity:
Archive Operations:
Directory Listing:
sort (name|size|mtime), order (asc|desc)Backend Management:
System Monitoring:
Open the container files URL in your browser for visual file management:
https://{project}-{container}-files.{server}.containers.hoody.icuInteractive web interface with:
Perfect for: Daily file management, quick edits, browsing cloud storage, reviewing logs, editing config files—all without leaving the browser.
Works on any device: Phone, tablet, laptop—same interface everywhere.
One API for all your storage:
# Container files URL: https://{project}-{container}-files.{server}.containers.hoody.icu
# Local container filescurl "https://{project}-{container}-files.{server}.containers.hoody.icu/documents/report.pdf"
# Google Drivecurl "https://{project}-{container}-files.{server}.containers.hoody.icu/api/v1/files/Work/report.pdf?backend=backend_drive_abc"
# Amazon S3curl "https://{project}-{container}-files.{server}.containers.hoody.icu/api/v1/files/backups/data.zip?backend=backend_s3_xyz"
# Dropboxcurl "https://{project}-{container}-files.{server}.containers.hoody.icu/api/v1/files/Photos/vacation.jpg?backend=backend_dropbox_123"Same URL pattern. Different storage. No complexity.
Connect storage providers once:
Response:
{ "id": "backend_drive_abc123", "type": "drive"}Now access all Drive files:
# List Drive rootcurl "https://{project}-{container}-files.{server}.containers.hoody.icu/?backend=backend_drive_abc123&json"
# Download Drive filecurl "https://{project}-{container}-files.{server}.containers.hoody.icu/Documents/report.pdf?backend=backend_drive_abc123" \ --output report.pdfMount persists across container restarts. Connect once, use forever.
Choose format for your use case:
# Interactive file browseropen "https://{project}-{container}-files.{server}.containers.hoody.icu/documents/"Interactive file browser with visual navigation, search, sortable columns, and upload interface (if permitted).
# Structured datacurl "https://{project}-{container}-files.{server}.containers.hoody.icu/documents/?json"{ "kind": "Index", "paths": [ { "name": "report.pdf", "path_type": "File", "size": 524288, "mtime": 1699564800000 } ]}# Clean text for scriptscurl "https://{project}-{container}-files.{server}.containers.hoody.icu/images/?simple"photo1.jpgphoto2.jpgvacation/One item per line. Directories end with /.
Ensure downloads are complete and uncorrupted:
# Container files URLFILES_URL="https://{project}-{container}-files.{server}.containers.hoody.icu"
# 1. Get expected hashexpected=$(curl -s "$FILES_URL/api/v1/files/large-file.bin?hash")
# 2. Download filecurl "$FILES_URL/api/v1/files/large-file.bin" -o large-file.bin
# 3. Verifyactual=$(sha256sum large-file.bin | awk '{print $1}')
if [ "$expected" = "$actual" ]; then echo "✓ Download verified"else echo "✗ Download corrupted"fiCritical for:
Archive preview without extraction:
curl "https://{project}-{container}-files.{server}.containers.hoody.icu/backup.tar.gz?preview"# Lists contents without downloading full archiveBase64 encoding for embedding:
curl "https://{project}-{container}-files.{server}.containers.hoody.icu/config.json?base64"# Returns: eyJrZXkiOiJ2YWx1ZSJ9Custom content types:
curl "https://{project}-{container}-files.{server}.containers.hoody.icu/data.txt?content-type=text/csv"# Forces download as CSVQuick directory download as zip:
# Download entire directory as .zip archivecurl "https://{project}-{container}-files.{server}.containers.hoody.icu/documents/?zip" \ -o documents.zip
# Download remote directory from cloud storagecurl "https://{project}-{container}-files.{server}.containers.hoody.icu/Work/?zip&backend=backend_drive" \ -o work-files.zipThe file service runs as root inside the container, but the files and folders it
creates are owned by your container user (user), not root — so the interactive
shell and your processes can read, edit, and delete everything the file manager makes.
This applies to every operation that creates a new inode — uploads, new folders
(?mkdir), touch, appends to a new file, archive extraction (every extracted entry),
copies, downloads (?download_from), and any parent directories created along the way.
Existing files keep their owner: overwriting, appending to, or moving an existing
file never changes who owns it.
Operator configuration (CLI flags / env on the file service):
| Flag | Env | Default | Meaning |
|---|---|---|---|
--default-create-owner <spec> | HOODY_FILE_MANAGER_DEFAULT_CREATE_OWNER | user | Owner for newly-created inodes. spec is user, user:group, uid, uid:gid, or none/off to disable (inherit the process owner = root). |
--allowed-create-owners <csv> | HOODY_FILE_MANAGER_ALLOWED_CREATE_OWNERS | (empty) | Owners a client owner= override may request (the default owner is always allowed). Resolved per request. |
Ownership is fail-closed: when the feature is active the service verifies it can
change ownership at startup, and if a per-operation ownership change unexpectedly fails
the request returns 500 and the partially-created file/dir is rolled back — a created
file is never silently left owned by root.
Every mutation is recorded. Every time a file is created, written, appended, deleted, moved, copied, or has its permissions changed, the journal captures it — along with a content-addressable blob snapshot of the file at that moment. This means you can read any file as it existed at any past revision, at any point in time, or compute diffs between any two versions.
The journal is best-effort: file operations always succeed even if journaling encounters an error. But under normal conditions, every mutation is captured, giving you a complete audit trail of your container’s filesystem.
See every revision of a file:
hoody files get src/app.ts --history --limit 50import { HoodyClient } from '@hoody-ai/hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://api.hoody.icu', token: process.env.HOODY_TOKEN });const containerClient = await client.withContainer({ id: CONTAINER_ID, project_id: PROJECT_ID, server: SERVER });
const history = await containerClient.files.get('src/app.ts', { history: '', limit: 50 });
for (const rev of history.data.revisions) { console.log(`#${rev.seq} [${rev.op}] ${rev.ts}`);}curl "https://{project}-{container}-files.{server}.containers.hoody.icu/api/v1/files/src/app.ts?history&limit=50"Retrieve the exact content of a file at any point in its history:
# By revision numberhoody files get src/app.ts --revision 3
# By timestamphoody files get src/app.ts --at "2026-03-19T14:30:00Z"// By revisionconst v3 = await containerClient.files.get('src/app.ts', { revision: 3 });
// By timestampconst yesterday = await containerClient.files.get('src/app.ts', { at: '2026-03-19T14:30:00Z' });# By revisioncurl "https://{project}-{container}-files.{server}.containers.hoody.icu/api/v1/files/src/app.ts?revision=3"
# By timestampcurl "https://{project}-{container}-files.{server}.containers.hoody.icu/api/v1/files/src/app.ts?at=2026-03-19T14:30:00Z"Compute a unified diff between any two versions of a file:
hoody files get src/app.ts --diff --from-seq 1 --to-seq 3const diff = await containerClient.files.get('src/app.ts', { diff: '', from_seq: 1, to_seq: 3 });console.log(diff);curl "https://{project}-{container}-files.{server}.containers.hoody.icu/api/v1/files/src/app.ts?diff&from_seq=1&to_seq=3"Search across all mutations in your container and monitor journal health:
# Query recent writes under src/hoody files journal query --path src/ --op write --limit 20
# View journal storage statisticshoody files journal stats// Query journal entriesconst entries = await containerClient.files.journal.query({ path: 'src/', op: 'write', limit: 20 });
// Get journal statsconst stats = await containerClient.files.journal.getStats();console.log(`${stats.data.total_entries} entries, ${stats.data.total_blobs} blobs`);# Query journalcurl "https://{project}-{container}-files.{server}.containers.hoody.icu/api/v1/journal?path=src/&op=write&limit=20"
# Journal statscurl "https://{project}-{container}-files.{server}.containers.hoody.icu/api/v1/journal/stats"Different tools for different storage:- AWS CLI for S3- Google Drive SDK- Dropbox API- SFTP client- WebDAV client
Each with different authentication, different SDKs, different patterns.Problems:
One HTTP API for everything:https://{project}-{container}-files.{server}.containers.hoody.icu/api/v1/files/{path}?backend={provider}Advantages:
Your phone can now access:
// From mobile browserawait fetch('https://{project}-{container}-files.{server}.containers.hoody.icu/documents/contract.pdf?backend=backend_drive') .then(r => r.blob()) .then(blob => { // View PDF on phone });No Google Drive app needed. No Dropbox app. No S3 client. Just HTTP.
Access files from multiple providers simultaneously:
// List all mounted backendsconst filesUrl = 'https://{project}-{container}-files.{server}.containers.hoody.icu';
const backends = await fetch(filesUrl + '/api/v1/backends').then(r => r.json());
// Access files from eachfor (const backend of backends.backends) { const files = await fetch(filesUrl + `/?backend=${backend.id}&json`) .then(r => r.json());
console.log(`${backend.type}: ${files.paths.length} files`);}Production-grade file downloads:
async function verifiedDownload(path, backend) { // 1. Get expected hash const hashResponse = await fetch( `/api/v1/files${path}?backend=${backend}&hash` ); const expectedHash = await hashResponse.text();
// 2. Download file const fileResponse = await fetch( `/api/v1/files${path}?backend=${backend}` ); const blob = await fileResponse.blob();
// 3. Verify hash (browser) const arrayBuffer = await blob.arrayBuffer(); const hashBuffer = await crypto.subtle.digest('SHA-256', arrayBuffer); const actualHash = Array.from(new Uint8Array(hashBuffer)) .map(b => b.toString(16).padStart(2, '0')) .join('');
if (actualHash === expectedHash) { console.log('✓ Download verified'); return blob; } else { throw new Error('Download corrupted - hash mismatch'); }}Ensure all local files exist in cloud backup:
import requests
files_url = 'https://{project}-{container}-files.{server}.containers.hoody.icu'
def verify_backup(local_path, backup_backend): # Get local listing local = requests.get(f'{files_url}{local_path}?json').json()
# Get backup listing backup = requests.get( f'{files_url}/api/v1/files{local_path}', params={'backend': backup_backend, 'json': ''} ).json()
local_files = {p['name']: p['size'] for p in local['paths'] if p['path_type'] == 'File'} backup_files = {p['name']: p['size'] for p in backup['paths'] if p['path_type'] == 'File'}
missing = set(local_files.keys()) - set(backup_files.keys()) size_mismatch = [ name for name in local_files if name in backup_files and local_files[name] != backup_files[name] ]
if not missing and not size_mismatch: print(f'✓ Backup complete: {len(local_files)} files verified') else: print(f'✗ Missing {len(missing)} files, {len(size_mismatch)} size mismatches')AI accesses files via HTTP:
// AI agent reads code fileconst filesUrl = 'https://{project}-{container}-files.{server}.containers.hoody.icu';
const code = await fetch( filesUrl + '/app/main.js?backend=backend_drive').then(r => r.text());
// AI analyzesconst analysis = await openai.chat.completions.create({ model: 'gpt-4', messages: [{ role: 'user', content: `Review this code:\n\n${code}` }]});
// AI can directly access your files from cloud storage// No downloading to local machine neededCompare local vs remote, sync differences:
#!/bin/bash
FILES_URL="https://{project}-{container}-files.{server}.containers.hoody.icu"
# Get local directorylocal=$(curl -s "$FILES_URL/documents/?json")
# Get remote directoryremote=$(curl -s "$FILES_URL/api/v1/files/documents/?backend=backend_s3&json")
# Compare and download missing filesecho "$remote" | jq -r '.paths[] | select(.path_type == "File") | .name' | \while read filename; do if ! echo "$local" | jq -e ".paths[] | select(.name == \"$filename\")" > /dev/null; then echo "Downloading: $filename" curl "$FILES_URL/documents/$filename?backend=backend_s3" \ -o "documents/$filename" fidoneStop installing provider-specific tools:
# Traditional: Different CLI for each provideraws s3 cp s3://bucket/file.pdf ./ # AWS CLIgcloud storage cp gs://bucket/file.pdf ./ # Google CLIaz storage blob download ... # Azure CLIrclone copy dropbox:file.pdf ./ # Rclone
# Hoody: One HTTP interfaceFILES_URL="https://{project}-{container}-files.{server}.containers.hoody.icu"
curl "$FILES_URL/file.pdf?backend=backend_s3" -o file.pdfcurl "$FILES_URL/file.pdf?backend=backend_gcs" -o file.pdfcurl "$FILES_URL/file.pdf?backend=backend_azure" -o file.pdfcurl "$FILES_URL/file.pdf?backend=backend_dropbox" -o file.pdfYour phone accesses ALL your storage:
// Phone browserconst filesUrl = 'https://{project}-{container}-files.{server}.containers.hoody.icu';
const file = await fetch( filesUrl + '/documents/contract.pdf?backend=backend_drive');const blob = await file.blob();
// View PDF directly in mobile browser// No app installation neededAccess Google Drive, S3, Dropbox—all from mobile browser. Because files are HTTP.
Embed file browsers in docs:
<!-- Live file browser in documentation --><iframe src="https://demo-files.hoody.icu/examples/?backend=backend_demo&readonly=true" height="400" />Users see actual files, not just descriptions.
Verify backups via HTTP:
#!/bin/bash# Nightly backup verificationFILES_URL="https://{project}-{container}-files.{server}.containers.hoody.icu"
for file in $(curl -s "$FILES_URL/critical/?simple"); do # Get hash from local local_hash=$(curl -s "$FILES_URL/critical/$file?hash")
# Get hash from S3 backup backup_hash=$(curl -s "$FILES_URL/api/v1/files/critical/$file?backend=backend_s3&hash")
if [ "$local_hash" != "$backup_hash" ]; then echo "⚠️ Backup mismatch: $file" # Re-upload to S3 via other tools or trigger alert fidoneAI organizes your files:
const filesUrl = 'https://{project}-{container}-files.{server}.containers.hoody.icu';
// AI agent lists filesconst files = await fetch(filesUrl + '/?backend=backend_drive&json') .then(r => r.json());
// AI analyzes and categorizesfor (const file of files.paths) { if (file.path_type === 'File') { const content = await fetch( filesUrl + `/${file.name}?backend=backend_drive` ).then(r => r.text());
// AI determines category const category = await ai.categorize(content);
// AI can move files, create folders, organize automatically }}Move files between cloud providers:
// Read from Google Driveconst file = await fetch( '/api/v1/files/document.pdf?backend=backend_drive').then(r => r.blob());
// Upload to S3 (via other tools/APIs)// Or use hoody-files to bridge providersConnect all providers at container creation:
const providers = [ { type: 'drive', credentials: googleCreds }, { type: 's3', credentials: awsCreds }, { type: 'dropbox', credentials: dropboxCreds }];
for (const provider of providers) { await fetch(`/api/v1/backends/${provider.type}`, { method: 'POST', body: JSON.stringify(provider.credentials) });}
// Now all storage accessible through one interfaceFor production data, system backups, or compliance:
hash=$(curl -s "$URL?hash")curl "$URL" -o fileecho "$hash file" | sha256sum -cMatch format to use case:
Listings change infrequently:
const cache = new Map();
async function getDirectory(path, backend, ttl = 60000) { const key = `${path}:${backend}`; const cached = cache.get(key);
if (cached && Date.now() - cached.time < ttl) { return cached.data; }
const data = await fetch(`${path}?backend=${backend}&json`) .then(r => r.json());
cache.set(key, { data, time: Date.now() }); return data;}Verify backends are healthy:
# Test connectioncurl "https://{project}-{container}-files.{server}.containers.hoody.icu/api/v1/backends/{backend_id}/test"
# Check if successful before bulk operationsCloud providers throttle API calls:
// Add delays between requestsfor (const file of files) { await downloadFile(file); await new Promise(r => setTimeout(r, 100)); // 100ms delay}
// Or use Cache backend to reduce provider API callsUnlimited. Mount Google Drive, S3, Dropbox, OneDrive, Box, and 50+ others all in one container. Each gets a unique backend ID. Access files from any provider through the same HTTP interface.
Yes! Backend configurations are stored in the container’s filesystem. After restart, all previously mounted storage is automatically reconnected. No need to re-authenticate on every restart.
Yes! Mount different Google Drive accounts, different S3 buckets, or different Dropbox folders—each as a separate backend with unique ID. Perfect for multi-tenant scenarios or managing multiple client accounts.
Local files: sub-millisecond access. Cloud files: 50-500ms depending on provider and distance. Use Cache backend to speed up frequently accessed remote files. Or sync important files locally.
Yes! AI makes standard HTTP requests to hoody-files endpoints. It can list directories, read files, verify hashes—all via HTTP. No provider-specific SDKs needed. AI understands HTTP natively.
Yes, but limited to local container storage or specific backends that support writing. Most cloud providers require OAuth scopes for write access. Check backend documentation for write capabilities.
Use container proxy permissions to control who can access files. Configure IP whitelist, password auth, or JWT validation. Files are private by default through cryptographic container URLs.
Absolutely! Open the hoody-files URL in your mobile browser. HTML format gives you a visual file browser. JSON format works for custom mobile apps. Your phone can now access Google Drive, S3, local container files—all through one interface.
File operations return 401 Unauthorized. Re-authenticate the backend with fresh credentials using POST /api/v1/backends/{type} with same backend configuration. The backend ID remains the same.
Problem: Cannot mount storage provider
Solutions:
Verify credentials are correct:
# Check OAuth tokens haven't expired# Verify API keys are valid# Ensure client_id/client_secret matchCheck network connectivity:
Problem: File exists but getting 404 response
Check:
Path is case-sensitive:
# ✅ Correct: /documents/file.pdfVerify file exists:
# List parent directorycurl "https://{project}-{container}-files.{server}.containers.hoody.icu/documents/?json"Backend ID correct:
# List all backendsGET /api/v1/backends# Use correct backend_idProblem: Downloaded file hash doesn’t match expected
Possible causes:
Solution:
# Use curl resume supportcurl -C - "$URL" -o file
# Verify againecho "$expected_hash file" | sha256sum -cProblem: Cloud provider returning too many requests error
Solutions:
Add delays between requests:
for (const file of files) { await fetch(fileUrl); await new Promise(r => setTimeout(r, 200)); // 200ms delay}Use Cache backend:
# Mount cache in front of providerPOST /api/v1/backends/cache{ "remote": "s3_backend:bucket", "chunk_size": "10M"}Batch operations when possible - Download multiple files in one session
Problem: Timeout or incomplete download for large files
Solutions:
Use curl with resume support:
curl -C - "https://{project}-{container}-files.{server}.containers.hoody.icu/large-file.bin" -o large-file.binCheck disk space before downloading:
size=$(curl -sI "$URL" | grep Content-Length | awk '{print $2}')available=$(df -P . | tail -1 | awk '{print $4}')# Ensure available > size before downloadingDownload in chunks if supported:
# Some backends support Range requestscurl -r 0-104857600 "$URL" > part1 # First 100MBcurl -r 104857601- "$URL" > part2 # Restcat part1 part2 > complete-fileExplore other data services:
SQLite
Serverless databases via HTTP—query, KV store, time-travel, all through HTTP.
Exec
Transform scripts into HTTP endpoints—your code becomes an API automatically.
cURL
Complex HTTP operations simplified—transform any REST API into a simple GET request.
Master file operations:
Every file is a URL.
Local and cloud unified.
Access from anywhere.
One HTTP interface for everything.
This is how files work in the HTTP era.