The TheFluxTrain CLI (tft) lets you call TheFluxTrain APIs from your terminal. Sign in once with a device code, then run generation and other commands without hand-writing HTTP requests.
pip install thefluxtrain-cli
Or with uv:
uv tool install thefluxtrain-cli
Confirm the install:
tft --help
tft login
tft commands
tft whoami
tft login — starts device-code sign-in (see below).tft commands — lists available API command groups.tft whoami — shows the signed-in account and API key id.After login, run any command listed by tft commands. For example:
tft image-generate generate --prompt "a cat in a spacesuit" --model flux-2-pro
Use tft <group> <command> --help for flags and examples for that endpoint.
You do not paste an API key manually. The CLI creates one for you after you approve it in the browser.
tft login in your terminal. You will see a short code and a link.Credentials are saved to ~/.config/thefluxtrain/credentials.json. To sign out and clear local data:
tft logout
You can also create and manage API keys manually on API keys. Keys created in the browser work for REST calls; the CLI uses its own key after tft login.
The CLI keeps its available commands, flags, and help text up to date for you.
tft sync to refresh the command list at any time.If the command list is unavailable, run tft login or tft sync after signing in.
Optional environment variables:
| Variable | Default |
|---|---|
THEFLUXTRAIN_API_URL | https://api.thefluxtrain.com/api/v1 |
THEFLUXTRAIN_CONFIG_DIR | ~/.config/thefluxtrain |
THEFLUXTRAIN_CACHE_DIR | ~/.cache/thefluxtrain |
THEFLUXTRAIN_RENDER_API | https://render.thefluxtrain.com |
Point THEFLUXTRAIN_API_URL at a local or staging API when developing against your own backend.
Point THEFLUXTRAIN_RENDER_API at a local render service when developing media info or cloud render (same as frontend VITE_RENDER_API).
| Command | Description |
|---|---|
tft login | Device-code sign-in via thefluxtrain.com |
tft logout | Clear local credentials and local CLI data |
tft sync | Refresh the CLI command list |
tft whoami | Show logged-in user and API key id |
tft commands | List all available API command groups |
tft media info | Get video, audio, or image metadata via the render service API |
Commands for image generation, Flow Studio, video, jobs, and more appear after the command list is refreshed. Run tft commands to see the current list.
Probe video, audio, or image files through the render service API:
tft media info --file ./clip.mp4
tft media info --url UPLOAD_URL --output json
Set THEFLUXTRAIN_RENDER_API for local render service development (default: https://render.thefluxtrain.com, same as frontend VITE_RENDER_API).
Use the returned data.duration for audio clips; use data.width, data.height, and data.duration for video/image createAsset apply actions.
Use the CLI to list, create, read, and edit Flow Studio graphs programmatically. Changes are saved to your account—the same flows you open in the browser at Flow Studio.
Authentication is the same as other CLI commands: run tft login once. You do not need a separate API key for Flow Studio.
If flow-studio commands are missing, run tft sync to refresh the command list after a backend update.
| Command | Description |
|---|---|
tft flow-studio list | List your saved flows (id, name, timestamps) |
tft flow-studio create | Create a blank flow or duplicate an existing one |
tft flow-studio get | Fetch one flow's metadata and full graph state |
tft flow-studio apply | Apply ordered graph mutations (create/update/delete/connect) |
tft flow-studio nodes | List available node types, ports, and usage hints |
tft flow-studio collections | List collections for wiring input-collection nodes |
Run tft flow-studio <command> --help for flags and copy-pastable examples.
tft flow-studio list
tft flow-studio create --name "Product ad pipeline"
definitionId values and port ids:tft flow-studio nodes
tft flow-studio get --flow-id YOUR_FLOW_ID
tft flow-studio apply --flow-id YOUR_FLOW_ID --actions '[
{"op":"create","definitionId":"utils-prompt-template","position":{"x":100,"y":100}},
{"op":"create","definitionId":"flux-2-pro","position":{"x":400,"y":100}}
]'
input-collection nodes) — list collections first, then pass the returned id (not the display name) in an update action:tft flow-studio collections
Duplicate an existing flow instead of building from scratch:
tft flow-studio create --name "Copy of demo" --source-flow-id YOUR_FLOW_ID
Pass --actions as a JSON array. Each element is one mutation applied in sequence. Property names inside actions use camelCase (matching the graph JSON in the browser).
createAdd a node to the canvas.
{
"op": "create",
"definitionId": "utils-prompt-template",
"position": { "x": 100, "y": 100 },
"id": "optional-custom-node-id",
"label": "Optional label"
}
updatePatch fields on an existing node (position, label, size, inputs, internalState, dynamicInputs, dynamicOutputs).
{
"op": "update",
"nodeId": "node-uuid",
"patch": {
"inputs": { "prompt": "A cat in a spacesuit" }
}
}
For input-collection nodes, set inputs.folder_id to a collection id from tft flow-studio collections.
deleteRemove a node (its connections are removed too).
{ "op": "delete", "nodeId": "node-uuid" }
connectWire an output port to an input port. nodeId must equal targetNodeId.
{
"op": "connect",
"nodeId": "target-node-id",
"sourceNodeId": "source-node-id",
"sourceOutputId": "output-port-id",
"targetNodeId": "target-node-id",
"targetInputId": "input-port-id"
}
Prefer small batches of actions; if apply fails, read the error message and fix the batch before retrying.
Flow Studio commands return the standard API envelope (data / error). Useful fields:
| Command | Key data fields |
|---|---|
list | flows[] with id, name, created_at, updated_at |
create | flow_id, name, flow (graph state) |
get | flow_id, name, flow (nodes, connections, viewport, videoTaskList) |
apply | flow_id, flow (updated graph state) |
nodes | nodes[] with id, inputs/outputs, use_when, avoid_when |
collections | collections[] with id, name, description, source |
Use --output yaml on any command for YAML instead of JSON. Default output is compact single-line JSON; use --output pretty for indented JSON. Use --fields data.url to print a single value without parsing JSON.
--actions$ or backticks.@:tft flow-studio apply --flow-id YOUR_FLOW_ID --actions @actions.json
Or read from stdin: --actions -
While you edit a flow from the CLI or REST API, open it in Flow Studio and turn on Live sync in the toolbar. The canvas reloads from your saved graph every few seconds in read-only mode so you can watch changes without conflicting edits. Turn live sync off to edit manually in the browser.
The CLI calls the same endpoints as the REST API (see Authentication):
| Method | Path |
|---|---|
GET | /flow-studio/flows |
POST | /flow-studio/flows |
GET | /flow-studio/flows/{flow_id} |
POST | /flow-studio/flows/{flow_id}/apply |
GET | /flow-studio/nodes |
GET | /flow-studio/collections |
Flow Studio commands edit graph structure only—they do not run generation jobs. To execute nodes, use the browser or call image, video, and other generation endpoints directly.
The video-editor command group edits timeline projects stored in your account. Projects sync with the browser video editor at /video-editor.
tft media info --file ./clip.mp4
tft files upload --file ./clip.mp4
tft video-editor create --name "My edit"
mediaLibrary entry + timeline clip with sourceAssetId):tft video-editor apply-asset --project-id YOUR_PROJECT_ID --actions '[
{"op":"createAsset","type":"video","trackId":"track-1","cloudUrl":"UPLOAD_URL",
"name":"clip.mp4","width":1920,"height":1080,"duration":10.5}
]'
Add an audio clip (duration only — no width/height):
tft video-editor apply-asset --project-id YOUR_PROJECT_ID --actions '[
{"op":"createAsset","type":"audio","trackId":"track-2","cloudUrl":"UPLOAD_URL",
"name":"voice.mp3","duration":6.0}
]'
Re-use the same upload on another track or time with sourceAssetId from the prior response:
tft video-editor apply-asset --project-id YOUR_PROJECT_ID --actions '[
{"op":"createAsset","type":"video","trackId":"track-2","sourceAssetId":"media-abc123"}
]'
tft video-editor get --project-id YOUR_PROJECT_ID
The CLI provides four apply commands that call the same REST endpoint:
tft video-editor apply-asset — asset ops (clips + media)tft video-editor apply-subtitle — subtitle ops (enable/disable, per-word edits)tft video-editor apply-timeline — timeline ops (tracks + settings)tft video-editor apply-story — project story text (--text)Pass --actions as a JSON array with camelCase fields.
| op | Purpose |
|---|---|
createAsset | Add clip on timeline. Video/image: new upload needs cloudUrl, width, height, duration. Audio: new upload needs cloudUrl, duration. Re-use any media type with sourceAssetId only. |
updateAsset | Patch asset fields (assetId, patch) |
deleteAsset | Remove asset (assetId) |
splitAsset | Split video at asset-relative times (assetId, splitTimes[] — seconds from clip start, not global timeline) |
| op | Purpose |
|---|---|
updateAsset | Toggle subtitles or set/replace full subtitle track (patch.subtitlesEnabled, patch.subtitles) |
updateSubtitleWord | Patch one word by index (assetId, wordIndex, wordPatch) |
| op | Purpose |
|---|---|
createTrack | Add a track (name, optional id) |
deleteTrack | Remove track and its assets (trackId) |
updateSettings | Patch composition settings (patch: width, height, fps, backgroundColor) |
Set the project narrative stored in the Story tab:
tft video-editor apply-story --project-id YOUR_PROJECT_ID --text "Scene 1: Opening shot..."
tft video-editor apply-story --project-id YOUR_PROJECT_ID --text ""
The get response includes project.story. You can also pass [{"op":"updateStory","text":"..."}] via --actions on the apply endpoint.
Get width, height, and duration with tft media info --file ./clip.mp4 or --url UPLOAD_URL.
Open the project in the video editor and turn on Live sync in the toolbar. The timeline reloads from Supabase every few seconds in read-only mode while CLI or REST edits are applied. Turn live sync off to edit manually.
| Method | Path |
|---|---|
GET | /video-editor/projects |
POST | /video-editor/projects |
GET | /video-editor/projects/{project_id} |
POST | /video-editor/projects/{project_id}/apply |
POST | /video-render/media/info |
DELETE | /video-editor/projects/{project_id} |
| What you see | What to try |
|---|---|
| Login timed out | Run tft login again and complete authorization in the browser before the code expires. |
| Command list unavailable | Run tft login or tft sync. |
| 403 Unauthorised | Run tft logout, then tft login again. Or revoke the old key on API keys and sign in fresh. |
| Command not found | Run tft sync to refresh the command list — new API endpoints are added server-side. |
flow-studio group missing | Run tft sync after upgrading the backend; confirm with tft commands. |
| Apply returns 422 | Check action JSON: valid op, camelCase fields, and port ids from tft flow-studio nodes. |
| Apply returns 404 | Confirm --flow-id exists (tft flow-studio list) and belongs to your account. |
video-editor group missing | Run tft sync after upgrading the backend; confirm with tft commands. |
| Video apply returns 422 | Check action JSON; video/image need cloudUrl + dimensions from tft media info; audio needs cloudUrl + duration. (Tip: use apply-asset for clip/media ops.) |