Speech and images
Besides text models, Flow has three nodes that work with files: Speech → Text transcribes audio, Text → Speech voices text and Generate Image draws a picture. All three are built the same way: you pick a model from the shared media catalog, the node finds the right provider itself, calls it and puts the result into the workspace file storage.
On the free plan the media nodes are available but run into two things at once: 100 credits a month with no right to go negative (image generation spends them noticeably faster than text) and publishing to the web chat only — a Telegram bot, a webhook and a schedule are paid. Current limits are on the Plan limits page.
The media model catalog
The media catalog is separate from the text model catalog (Model catalog) and is assembled from the vendor’s live API, so specific models come and go between releases. Today 38 models are available for images, 18 for speech synthesis and 4 for recognition.
To recount against the repository:
grep -o "modality: \(image\|tts\|stt\)" backend/config/media/openrouter.yaml | sort | uniq -c
The node’s form changes with the model
This is the first thing that surprises people. A media node has few fields of its own, and they stay the same under any model: Model and an optional Connection Id with your own key are there on all three; image generation adds a Prompt, and image generation and speech synthesis carry a Node mode switch plus the settings for acting as an agent tool. Everything that describes the picture or the voice itself — size, quality, number of images, voice, format — arrives from the selected model: every catalog entry carries its own parameter description, and the editor builds the form from it. Change the model and some of those fields disappear, some appear, and the defaults become different.
Generate Image node settings
Size, Quality and Number of images come from the selected model
Used when no 'prompt' input is connected
Empty means the platform key
It works this way because the vendors never agreed with each other: one model sizes images
as 1024x1024, another by aspect ratio 16:9, a third has a transparent background and a
compression level. Keeping the union of all parameters in a single form would mean showing
fields that the selected model rejects with a 400.
Practical consequences:
- Parameters do not carry over between models. A key that is absent from the new model’s schema is silently dropped at run time rather than sent to the provider.
- The parameter list is whatever the vendor reported about the model. It is incomplete: for instance, which audio formats a particular speech synthesis accepts is not exposed by discovery, and such specifics have to be patched by hand in the platform catalog.
- Free-text parameters are rendered as templates. You can substitute
{{ variables.glossary }}into the hint for speech recognition. Parameters with a fixed list of values (voice, format, size) are not rendered as templates: substitution could only turn a valid value into an invalid one.
Speech recognition
A file (audio) on the input, text on the output. The file can come from anywhere: a chat
attachment, a voice message from Telegram, a file from storage. Recognition models have a
minimum of parameters — the language and an optional hint with names and jargon, which
noticeably improves the transcript on domain terms.
- Execute + Data
- Execute + Data + Streaming
Speech synthesis
Text on the input, an audio file on the output. The main quirk: a voice is required almost
everywhere and is published separately for every model. Voices are not shared — with one
vendor they are Zephyr and Puck, with another aura-2-thalia-en, and every model has its
own list. That is why the voice is a model parameter rather than a node field: the catalog
fills in the first voice by default so that a freshly added node runs without manual setup.
Out of 18 synthesis models, the catalog lists concrete voices as a dropdown for 12; for the rest the voice is a free-form string (there the vendor expects a voice identifier from your own account, including a cloned one). To recount:
python3 -c "import yaml;m=yaml.safe_load(open('backend/config/media/openrouter.yaml'))['models'];t=[x for x in m if x['modality']=='tts'];print(len(t), sum(1 for x in t if x['param_schema']['properties']['voice'].get('enum')))"
Image generation
A single node covers both modes, and there is no switch between them:
- From text (text2img) — the
imageinput is unused, the picture is drawn from the prompt. - From text and a picture (img2img, editing) — a file arrives at the
imageinput and goes to the provider as a reference. The mode is decided by the presence of an edge, not by a checkbox in the settings.
The prompt comes from the connected prompt input, and if that input is not connected or
arrived empty — from the field in the node’s settings. Most models have a “number of images”
parameter, so the images output is a list of files, not a single file.
- Execute + Data
- Data
The image port disappears on models that do not accept a reference. This is not an
editor glitch: the ability to take an input picture is declared on every catalog model, and
the node asks the server for its actual set of ports whenever the model changes. Today none
of the 38 image models refuses one — but the catalog is regenerated from the vendor’s answer
in full, so the port may disappear after some future update, and that is not a breakage. If
the edge was already drawn and you selected such a model, the run will honestly fail with
“the model does not accept an input image” instead of sending bytes the provider would
ignore.
python3 -c "import yaml;m=yaml.safe_load(open('backend/config/media/openrouter.yaml'))['models'];i=[x for x in m if x['modality']=='image'];print(len(i), sum(1 for x in i if not ((x.get('capabilities') or {}).get('accepts_input_image') or (x.get('capabilities') or {}).get('requires_input_image'))))"
One more honest refusal: if the provider answered with success but without a picture (usually
its own content policy firing), the node counts as failed rather than successful with an
empty result. Its on_error branch can be enabled just like on any step node.
Where the generated file goes
None of the three nodes returns bytes into the graph. The result is saved into the
workspace file storage, and what travels along the wire is a
reference: metadata (name, mime type, size) and a locator of the form filestore://….
The bytes are loaded only by whoever actually needs them — for example, when the file goes to
a model or to the user.
There are three reasons for this: the result is visible in the Files section and is not lost together with the execution log; the execution log does not blow up with embedded base64; and the same file can be passed between nodes without copying it.
For the user to see the file, connect the node’s output to the files input of the Exit
node — that is a separate port meant for files
(Entry and exit). In the chat a picture shows as a picture, audio as
a player, everything else as an attachment.
- Execute + Data
- Execute + Data + Streaming
- Data
Pictures and voicing as agent tools
Generate Image and Text → Speech can work not as a graph step but as a tool the agent calls itself whenever it sees fit. The switch is the operation mode field in the node’s settings: in step mode the node has the usual execute and result ports, in tool mode only the output that plugs into an agent. This mechanism is covered in detail in Agent tools.
What matters about tool mode:
- The file is attached to the agent’s answer automatically — there is no need to connect the node to Exit.
- Tool arguments cannot carry bytes, so “redraw this photo” works differently: the agent uses as a reference the picture the user attached to this turn of the conversation (it can be turned off with a checkbox in the advanced settings).
- Whether to show the result to the model itself is a separate checkbox, off by default: a picture inserted into the dialogue costs input tokens on every subsequent call.
Speech recognition cannot be a tool, and that is deliberate: audio from the user already arrives as a turn attachment, so a separate tool for it would add nothing.
Provider keys
| Where the key comes from | What to set up | When it applies |
|---|---|---|
| Platform key | the OPENROUTER_API_KEY environment variable on the server | by default, when the integration field is empty |
| Your own key (BYO) | an OpenRouter-kind integration in the Integrations section, then selecting it in the node's field | when it is selected in a particular node's settings |
The key is chosen per node, with a field in the advanced settings section (— select a connection — means “the platform key”). The integration is created in Settings → Integrations with the Add integration button; see Integrations.
A media key is an integration of its own kind, and the free plan’s ban on connecting your own LLM models does not apply to it; only the general limit on the number of integrations does. For connecting your own text models see Your own models.
The catalog loads without a key as well: the models are visible in the list, and the error arrives at run time. An empty model list on a node means the catalog did not load at all — that is an installation problem, not a workflow setting.
What it costs
Every generation is a separate Media generation spending entry in the Usage section. The price is taken in one of two ways:
- The provider reported the cost of the request — that is what is charged, converted into credits at the common rate with the platform’s markup. Images are counted this way.
- The provider did not report the cost — a flat rate per call is charged. That is how speech synthesis works: it answers with a ready audio file that has no cost field in it at all.
Both branches are calibrated so that a typical call costs roughly the same regardless of whether the response happened to carry a cost. How credits work in general — Credits and spending.
If you use your own key, the generation is not charged in credits — you already paid the provider directly. Only the workflow run itself is billed.
If it did not work
| Symptom | Cause | What to do |
|---|---|---|
| "Unknown media model" at run time | the model disappeared from the vendor's catalog | open the node and pick a model again |
| The model list in the node is empty | the catalog did not load | check the server installation; no workflow setting cures this |
| An error about a missing key | neither a platform key nor an integration | point the node's field at an OpenRouter integration |
| The input picture port is gone | the selected model does not accept a reference | pick a model that can do img2img |
| "The model does not accept an input image" | the edge is left over from the previous model | delete the edge or go back to the previous model |
| The node failed even though the provider answered with success | a response without a picture — usually the content policy | rephrase the prompt |
| The file was generated but the user does not see it | the output is not connected to the files port of the Exit node | connect it to the files port |
Workspace files
Where generated files live, how to reference them and what happens to the quota.
Agent tools
How the agent decides on its own to draw a picture or voice an answer.
Credits and spending
What the bill is made of and where to see the breakdown.
Node reference
Full port and field lists for the three media nodes.