FileHutch
Documentation

Guides

Storage

FileHutch owns the control plane. The bytes live in a bucket — ours or yours — and your application never learns which.

Two ways in#

Managed. Create a project and upload immediately. Cloudflare R2 underneath, with an allowance included in your plan.

Bring your own bucket. Connect one under Storage → Connect a bucket. Credentials are encrypted at rest and never returned after creation. FileHutch verifies by writing and deleting a probe object before it will use the bucket.

Provider What it needs
Cloudflare R2 Account ID, keys
Amazon S3 Region, keys
Backblaze B2 Region, keys
Hetzner Object Storage Region (fsn1, nbg1, hel1), keys
DigitalOcean Spaces Region (nyc3, ams3, …), keys
Wasabi Region, keys
Tigris Keys
Google Cloud Storage HMAC keys, from Settings → Interoperability
MinIO Your server's URL, keys

Every one of them stores files, signs URLs and moves between the others. Named transforms render only on R2 behind a Cloudflare zone; see Transforms.

CORS#

Browsers PUT directly to your bucket, so it has to allow your origin. The connection page prints the exact JSON for your bucket; it looks like this.

JSON

[{ "AllowedOrigins": ["https://app.example.com"],
   "AllowedMethods": ["PUT", "GET", "HEAD"],
   "AllowedHeaders": ["Content-Type", "Content-MD5", "x-amz-*"],
   "ExposeHeaders": ["ETag"], "MaxAgeSeconds": 3600 }]

If a browser upload fails with a network error, this is almost always why.

Importing files already in your bucket#

Connecting a bucket you already use does not copy anything, and neither does importing from it. Storage → Import existing files lists every object under a prefix and registers each one as a file where it is: same bucket, same key, a new file ID. Run it again to pick up objects added since; the ones already imported are left alone and keep their IDs.

When it finishes, download the key-to-ID map (CSV) and backfill your own column from it: every object key under the prefix, with the file ID that now stands for it.

HTTP

POST /api/v1/storage_imports              { "storage_connection": "conn_…", "prefix": "avatars/",
                                            "visibility": "public", "policy": "avatars" }
GET  /api/v1/storage_imports/imp_…        # files_found, files_adopted, files_existing, files_failed
GET  /api/v1/storage_imports/imp_…/files  # { files: [{ key, file_id }], next_after }; pass after= for the next page
POST /api/v1/files/adopt                  { "storage_connection": "conn_…", "key": "avatars/42.png" }

files/adopt does one object and answers 201 when it is new, 200 when it was already a file, so a backfill that stops half-way can simply run again.

A policy given to an import sets the files' visibility and their named transforms. Its type and size limits guard new uploads and are not applied to what is already in the bucket. A file you deleted in FileHutch is not brought back by importing its key again.

Moving between buckets#

Storage → Move copies every ready file onto another connection, verifies each one, and repoints the record. File IDs do not change, so your application carries on without noticing. You can watch it fill in as it runs.

A move never deletes the source object. If something goes wrong, the originals are still there. Once you are satisfied, Storage offers to delete them — a separate, deliberate step that names how many objects it will remove and leaves your files exactly where they are now. Re-running a move picks up whatever is still elsewhere, so an interrupted move is safe to repeat.

HTTP

POST /api/v1/storage_migrations   { "target": "conn_…" }
GET  /api/v1/storage_migrations   # files_total, files_done, files_failed

Leaving#

GET /api/v1/manifest lists every ready file with its object key, checksum and metadata. With a bucket you own and that manifest, you have everything you need without asking us for anything.