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

Model*
OpenAI: GPT Image 1
Prompt
A watercolour red panda on a bamboo branch

Used when no 'prompt' input is connected

Size
1024x1024
Quality
medium
Number of images
1
Connection Id
Platform key

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

Speech → Text
AudioText

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.

Entry
Speech → Text
AI Agent
Exit
  • Execute + Data
  • Execute + Data + Streaming
Voice message → text → agent's answer.

Speech synthesis

Text → SpeechT
TextAudio

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

Generate ImageT
PromptImages
Input image

A single node covers both modes, and there is no switch between them:

  • From text (text2img) — the image input is unused, the picture is drawn from the prompt.
  • From text and a picture (img2img, editing) — a file arrives at the image input 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.

Entry
Generate ImageT
Exit
  • Execute + Data
  • Data
Editing a photo that was sent in: the picture arrives over a pure data wire, the text triggers the node.

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.

Entry
AI Agent
Text → SpeechT
Exit
  • Execute + Data
  • Execute + Data + Streaming
  • Data
Voicing the answer: the text goes both into the reply and into speech synthesis, and the file is attached to the same message.

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 fromWhat to set upWhen it applies
Platform keythe OPENROUTER_API_KEY environment variable on the serverby 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 fieldwhen 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 SettingsIntegrations 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

SymptomCauseWhat to do
"Unknown media model" at run timethe model disappeared from the vendor's catalogopen the node and pick a model again
The model list in the node is emptythe catalog did not loadcheck the server installation; no workflow setting cures this
An error about a missing keyneither a platform key nor an integrationpoint the node's field at an OpenRouter integration
The input picture port is gonethe selected model does not accept a referencepick a model that can do img2img
"The model does not accept an input image"the edge is left over from the previous modeldelete the edge or go back to the previous model
The node failed even though the provider answered with successa response without a picture — usually the content policyrephrase the prompt
The file was generated but the user does not see itthe output is not connected to the files port of the Exit nodeconnect it to the files port