Pro API: generate videos with sound

A clip from a prompt, or an animation of a still you supply. The file is an MP4, with sound when you ask for it.

Submit a video

POST /videos

json
{ "prompt": "a woman in a white linen dress walks along a beach at sunset, wind in her hair, she turns and smiles at the camera",
  "width": 1080, "height": 1920, "audio": true }
FieldTypeDefaultWhat it does
promptstringrequiredThe action, the mood, the light. Describe the movement.
width, heightint720 x 1280Vertical by default. See the price grid below.
qualitystringmax: Ultra mode (the finest render), see Ultra mode.
consistencystringstandardenhanced: enhanced consistency, steadier movement, faces and background over time, up to 721 frames per shot (30 s at 24 fps, 24 s at 30), extensions included. Quality mode only. See Enhanced consistency.
modestringltx25ltx25: engine 2.5 (sound, automatic duration, 1080p, end frame). turbo: retired on 11 October 2026; a turbo request is rendered in Quality 480p (same orientation, same length, 241 frames at most, fixed length), at the Quality 480p price.
duration_autobooltrueThe engine picks the length from the prompt, up to 20 s at 24 fps (16 s at 30, 8 s at 60: the cap counts frames, see fps), and length is then ignored. Set false for a fixed length, given by length. With consistency enhanced above 30 fps, the length is always fixed (fps_requires_fixed_duration otherwise).
lengthint121Length in frames, at the fps rate, always of the form 8n+1; the duration is (length - 1) / fps. 481 at most (721 with consistency enhanced). At 24 fps: 73 (3 s), 121 (5 s), 241 (10 s), 361 (15 s), 481 (20 s; 721, that is 30 s, in enhanced). At 30: 241 (8 s), 481 (16 s; 721, 24 s). At 60: 241 (4 s), 481 (8 s; 721, 12 s). Used for a fixed length (duration_auto set to false); ignored with automatic duration.
fpsint30Frame rate, in frames per second: 24, 30 or 60. 30 by default, the recommended setting (crisp movement); 60, the smoothest; 24, the cinema rate. 50, with consistency enhanced only, gives a slight slow motion (about 0.83x: the engine sets the movement as for 60 and plays it at 50). length counts frames at this rate. The price does not change (flat rate per clip); the maximum length does, since it counts frames. An extension keeps the frame rate of the previous clip, a sequence that of its first shot. A rate the mode does not have: invalid_fps, which lists the ones it has.
audioboolfalseSound generated along with the picture (ambience, voice). Doubles the price of the clip (see Formats and price).
imagestringStart frame in base64: the video animates this image.
end_imagestringEnd frame in base64, alongside image: the clip transitions from one to the other (engine 2.5 only). A transition comes out at the requested size, from 480p to 1080p, and is billed as a clip of that format.
keyframesarrayKeyframes (Quality mode): 1 to 4 objects { "image": base64, "at": seconds, "strength": 0.1 to 1 (1 by default) }. The clip shows each image at its moment, for example a character in the middle of a shot that starts and ends on an empty location. The length becomes fixed (length, duration_auto forced to false); 0 < at < length, at least 0.5 s between two keyframes; accurate to about a third of a second. Works with image and end_image. Not in Ultra or Multi-Ref; in an extension, only with consistency enhanced (see Enhanced consistency). Same price as a clip of the same format.
lora, lora_strengthstring, numberA trained character on the account (name from GET /loras) and its strength (0 to 1).
lora2, lora2_strengthstring, numberA second character.
multirefobjectCast 1 to 4 characters (12 in Ultra) from GET /characters, plus saved objects, animals and a location from GET /subjects, called by @Name in the prompt, see Multi-Ref Video.
seedintrandomSeed of the render, from 0 to 4294967295. Same seed, same prompt, same image and same settings (format, length, sound, mode): the same clip, with slight differences if the graphics card changes. With automatic duration, a length chosen differently changes the clip: for a strict reproduction, set duration_auto to false and give length.
stepsintengineLeave empty.

Default frame rate: 30 fps. Clips used to be rendered at 24 fps. Without fps, a clip of the same length is therefore shorter than before (241 frames: 8 s instead of 10 s). Send "fps": 24 to keep the former behaviour.

The answer:

json
{ "jobId": "...", "seed": 908172, "cost": 30 }

The cost is computed and announced at submission, before anything is charged. The seed is returned too, whether you gave it or it was drawn at random.

Formats and price

A video is priced per format, as a flat rate independent of its length. Base: 5 credits.

The flat rate is multiplied by 2 at 720p and by 3 at 1080p, and then sound doubles the price. The factors apply to the base rate, not to each other: 1080p with sound costs 5 x 3 x 2 = 30 credits. Up to 481 frames, that is 20 s at 24 fps, length changes nothing (5 s or 20 s, same rate). Beyond 481 frames (up to 721, with consistency enhanced only, fixed length), the price of the format is doubled. The frame rate (fps) does not change the price, but the tiers count frames: at 30 fps, the normal price goes up to 16 s; at 60, up to 8 s (12 s at double the price in enhanced).

One exception: in Quality, 480p without sound costs 2 credits, whatever its length (since 11 October 2026). Multi-Ref and enhanced consistency keep the flat rate of the format, 5 credits.

FormatShort sideExample sizesSilentWith sound
480p480480 x 832, 832 x 4802 (5 in Multi-Ref and enhanced consistency)10
720p720720 x 1280, 1280 x 7201020
1080p10801080 x 1920, 1920 x 10801530

The size you get back. The engine works in multiples of 32 and in two stages, so the file comes out at 768 x 1280 (or 1280 x 768) for 720p and 1088 x 1920 (or 1920 x 1088) for 1080p. 480p stays at 480 x 832 (or 832 x 480), except in enhanced consistency. Worth knowing if you stitch or overlay our renders.

The turbo lane was retired on 11 October 2026: a turbo request is rendered in Quality 480p and priced like it (2 credits without sound, 10 with sound).

Know the price before you submit

POST /videos/quote takes the same body as POST /videos and runs the same calculation, but nothing is created: no render, no credit held, no quota consumed. Call it before you commit, especially from an agent working against a budget.

json
{ "cost": 30, "mode": "ltx25", "format": "1080p", "audio": true, "fps": 30, "duration_auto": true,
  "seconds_max": 16, "detail": "flat 5 x 3 (1080p) x 2 (sound) = 30" }

detail spells out the figure; fps is the frame rate used, and seconds_max the length in seconds at that rate. With "consistency": "enhanced", the quote accepts a fixed length up to 721 frames and applies the rate beyond 481 frames (x 2 (over 20 s) in detail at 24 fps, x 2 (over 481 frames) at another rate). The same route exists for images, POST /images/quote, which always answers { "cost": 1, ... }: an image always costs 1 credit.

For an Ultra clip billed per second, the quote gives the billed length in seconds, and detail spells out the price per second.

Poll, download, cancel

The same routes as images, under /videos.

  • GET /videos/{jobId}: pending / completed / failed, same fields. An enhanced extension can end as failed with "error": "extension_not_applied" (the engine did not take up the end of the previous clip): nothing is charged, submit it again.
  • GET /videos/{jobId}/file: the MP4 (Content-Type: video/mp4).
  • POST /videos/{jobId}/cancel: cancels, and nothing is charged.

In Ultra billed per second, a render that is already computing no longer stops: the cancel answers 409 render_in_progress, and the clip is delivered and billed. A clip the engine refuses on output ends as failed, with a sentence in detail and the engine's code in reason: nothing is charged.

A video takes 1 to 5 minutes, longer when the engine starts cold. Poll every 3 seconds and download as soon as the status is completed: the file is not archived.

Several shots in one clip

A single prompt can hold several shots with hard cuts, up to 20 seconds in all at 24 fps (16 s at 30, the default). Describe them in order: Shot 1: <framing>, <action>. Cut to Shot 2: .... Repeat in every shot what has to stay the same (character, outfit, place, light), and end with one line of shared mood.

Leave out timecodes: keep duration_auto on, and the engine picks the length (up to 20 s at 24 fps, 16 s at 30) and where to cut. Two to four shots per clip work best.

text
A cinematic sequence of three shots, same woman, same place, same lighting throughout.
Shot 1: Wide shot, night, light rain on a narrow Paris street with glowing café windows. A woman in a long beige trench coat walks toward the camera under a transparent umbrella, wet cobblestones reflecting warm yellow streetlights.
Cut to Shot 2: Close-up on her face under the umbrella, raindrops on the plastic, warm café light on her cheek, she smiles softly.
Cut to Shot 3: Medium shot from inside the café through the rainy window, she pushes the door open and steps in, warm light spilling onto the wet pavement.
Calm, melancholic, romantic mood.

Several shots in one clip, or a sequence? They do different things:

Several shots in one clipSequence (extensions)
Between shotsHard cutsNo cut: each shot carries the movement on
Length20 s at most at 24 fps (16 s at 30)Longer
RendersOneOne per shot
PriceOne clipOne clip per shot

Extend a clip

A clip can be extended: the next shot carries on the movement of the previous one, with no stop at the join. It is guided by roughly the last second of the previous clip, not by a single still frame. Add to POST /videos:

FieldTypeDefaultWhat it does
extend_job_idstringThe clip to extend: a render from your licence, taken shortly after it finishes.
extend_framesint25How much of the end of the clip guides the next shot, in frames: 9, 17, 25, 33, 41 or 49 (25 is about 1 s at 24 fps).
extend_videostringInstead of extend_job_id: the end of a clip you have yourself, as a base64 MP4 of exactly extend_frames frames, 8 MB at most.

The extended shot keeps the resolution of the previous clip: width and height are ignored with extend_job_id, and with extend_video the size is read from the file you send. It holds only the new frames, so the shots laid end to end form one continuous video. It keeps the frame rate of the previous clip: leave out fps (it takes it over) or give the same one; another one is refused (extend_fps_mismatch). length is the new duration of the shot (121 = 5 s more at 24 fps), and it is exact: automatic duration does not apply to an extension. An extension costs the price of a clip.

Limits: no end frame or keyframes at the same time, except with enhanced consistency.

The sound carries on at the join. With audio: true, when the end of the previous clip has a sound track, the new shot picks up that sound instead of making up a new one. With extend_job_id, the cut end keeps the clip's sound; with extend_video, send the end with its sound. Without a sound track in that end, in enhanced consistency and in Ultra, the sound starts fresh with the shot: keep the same ambience in every prompt, so the join is heard as little as possible.

Extend a clip shortly after it finishes. Later on it is no longer available to us (clip_unavailable), and you send its end yourself in extend_video.

Extend an Ultra shot, forward or backward

A shot rendered in Ultra per second is extended in Ultra too, forward or backward; any other clip is extended as above. Send POST /videos with "quality": "max" and:

FieldTypeDefaultWhat it does
extend_job_idstringThe Ultra shot to extend, rendered by your licence less than 24 hours ago.
extend_upload_idstringInstead, for an older shot: the id returned by POST /extensions (below), once its original file is uploaded.
extend_directionstringforwardforward renders what comes next; backward renders the moments just before the shot, to cut in front of it.
  • The render holds only the new part, as long as an Ultra clip (length / fps, 5 s with the automatic length). It takes the resolution of the shot (width and height are ignored) and joins its last frame (forward) or its first (backward).
  • No start image, references (multiref), motion or enhanced consistency. backward does not go into a sequence, which files its shots one after the other.
  • Price: the new part at the Ultra price per second, plus, per second of the shot you extend, 2 credits in 480p, 5 in 720p and 13 in 1080p (ultra.extend_per_second in GET /pricing). Those seconds are rounded up to the next second (to within 0.1 s) and count at 24 frames per second: a shot at a higher frame rate counts pro rata (2 s at 60 fps count as 5 s). POST /videos/quote with the same fields gives extend_seconds and the exact price.
  • A shot older than 24 hours: upload its original file, as delivered by GET /videos/{jobId}/file. POST /extensions with { "size": <bytes> } returns upload_id, upload_url, headers and expires_in (900 s); send the file with PUT upload_url and exactly those headers, then pass extend_upload_id within 2 hours. An upload serves one render: an MP4 of 4 to 30 s, 60 MB and 1080p at most. Before the render, every video sent to the Ultra mode (a motion or a video to extend) is checked, and nothing is charged if it is refused (400 input_video_out_of_limits): 24 to 60 frames per second, a width/height ratio of 0.4 to 2.5 and, for one render, 30 s and 10 files at most for the videos and reference sounds together. For a realistic face, a re-encoded or edited file is refused (400 engine_refused): only the original goes through. A video the Ultra mode did not render (a render from another mode, for example) is not extended in Ultra when it shows a realistic face (400 extension_real_face, nothing is charged): extend it in Quality mode, or redo the shot in Ultra.

Enhanced consistency

With "consistency": "enhanced", the clip is rendered by a lane that holds movement, faces and background steadier over time. It goes up to 721 frames per shot, that is 30 s at 24 fps and 24 s at 30 (length, fixed length) and is mostly meant for long videos:

  • First shot: a normal clip (prompt, start frame, keyframes, end_image), up to 721 frames.
  • Extension: extend_job_id (or extend_video) as in Extend a clip, with the same consistency. length is still the new duration (up to 721, that is 30 s more at 24 fps). An enhanced extension also takes keyframes, whose at counts from the first new frame, and an end frame end_image, set on the last frame of the shot: for example the start frame of the first shot, so that a long video loops.

Quality mode only (mode ltx25, without quality: "max"), up to 1080p, not in Multi-Ref (400 consistency_mode). Price: the flat rate of the format (480p without sound: 5 credits) up to 481 new frames (20 s at 24 fps), doubled beyond (see the price grid above). Frame rates: 24, 30, 50 or 60. Above 30, the length is fixed (fps_requires_fixed_duration otherwise); 50 gives a slight slow motion (see fps). The render takes longer than a standard clip, and its start may wait for a larger graphics card (long shots). A sequence keeps the same consistency for all its shots. Size delivered: the same as in Quality at 720p and 1080p; at 480p, the short side comes out at 448 (448 x 832, 832 x 448, 448 x 448), as this lane works in multiples of 64.

Some prompts are not supported yet. For them, a single shot of 20 s or less (15 s with an end_image, at a fixed length) is rendered in standard consistency, at the same price. Beyond that (length above 481 with duration_auto: false, or above 361 with an end_image), in an extension or in a sequence, the request is refused with 400 consistency_explicit: shorten the shot or ask for consistency: "standard".

Long video (sequences)

A clip lasts 481 frames at most, that is 20 s at 24 fps (721 with enhanced consistency, 30 s at 24 fps). For longer, a sequence files each shot as its render finishes, then joins them into a single MP4 without re-encoding. One minute, for example, is 3 shots of 20 s, or 2 shots of 30 s in enhanced consistency, at 24 fps.

  1. POST /sequences opens a sequence and answers { "sequence_id": "seq_...", ... }.
  2. POST /videos with "sequence_id": "seq_..." for each shot: the first one as usual, the next ones with extend_job_id set to the previous shot. Each shot is filed in the sequence, in submission order, as soon as GET /videos/{jobId} sees it finished.
  3. POST /sequences/{id}/export once the last shot is finished. It answers with seconds, resolution and file_url.
  4. GET /sequences/{id}/file: the assembled video (MP4).

Alongside: GET /sequences/{id} gives the state (shots filed or still expected), POST /sequences/{id}/clips with { "job_id": "..." } files a render by hand, and DELETE /sequences/{id} deletes the sequence at once.

Rules. Every shot has the same resolution (extensions keep it) and the same sound choice, all with sound or all silent, the same consistency and the same frame rate (fps): the first shot sets them, and a shot that differs is refused (409 consistency_mismatch, 409 sequence_fps_mismatch; 422 encoding_mismatch for a shot filed by hand). A shot without fps takes the sequence's. At most 30 shots per sequence and 5 sequences in progress per licence.

What we keep, and for how long. The shots of a sequence in progress are erased 30 minutes after the last one was added, and the assembled video 24 hours after the export. Then everything is gone.

Export the project folder

A project folder gathers in a ZIP everything needed to pick a video up elsewhere or to automate it: the shots, their references, the edit, and a manifest that can render each shot again. It is free: nothing is rendered.

  • POST /videos/{jobId}/export: one render of POST /videos (single shot, Multi-Ref, extension).
  • POST /sequences/{id}/project: every shot of a sequence, end to end.
  • Optional body, the same choices as the app's export window:
    • include_media (true by default): the videos of the shots and their signed originals. false: no video at all (no clip, no original, no film); the shots keep their text, settings and references ("state": "expired") and the manifest carries videos_included: false.
    • include_references (true by default): the views of the characters and the images of the elements, in references/. false: none is copied; their names, @mentions, id and fingerprints stay in the manifest (references_included: false).
    • exclude_references: the references not to copy, one by one, by id (character_id, subject_id), name or @mention, for example ["@Parc", "chr_..."] (200 at most). The manifest carries references_included: "partial" and "included": false on each one left out (false if all are). An entry that names no reference of the export: 400 unknown_reference.
    • include_film (sequence only, false by default): the film of a sequence already assembled; it needs include_media (otherwise 400 invalid_export_options, like an option that is not a boolean, or an exclude_references that is not a list of texts).
    • manifest_only: true: the manifest alone, without a ZIP.

The answer is { "manifest": { ... }, "file_url": "...", "export_id": "exp_...", "expires_at": "...", "bytes": n }, and GET /exports/{id}/file downloads the ZIP. One export at a time per licence (409 export_in_progress), 2 GB at most (413 export_too_large: export the shots one by one).

FileWhat it holds
tendre-project.jsonThe manifest (schema tendre.project/1, English keys). For each shot: its state, the prompt you typed (prompt) and the one sent to the engine (prompt_sent), the settings, the seed, the cost, the extension (extends, direction), the references linked to their files, and api_request, the POST /videos body that renders the shot again (null if an image was only sent in base64); at its head, references_included: true in the ZIP (the reference files are in references/), false with manifest_only (references without files: files empty, sha256 fingerprints only); a manifest without this field (older exports) reads as true. A folder exported by the app, where the client picks the references one by one, carries true, false or "partial", and each reference may carry included (absent: like the head, false if the head is false, otherwise true); a reference left out keeps its name, @mention, id and description, with files empty. project.settings holds the settings of the first shot (quality, width, height, fps, audio, output_format "mp4") and settings_from_shot, that shot's id. A folder exported by the app may hold an app block reserved for the app: ignore it
shots/NN/clip.mp4The render of each shot, while the server still has it; shots/NN/original.mp4 or .mov: the engine's signed file, byte for byte, if it still exists
references/The views of the characters and the images of the locations, objects, animals, styles and outfits used
timeline.otio, timeline.fcpxmlThe edit (OpenTimelineIO; FCPXML for Final Cut Pro and DaVinci Resolve), media by path relative to the folder
film.mp4The assembled video of a sequence, only with include_film: true and if it was already assembled (POST /sequences/{id}/export); otherwise assembled is null in the manifest

What we keep, and for how long. For each accepted POST /videos, the server keeps a record of the render without any image or video: the text, the settings, the identifiers (an image sent in base64 only leaves its fingerprint). It is erased 24 hours after submission; past that, the export answers 404 unknown_export. The ZIP is erased 45 minutes after its first download, 2 hours after its creation at most. A shot the server no longer has comes out as "state": "expired", without its file but with its text, settings and references: an agent can render it again with api_request.

Sequence. Assembling erases the shots one by one. To get the shots and the film, export the project before assembling, then assemble and export again with { "include_film": true }: the film is added, and the shots then come out as expired.

Ultra mode

The Ultra mode is the finest video render. Add "quality": "max" to POST /videos (with mode ltx25, the default). It is billed per second, or at a fixed price with a LoRA or in an extension of a clip not rendered per second; extending an Ultra shot, forward or backward, is billed per second. POST /videos/quote gives the exact price of your request.

Per second

The clip is billed per second according to its resolution, sound included:

ResolutionPrice
480p4 credits per second
720p8 credits per second
1080p20 credits per second
  • Length: 4 to 30 s at 24 frames per second, known before the render: 5 s with the automatic length (duration_auto, the default), otherwise length / fps (24 when fps is absent), rounded to the second.
  • Format: the one asked, at the nearest ratio; 480p is rendered in 480p. Above 1080p: resolution_outside_ultra.
  • Start image (image): possible. The engine may refuse a text or an image on submission: 400 engine_refused, with its reason in detail and its code in code; nothing is charged.
  • No seed: seed is ignored and the answer gives none.
  • Cancel: free while the render waits its turn; once it computes, 409 render_in_progress, and the clip is delivered and billed.
  • No end image, no keyframes and no enhanced consistency in Ultra.

At a fixed price

With a LoRA or in an extension of a clip not rendered per second, Ultra is rendered with the full video model, at a fixed price:

Format askedSize deliveredFixed price per clip
480p or 720pAs in the table above at 720p (1280 x 768), a 480p request is rendered in 720p40 credits
1080pAs in the table above (1920 x 1088)60 credits

The price is fixed per format: sound and length change nothing. Ultra starts at 720p: a 480p request is rendered, and billed, in 720p. POST /videos/quote gives the exact price. 1080p (1920 x 1088) is the ceiling, in every mode: 4K is no longer offered. A larger size is refused (ultra_4k_unavailable with quality: "max", resolution_above_quality without).

  • Length: 481 frames at most, like any clip (20 s at 24 fps, 16 s at 30, 8 s at 60). Extensions and long videos work in Ultra too. An extension keeps the size of the clip it continues, and below 720p it is billed at the 720p price.
  • Time: several minutes for a 5 s clip, longer for a 20 s clip. The start may wait for a large graphics card to become free.
  • No transition (end_image) in Ultra (ultra_incompatible).
  • No keyframes (keyframes) in Ultra (keyframes_mode).
  • A character LoRA from your account applies in Ultra as in the normal mode.

Multi-Ref Video

A clip where 1 to 4 characters (up to 12 in Ultra) play, each one recognisable, in an optional location, with saved objects, animals and a saved location when you need them. The characters come from GET /characters: made from one photo through the API, or in the app. The objects, animals and locations come from GET /subjects. Add the multiref object to POST /videos (permission videos) and call each character by @Name in the prompt:

json
{
  "prompt": "@Lea walks toward @Tom in a sunny kitchen and hands him a cup. Sound: footsteps, soft music.",
  "multiref": {
    "characters": [
      { "character_id": "chr_8f3a...", "name": "Lea", "description": "a 28 year old woman with long red hair" },
      { "character_id": "chr_21bc...", "name": "Tom", "description": "a 30 year old man", "strength": 1.2 }
    ],
    "background_b64": "<optional: an image of the location>",
    "background_description": "a sunny kitchen"
  },
  "width": 1088, "height": 1920,
  "audio": true
}
Field of multirefWhat it does
characters[].character_idA character from GET /characters
characters[].nameOne word (letters, digits, _), the one written after @ in the prompt
characters[].descriptionA short description in English; by default, its kind
characters[].strengthWeight of the likeness, 0 to 2 (1 by default)
characters[].sheet_idOptional: which sheet to use for a character that has several (the most recent by default)
characters[].voiceWith "audio": true, characters[0] and characters[1]: character (the default) is the voice of its profile when it is online (has_voice on the character), otherwise a voice made by the model; generated asks for a voice made by the model. The answer says which one was used (voices).
characters[].voice_b64Optional, characters[0] and characters[1] only, with "audio": true: another voice for the character, an audio file (WAV, MP3, OGG, FLAC) in base64, 4 MB at most (a short excerpt is enough), which takes precedence over the profile's
background_b64, background_descriptionOptional location: an image and a few English words
  • Prompt in English, one paragraph in the present tense; Sound: for the sound, lines of dialogue in quotes, exactly 2 people and where each one stands when it helps, no negations. A creature is described by what it is. An @Name that matches no character is refused (unknown_mention).
  • Formats (width x height): portrait 480 x 832, 720 x 1280, 1088 x 1920; landscape 832 x 480, 1280 x 720, 1920 x 1088; square 480 x 480, 720 x 720, 1088 x 1088. No 4K; 480p in Quality only.
  • Modes: Quality (the default), or Ultra (quality: "max"). No extension and no transition.
  • Length: automatic (duration_auto, the default), or fixed with duration_auto: false and length 121 (5 s), 241 (10 s) or 481 (20 s) at 24 fps; at another rate, the same durations counted in frames, 481 at most (at 30: 145, 297, 481; at 60: 297, 481).
  • Frame rate: fps 24, 30 (the default) or 60, as for any clip; length counts frames at this rate, 481 at most (16 s at 30, 8 s at 60).
  • Price: the price of a clip. In Quality, the flat price of the format in the grid above, doubled with sound; 480p without sound stays at 5 credits (the low price of Quality does not apply to Multi-Ref). POST /videos/quote with "multiref": true gives the exact price.
  • The server fits the views to the exact format of the video: send the characters as they are. A character that has only one view is rendered, with warnings limited_likeness in the answer: the likeness is limited.
  • Profile voices: with sound, characters[0] and characters[1] speak with the voice of their profile by default, when it is online (has_voice); voice generated lets the model make one. The answer gives the voice used for each one in voices (character_id, voice: character, custom or generated).
  • Character voices (voice_b64): the model makes the character speak with that voice, lips included; the server takes its first 5 seconds. Two voices at most, on characters[0] and characters[1] (the engine supports 2 voices). Order characters like the dialogue: characters[0] speaks first, characters[1] answers. It needs sound ("audio": true), otherwise voice_requires_audio; a voice on another character: voice_slot; an unreadable file: unsupported_audio_format; over 4 MB: audio_too_large. Only the first 5 seconds are used: send a short excerpt. The whole request body stays under 30 MB (past that, a 413 with no JSON body). No extra charge.
  • Polling, download and cancel are those of any video (see Poll, download, cancel, above).

Saved objects, animals and locations. Two more fields of multiref cast the subjects you saved from your own images:

Field of multirefWhat it does
subjects[]Objects and animals (subject_id, then optional name, description, strength). They share the slots with the characters: 4 references at most in all; in Ultra, up to 30 images in all (see below).
locationOne saved location as the setting (subject_id, then optional name, description, strength): its images, with a strength of its own. It replaces background_b64 (both together: multiref_incompatible). It needs at least one character, object or animal with it.

name defaults to the first word of the subject's name, and each @Name in the prompt is matched like a character's. A location passed in subjects, or an object passed as location, is refused (invalid_subject_type). The price does not change: it is the price of a clip. A subject plays with its first 5 images (all of them in Ultra, 6 at most).

json
"multiref": {
  "characters": [{ "character_id": "chr_8f3a...", "name": "Lea" }],
  "subjects": [{ "subject_id": "sub_1c2d...", "name": "Rex" }],
  "location": { "subject_id": "sub_9e8f...", "name": "Loft" }
},
"prompt": "@Lea throws a ball to @Rex across @Loft. Sound: barking, laughter."

In Ultra, a Multi-Ref clip follows the rules of the Ultra billed per second: the same price per second, sound included, 4 to 30 s at 24 frames per second (5 s with the automatic length), 480p to 1080p, no seed.

  • Each character goes with its character sheet (or, without one, with all its images), assembled by the server into a single image; each object, animal or location with all its images (6 at most).
  • The first render of a character prepares that image, a few tens of seconds more: its jobId then starts with sdp-, and is followed like the others.
  • No voice: voice_b64 and the profile voice are ignored (warnings voice_ignored), and strength has no effect.
  • The engine may refuse a reference image (a real face, for example): the render ends as failed with the reason, and nothing is charged.

Styles, outfits and motion. In Ultra only (quality: "max"), three more fields of multiref; elsewhere the request is refused (multiref_ultra_only), never rendered without them.

Field of multirefWhat it does
styles[]Saved styles (subject_id, then optional name, description): the whole clip takes their look (technique, light, colours, textures), never their content.
outfits[]Saved outfits (same fields, plus worn_by), worn as on their images. worn_by (optional) is the name of one of the characters: it adds "<Name> wears <Outfit>." at the head of the scene. Without it, write it in the prompt: @Lea wears @RedGown.
motionOne motion video of 4 to 30 s: { "motion_id": ... } (uploaded with POST /motions) or { "video_b64": ... } (MP4, MOV, WebM or MKV, 20 MB at most), and an optional description of the movement in English. mode says what the clip takes from it: camera (the default), its camera movement, framing and rhythm; choreography, the body movements, gestures and their rhythm. Never the person or the place of the video; the same price in both modes. The server brings it down to about 410,000 pixels (856 × 482 in 16:9), at 24 frames per second, without sound; its width/height ratio must be between 0.4 and 2.5.
  • 30 images at most in Ultra, characters, subjects, styles, outfits and location together (multiref_too_many_images), with 12 characters at most (multiref_characters_required beyond). 8 are recommended: from 9 to 12, the render goes through with warnings many_characters, as some may be missing or mixed up. A style outside styles, or an outfit outside outfits, is refused (invalid_subject_type).
  • Price of the motion: 3 credits per second of the motion video (rounded up to the next second), on top of the render, read in GET /pricing (ultra.motion_per_second); POST /videos/quote with the same multiref.motion gives motion_seconds and the exact price.
  • Direct upload, for a heavy motion video: POST /motions with { "size": <bytes> } (20 MB at most) returns motion_id, upload_url, headers and expires_in (900 s); send the file with PUT upload_url and exactly those headers, then pass multiref.motion.motion_id to POST /videos within 2 hours. A motion_id serves one render.
json
"quality": "max",
"multiref": {
  "characters": [{ "character_id": "chr_8f3a...", "name": "Lea" }],
  "styles": [{ "subject_id": "sub_a1b2..." }],
  "outfits": [{ "subject_id": "sub_c3d4...", "name": "RedGown", "worn_by": "Lea" }],
  "motion": { "motion_id": "4f0c2a9b1d7e6a35", "description": "a low tracking shot from the side" }
},
"prompt": "@Lea walks along a seaside promenade at sunset."

The error codes of styles, outfits and motion are in Limits and errors.

With the MCP server, the agent does all this with tendre_multiref_video: ask for the scene and name the characters, objects, animals and the location (subjects, location_id).

Face swap

The person filmed in your own video becomes one of your characters: face, hair, body and outfit (or the head alone, see part), keeping their gestures, their lips and the setting, and their voice unless the character has its own (see voice). It is the app's Face swap, on one passage of 20 s at most: one character, with no chaining and no transition. The request is POST /faceswap (permission videos):

json
{
  "video_b64": "<the video in base64: MP4, MOV, WebM or MKV, 20 MB at most>",
  "character_id": "chr_8f3a...",
  "start": 2,
  "duration": 6,
  "scene": "@Lea wears a long red coat.",
  "zone": "normal",
  "likeness": 1
}
FieldWhat it does
video_b64The video, 20 MB at most, with one person clearly visible. Past that, trim or compress it first, or go through the MCP tool tendre_face_swap, which takes up to 95 MB.
character_id, sheet_idA character from GET /characters, and its sheet if it has several (the most recent by default).
nameOptional, 60 characters at most: the character's name, by default the one it has in GET /characters. A character created in the app only has its name online once the app has sent it (app 0.9.1 and later; otherwise name is null in GET /characters): give one to call it @Name in scene, otherwise call it Image 1.
start, durationThe passage, in seconds: from start (0 by default), for duration (by default up to the end of the video, 20 s at most).
sceneOptional, in English, 400 characters at most: what should change or be made precise. The character is called @Name (the first word of its name, see name) or Image 1. Without a scene, it moves exactly like the person on camera. Never make the character speak in it, nor describe its mouth, gaze or head ("says", "looks at the camera", "smiles"): it copies them from the video, and an instruction makes it invent them, with lips out of sync and a turned head. Describe what changes around it: outfit, accessory, light.
descriptionOptional, in English, 360 characters at most: who the character is ("a 28 year old woman with long red hair, wearing a black leather jacket"). By default, its kind and its outfit.
partfull (the default): the whole character. head: only the head (face and hair); the filmed body, clothes and setting stay. The character is then rendered from the three portraits of its character sheet, cropped to the head (heads_only in the answer); a character with one or two views keeps them whole. Its default description gives only its kind, without the outfit, and zone is tight by default.
zoneThe space around the filmed silhouette where the character can be drawn: tight, normal (the default) or wide (room for big ears, wings, a broad build). With part head, it is the space around the head, tight by default.
likenessHow closely the character sticks to its views, from 0.5 to 2 (1 by default).
audiotrue by default: the sound of the video is kept, and the lips follow it. false: a silent video.
voicecharacter (the default): the sound of the video is converted into the voice of the character's profile when it has one online (has_voice in GET /characters), in the same render and without changing its pace, so the lips stay in sync. Without a profile voice, the filmed voice stays. original: keep the filmed voice. The answer says which one was used (voice: character, custom, original, or none without sound).
voice_b64Optional, with sound: another voice for the character (WAV, MP3, OGG, FLAC, 2 MB at most; its first 20 seconds are used), which takes precedence over the profile's. The sound of the video is converted into this voice, without changing its pace.
seedSeed of the render, from 0 to 4294967295, to render it again or vary it.

Video and voice together: 22 MB at most, so that the request body stays under 30 MB (past that, a 413 with no JSON body).

  • The server prepares everything. It cuts the passage, sets it to the working frame rate (30 fps for a source at 29 fps or more, 50 and 60 included, 24 below) and to the working format (multiples of 128, a short side of 768, the ratio closest to your video), and fits the character's views to that format.
  • What you get back is the video of the passage at the working format, about one megapixel (768 x 1408 for a vertical video, for example), with the proportions of your video within 5%. Full resolution and several characters in a row stay specific to the tendre.AI desktop app: it pastes the character back onto the original video at its full resolution, and chains several characters.
  • Price: 6 credits per second of the prepared passage (seconds in the answer), rounded up to the next credit. The answer gives jobId, seed, cost and the prepared passage (width, height, length, fps, seconds, audio, voice, views, part). A character that has only one view is rendered, with warnings limited_likeness.
  • No quote: neither POST /videos/quote nor the MCP's tendre_quote knows Face swap. To work the price out before you submit, read grid.body_swap in GET /pricing: seconds of the passage x per_block_720p (the price of a block) / block_seconds (the length of a block), rounded up to the next credit.
  • Some scenes are rendered from the character's three portraits cropped to the head (heads_only in the answer), and the outfit of its views is no longer imposed: the scene decides.
  • A Face swap counts as a use of the character and pushes back its erasure, like Multi-Ref.
  • Polling, download and cancel are those of any video (see Poll, download, cancel, above): GET /videos/{jobId}, GET /videos/{jobId}/file, POST /videos/{jobId}/cancel.
  • Errors of its own: video_required, video_too_large, unreadable_video, invalid_start, passage_too_short, invalid_duration, invalid_zone, invalid_likeness, invalid_scene, invalid_description, invalid_character_name, invalid_part, invalid_voice_choice, character_required, unknown_character, audio_too_large (a voice past 2 MB), face_swap_too_large (the passage is still too heavy once prepared: shorten duration), server_busy (two videos are already being prepared: retry after Retry-After, 20 s), face_swap_unavailable and face_swap_price_missing (Face swap closed, or without a price: retry later). What each one means: Limits and errors.
  • Your video passes through our servers while it is prepared, then goes to the graphics card that renders it, like any source image.

Only use videos of people who have given their consent: it is a rule of the terms of use.

With the MCP server, the agent does all this with tendre_face_swap.

Windows · Mac · Cloud or local

Generate privately. Tonight.

Download tendre.AI for free and generate without limits, privately, on your own GPU.

OS Windows 10/11GPU NVIDIA 8GB+VRAM 12GB recommendedDisk 20GB

Mac: Apple silicon (M1 or later), macOS 13 or later. Intel Macs are not supported.

Local image generation on Mac needs an M4 with 24 GB of memory, or an M2 or M3 Pro, Max or Ultra with 24 GB. Below that, everything runs online with credits. Retouching and video run online on Mac for now.

Everything about tendre.AI for Mac

No NVIDIA GPU? Start 100% in the cloud: 5 free images and 1 free video every day. An NVIDIA card (8GB+) unlocks unlimited local generation.