Rendering on a server
Run Fluvie behind an HTTP API. You POST a video, the server renders it, and you get back a download URL. Use it as a render backend for a web app, an online editor, or a batch pipeline.
Quick start
Section titled “Quick start”cp deploy/env/server.env.example deploy/env/server.env # set API_TOKEN and CLEANUP_TOKENdocker compose -f deploy/docker-compose.yml up --buildSubmit a render and poll until it is done:
# Create a render of the bundled "demo" composition.curl -s -X POST http://localhost:8080/v1/renders \ -H "Authorization: Bearer $API_TOKEN" \ -H "content-type: application/json" \ -d '{"key":"demo"}'# => {"id":"rnd_...","status":"queued","expiresAt":"..."}
# Poll the job.curl -s http://localhost:8080/v1/renders/rnd_... \ -H "Authorization: Bearer $API_TOKEN"# => {"id":"rnd_...","status":"succeeded","code":"Video build() { ... }","spec":{"fluvieSpec":1,"scenes":[...]},"video":{"downloadUrl":"http://localhost:8080/v1/files/rnd_.../video?token=..."}}
# Download the file.curl -L -o demo.mp4 "<the downloadUrl from above>"For AI prompt and edit renders, code (the printed Dart Video build()) and
spec (the authored VideoSpec JSON) appear early, while status is still
running, so an editor can show the authored source before the file is ready.
The package is fluvie_server: one binary
that also hosts the MCP server at /mcp and a documentation helper
at /v1/docs, each toggled by an environment variable. The server is
dart compile exe; the image carries the Flutter SDK and ffmpeg because rendering
captures frames with flutter test then encodes with ffmpeg. No display server is
needed.
What you can render
Section titled “What you can render”A request body has exactly one input:
{"key":"demo"}renders a composition registered in the render project.{"spec":{...}}renders aVideoSpecJSON document (see authoring-with-specs). This is what an editor sends.{"prompt":"a 10s coffee promo"}authors a spec with an LLM, then renders it.{"edit":{"base":{...},"change":"make the title pop"}}refines a spec, then renders it.
Add options, visibility, and ttl:
{ "spec": { "fluvieSpec": 1, "scenes": [ /* ... */ ] }, "options": { "format": "mp4", "aspect": "reels", "quality": "high", "poster": "1.5s" }, "visibility": "private", "ttl": "48h"}A prompt or edit request needs an AI key in the environment
(ANTHROPIC_API_KEY, GEMINI_API_KEY, or MISTRAL_API_KEY); without one the
server answers 503.
The API
Section titled “The API”| Method | Path | Auth | Purpose |
|---|---|---|---|
| POST | /v1/renders | API token | Create a render job (202). |
| GET | /v1/renders/{id} | API token | Job status, with progress, code, spec, and download links. |
| GET | /v1/files/{id}/{kind} | public: none, private: token | Download video or poster. |
| POST | /v1/maintenance/cleanup | Cleanup token | Delete expired files. |
| GET | /v1/schema/video-spec | none | The VideoSpec JSON schema, for an editor. |
| GET | /v1/healthz, /v1/readyz | none | Liveness and readiness. |
Status codes: 202 accepted, 400 invalid body, 401 bad token, 404
unknown, 410 expired, 413 body too large, 429 rate limit exceeded (with a
Retry-After header), 503 AI not configured.
Local files or S3
Section titled “Local files or S3”The default backend writes files to a local directory (LOCAL_STORAGE_DIR, a
Docker volume). Switch to any S3-compatible bucket (AWS, MinIO, DigitalOcean
Spaces, Backblaze B2, Cloudflare R2) by setting STORAGE_BACKEND=s3 and the
S3_* variables. Test the S3 path locally with the bundled MinIO:
docker compose --profile s3 up --buildPublic or private
Section titled “Public or private”visibility defaults to private (set PUBLIC_BY_DEFAULT=true to flip it). A
private download URL carries a signed token bound to that one file and expiry; a
public URL has none. Either way the client fetches the same
/v1/files/{id}/{kind} endpoint: local files stream, S3 files redirect to a
presigned (private) or public URL.
Cleaning up expired files
Section titled “Cleaning up expired files”Every render gets an expiry of FILE_TTL from creation. Two things remove
expired files:
- The built-in timer, every
CLEANUP_INTERVAL(set it to0to disable). - The
POST /v1/maintenance/cleanupendpoint, for an external cron. It uses the separateCLEANUP_TOKEN, so a cron box never holds render rights.
curl -s -X POST http://localhost:8080/v1/maintenance/cleanup \ -H "Authorization: Bearer $CLEANUP_TOKEN" -d '{"dryRun":true}'# => {"scanned":12,"deletedFiles":4,"deletedJobs":2,"freedBytes":...,"dryRun":true}Rendering your own videos
Section titled “Rendering your own videos”By default the server renders against the bundled example app. To render your
own compositions, point RENDER_PROJECT at a Flutter project that contains the
capture harness (test/render/capture_harness_test.dart). Spec and prompt
renders work against any harness; a key render needs that key registered in
the project.
Specs that reference remote Image/Clip URLs need a render project whose media
client is allowed to fetch them. The bundled example uses an offline client for
deterministic tests, so it resolves only bundled assets.
Running the example locally or hosted
Section titled “Running the example locally or hosted”The example app renders either way:
- Desktop, no server:
flutter run -d linux(ormacos/windows). It spawns the Fluvie CLI, so you need a Dart SDK and ffmpeg. This is the clone-and-run path. - Web or hosted, via the API: build with
--dart-define=FLUVIE_API_URL=https://your-server(and--dart-define=FLUVIE_API_TOKEN=...). The app callsfluvie_serverand shows the download URL.
On-device rendering on mobile is supported by
fluvie_mobile_encoder: it drives Fluvie’s capture
loop in the running app and encodes with the platform’s native hardware encoder,
so nothing leaves the device. On the web it is not wired yet (the in-browser
ffmpeg encoder is unconnected), so the hosted web example renders through
fluvie_server.
Where to next
Section titled “Where to next”- Authoring with specs: the
VideoSpecJSON thespec,prompt, andeditendpoints produce and consume. - Exporting your video: the local command-line renderer the server wraps, and every export format.