Inspiration
Generative AI tools are everywhere, but the output is treated as disposable. You type a prompt, generate an image, download it, and the story behind it disappears. A year later you're staring at download (7).png with no idea which model, prompt, seed, or settings produced it.
That struck us as a storage problem more than an AI problem. A generation is a deterministic function of its parameters. If those parameters are preserved, the result is reproducible. Throw them away and you've turned a recipe into an artifact. As synthetic media becomes ubiquitous, provenance is no longer a luxury—it is becoming infrastructure, with initiatives such as the EU AI Act and C2PA moving in that direction.
We built GenMedia: Multi-Modal B2 Studio to solve exactly that problem. Rather than another prompt box, we built a studio with a memory. Backblaze B2's S3-compatible API and generous free tier made it the perfect storage backbone.
What it does
GenMedia is a full-stack platform for generating images, videos, and audio, where every generated asset is stored together with its complete provenance.
The application consists of four primary experiences:
- Home
- Studio (
/generate) - Media Vault (
/gallery) - Settings
Key Features
Provenance-first storage – Every generation stores two objects in Backblaze B2:
- The generated asset
- A metadata sidecar JSON containing the prompt, negative prompt, provider, model, seed, guidance scale, timestamps, and generation parameters.
Backblaze B2 as the database – User accounts are stored as:
users/{md5(email)}.json
No PostgreSQL, MongoDB, or Redis is required.
- Human-readable filenames
"a neon city at dusk"
↓
a_neon_city_at_dusk_f3a91c.png
Complete asset lifecycle
- Generate
- List
- Stream
- Rename
- Delete
Private-by-default storage – Backend media proxying keeps the bucket private without exposing public ACLs.
The storage invariant is:
outputs/{user}/{file}
⇔
metadata/{user}/{file}.json
Every operation preserves this one-to-one relationship.
How we built it
Frontend
- React 18
- Vite
- Tailwind CSS
- React Router v6
- Axios
A centralized API client handles authentication, routing, and consistent error handling.
Backend
- FastAPI
- Uvicorn
- Pydantic v2
- Boto3
Architecture
api/routes/{auth,generate,storage,health}.py
↓
services/{genblaze,b2,metadata,auth}_service.py
↓
models/{media,request_models}.py
↓
core/{config,constants,logger}.py
Services are implemented as import-time singletons so expensive clients (such as the B2 connection) are initialized once and reused.
Generation supports multiple providers including:
- GMI Cloud (Flux Schnell)
- OpenAI DALL·E 3
- Runway
- ElevenLabs
The application first attempts the Genblaze SDK and gracefully falls back to direct HTTP APIs when necessary.
To improve reliability, downloads are attempted across multiple CDN mirrors. With independent mirror failures, the overall failure probability decreases dramatically because the pipeline only fails when every mirror fails.
Deployment targets both:
- Vercel (React + FastAPI Serverless)
- Google Cloud Run (Docker)
Challenges we ran into
Localhost in production
Our most frustrating bug wasn't in AI—it was configuration.
After deployment, login requests failed with a generic Network Error. Browser inspection revealed that the frontend was attempting to call:
http://localhost:8000/api/v1/auth/login
instead of the production backend.
The cause was copying .env.example directly into production. Since Vite replaces VITE_* variables during the build process, the localhost URL became permanently embedded in the shipped JavaScript bundle.
Switching from an explicit !== undefined check to a simpler || fallback made the configuration significantly safer.
Serving a SPA and an API together
Initially, requests to / returned FastAPI JSON instead of the React application because routing precedence was incorrect.
We also discovered that our OpenAPI schema path had drifted, causing /docs to fail until the deployment rewrites were corrected.
Serverless execution
FastAPI applications running on serverless platforms are imported rather than executed directly.
That means code inside:
if __name__ == "__main__":
never executes.
Learning this explained why local development behaved differently from production.
Synchronizing metadata
Every generated asset consists of two independent objects:
- Media
- Metadata
Operations like rename therefore become distributed transactions:
copy asset
copy metadata
delete asset
delete metadata
Without cross-object atomicity, failures can orphan metadata, so preserving this invariant became central to the storage layer.
Accomplishments we're proud of
- Zero-database architecture powered entirely by Backblaze B2.
- Complete provenance stored alongside every generated asset.
- Graceful degradation across storage, AI providers, and CDN mirrors.
- Single codebase deployable to both serverless and container environments.
- Private-by-default media delivery through backend proxying.
What we learned
The biggest lesson was understanding the difference between build-time and runtime configuration.
Frontend VITE_* variables are baked into the JavaScript bundle during compilation, while backend variables are evaluated at application startup. That distinction explained our most difficult production issue.
We also learned that:
.env.examplefiles are documentation—not production configuration.- Object storage is far more capable than we originally assumed.
- Health checks should validate real services rather than simply checking whether environment variables exist.
- Simpler configuration fallbacks are often safer than clever conditional logic.
What's next
We're excited to continue evolving GenMedia with:
- bcrypt/Argon2 authentication and JWT sessions
- C2PA Content Credentials for cryptographically verifiable provenance
- Presigned Backblaze B2 URLs backed by a CDN
- Asynchronous media generation with job queues
- Semantic search across provenance metadata using embeddings
- One-click Reproduce This Asset to regenerate media directly from stored metadata or create variations by changing only the seed
GenMedia demonstrates that provenance is not an afterthought—it is a first-class feature. By combining multi-modal AI generation with durable, low-cost object storage, we've built a platform where every generated asset retains the information needed to understand, audit, reproduce, and evolve it long after creation.
Built With
- elevenlabs
- fastapi
- genblaze
- openai
- react
- runway
- s3-compatible)-ai-orchestration:-genblaze-sdk-(open-source-python-sdk-by-backblaze-for-gmi-cloud
- tailwind
- vite
Log in or sign up for Devpost to join the conversation.