Skip to content
Hoody.com

hoody-files connects 49 remote storage backends, including Google Drive, Dropbox, and S3, through a single HTTP API. It is a hoody-kit service rather than a Foundation feature, but cloud mounting is documented here with the rest of container storage.

Every admitted backend is a network service. The container’s own filesystem is not one of them: hoody-files serves local files natively over the same API, and the backend allowlist deliberately excludes rclone’s filesystem and wrapping types so that a connected backend can never be re-pointed at the container’s disk.

Every storage backend hoody-files supports is both HTTP-accessible and POSIX-mountable. You can read Google Drive through the HTTP API, or mount it and treat it as a local directory.


Complete hoody-files documentation:

Overview:

Mounting cloud storage:

File operations:

Backend management:


hoody-files turns a cloud storage API into HTTP endpoints and into a mountable filesystem.

Google Drive API (OAuth, MIME types, pagination complexity)
↓
hoody-files abstraction
↓
┌─────────────────┐
│ HTTP API │ ← curl, fetch, any HTTP client
│ POSIX Mount │ ← ls, cp, cat, any filesystem tool
└─────────────────┘

Every backend is reachable two ways:

  • HTTP-accessible through a REST API, from any language.
  • POSIX-mountable through FUSE, so ls, cp, cat, and other standard tools work on it.

The file-access interface is the same for every backend.

Terminal window
# Instead of configuring each backend separately:
# - Google Drive API (OAuth, pagination, MIME types)
# - Dropbox API (cursors, batching, webhooks)
# - S3 API (buckets, keys, multipart uploads)
# ... and the rest
# One unified HTTP API:
GET /api/v1/files/{path}?backend={backend_id}
# Works identically for Google Drive, S3, Dropbox, OneDrive, etc.

The full backend list is further down this page.


Prerequisite: OAuth credentials. OAuth-backed providers (Google Drive, Dropbox, OneDrive, Box, and so on) accept a client_id / client_secret from the provider console plus a token JSON blob from an OAuth consent flow. The client id and secret can be left out for providers that offer a shared client, but they do not replace authorization: you still supply a token (or another supported credential). Google Drive’s shared client is being retired during 2026, so use your own client there; for other providers, your own client is also recommended for full quota. The CLI is not interactive; pass the values as flags. For your own OAuth client, run hoody files backends drive create -c <container-id> --client-id <id> --client-secret <secret>. To supply a pre-obtained provider token, add --token to that same command: hoody files backends drive create -c <container-id> --token '{"access_token":"ya29..."}'. Keep it after the subcommand, where it names the provider’s token; a --token typed before the subcommand is the Hoody API bearer instead, and sending a provider token there hands it to the wrong host. The CLI prints only a success message and does not return the backend id, so run hoody files backends list -c <container-id> -o json afterwards to read it. Key/secret backends (S3, B2, etc.) need the provider’s access-key pair instead.

Connect a storage provider. The connection persists, so you configure a provider once and reuse it:

Terminal window
curl -X POST "https://$PROJECT-$CONTAINER-files-1.$SERVER.containers.hoody.com/api/v1/backends/drive" \
-H "Content-Type: application/json" \
-d '{
"client_id": "your-app.apps.googleusercontent.com",
"client_secret": "your-secret",
"token": "{\"access_token\":\"ya29...\"}"
}'
# 201 Response: {"success": true, "message": "drive backend connected successfully", "data": {"id": "9b4d3e2f5a6c7890", "type": "drive", "vfs_backend_type": "drive", "config": {"client_id": "***", "client_secret": "***", "token": "***"}, "mount_paths": []}}

Step 2: Access files through the unified API

Section titled “Step 2: Access files through the unified API”

Every connected backend answers the same requests, whether it is Google Drive, S3, or SFTP:

Terminal window
# Read file from any connected backend
hoody files get "Documents/report.pdf" --backend 9b4d3e2f5a6c7890 -c <container-id>
# List directory on a backend (get lists directory paths too)
hoody files get "my-bucket/Documents/" --backend 1a2b3c4d5e6f7080 -c <container-id>

All 49 admitted backend types

Major cloud storage (9)

  • Google Drive - OAuth, service accounts, team drives
  • Dropbox - OAuth, batch operations, business accounts
  • Microsoft OneDrive - Personal, Business, SharePoint
  • Box - OAuth, or a developer access token
  • pCloud - EU/US data centers
  • MEGA - Username/password, encryption
  • Yandex Disk - OAuth, hard delete
  • Mail.ru Cloud - App passwords, speedup feature
  • Jottacloud - OAuth, version control

Object storage (10)

  • Amazon S3 / S3-compatible - AWS native plus MinIO, DigitalOcean Spaces, Cloudflare R2, Wasabi, Alibaba Cloud OSS (single s3 backend, provider selects the flavor)
  • Azure Blob Storage - Hot/cool/archive tiers
  • Azure Files - SMB in cloud
  • Google Cloud Storage - Standard/nearline/coldline/archive
  • Backblaze B2 - Cost-effective, CDN integration
  • Oracle Object Storage - OCI integration
  • OpenStack Swift - OpenStack deployments
  • QingStor - Asian provider
  • Internet Archive - archive.org S3-compatible
  • Sia - Decentralized blockchain storage

File protocols (5)

  • SFTP/SSH - Secure file transfer
  • FTP/FTPS - Classic (with TLS option)
  • WebDAV - Nextcloud/ownCloud compatible
  • SMB/CIFS - Windows shares, NAS
  • HTTP/HTTPS - Read-only web servers

File sharing services (8)

  • 1Fichier - French file sharing
  • Gofile - Anonymous & authenticated
  • Pixeldrain - Direct file sharing
  • Put.io - Download completion
  • Linkbox - linkbox.to integration
  • premiumize.me - Premium links
  • SugarSync - Cloud sync
  • PikPak - Cloud download manager

Enterprise services (7)

  • Citrix ShareFile - Enterprise sharing
  • Files.com - Managed file transfer
  • Enterprise File Fabric - Multi-cloud
  • Koofr - European (Digi Storage)
  • HiDrive - German cloud
  • Seafile - Open-source enterprise
  • Quatrix - Enterprise collaboration

Specialty services (6)

  • Google Photos - Photo/video management
  • Cloudinary - Image/video CDN
  • ImageKit.io - Image optimization
  • Proton Drive - Zero-knowledge encryption
  • iCloud Drive - Apple ecosystem
  • Zoho WorkDrive - Workspace integration

Legacy and specialized (4)

  • Hadoop HDFS - Big data
  • OpenDrive - Cloud storage
  • Akamai NetStorage - CDN storage
  • Ulož.to - Czech file sharing

Total: 49 backend types

Storj is not available: connecting it returns 400. A saved Storj backend is kept but not connected, and its mounts come back with status error; both can be deleted.

Git repositories are not supported as remote backends, and ?type=git requests are rejected.

rclone ships filesystem, caching, and wrapping types that hoody-files refuses to instantiate: local, alias, cache, chunker, combine, compress, crypt, hasher, memory, and union. Every one of them either reads and writes the container filesystem directly or re-points at another remote, which takes the backend outside the scope hoody-files is meant to serve. A request naming one is rejected before any connection is made or reused.

That is a limit on rclone backends, not on local file access. The container’s own filesystem is served natively by hoody-files over the same HTTP API, without a backend at all: see Container Storage.

Connection parameters are filtered too. A backend’s config cannot carry keys that make rclone read a local credential source or pivot to ambient host credentials, so credential-file options such as service_account_file, box_config_file, key_file, config_file and shared_credentials_file are rejected, and so are ambient-auth flags such as env_auth when enabled (false is accepted). Pass credentials by value instead. Keys that are not plain option names (such as global.*), rclone’s config_* setup keys, Kerberos settings (kerberos_ccache, service_principal_name, and use_kerberos when true), Azure connection_string and WebDAV unix_socket are refused as well, and every value must be a string, number, boolean or null.

For detailed configuration parameters, see:


Without an abstraction layer, an application implements each provider’s own API:

// Google Drive: OAuth, MIME types, fileId abstraction
const drive = google.drive({version: 'v3', auth});
const file = await drive.files.get({fileId: 'abc123', alt: 'media'});
// Dropbox: different auth, different structure
const dbx = new Dropbox({accessToken: TOKEN});
const file = await dbx.filesDownload({path: '/folder/file.pdf'});
// S3: buckets, keys, and regions instead of paths
const s3 = new AWS.S3();
const file = await s3.getObject({Bucket: 'my-bucket', Key: 'folder/file.pdf'}).promise();
// Without the unified layer, provider and backend integrations require separate implementations

hoody-files collapses those into one request shape:

Terminal window
# Same API for every backend - just change backend parameter
curl "https://$PROJECT-$CONTAINER-files-1.$SERVER.containers.hoody.com/api/v1/files/folder/file.pdf?backend={backend_id}"
# Works for Google Drive, Dropbox, S3, OneDrive, Box, SFTP, WebDAV, etc.

You learn one file API for every backend. Application code names a backend id rather than a provider SDK, so moving from S3 to Dropbox changes the backend id and the path layout (S3 paths start with the bucket name). hoody-files translates each HTTP call into the provider’s own protocol and returns errors as an HTTP status with a message. Which status you see depends on where the call failed (for example 502 for a connection failure, 404 when the path is not found).


Connected backends share the same API, but the operations available depend on the provider and its credentials. HTTP/HTTPS backends are read-only; uploads, deletes, and directory creation are refused.

Read files

Read file contents over HTTP:

Terminal window
GET /api/v1/files/{path}?backend={id}

This works for text, binary, and any other file type.

Verify a file hash

Get a file’s SHA256 hash as JSON ({"hash": "<hex digest>", "algorithm": "sha256"}):

Terminal window
GET /api/v1/files/{path}?backend={id}&hash

Compare the hash field against your local digest to check integrity.

List directories

Browse directories as JSON:

Terminal window
GET /api/v1/files/{path}/?backend={id}

Remote listings come back unsorted and unfiltered; sort and filter the returned entries in your application.

Read file metadata

Remote ?backend= requests do not support &stat, and sending it downloads the file. List the parent directory instead and read each entry’s size, directory flag, and modified time:

Terminal window
GET /api/v1/files/{parent-path}/?backend={id}

See the Hoody Files API for the complete set of operations.


The three requests below connect the backend, list the root directory, and read a file. After that, every file in the account is reachable over HTTP.

Terminal window
# Connect Google Drive
curl -X POST "https://$PROJECT-$CONTAINER-files-1.$SERVER.containers.hoody.com/api/v1/backends/drive" \
-H "Content-Type: application/json" \
-d '{
"client_id": "your-app.apps.googleusercontent.com",
"client_secret": "your-client-secret",
"token": "{\"access_token\":\"ya29.a0...\"}"
}'
# Response includes backend ID
{
"success": true,
"message": "drive backend connected successfully",
"data": {
"id": "9b4d3e2f5a6c7890",
"type": "drive",
"vfs_backend_type": "drive",
"config": {"client_id": "***", "client_secret": "***", "token": "***"},
"mount_paths": []
}
}

One application can read from several providers at once. This pattern covers backup verification, data migration, and multi-cloud setups:

Terminal window
# Connect all your storage
POST /api/v1/backends/drive → backend_drive_abc
POST /api/v1/backends/dropbox → backend_dropbox_xyz
POST /api/v1/backends/s3 → backend_s3_def
# Now access any file from any backend
GET /api/v1/files/project-data.json?backend=backend_drive_abc
GET /api/v1/files/project-data.json?backend=backend_dropbox_xyz
GET /api/v1/files/my-bucket/project-data.json?backend=backend_s3_def
# Same API, different storage providers

A container can push its own backups to a cloud provider:

Terminal window
# Upload container backup to Google Drive
tar czf backup.tar.gz /hoody/storage/myapp/
curl -X PUT "https://$PROJECT-$CONTAINER-files-1.$SERVER.containers.hoody.com/api/v1/files/Backups/backup.tar.gz?backend=backend_drive_abc" \
--data-binary "@backup.tar.gz"
# Hoody Files handles OAuth, chunking, retries

Read from the cloud, process in the container, write the results back:

Terminal window
# Download dataset from S3
curl "https://$PROJECT-$CONTAINER-files-1.$SERVER.containers.hoody.com/api/v1/files/my-bucket/datasets/data.csv?backend=backend_s3_abc" > data.csv
# Process locally
python process.py data.csv > results.json
# Upload results to Dropbox
curl -X PUT "https://$PROJECT-$CONTAINER-files-1.$SERVER.containers.hoody.com/api/v1/files/Results/results.json?backend=backend_dropbox_xyz" \
--data-binary "@results.json"
# No cloud SDK involved; the transfers are plain HTTP

Cloud storage files can be exposed through a container URL:

Terminal window
# Connect S3 (the bucket is addressed in the path, e.g. my-public-files/images/logo.png)
POST /api/v1/backends/s3 {"provider": "AWS", "access_key_id": "AKIA...", "secret_access_key": "...", "region": "us-east-1", "endpoint": "s3.us-east-1.amazonaws.com"}
# Now serve files via Hoody Proxy
GET https://{project}-{container}-files.../api/v1/files/my-public-files/images/logo.png?backend=backend_s3_abc
# S3 objects are now served over HTTP

Consumer cloud services that authenticate with OAuth:

  • Google Drive (team drives, service accounts)
  • Dropbox (batch operations, business accounts)
  • OneDrive (personal, business, SharePoint)
  • Box (OAuth, or a developer access token)
  • pCloud (EU/US data centers)

See the Cloud Storage API for mounting instructions.

Object storage backends include:

  • AWS S3 (original)
  • Wasabi (cheaper than S3)
  • Backblaze B2 (affordable, fast)
  • DigitalOcean Spaces
  • Cloudflare R2 (zero egress fees)
  • Azure Blob Storage
  • Google Cloud Storage
  • Alibaba Cloud OSS

See the Object Storage API for provider-specific configuration. AWS and S3-compatible stores use the s3 backend with a provider value; Azure Blob, Google Cloud Storage, and Backblaze B2 have dedicated backends of their own.

Servers reachable over standard file protocols:

  • SFTP (secure file transfer over SSH)
  • FTP/FTPS (legacy file transfer)
  • WebDAV (Nextcloud, ownCloud, HTTP-based)
  • SMB/CIFS (Windows network shares, NAS devices)
  • HTTP/HTTPS (web servers, static file hosting)

See the File Protocols API for protocol mounting.


The endpoint and query parameters are the same for every provider. Remote paths follow the provider’s own layout; for example, S3 paths begin with the bucket name.

Terminal window
# List a directory (identical for every backend)
hoody files get "folder/" --backend $BACKEND_ID -c <container-id>
# Read a file (identical for every backend)
hoody files get "folder/file.txt" --backend $BACKEND_ID -c <container-id>

Switching provider changes the backend id and the provider-specific path:

// Change from S3 to Dropbox
let backend = 'backend_s3_abc';
let path = 'my-bucket/data.json'; // S3 paths start with the bucket name
backend = 'backend_dropbox_xyz';
path = 'data.json';
const file = await fetch(`/api/v1/files/${path}?backend=${backend}`);

A container can hold several backends at the same time. Active shared connections are capped at 100 in total and 10 per remote host by default (for cloud backends without a host setting, the per-host cap applies per backend type). Connecting past the cap is refused:

Terminal window
# Personal Google Drive
backend_drive_personal
# Work Google Drive
backend_drive_work
# AWS S3 production
backend_s3_prod
# AWS S3 backups
backend_s3_backup
# Dropbox archive
backend_dropbox_archive
# All accessible from one container via hoody-files

The backend can be a runtime choice rather than a build-time one:

// User chooses storage provider
const backend = userPreference; // 'backend_drive_abc' or 'backend_s3_xyz'
// App code doesn't care which provider
async function saveFile(path, content, backend) {
return fetch(`/api/v1/files/${path}?backend=${backend}`, {
method: 'PUT',
body: content
});
}
// Works with Google Drive, S3, Dropbox, OneDrive, etc.

Copy a file between two providers. Run these commands inside the container: the file is downloaded to a temporary copy there and uploaded from it to the destination provider. The source file stays in place.

Terminal window
# Read from Google Drive
curl "https://$PROJECT-$CONTAINER-files-1.$SERVER.containers.hoody.com/api/v1/files/backup.zip?backend=backend_drive_abc" \
> backup.zip
# Upload to S3
curl -X PUT "https://$PROJECT-$CONTAINER-files-1.$SERVER.containers.hoody.com/api/v1/files/my-bucket/backup.zip?backend=backend_s3_xyz" \
--data-binary "@backup.zip"
# Both transfers use the container's bandwidth, not your own machine's
rm backup.zip

Write the same backup to several providers for redundancy:

Terminal window
# Create backup
tar czf critical-data.tar.gz /hoody/storage/production/
# Upload to 3 different cloud providers
# Each destination has its own bucket (S3, GCS, B2 paths start with the bucket name)
for destination in \
"backend_s3_primary:my-s3-bucket/Backups/critical-data.tar.gz" \
"backend_gcs_backup:my-gcs-bucket/Backups/critical-data.tar.gz" \
"backend_backblaze_archive:my-b2-bucket/Backups/critical-data.tar.gz"; do
backend=${destination%%:*}
path=${destination#*:}
curl --fail-with-body -X PUT "https://$PROJECT-$CONTAINER-files-1.$SERVER.containers.hoody.com/api/v1/files/$path?backend=$backend" \
--data-binary "@critical-data.tar.gz"
done
# Same data in 3 locations (AWS, Google, Backblaze)
# A successful PUT means the file was accepted. For a cached backend the upload to
# the provider can finish afterwards, and a failure there is not reported in the response.

The same three stages written out step by step:

Terminal window
# 1. Download dataset from Dropbox
curl "https://$PROJECT-$CONTAINER-files-1.$SERVER.containers.hoody.com/api/v1/files/datasets/raw-data.csv?backend=backend_dropbox_abc" \
> raw-data.csv
# 2. Process in container
python analyze.py raw-data.csv > analysis-results.json
# 3. Upload results to Google Drive
curl -X PUT "https://$PROJECT-$CONTAINER-files-1.$SERVER.containers.hoody.com/api/v1/files/Results/analysis-results.json?backend=backend_drive_xyz" \
--data-binary "@analysis-results.json"
# Cloud → Container → Cloud (all via HTTP)

Consumer OAuth requires a person to complete the consent flow. Service account credentials do not expire with a user session, so scheduled tasks keep working.

Pass the credential by value, not by path. Config keys that point rclone at a file on disk are rejected, service_account_file and box_config_file among them, because a backend must never be able to read the container’s filesystem for credentials. Enabled env_auth and the other flags that pivot to ambient host credentials are rejected too.

Terminal window
# Google Drive: service account, with the key material sent inline
{
"service_account_credentials": "{\"type\":\"service_account\", ...}",
"team_drive": "0ABC123xyz"
}

Cloud Storage API lists the parameter each provider accepts for an inline credential.

Authorize the credential for read-only access when you obtain its OAuth token, then connect with that token. For Google Drive, request only the drive.readonly scope in the consent flow:

Terminal window
# Google Drive: a token issued for drive.readonly only
{
"token": "{\"access_token\":\"ya29...\",\"token_type\":\"Bearer\"}"
}
# Prevents accidental modifications
# Limits damage if credentials compromised

Setting scope on the connect request does not remove permissions from a token that was already issued with more access.

A backend connection survives container restarts:

Terminal window
# Connect backend once
POST /api/v1/backends/drive → backend_drive_abc
# Use in all future requests
GET /api/v1/files/data.json?backend=backend_drive_abc
# The saved backend configuration stays until you delete it.
# Idle connections can be released and are recreated on the next request.
Terminal window
# Verify connection works
GET /api/v1/files/?backend=backend_drive_abc
# Should return directory listing
# If error: OAuth expired, credentials wrong, or network issue

See Managing Backends for testing and troubleshooting.

Encrypt sensitive data before it leaves the container

Section titled “Encrypt sensitive data before it leaves the container”

rclone’s crypt wrapper is not an admitted backend type, and neither are the other wrapping types, so there is no POST /api/v1/backends/crypt. Encrypt the file in the container and upload the ciphertext, or use a provider that encrypts on its own side, such as Proton Drive.


Does hoody-files download a whole file first?

Section titled “Does hoody-files download a whole file first?”

No. A download through /api/v1/files/{path}?backend={id} streams the file as it arrives from the provider, so memory use stays small whatever the file size. A single byte range (Range: bytes=...) answers 206 Partial Content with just those bytes. A request whose ranges leave more than one that fits inside the file gets the whole file (200). So does one sent with If-Range, because a cloud file’s ETag is weak and can’t prove the file is unchanged. ?hash reads the whole file before it answers, and ?base64 holds the file in memory and accepts files up to 50 MiB.

Can I use hoody-files without mounting cloud storage?

Section titled “Can I use hoody-files without mounting cloud storage?”

Yes. hoody-files also provides:

  • Local filesystem access to the container’s own files, over the same HTTP API.
  • WebDAV server for local mounting. SFTP mounting is provided separately, by the Hoody API’s SSH proxy.

Cloud mounting is optional. hoody-files works the same way for local storage.

Requests through the backend usually fail with a backend connection error (502) or a not-found error (404) rather than a 401, so check the error message. If the token includes refresh credentials, access tokens can refresh automatically. Otherwise, replace the token with PUT /api/v1/backends/{id} once no mount is using the backend, or disconnect and reconnect it (disconnecting also removes the backend’s mounts):

Terminal window
# Disconnect expired backend
DELETE /api/v1/backends/backend_drive_abc
# Reconnect with fresh OAuth token
POST /api/v1/backends/drive
{"client_id": "...", "client_secret": "...", "token": "{\"access_token\":\"NEW_TOKEN\"}"}

Can I connect the same provider more than once?

Section titled “Can I connect the same provider more than once?”

Yes. Personal and work Google Drive accounts can both be connected:

Terminal window
POST /api/v1/backends/drive {"token": "{\"access_token\":\"PERSONAL_ACCESS_TOKEN\",\"token_type\":\"Bearer\"}"} → backend_drive_personal
POST /api/v1/backends/drive {"token": "{\"access_token\":\"WORK_ACCESS_TOKEN\",\"token_type\":\"Bearer\"}"} → backend_drive_work
# Access both simultaneously
GET /api/v1/files/data.json?backend=backend_drive_personal
GET /api/v1/files/data.json?backend=backend_drive_work

Yes, with HTTP PUT. Send the file body to PUT /api/v1/files/{path}?backend={id}. The root path /{path} does not take ?backend=; it addresses the local filesystem or a one-off remote given by server and type parameters. See Hoody Files API for upload endpoints.

What’s the performance like compared to native SDKs?

Section titled “What’s the performance like compared to native SDKs?”

Transfers: run over the provider’s own transport, so the transfer itself is close to native speed. A remote read starts responding as soon as the first bytes arrive from the provider. Some backends (notably Google Drive) use HTTP/1.1 rather than HTTP/2 by default, which adds a little per-request overhead. Other backends keep HTTP/2 enabled.

Batch operations: slightly slower, because each request carries HTTP overhead.

High throughput: run the cloud provider’s own SDK inside the container. hoody-files trades maximum throughput for a single interface.


Problem: POST /api/v1/backends/{provider} returns error

Solutions:

  1. OAuth token issues:

    • Verify token is valid and not expired
    • Check OAuth scopes include required permissions
    • Regenerate token if necessary
  2. Credential errors:

    • S3: Verify access_key_id and secret_access_key
    • SFTP: Check SSH key or password
    • Test credentials with provider’s native tools first
  3. Network connectivity:

    Terminal window
    # Test from container
    curl https://www.googleapis.com # Google Drive
    curl https://api.dropboxapi.com # Dropbox
    # Should return response (not timeout)

Problem: GET /api/v1/files/?backend={id} returns empty or incomplete

Check:

  1. Path is correct:

    Terminal window
    # Root directory
    GET /api/v1/files/?backend={id}
    # Specific folder
    GET /api/v1/files/Documents/?backend={id}
  2. Backend has data:

    • Log into cloud provider’s web interface
    • Verify files exist in expected location
  3. Permissions:

    • OAuth scopes allow listing
    • Service account has access to folder/bucket

Problem: the cloud provider is throttling requests. This does not always arrive as HTTP 429, so check the error message as well as the status.

Solutions:

  1. Implement exponential backoff:

    async function fetchWithRetry(url, retries = 3) {
    for (let i = 0; i < retries; i++) {
    const response = await fetch(url);
    if (response.status < 500 && response.status !== 429) return response;
    await sleep(Math.pow(2, i) * 1000); // 1s, 2s, 4s
    }
    }
  2. Cache in your own code. rclone’s cache backend is not admitted, so there is no POST /api/v1/backends/cache. Hold directory listings and hot files in the container and re-read them from there.

  3. Reduce request frequency:

    • Batch operations where possible
    • Cache directory listings
    • Use webhooks instead of polling

Storage pages:

hoody-files reference: