Skip to content

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:

  • 🌐 Web File Manager - Visual file browser with built-in code editor—main entry point for interactive file management
  • 📖 Read Files - Stream content via HTTP from any mounted storage
  • ⬇️ Download Files - With progress tracking and integrity verification
  • ℹ️ Get Metadata - Size, type, modification time without downloading
  • 🔐 Verify Hashes - SHA256, MD5 integrity checking
  • 📁 List Directories - Browse with sorting, filtering, multiple formats
  • 🔗 Mount 60+ Providers - Google Drive, S3, Dropbox, SFTP, WebDAV, and more
  • 🌐 Multiple Formats - HTML browser, JSON API, simple text for scripts
  • 📦 Archive Preview - Inspect .tar.gz/.zip contents without extracting
  • 🗜️ Quick Archive Download - Download any directory as .zip with ?zip parameter
  • 🔄 Base64 Encoding - Embed files in JSON or data URLs

Official Technical Reference:

For complete endpoint documentation with all parameters, responses, and examples:

File Reading & Downloading:

  • GET /api/v1/files/{path} - Read/download file content
    • Query params: backend, base64
  • GET /{path} - Alternative endpoint with HTML/JSON/simple formats
    • Query params: json, simple, backend, content-type
  • HEAD /{path} - Get metadata without downloading
    • Returns: Content-Length, Content-Type, Last-Modified, ETag, Accept-Ranges

File Integrity:

Archive Operations:

  • GET /{path}?zip - Download directory as .zip archive
    • Recursively archives entire directory tree
    • Perfect for quick backups or file transfers

Directory Listing:

Backend Management:

System Monitoring:


Open the container files URL in your browser for visual file management:

https://{project}-{container}-files.{server}.containers.hoody.icu

Interactive web interface with:

  • 📁 Visual folder navigation - Click folders to browse
  • 🔍 Built-in search - Find files quickly
  • ↕️ Sortable columns - Sort by name, size, date
  • ✏️ Code editor - Edit text files directly in browser with syntax highlighting
  • 📤 Upload interface - Drag-and-drop file uploads (if permitted)
  • 📥 Download manager - Click to download files
  • 🗜️ Archive creation - Create .zip/.tar.gz directly
  • 👁️ File preview - View images, PDFs, text files inline

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:

Terminal window
# Container files URL: https://{project}-{container}-files.{server}.containers.hoody.icu
# Local container files
curl "https://{project}-{container}-files.{server}.containers.hoody.icu/documents/report.pdf"
# Google Drive
curl "https://{project}-{container}-files.{server}.containers.hoody.icu/api/v1/files/Work/report.pdf?backend=backend_drive_abc"
# Amazon S3
curl "https://{project}-{container}-files.{server}.containers.hoody.icu/api/v1/files/backups/data.zip?backend=backend_s3_xyz"
# Dropbox
curl "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:

POST Mount Google Drive storage backend
/api/v1/backends/drive
Click "Run" to execute the request

Response:

{
"id": "backend_drive_abc123",
"type": "drive"
}

Now access all Drive files:

Terminal window
# List Drive root
curl "https://{project}-{container}-files.{server}.containers.hoody.icu/?backend=backend_drive_abc123&json"
# Download Drive file
curl "https://{project}-{container}-files.{server}.containers.hoody.icu/Documents/report.pdf?backend=backend_drive_abc123" \
--output report.pdf

Mount persists across container restarts. Connect once, use forever.

Choose format for your use case:

Terminal window
# Interactive file browser
open "https://{project}-{container}-files.{server}.containers.hoody.icu/documents/"

Interactive file browser with visual navigation, search, sortable columns, and upload interface (if permitted).

Ensure downloads are complete and uncorrupted:

Terminal window
# Container files URL
FILES_URL="https://{project}-{container}-files.{server}.containers.hoody.icu"
# 1. Get expected hash
expected=$(curl -s "$FILES_URL/api/v1/files/large-file.bin?hash")
# 2. Download file
curl "$FILES_URL/api/v1/files/large-file.bin" -o large-file.bin
# 3. Verify
actual=$(sha256sum large-file.bin | awk '{print $1}')
if [ "$expected" = "$actual" ]; then
echo "✓ Download verified"
else
echo "✗ Download corrupted"
fi

Critical for:

  • Production deployments
  • Backup verification
  • Large file transfers
  • Compliance requirements

Archive preview without extraction:

Terminal window
curl "https://{project}-{container}-files.{server}.containers.hoody.icu/backup.tar.gz?preview"
# Lists contents without downloading full archive

Base64 encoding for embedding:

Terminal window
curl "https://{project}-{container}-files.{server}.containers.hoody.icu/config.json?base64"
# Returns: eyJrZXkiOiJ2YWx1ZSJ9

Custom content types:

Terminal window
curl "https://{project}-{container}-files.{server}.containers.hoody.icu/data.txt?content-type=text/csv"
# Forces download as CSV

Quick directory download as zip:

Terminal window
# Download entire directory as .zip archive
curl "https://{project}-{container}-files.{server}.containers.hoody.icu/documents/?zip" \
-o documents.zip
# Download remote directory from cloud storage
curl "https://{project}-{container}-files.{server}.containers.hoody.icu/Work/?zip&backend=backend_drive" \
-o work-files.zip

The 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):

FlagEnvDefaultMeaning
--default-create-owner <spec>HOODY_FILE_MANAGER_DEFAULT_CREATE_OWNERuserOwner 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:

Terminal window
hoody files get src/app.ts --history --limit 50

Retrieve the exact content of a file at any point in its history:

Terminal window
# By revision number
hoody files get src/app.ts --revision 3
# By timestamp
hoody files get src/app.ts --at "2026-03-19T14:30:00Z"

Compute a unified diff between any two versions of a file:

Terminal window
hoody files get src/app.ts --diff --from-seq 1 --to-seq 3

Search across all mutations in your container and monitor journal health:

Terminal window
# Query recent writes under src/
hoody files journal query --path src/ --op write --limit 20
# View journal storage statistics
hoody files 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:

  • Multiple tools to learn
  • Different APIs per provider
  • Complex authentication flows
  • No unified interface
  • AI can’t easily access (provider-specific SDKs)
One HTTP API for everything:
https://{project}-{container}-files.{server}.containers.hoody.icu/api/v1/files/{path}?backend={provider}

Advantages:

  • ✅ One interface for 60+ providers
  • ✅ Consistent authentication (mount once)
  • ✅ Same HTTP patterns everywhere
  • ✅ AI-native (standard HTTP requests)
  • ✅ Observable (all file access logged)
  • ✅ Embeddable (file browsers in iframes)
  • ✅ Script-friendly (simple text format)

Your phone can now access:

// From mobile browser
await 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 backends
const filesUrl = 'https://{project}-{container}-files.{server}.containers.hoody.icu';
const backends = await fetch(filesUrl + '/api/v1/backends').then(r => r.json());
// Access files from each
for (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 file
const 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 analyzes
const 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 needed

Compare local vs remote, sync differences:

#!/bin/bash
FILES_URL="https://{project}-{container}-files.{server}.containers.hoody.icu"
# Get local directory
local=$(curl -s "$FILES_URL/documents/?json")
# Get remote directory
remote=$(curl -s "$FILES_URL/api/v1/files/documents/?backend=backend_s3&json")
# Compare and download missing files
echo "$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"
fi
done

Stop installing provider-specific tools:

Terminal window
# Traditional: Different CLI for each provider
aws s3 cp s3://bucket/file.pdf ./ # AWS CLI
gcloud storage cp gs://bucket/file.pdf ./ # Google CLI
az storage blob download ... # Azure CLI
rclone copy dropbox:file.pdf ./ # Rclone
# Hoody: One HTTP interface
FILES_URL="https://{project}-{container}-files.{server}.containers.hoody.icu"
curl "$FILES_URL/file.pdf?backend=backend_s3" -o file.pdf
curl "$FILES_URL/file.pdf?backend=backend_gcs" -o file.pdf
curl "$FILES_URL/file.pdf?backend=backend_azure" -o file.pdf
curl "$FILES_URL/file.pdf?backend=backend_dropbox" -o file.pdf

Your phone accesses ALL your storage:

// Phone browser
const 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 needed

Access 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 verification
FILES_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
fi
done

AI organizes your files:

const filesUrl = 'https://{project}-{container}-files.{server}.containers.hoody.icu';
// AI agent lists files
const files = await fetch(filesUrl + '/?backend=backend_drive&json')
.then(r => r.json());
// AI analyzes and categorizes
for (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 Drive
const 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 providers

Connect 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 interface

For production data, system backups, or compliance:

Terminal window
hash=$(curl -s "$URL?hash")
curl "$URL" -o file
echo "$hash file" | sha256sum -c

Match format to use case:

  • HTML - Interactive browsing in browser
  • JSON - API integration, processing
  • Simple - Shell scripts, piping to other commands

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:

Terminal window
# Test connection
curl "https://{project}-{container}-files.{server}.containers.hoody.icu/api/v1/backends/{backend_id}/test"
# Check if successful before bulk operations

Cloud providers throttle API calls:

// Add delays between requests
for (const file of files) {
await downloadFile(file);
await new Promise(r => setTimeout(r, 100)); // 100ms delay
}
// Or use Cache backend to reduce provider API calls

How many cloud providers can I mount simultaneously?

Section titled “How many cloud providers can I mount simultaneously?”

Unlimited. 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.

Do mounted backends persist across container restarts?

Section titled “Do mounted backends persist across container restarts?”

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.

Can I mount the same provider multiple times?

Section titled “Can I mount the same provider multiple times?”

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.

What’s the performance difference between local and cloud files?

Section titled “What’s the performance difference between local and cloud files?”

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.

What happens if backend credentials expire?

Section titled “What happens if backend credentials expire?”

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:

  1. Verify credentials are correct:

    Terminal window
    # Check OAuth tokens haven't expired
    # Verify API keys are valid
    # Ensure client_id/client_secret match
  2. Check network connectivity:

GET Test backend connection
/api/v1/backends/{backend_id}/test
Click "Run" to execute the request
  1. Review provider documentation:
    • Google Drive requires OAuth with drive.readonly scope
    • S3 needs correct region and credentials
    • Dropbox tokens have app-specific permissions

Problem: File exists but getting 404 response

Check:

  1. Path is case-sensitive:

    /Documents/file.pdf
    # ✅ Correct: /documents/file.pdf
  2. Verify file exists:

    Terminal window
    # List parent directory
    curl "https://{project}-{container}-files.{server}.containers.hoody.icu/documents/?json"
  3. Backend ID correct:

    Terminal window
    # List all backends
    GET /api/v1/backends
    # Use correct backend_id

Problem: Downloaded file hash doesn’t match expected

Possible causes:

  1. Incomplete download - Re-download with resume support
  2. File modified during download - Re-download to get latest
  3. Network corruption - Use TCP retransmission, verify network
  4. Wrong hash algorithm - Ensure using SHA256

Solution:

Terminal window
# Use curl resume support
curl -C - "$URL" -o file
# Verify again
echo "$expected_hash file" | sha256sum -c

Problem: Cloud provider returning too many requests error

Solutions:

  1. Add delays between requests:

    for (const file of files) {
    await fetch(fileUrl);
    await new Promise(r => setTimeout(r, 200)); // 200ms delay
    }
  2. Use Cache backend:

    Terminal window
    # Mount cache in front of provider
    POST /api/v1/backends/cache
    {
    "remote": "s3_backend:bucket",
    "chunk_size": "10M"
    }
  3. Batch operations when possible - Download multiple files in one session

Problem: Timeout or incomplete download for large files

Solutions:

  1. Use curl with resume support:

    Terminal window
    curl -C - "https://{project}-{container}-files.{server}.containers.hoody.icu/large-file.bin" -o large-file.bin
  2. Check disk space before downloading:

    Terminal window
    size=$(curl -sI "$URL" | grep Content-Length | awk '{print $2}')
    available=$(df -P . | tail -1 | awk '{print $4}')
    # Ensure available > size before downloading
  3. Download in chunks if supported:

    Terminal window
    # Some backends support Range requests
    curl -r 0-104857600 "$URL" > part1 # First 100MB
    curl -r 104857601- "$URL" > part2 # Rest
    cat part1 part2 > complete-file

Explore other data services:

SQLite

Serverless databases via HTTP—query, KV store, time-travel, all through HTTP.

Explore SQLite →

Exec

Transform scripts into HTTP endpoints—your code becomes an API automatically.

Explore Exec →

cURL

Complex HTTP operations simplified—transform any REST API into a simple GET request.

Explore cURL →

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.