Generate a NEW image of the SAME PERSON/character as an existing photo or
portrait — dress them differently, move them somewhere else, make an avatar. This
is the tool for "here is my photo, now put me in a suit / in a park", and also
the ONLY tool for "generate another picture of <character I already made>".
Pass exactly ONE of:
character: the name you gave a character via create_character_from_description
or create_character_from_photo(..., name=...). Looked up in your own list —
see list_characters(). This is the normal way once a character has a name.
photo_media_id: a mediaId directly (create_character_from_photo's
`portrait_media_id`, or any earlier generate_image media_id) — for a one-off
face you haven't registered as a named character.
prompt: describe the NEW scene/outfit and say it is the same person, e.g.
"the same man, now in a navy suit in a bright office".
Why this and not entity_id/character_slot_index: a character's Flow CARD is not
what carries resemblance into a NEW scene — the reference image itself does
(verified live, repeatedly). Combining entity_id with an image reference in the
same call also fails outright (HTTP 500). So this always generates from the
reference image alone, never from entity_id — that is deliberate, not a
workaround to fix later.
project_id: put the result into a project made with create_project instead of the
account's default one. photo_media_id itself stays account-bound, as always.
输入参数modelaspectprompt必填characterproject_idphoto_media_idinclude_preview
Generate an image from a text prompt via Google Flow.
model — pass ONE of these exact ids (from GET v1/flow/models, verified 2026-07-25):
BELUGA = "Nano Banana 2" — the DEFAULT, ~30s (legacy id NARWHAL is accepted as an alias)
GEM_PIX_2 = "Nano Banana Pro" — highest quality, ~40s, has a separate DAILY
per-account quota (429 rotates accounts)
HARBOR_SEAL = "Nano Banana 2 Lite" — ~40s
Anything else is rejected with HTTP 400 — there is no silent fallback, so a wrong id
wastes a round trip, not credits. Do NOT guess names like "nano_banana_pro": that is
the model FAMILY key from the config, not the id the generate call takes.
image_inputs: mediaIds from prior generations, edit-in-place (keeps them pixel-exact).
control_images: up to 10 base64 image bytes (no data: prefix) of a locally rendered layout/control
image — uploaded first, then prepended to image_inputs so the generator follows an exact
spatial layout instead of prose. For whole-scene layout control prefer generate_scene.
seed: 1..900000, fixes the generation so the same request returns the same image. Omit
for a random seed. Needed to compare two prompts, two models or two control images
honestly — without it every run is a fresh roll and the difference you are measuring
drowns in noise. Measured 2026-08-09: with an UNCHANGED control image a fixed seed
reproduces the frame to ~0.1%; once the control image changes, the seed no longer keeps
the character — for that use image_inputs with a reference frame's mediaId.
project_id: put the result into a project made with create_project instead of the
account's default one — one project per end client keeps their work separate. Omit it
and nothing changes from before.
include_preview: ON by default since 2026-08-21 — a generated image nobody can see
in the chat is half a result. Pass False to get the plain dic
输入参数seedmodelaspectprompt必填project_idimage_inputscontrol_imagesinclude_preview
Generate a full song from a text brief via Google Flow Music (Lyria).
prompt — free text: genre, mood, BPM, instruments, and whether you want vocals or an
instrumental (e.g. "dreamy synthwave, retro 80s, 110 bpm, instrumental"). The Producer
agent turns it into a finished track (usually ~2-3 minutes, and it returns TWO variants).
model — "lyria" (Lyria 3.5, the default flagship) or "lyria-pro" (Lyria 3 Pro, legacy).
lyrics — optional explicit words to sing; omit for the model to write its own / stay
instrumental.
SAVE THE RESULT. Each returned clip carries a public `audio_url` (a Google Cloud Storage
.m4a, no auth header needed) plus `download_path` on our server. Unless the user said
otherwise, download the audio into their project. Fields per clip: clip_id, title,
duration (seconds), lyrics, audio_url, wav_url, image_id (cover, feeds music_video),
image_url. Returns {"clips": [...], "media": [...], "conversation_id": "..."} when done,
or a job_id to poll with check_job if it isn't ready within ~90s.
conversation_id — pass the one from an earlier result to keep working on the same track
("same song but 30 seconds shorter"); the Producer remembers what it just made. Omit it
for a fresh start. Every result carries one, so a follow-up is always possible.
输入参数modellyricsprompt必填conversation_id
Talk to the Google Flow Music Producer agent instead of one-shotting a track.
Use this when the work is a conversation rather than a single brief: asking what it can
do, having it write lyrics first, or editing a track it already made ("drop the vocals",
"same song, 30 seconds shorter", "make the chorus bigger"). For a plain "make me a song
about X", music_generate is the shorter path.
message — what to say. conversation_id — omit on the first message, then pass the one
that comes back so the Producer keeps its memory of the track. model — same choices as
music_generate.
Returns {"conversation_id", "text", "suggested_actions", "clips": [...], "media": [...]}.
`text` is the Producer's reply. `suggested_actions` are the follow-ups it offers — show
them to the user, they are good prompts for the next turn. `clips` is EMPTY when the
Producer only talked, and filled when it decided to actually make audio; treat a filled
`clips` exactly like a music_generate result and save the audio.
A reply is usually seconds, but a turn that produces a song takes minutes and comes back
as a job_id to poll with check_job.
输入参数modelmessage必填conversation_id
Split a track we generated into stems (vocals, instrumental, drums, bass, …).
clip_id — the id of a clip returned by music_generate (or music_video's song). Runs on
the same account that made the track. Returns {"stems": {name: "/v1/media/..."}, "media":
[...]} when done, or a job_id to poll with check_job. Each stem is a downloadable .m4a on
our server (public download_path). Save the ones you need into the user's project.
输入参数clip_id必填
List the user's music tracks.
输入参数limit
Get metadata for a single music track.
输入参数clip_id必填
Download the raw .m4a file of a track to the server.
输入参数clip_id必填
Check remaining flowmusic credits and subscription tier.
无需输入参数。
Build a location precisely from a scene-layout/v2 map and generate its plate.
This is the "exact location building" path: instead of describing the room in prose
(which the model reinterprets — furniture drifts, depths change between shots), it
renders a control image from the map's boxes (each object a colored block at its true
position/size/depth, plus a 1-meter floor grid in true perspective), then asks the
generator to REPLACE each block with its real object in the same camera — so the plate
follows the geometry pixel-wise. Same room from another camera_id is the same space.
scene_layout: a scene-layout/v2 dict — {format, units, room{w,d,h}, objects{...boxes},
cameras{<id>:{pos,look_at,fov_deg,framing?}}, ...}. The camera must carry fov_deg.
camera_id: which camera in scene_layout.cameras to render from.
aspect: PORTRAIT (9:16), LANDSCAPE (16:9), SQUARE, PORTRAIT_THREE_FOUR (3:4) or
LANDSCAPE_FOUR_THREE (4:3). 9:16 and 16:9 from one camera share the vertical FOV, so
depths are identical across aspects.
style / extra: prepended / appended prose (art style, mood) — geometry comes from the map.
A box may carry `rot: [rx, ry, rz]` (degrees around the world X/Y/Z axes) and `pivot:
[x, y, z]`, applied as p' = Rx·Ry·Rz·(p − pivot) + pivot, pivot defaulting to the box
centre. That is how a POSE is expressed — a raised arm is a box rotated about the
shoulder. Measured live 2026-08-09: moving a hand by 2–20 cm is ignored by the
generator, rotating the arm by 15–60° comes through. Build motion out of angles.
seed / image_inputs: same meaning as in generate_image. For a SEQUENCE of frames of one
character, generate a reference frame first, then pass its mediaId as image_inputs on
every following frame — that is what keeps the face, clothes and colours identical.
A fixed seed alone does not: it only repeats an unchanged control image.
depth: render the guide as a plain grey
输入参数seeddepthextrastyleaspectcamera_id必填backgroundproject_idcalibrationcontrol_pngimage_inputsscene_layout必填
Generate a video from a text prompt via Google Flow (Veo models).
video_model_key options: abra_t2v_4s (default, 7 credits), veo_3_1_t2v_lite
(10cr, visibly cleaner), veo_3_1_t2v (100cr, "Quality" tier), veo_3_1_t2v_fast (20cr).
project_id: put the clip into a project made with create_project instead of the
account's default one — one project per end client keeps their work separate.
Video generation routinely takes 1-3+ minutes, so this almost always returns
a job_id rather than a finished result — call check_job with it, waiting a
few seconds between checks, until status is 'done' or 'failed'.
beats: how many DISTINCT action phases the prompt describes — "ears flick back",
"tail lashes", "strikes the ball", "ball flies off" are four. Give it and the server
picks the clip length for you (about two phases per second) and says in the reply what
it picked and why. Measured 31.08.2026: the model STRETCHES whatever you describe over
whatever length you ask for, so one phase in four seconds comes out as slow motion, and
the same text at six seconds instead of four loses 15% of its movement. Adjectives are
not phases: "springy cartoon motion" adds nothing. Leave beats out and the length you
passed is used unchanged.
seed (all video tools): the result always reports the seed it used. Same seed + same
prompt + same inputs = the same clip, so a good take can be re-made. A fixed seed does
NOT keep the composition when the prompt changes — use edit_video for that.
输入参数seedbeatsvoiceaspectprompt必填project_idvoice_textvoice_styleinclude_previewvideo_model_key
Rewrite a video from a plain description: "make the sky stormy", "take the
passer-by out of the shot", "warmer light", "put the product from the image on the
table".
Source — ONE of:
media_id: id from a previous generate_video* result (OUR OWN generation). Job is
pinned to the account that produced it.
video_base64: base64-encoded YOUR OWN clip (mp4/mov/avi/3gp, up to 60s/1GB).
Worker uploads it via Flow's Scotty resumable upload (reversed via
Flow Web Ingredients, 2026-10-14) onto a healthy account, then runs
the edit on that account. The upload itself costs no credits.
start_sec / end_sec: edit only this piece of the source (both or neither, at most
10 s). The result is ONLY that piece — not the full clip with the piece replaced.
reference_images_base64: up to 5 raw base64 images (no data: prefix); the object in
the image is put into the clip — say in the prompt what to do with it.
20 credits per edit (abra_edit_360p: 10). Not a mask tool — describe the change and
where it goes ("on the left", "in the sky"); mask/style image types are not honored.
Prompt template that lands best — fill each part, one or two sentences each:
Goal: what changes ("Replace the man on the left with the character from image 1").
Source: "The source video drives camera, motion, timing and lip movement."
Reference: what to take from each image ("use only the character's look; ignore
the image background").
Scene: describe the room/background in words (helps the model keep it intact).
Keep: "Everything else stays exactly as in the source."
Timing: "The new character inherits the exact movement and position of the one
it replaces, from the first frame to the last."
Draft on abra_edit_360p (10 credits) to check the edit lands, then run abra_edit; the
seed does not carry an edit take across resolutions, s
输入参数seedaspectprompt必填end_secmedia_idstart_secvideo_base64include_previewvideo_model_keyreference_images_base64
Re-make a video with YOUR character in place of the person in it: same camera,
movement, gestures, timing and mouth movement, the character's look.
character: name from list_characters (its portrait is used as the look).
Source — ONE of media_id (a video this service made) or video_base64 (your own clip).
who: which person to replace when there are several ("the man on the left").
scene: one sentence about the room/background — helps keep it intact.
extra: anything else for the model ("keep the on-screen text").
start_sec/end_sec: recast only that piece (at most 10 s); the result is that piece.
Two steps:
1. final=False (default) -> draft on abra_edit_360p, 10 credits: cheap check that
the recast works on this clip (who gets replaced, does the scene hold).
2. final=True -> 720p on abra_edit, 20 credits. NOTE: for edits the seed does NOT
carry a take across resolutions (checked live 2026-10-15: the draft kept the
person's hoodie, the 720p run replaced the whole body), so expect the final to
differ in details from the draft. To repeat a FINAL take, reuse its seed with
final=True.
Only for footage the user owns or is licensed to use; never put a real person's
likeness into a clip without their consent.
输入参数whoseedextrafinalsceneaspectend_secmedia_idcharacter必填start_secvideo_base64include_preview
Continue a video this service already generated — Flow writes what happens next.
media_id: the id from a previous generate_video* result (the bare id, not the
/v1/media/... path). Only videos made through this service can be extended: the id
belongs to a specific Google account's project, so the job is pinned to that account
instead of rotating. An id we never produced comes back as a clear error, not a
mystery failure.
Use this for shots that need to run longer than one generation, or to keep a scene
going without re-describing it: the continuation inherits the source clip's world,
which a fresh text-to-video call cannot do.
Default model veo_3_1_extension_lite: 10 credits, adds 8 seconds.
project_id: which project the continuation lands in. The job is pinned to the source
clip's account regardless, so pass the project the source was generated in (or omit it).
输入参数seedaspectprompt必填media_id必填project_idinclude_previewvideo_model_key
generate_video_between_frames
Generate the transition between TWO given frames: the video starts at the first
image and ends at the second one, Flow invents everything in between.
This is Flow's "Кадры" with both slots filled — a different mechanism from
generate_video_from_image, which only pins the FIRST frame and lets the ending drift.
Use this when the ending matters: a logo that must resolve exactly, a character that
must land in a specific pose, two stills from a storyboard that need connecting.
Both images are raw base64 bytes, no data: URI prefix. The default model
(veo_3_1_interpolation_lite, 10 credits, 8 seconds) is the cheap one — transitions
are something you retry a few times, and defaulting to the 100-credit key would make
every experiment expensive.
beats: how many DISTINCT action phases the prompt describes — "ears flick back",
"tail lashes", "strikes the ball", "ball flies off" are four. Give it and the server
picks the clip length for you (about two phases per second) and says in the reply what
it picked and why. Measured 31.08.2026: the model STRETCHES whatever you describe over
whatever length you ask for, so one phase in four seconds comes out as slow motion, and
the same text at six seconds instead of four loses 15% of its movement. Adjectives are
not phases: "springy cartoon motion" adds nothing. Leave beats out and the length you
passed is used unchanged.
project_id: put the clip into a project made with create_project instead of the
account's default one.
beats: how many DISTINCT action phases the prompt describes. The server then picks
the clip length itself (about two phases per second) and says so in the reply. For a
first+last-frame transition keep it low: the two frames already fix the endpoints, and
asking for more seconds than the action needs only stretches it — measured 31.08.2026,
the same text at six seconds instead of four lost 15% of its movement.
输入参数seedbeatsaspectprompt必填project_idinclude_previewvideo_model_keyend_image_base64必填start_image_base64必填
generate_video_from_image
Generate a video that starts from a given image (i2v — image-to-video).
Pass exactly one start: start_image_base64 (raw base64 image bytes, no data: URI
prefix) or start_media_id (media_id of one of YOUR finished videos — the clip then
starts from that video's `frame`, "last" by default, taken server-side).
Mutually exclusive with reference-image (r2v) generation.
Chain clips: every finished video carries `last_frame_path` and its media_id, so
clip 2 = generate_video_from_image(prompt="...", start_media_id=<clip 1 media_id>),
clip 3 the same from clip 2's media_id. The chained job runs on the account that
made the source clip.
For "make content that looks like this real photo of a person" specifically,
prefer generate_video_with_reference (r2v) over this — see its docstring for why.
i2v is better suited to "continue visually from this exact frame" (e.g. chaining
clips), not identity preservation across a whole new scene.
beats: how many DISTINCT action phases the prompt describes — "ears flick back",
"tail lashes", "strikes the ball", "ball flies off" are four. Give it and the server
picks the clip length for you (about two phases per second) and says in the reply what
it picked and why. Measured 31.08.2026: the model STRETCHES whatever you describe over
whatever length you ask for, so one phase in four seconds comes out as slow motion, and
the same text at six seconds instead of four loses 15% of its movement. Adjectives are
not phases. Leave beats out and the length you passed is used unchanged.
project_id: put the clip into a project made with create_project instead of the
account's default one.
voice / voice_style / voice_text: optional narration — same semantics as
generate_video_with_reference. Only works with speech-capable Veo 3.1 i2v models.
输入参数seedbeatsframevoiceaspectprompt必填project_idvoice_textvoice_stylestart_media_idinclude_previewvideo_model_key另有 1 个
generate_video_with_reference
Generate a video using up to 7 reference images (r2v — e.g. a consistent
character/subject across the video). reference_images_base64: raw base64-encoded
image bytes (no data: URI prefix), up to 7 with Omni Flash keys (abra_r2v_*, the
default), up to 3 with Veo r2v keys (veo_3_1_r2v_*). Mutually exclusive with start-image
(i2v) generation.
THIS IS THE RECOMMENDED WAY to get content that actually looks like a real person
from a real photo — confirmed live 2026-07-11: r2v conditions generation on the
reference photo's actual pixels and reliably preserves the real face, unlike the
character system's `generate_character_image`/`generate_character_turnaround`
(those condition on a TEXT description + a loosely-attached reference and were
confirmed to drift to an unrelated-looking person). For a still IMAGE rather than
video, use this and read `first_frame_path` (or `last_frame_path`) off the result once done (via
check_job) — same-day testing found no reliable image-only equivalent: a plain
generate_image call with image_inputs pointed at an external photo was confirmed
to silently ignore the reference entirely.
project_id: put the clip into a project made with create_project instead of the
account's default one.
entity_id: optional — the entityId of a character you created and voiced with
set_character_voice. Only meaningful together with video_model_key="veo_3_1_r2v_lite"
(the one r2v model that generates speech natively); for abra_r2v_* models passing
entity_id has no effect (and was known to 500 historically). When set with the right
model, Flow attaches the character's audioReferences and lip-syncs the generated
speech in the assigned TTS voice. The job runs on the character's own Google account
(that's where the entity and its audioReferences live) — so an account different
from your other calls is normal here, no action needed.
voice / voice_style / voice_text: o
输入参数seedvoiceaspectprompt必填entity_idproject_idvoice_textvoice_styleinclude_previewvideo_model_keyreference_images_base64必填
Generate a portrait or body image for an existing Flow character.
entity_id: the character's entityId — from create_character or
create_character_from_photo, or create_character_from_description's own result
(which already calls this for slot 0 — no need to call it again for that slot).
character_slot_index: 0 for portrait, 1 for body. Flow keeps the two slots
visually consistent server-side once they share an entityId — no reference
image needed for the body shot.
ONE-SHOT PER SLOT, confirmed live 2026-07-25: each slot can be written exactly
once — a second generate_character_image call into an ALREADY-FILLED slot fails
(HTTP 500), it does not overwrite. Also confirmed: the FIRST write into a slot
500s on a card with no personality_notes saved yet — call update_character with
personality_notes before the first generate_character_image on a fresh entity_id.
If you're using create_character_from_description, both of these are already
handled for slot 0; this caveat mainly matters if you're driving slot 1 (body) or
entity_id yourself.
THIS TOOL DOES NOT PLACE A CHARACTER INTO A NEW SCENE — it only ever (re)writes
the character's own portrait/body slot, once. For "generate a picture of this
character doing X", use generate_with_face(character=..., prompt=...) instead,
which conditions on the reference image, not entity_id.
CAVEAT confirmed live 2026-07-11: for a character seeded from a REAL PHOTO
(create_character_from_photo), this text-prompt generation is NOT reliable for
identity — it conditions on a text description, not the real photo's pixels, and
was confirmed to drift to an unrelated-looking person even with a detailed
physical description saved on the entity. Fine for invented/stylized characters.
If the goal is "content that looks like a real person," use
generate_video_with_reference (r2v) instead — see its docstring.
Only works for the account whose
输入参数modelaspectprompt必填accountentity_id必填character_slot_index必填
Create a new, empty Flow character (just gets you an entity_id — no portrait/body,
no description yet). Plain HTTP, no browser involved.
Low-level building block — for "make me a character from a description and generate
its portrait", call create_character_from_description instead of this; it does this
step plus the two below in the right order for you.
Next steps after you have entity_id, IN THIS ORDER (reversing them 500s on the first
portrait write, confirmed live 2026-07-25): 1) update_character with
personality_notes (and display_name) — the slot generation below needs a saved
description to succeed at all; 2) generate_character_image with
character_slot_index=0 (portrait), then =1 (body) — each slot accepts exactly ONE
write, a second call into an already-filled slot 500s rather than overwriting.
输入参数account必填
create_character_from_photo
Create a new Flow character whose portrait (and optionally body) is a REAL
PHOTO, not a text description or generation. Both photos become the character's
slot-0 (portrait) and slot-1 (body) images directly — Flow's own upload mechanism
in its New Character composer, discovered live 2026-07-11 — no image generation
call happens for either slot, so resemblance is exact, not "similar style". Boots
the on-demand Chrome for this account (~15-30s, longer if body_photo_base64 is
also given), same as create_character, no Android/recaptcha involved.
photo_base64: raw base64-encoded image bytes, no data: URI prefix — portrait.
body_photo_base64: same format, optional — ideally a full-body photo of the same
person, for a guaranteed-exact body image (no generation at all for that slot).
physical_description: IMPORTANT, look at the photo yourself and write this —
a detailed physical description (build, hair color/style, eye color, clothing,
distinguishing features), similar in spirit to: "Adult male, mid-20s, ~180cm,
lean build. Medium-brown wavy hair, light blue-grey eyes, faint stubble. Wears
a dark grey hoodie, dark jeans, white sneakers." This gets saved into the same
"Character Info" field as Flow's own UI. CORRECTED same day as this tool was
first built: an earlier version of this doc claimed body/multi-view generation
can never stay consistent with a photo-seeded portrait — WRONG, confirmed live.
The actual missing ingredient was this detailed text spec — with it, both
generate_character_turnaround and generate_character_expression_sheet below
produce genuinely consistent, on-model results (reproduced live against Flow's
real UI, not just guessed). Skip this only for a bare portrait-only character
with no further generation planned.
Next step after you have entity_id: update_character for a display name, then
generate_character_turnaround / generate_character_expression_she
输入参数nameaccount必填photo_base64必填body_photo_base64physical_description
create_character_from_description
Invent a new character from a text description and generate its portrait — the
full "make me a character, no photo involved" path in ONE call, instead of gluing
together create_character + update_character + generate_character_image yourself.
name: short and unique among YOUR OWN characters (see list_characters()) — how
you'll refer to this character afterwards, e.g. character="detective_marlowe" in
generate_with_face.
description: physical description — build, hair, eyes, clothing, distinguishing
features. Saved on the character's card AND used to generate the portrait, so
write it as concretely as you would any image prompt.
account: Google account (and its project) to create the character under.
The portrait is written to Flow's card exactly ONCE — that slot cannot be
regenerated. Calling this again with the SAME name does not retry it: it fails
fast with a clear message instead of hitting a raw HTTP 500 downstream. Want a
different look? Use a new name.
Returns {"name", "entity_id", "portrait_media_id", "portrait_url", "hint"} once
done. Next step for ANY further image of this character: generate_with_face(
character=name, prompt="...") — never entity_id/character_slot_index, which only
works for that one portrait write and cannot place the character into new scenes.
输入参数name必填modelaspectaccount必填description必填display_nameportrait_prompt
List the characters YOU created (create_character_from_description, or
create_character_from_photo with name=...) and can now generate more images of
via generate_with_face(character=<name>, prompt=...). Read-only, no Flow calls,
no credits spent.
Returns {"characters": [{"name", "source", "description", "portrait_media_id",
"entity_id", "project", "account", "created_at"}, ...]}, oldest first.
`source` is "description" (create_character_from_description) or "photo"
(create_character_from_photo). `portrait_media_id` is what generate_with_face
actually uses — a character with it set to null has no usable reference yet.
无需输入参数。
generate_character_turnaround
Generate a single image showing the character's FULL BODY from 3 angles
(front/side/back) in one row, labeled — a "turnaround sheet". Pure HTTP as of
2026-07-26 — no browser involved (see below for what changed and what that does
and does NOT prove).
History, so the mechanism change doesn't erase the reason this tool exists:
confirmed live 2026-07-11 that the plain Android-bearer generate_character_image
path produces an INCONSISTENT result for this exact kind of multi-view prompt,
even with a detailed physical description already saved on the entity — a real
web composer (Chrome/CDP) driving Flow's own translation/agent layer was reliably
consistent for the same prompt. That is why this tool exists as a separate path
from plain image generation, and that finding still stands.
What changed 2026-07-26: this no longer drives that browser composer. The
underlying job type (generate_character_scene) moved to a THIRD mechanism, not
either of the two compared above — plain HTTP with image_inputs=[the character's
own portrait_media_id from the registry], the same face-preserving mechanism
generate_with_face uses. This was done to remove the last browser dependency
(worker.py's BROWSER_JOB_TYPES is now empty), not because this new path's
multi-view consistency was re-verified — it has NOT been checked live yet whether
image_inputs alone holds up as well as the old composer did for a 3-angle sheet.
Treat multi-view reliability here as unverified-but-plausible until confirmed by
eye against real output, not as re-proven.
Still recommend a detailed physical description saved first (create_character_from_photo's
physical_description param, or update_character's personality_notes) — that
recommendation carries over from the 2026-07-11 finding above; whether it still
matters mechanically on this new image_inputs path (vs. the composer's own
entityContext-driven translation layer
输入参数account必填entity_id必填outfit_descriptioncharacter_slot_index
generate_character_expression_sheet
Generate a single image showing the character's FACE with several different
expressions in one row, labeled — an "expression sheet". Same job type and same
history as generate_character_turnaround — see its docstring for the full story:
the 2026-07-11 finding (plain Android-bearer image path is inconsistent for
multi-view prompts, the real web composer was reliably consistent) is why this
tool exists, but as of 2026-07-26 it no longer drives that composer — pure HTTP
with image_inputs=[portrait_media_id] instead (no browser). Multi-view consistency
on THIS new path has not been re-verified live yet; the recommendation to keep a
detailed physical description saved on the entity carries over from the old
finding but hasn't been separately re-tested against the new mechanism either.
account: Google account that owns this entity_id's project.
expressions: list of expression labels, e.g. ["нейтральное выражение",
"удивление", "лёгкая улыбка", "смех"] (default if omitted). Keep to 3-5 for a
readable single-row layout.
character_slot_index: which slot to write into (0 = portrait, default — this
overwrites the existing portrait image with the expression sheet, matching how
this was used when reproducing the feature; 1 = body slot also works).
输入参数account必填entity_id必填expressionscharacter_slot_index
Create a NEW Flow project on one of your Google accounts and return its project_id.
Use this to give every one of YOUR OWN clients their own namespace: create a project
per client once, then pass that project_id to generate_image / generate_video so
their results never mix with anyone else's.
account — which Google account creates it (see flow_dashboard / check_credits for the
emails). The project is created under that account, but a project id is a label every
account accepts: generation keeps rotating accounts as before, so results stay grouped
by project_id, not by account. What is still account-bound is media_ids — a follow-up
that reuses one (image_inputs edit-in-place, extend_video, upscale) only works on the
account that produced that particular frame, exactly as today.
name — optional human-readable title, e.g. the client's name.
The returned project is usable for generation IMMEDIATELY. It may not show up in the
Flow web UI's project list (`registered: false` in the result says so) — that only
affects browsing in Google's own interface, not this API: every result comes back as
urls/media_ids in the generation response either way.
Returns {"project_id": ..., "account": ..., "registered": true|false}.
输入参数nameaccount必填
Speak `text` aloud with a Gemini TTS voice and return a WAV (24 kHz, mono, 16-bit).
Takes ~15-25 seconds. Costs no image/video quota. Russian, English and most other
languages work; the voice name only sets the timbre.
text: what to say (up to 5000 characters).
voice: one of list_voices() (default "Algieba").
style: optional delivery hint, e.g. "calm, slow" or "cheerful". Empty = neutral.
project_id: optional, a project from create_project; omit for the account's default.
include_preview: also return `wav_preview_b64`, the FIRST 8 KB of the file as base64.
Off by default: it is only the first fraction of a second and costs context. To get the
whole file open `download_url`, or call fetch_media(path=download_path, force_bytes=True).
Returns {audio_url, download_url, download_path, media_id, duration_sec_estimate,
size_bytes, voice} — `audio_url` is Google's signed link and EXPIRES, `download_url`
is ours. While still rendering, returns a job_id: call check_job.
输入参数text必填stylevoiceproject_idinclude_preview
Attach a preset Gemini TTS voice to a Flow character for auto-lipsync in future videos.
Synthesizes a short reference audio in the chosen voice and attaches its media_id to
the character's `audio_references`. This is a ONE-TIME setup per character: any
subsequent video generation that references this character will auto-lipsync with this
voice (same mechanism Flow Web uses when you set a voice via its UI).
Calling it again overwrites the voice. Pass sample_text=null for a short default sample
— only the resulting audio is kept as a reference; the content of the text is not used
for later videos.
character_id: entityId from create_character / create_character_from_photo.
account: Google account email that owns this character.
voice: one of list_voices() (default "Algieba").
style: optional delivery hint, e.g. "calm, slow".
sample_text: optional short sentence; default is a short neutral Russian sentence.
project_id: optional project; omit for the account's default.
Returns {entity_id, voice, media_id, audio_url, duration_sec_estimate, audio_references}.
输入参数stylevoiceaccount必填project_idsample_textcharacter_id必填
The 30 Gemini TTS voices usable with generate_voice: name, gender, short description.
Gender/description are a rough guide only; any voice reads any language.
无需输入参数。
Check projects for a Google account (or all). NOTE: for the credit BALANCE and a
full at-a-glance overview, prefer `flow_dashboard()` — it returns real credit
numbers per account + totals. This tool only returns the project list.
输入参数account
The account-pool control panel: for EVERY Google account, the real credit
balance, subscription tier, project count, and health status, plus totals — one
call, formatted for reading. Use this to answer "how many accounts, how many
credits on each and in total, what projects" — the operator's dashboard.
Returns a `summary` string (human-readable table) plus structured `accounts` and
`totals`. Credit numbers are live (Flow's /v1/credits). Credit REFRESH date is not
exposed by Flow's API, so it's shown as an estimate note, not a hard date.
无需输入参数。
Save a character's display name and/or personality notes. Without this, a
character stays 'Untitled Character' with no description even after portrait/body
images exist. account must be whichever Google account's project owns this entity_id.
输入参数account必填entity_id必填display_namepersonality_notes
Upscale an existing generated image (from a prior generate_image call) to a
higher resolution -- the same "1K/2K/4K" choice Flow offers when downloading.
account: the Google account whose project owns media_id. Do not guess it and do not
ask the user: the generate_image result you already have carries it in the
`account_used` field -- pass that value verbatim. A media_id only exists inside the
project of the account that produced it, so any other account returns a clean error.
target_resolution: "2K" or "4K". Not every image supports upscaling (depends on the
source model/size) and 4K may be locked behind a paid plan tier on some accounts --
both cases come back as a clean error rather than a crash, so just report it if that
happens instead of retrying.
Takes roughly 15-30s -- returns a job_id to poll with check_job if it is not done
within ~35s.
Returns {"media_id": <new upscaled mediaId>, "download_path": "/v1/media/....jpg",
"resolution": ...} when done.
输入参数account必填media_id必填target_resolution
Upscale an existing generated video (from a prior generate_video call) to a
higher resolution -- the same "270p/720p/1080p/4K" choice Flow offers when downloading.
Counts against the weekly free video limit (3 a week without a subscription), like
any other video operation, and spends the account's own Flow credits.
account: the Google account whose project owns media_id. Do not guess it and do not
ask the user: the generate_video result you already have carries it in the
`account_used` field -- pass that value verbatim. A media_id only exists inside the
project of the account that produced it, so any other account returns a clean error.
target_resolution: "1080p", "4K" or "from_360p" (omni_upsampler_360p -- lifts a 360p
draft to normal quality; not yet live-tested). 4K may be locked behind a paid plan tier on some
accounts -- comes back as a clean error rather than a crash, just report it.
Slower than generation: it is kicked off and then polled like a normal video
generation -- returns a job_id to poll with check_job if it is not done within ~40s.
Returns {"media_id": <new upscaled mediaId>, "download_path": "/v1/media/....mp4",
"resolution": ...} when done.
输入参数account必填media_id必填target_resolution
Check the status/result of a job previously returned by generate_image,
generate_video, generate_video_from_image, or generate_video_with_reference.
Besides status/result it reports PROGRESS, so you can tell the person something
truthful instead of "still waiting":
queue_position — how many jobs are ahead (1 = next to be picked up); only while queued
queue_seconds — how long it sat in the queue
run_seconds — how long the generation itself has been running
account_used — which Google account it ran on, once finished
hint — the same thing in one human sentence
A job that is queued behind others needs patience; one that has been running far longer
than its model's typical time (see list_models) is the one worth reporting as stuck.
Once status is 'done', SAVE THE FILES: the response carries `download_urls` (no auth
header needed) and `save_as` (filenames). Write them into the user's project — e.g.
./media/<save_as> — and tell them where. The URLs expire within the hour, and the
server's own copy can be reclaimed when its disk fills; the copy in their project is
the one that lasts.
输入参数job_id必填include_preview
Download a generated media file (video, frame) and return its bytes,
base64-encoded, so the file can be saved or shown even though the media endpoint
isn't publicly reachable with your MCP key.
THIS IS THE EXPENSIVE PATH — prefer `download_url` from the job result. Bytes returned
here travel as TEXT, and text is where context dies: a 480 KB audio stem is ~640 thousand
base64 characters, roughly 160 000 tokens, in ONE tool result. The same file behind
`download_url` costs about twenty. An inline image preview is cheap by comparison (it
travels as an image, ~450 tokens) — it is base64 in a text field that hurts.
Because of that this tool REFUSES big files by default and hands back a download link
instead. Pass force_bytes=True only when you genuinely need the bytes inside the
conversation and know what it costs.
path: the `download_path` / `first_frame_path` / `last_frame_path` value from a job
result (e.g. "/v1/media/<id>.mp4"), or just the bare filename.
max_kb: size ceiling for an in-band answer (default 64 KB ≈ 21 000 tokens).
Returns {"filename", "media_type", "base64"}. The download is scoped to your own
account group — you can only fetch media your key produced. Note videos can be
several MB, so the base64 payload is large; fetch only when you actually need the
bytes (to save/display), not to check status (use check_job for that).
On storage: the server keeps a local cache of results and, when it runs short of disk,
drops the OLDEST files first, whether fetched or not. A dropped file is fetched back
from the Google account that made it on the next request; if that takes a while you get
`retry_after_s` — call again then. If that account is disconnected, the error names it
and gives `reconnect_url`: tell the user to reconnect it, then retry. Only when the
original cannot be fetched back does it answer 410 with the date. Saving the bytes
locally is still the way to
输入参数path必填max_kbforce_bytes
Attach one of YOUR OWN Google accounts to the pool using an oauth_token captured
on the USER'S OWN machine (password/2FA never leave it). This is the self-serve
onboarding path — no server-side browser, no SSH.
PREFERRED PATH — no oauth_token handling at all: call flow_status; its
`next_step.command` holds a PERSONAL one-line command per OS (mac / linux / win) with
the owner's ticket inside. RUN IT YOURSELF in the user's terminal. The script submits
the token to the server by itself and prints "Готово — аккаунт ... подключён"; you do
NOT call add_account_token afterwards. (The same command is shown to the user in the
cabinet: https://openflowmcp.com/account#accounts.) Node 22+ is the only requirement.
FALLBACK (only if flow_status has no command, or the script says it could not submit
automatically) — run the generic command, which has no ticket and just prints a line:
curl -sL https://openflowmcp.com/onboard_local.js | node
Windows PowerShell:
irm https://openflowmcp.com/onboard_local.js -OutFile o.js; node o.js
It opens the user's own Chrome at accounts.google.com/EmbeddedSetup. Tell them to log
in and press Accept, then wait until the page hangs on a spinner (normal, the token is
already issued), then go BACK to the terminal window running the script and press
Enter — the script grabs the token only after Enter, it does not do it by itself. Do
not close the browser window before that. The script prints ONE JSON line: {"status":200,"oauth_token":...,
"email":...}; with a personal command it also adds "submitted":true, which means the
account is already connected and you are done. Without "submitted":true, pass
oauth_token and email here.
If it prints "need_email": true, the account address could not be read off the page —
ask the user which Google account they just signed into and pass that as `email`, or
re-run with [email protected] in front
输入参数email必填reset_dayoauth_token必填
Send a message to the openFlow human operator (no Telegram needed).
Replies come from a human, usually within hours; read them with support_replies.
输入参数contextmessage必填
Your support thread, including operator replies (from a human, usually within
hours). `since` is a unix timestamp: only newer messages.
输入参数since
Begin connecting a Google ANTIGRAVITY account (used by the Claude/Gemini models via
OmniRoute — not a Google Flow account). Returns `authorize_url` and `state`.
The next step is on the USER's side: give them `next_step` wording and the URL, they
sign in and press Accept in their browser and hand you the `code=...` value from the
antigravity.google/oauth-callback page. Then call add_antigravity_submit(code, state)
with the `state` returned here.
无需输入参数。
Finish connecting a Google Antigravity account started with add_antigravity_start.
`code` is the value of `?code=...` on the antigravity.google/oauth-callback page the
user landed on; `state` is what add_antigravity_start returned. Returns
{status, account_id, account_email, requires_project}. By default this also creates
the GCP project in one step (`complete=True`) and adds `project_id`. Pass
`complete=False` to defer and call `complete_antigravity_account(account_id)` later.
If auto-onboarding fails the result keeps `requires_project: true` and carries
`onboard_error`; retry with `complete_antigravity_account(account_id)`. Errors:
"unknown state" (start again) or "google_api_error" (the code was rejected or
already used; start again).
输入参数code必填state必填complete
complete_antigravity_account
Finish an Antigravity account started with add_antigravity_submit. Creates the GCP
project needed before the account can serve LLM requests. Idempotent — safe to retry;
already-onboarded accounts return their project_id.
输入参数account_id必填
List the Google accounts you connected for the LLM relay with their tier (Free/Pro/Ultra); refresh=True re-checks the tier with Google.
The model is chosen per request (chat tools / relay `model`); pin an account with the `X-Account-Email` header.
输入参数refresh
What this key still needs before it can do everything — READ-ONLY, spends nothing.
Call this BEFORE the first generation of a session, whenever a call is refused, and
whenever the user asks why something does not work. It answers in one shot: is a
Google account connected, how much of the free trial is left, is the key linked to a
GitHub identity, how much of this week's free limit is left (`weekly`).
The important field is `next_step`: it is the single most blocking thing right now,
with ready-made wording (`text` in Russian, `text_en` in English) and a `url` to open.
When it is null, nothing is missing.
DO NOT keep this to yourself. If `next_step` is set, say it to the user in their own
language and give them the URL — a step like signing in happens in THEIR browser
and cannot be done by you. When `next_step` is about connecting a Google account it
also carries `command` (personal, per OS): run it for the user in their terminal, it
submits the token by itself — no copying or pasting. add_account_token is only the
fallback. To add an **Antigravity** Google account from inside this chat, use
`add_antigravity_start` -> open the URL -> paste the `code` back with
`add_antigravity_submit(code, state)`. `support` is the operator's Telegram, the service's only
human channel; there is no email on file for a self-registered key, so nobody will
reach out to the user first.
无需输入参数。
Check whether the Flow generation backend is reachable and see current queue
depth (queued + warming + running jobs), plus disk state (`disk`: free_gb, below_target,
below_floor, refusing_jobs); status is "degraded" when disk is below the floor. Note: the Android emulator is now OFF —
generation runs on a pure-HTTP token path, so there's no warm-up delay anymore.
无需输入参数。
Live from Google, no credits, use to answer «is this model still working?».
Reads Google Flow's own current model catalog (modelConfig: usage keys, per-tier
credit prices, durations) and the availability status of each model family, through
one of the caller's connected accounts. READ-ONLY: nothing is generated and no credits
are spent. Cached for ~1 minute server-side. Use list_models instead for the short
curated list of ids this service's tools accept; use this one when you need Google's
ground truth (a key that is missing here has been withdrawn upstream).
Returns {models: <modelConfig>, statuses: <family availability or {"error": ...}>,
account_used: <email>}. Needs at least one connected Google account.
无需输入参数。
List every valid model id this service accepts — READ-ONLY, spends no credits,
calls nothing outside this service. Call this BEFORE generate_image/generate_video
if you're unsure of an exact id: guessing (e.g. the family key "nano_banana_pro" or
a display name like "Nano Banana 2" instead of the id "NARWHAL") gets a hard HTTP
400 on a call that already committed to a task, not a helpful list.
Returns:
images: {id: {label, typical_seconds, notes}} for the three valid `model` values
(generate_image, generate_character_image).
videos: {id: {label, credits, duration_s, mode, orientation}} for the curated
`video_model_key` values this server's tools accept via their Literal type —
mode is one of t2v/i2v/r2v (which generate_* tool it belongs to). `credits` or
`duration_s` is null where Google's docs never gave that number — a null does
NOT mean free/instant, it means unconfirmed.
defaults: the id each tool falls back to when you omit the parameter — unchanged
from before this tool existed, so already-working callers aren't affected.
Note: service/api.py (the HTTP layer these tools call through) additionally
accepts a wider set of rarer, exotic video_model_key values not listed here
(confirmed-real but uncommon r2v/i2v variants) — this tool only lists the ones
actually worth choosing from, to stay a short, readable list.
无需输入参数。
Запрос к Gemini 3 Pro через вашу подписку Google AI Pro.
Использует ваш подключённый Google-аккаунт (подключите на /connect-google).
Расходует вашу квоту Google, не наши токены.
Args:
messages: список {"role": "user|assistant", "content": "..."}.
system: системный промпт (опционально).
max_tokens: максимальное количество токенов в ответе (по умолчанию 65536 — максимум модели,
допустимо 1..65536). ВАЖНО: токены «размышления» Gemini 3 входят в этот
лимит — при малом значении ответ обрывается (finish_reason "length").
thinking_budget: бюджет токенов на размышление (необязательно): 0 — выключить
(если модель позволяет), -1 — динамически, N — верхняя граница.
thinking_level: уровень размышления (необязательно): "low" | "high"
(у Flash ещё "minimal" / "medium"). Приоритетнее thinking_budget.
account_email: форсировать конкретный Google-аккаунт из вашего пула
(например, "[email protected]"). Если такого в пуле нет — ошибка.
account_tag: форсировать аккаунты с указанным тэгом (round-robin
по подмножеству пула; тэги назначаются в кабинете).
Returns:
{"text": str, "usage": {"input_tokens": int, "output_tokens": int}}.
Raises:
MCPError: если ключ невалиден, Google не подключён, или квота исчерпана.
输入参数systemmessages必填max_tokensaccount_tagaccount_emailthinking_levelthinking_budget
Быстрый и дешёвый Gemini 3 Flash — для коротких запросов, классификации,
извлечения полей из текста. Идёт в вашу подписку Google, наши токены не тратит.
Args:
messages: список {"role": "user|assistant", "content": "..."}.
system: системный промпт (опционально).
max_tokens: максимальное количество токенов в ответе (по умолчанию 65536 — максимум модели,
допустимо 1..65536). ВАЖНО: токены «размышления» Gemini 3 входят в этот
лимит — при малом значении ответ обрывается (finish_reason "length").
thinking_budget: бюджет токенов на размышление (необязательно): 0 — выключить
(если модель позволяет), -1 — динамически, N — верхняя граница.
thinking_level: уровень размышления (необязательно): "low" | "high"
(у Flash ещё "minimal" / "medium"). Приоритетнее thinking_budget.
account_email: форсировать конкретный Google-аккаунт.
account_tag: форсировать аккаунты с указанным тэгом.
Returns:
{"text": str, "usage": {"input_tokens": int, "output_tokens": int}}.
Raises:
MCPError: если ключ невалиден, Google не подключён, или квота исчерпана.
输入参数systemmessages必填max_tokensaccount_tagaccount_emailthinking_levelthinking_budget
Запрос к Claude Sonnet 4.6 через вашу подписку Anthropic (если подключена) или
через подписку Google (Vertex). Поддерживает инструменты (tool_use).
Args:
messages: список {"role": "user|assistant", "content": "..."}.
system: системный промпт (опционально).
max_tokens: максимальное количество токенов в ответе.
tools: описание инструментов в формате Anthropic tool_use (опционально).
account_email: форсировать конкретный Google-аккаунт.
account_tag: форсировать аккаунты с указанным тэгом.
Returns:
{"text": str, "usage": {...}}. Если модель решила звать инструмент,
дополнительно вернётся "tool_calls": [...].
Raises:
MCPError: если ключ невалиден, подписка не подключена, или квота исчерпана.
输入参数toolssystemmessages必填max_tokensaccount_tagaccount_email
Какие LLM-модели доступны этому ключу прямо сейчас, и какая у ключа квота.
Делает два параллельных запроса: GET /llm/models (OmniRoute — список моделей и
подключённых Google-аккаунтов) и GET /v1/me (openFlow — plan + квоты). ETag от
/v1/me держится OmniRoute'ом в кэше ~5 минут; force_refresh=True добавляет
Cache-Control: no-cache, чтобы заставить обновить.
Returns:
{
"accounts": [{"email": "...", "credits": int, "models": [...]}, ...],
"models": [{"id": "gemini-3-pro", "label": "..."}, ...],
"quota": {"llm_rpm": int, "llm_tokens_per_day": int, "plan": "..."}
}
Raises:
MCPError: ключ невалиден.
输入参数force_refresh
Получить актуальный список LLM-моделей из каталога OmniRoute.
OmniRoute сам опрашивает Google раз в час; `force_live=True` просит релей
сходить в Google прямо сейчас (жёсткий rate-limit на стороне релея:
не чаще 10 секунд). Полезно, когда клиент видит 404 на недавно
добавленную модель.
Args:
force_live: True — передать `?live=1` на /v1/models и дождаться
свежего каталога; False (по умолчанию) — вернуть кэш.
Returns:
{
"models": [...], # OpenAI-совместимый список
"aliases": {"gemini-flash": "gemini-3.5-flash-lite", ...},
"last_refreshed_at": float, # unix timestamp
"source": "cache" | "live",
}
Raises:
MCPError: ключ невалиден или OmniRoute недоступен.
输入参数force_live