Video v2.0 Flash

Turn a still render into architectural footage. v2.0 Flash animates your image at 768p with a directed camera move, controllable scene activity and synchronized ambient sound — the same engine that drives Animate in the mnml studio.

How it works: you send a start frame and, optionally, a prompt and a camera move. The engine composes a cinematography brief around them — the camera beats, the in-scene motion, a stability tail and a sound clause — and renders a 10 or 15 second clip. Generation is asynchronous: you get a request ID back immediately and poll for the result.

Flash vs Cinematic:

  • • Output size: Flash renders 768p; Cinematic renders the 1080p refinement of the same source
  • • Cost: 1 credit per second, against Cinematic's 2
  • • Everything else is identical: the same parameters, the same defaults, the same camera vocabulary, the same clip lengths, the same end-frame morph

Output size is the only difference, which is why there is no resolution parameter: the endpoint you pick is the resolution you get.

Pricing

Video is billed per second, so a shorter clip costs less. Flash charges 1 credit per second of finished footage.

Clip lengthFlash (this endpoint)Cinematic
10 seconds (default)10 credits20 credits
15 seconds15 credits30 credits

An end frame is free. Supplying end_image turns the run into a first-to-last-frame morph at no extra cost — the price reads only the tier and the length.

Endpoint

HTTP Method
POST https://api.mnmlai.dev/v1/video-v20-flash

Request

Send a POST request with multipart/form-data. A start frame is the only required field — everything else has a working default.

Parameters

ParameterTypeDefaultDescription
imageRequiredFile-The start frame (JPEG, PNG, WebP). Max 15MB, min 1KB. Send this or image_url.
image_urlString-A publicly reachable URL for the start frame, instead of uploading bytes. See the note under Frame Requirements about which checks run on a URL.
promptRecommendedStringemptyWhat should happen in the clip. Optional — with no prompt, the camera and motion settings carry the direction on their own. Max 2000 characters.
raw_promptString-Bypasses prompt composition entirely and sends your text to the model verbatim. prompt, camera_movement and motion are ignored when this is set.
durationInteger10Clip length in seconds. 10 or 15. Any other value renders — and is billed as — a 10 second clip.
camera_movementStringstaticHow the camera moves. A preset name, a raw move token, or two tokens comma-joined. See Camera Movement.
motionStringsubtleHow much happens in the scene, independent of the camera. One of auto, subtle, balanced, dynamic.
end_imageFile-An optional end frame. Supplying one renders a smooth morph from the start frame to this one instead of a free animation. Free — it does not change the price.
end_image_urlString-A publicly reachable URL for the end frame, instead of uploading bytes.
seedIntegerrandomReuse a seed to get a repeatable result from the same inputs. Returned in the response either way.

Parameters this engine does not have

  • • No negative_prompt. This engine takes no exclusion list. The stability direction that other engines put in a negative prompt is written into the composed prompt as positive assertions instead. Sending the field has no effect.
  • • No aspect_ratio. The clip takes its shape from the start frame. Crop the frame to change the shape of the video.
  • • No resolution. The endpoint is the resolution — 768p here, 1080p on Cinematic.
  • • No audio switch. Every clip is scored with quiet ambience matched to the scene. The composed prompt bounds it to room tone and outdoor air — no music, voices or narration.

Camera Movement

The simplest way to set the shot is a preset name:

PresetThe shot
autoNo camera instruction at all — your prompt decides. Falls back to static if you sent no prompt.
staticLocked-off camera; only the scene moves. The default.
walkthroughTracks right while panning left — moving through a space.
room-showcaseClockwise orbit around the subject, holding centre focus.
drone-viewRises vertically, then tilts down toward the ground plane.
pull-backPulls back to reveal the full scene, then tilts up.
hero-shotPushes in on the subject, then tilts up the elevation.
detail-focusSlow optical zoom in, tightening the framing.

For finer control, send raw move tokens instead. Up to two, comma-joined, and they run as beats in the order you write them (“dolly in, then tilt up”). A third token is dropped: stacked moves beyond two resolve unpredictably, so the engine will not sell you one.

Move tokens
dolly-in      dolly-out
truck-left    truck-right
pedestal-up   pedestal-down
pan-left      pan-right
tilt-up       tilt-down
roll-left     roll-right
zoom-in       zoom-out
orbit-left    orbit-right
static

Migrating from Video AI? That endpoint's vocabulary — pan_left, pan_right, tilt_up, tilt_down, zoom_in, zoom_out, static — is accepted here too and maps to the equivalent move, so an existing request keeps its shot.

How camera and motion interact

  • • At the default motion=subtle, choosing a camera move drops the scene-activity direction: the move carries the shot on its own. Asking for a dolly and for near-motionless scenery at the same time reads as two competing briefs, and the weaker one tends to win.
  • • At balanced or dynamic, both ship — that combination is a real request (a drone move over a street with traffic in it).
  • • With camera_movement=static, scene cues always ship. They are the only motion the clip has.
  • • auto on either field suppresses that field's direction so your prompt has an empty stage — but only if you actually sent a prompt.

Frame Requirements

RuleWhat happens
Aspect ratio between 1:2.5 and 2.5:1Enforced. A frame outside this range is refused with INVALID_FRAME_ASPECT_RATIO before any credits are spent, and the message names the ratio it measured. Panoramic sources are the usual cause — crop, or extend the canvas toward a squarer shape.
Large framesHandled for you. Frames are downscaled to a 2048px long edge before submit. Nothing above that can reach a 768p or 1080p clip, so there is no quality cost — an 8K upscale is accepted and fitted rather than refused.
Format and sizeJPEG, PNG or WebP; 1KB to 15MB per frame.
Morph framesWhen you send an end frame, both frames are checked. For the smoothest morph, give the two frames the same dimensions — the model interpolates between them pixel for pixel.

URLs are not measured locally. The aspect check runs on uploaded bytes. A frame sent as image_url or end_image_url is passed through and judged by the renderer, so an out-of-range URL frame fails later instead of being refused up front — your credits are returned automatically (see Refunds), but you get the answer a minute later rather than immediately. Upload the bytes if you want the fast, free check.

Response

202 — job accepted
{
  "status": "success",
  "id": "a1b2c3d4-5e6f-7890-abcd-ef1234567890",
  "seed": 482913,
  "prompt": "morning light sweeps across the facade",
  "parameters": {
    "tier": "flash",
    "duration": 10,
    "resolution": "768P",
    "creditsPerSecond": 1,
    "cameraMovement": "pedestal-up,tilt-down",
    "motion": "subtle",
    "shape": "image-to-video",
    "audio": "ambient"
  },
  "cost": 10,
  "credits": 1240
}

cost is what this clip was charged and credits is your balance afterwards. The video itself is not ready yet — poll the status endpoint with the returned id.

Examples

cURL — animate a render
curl -X POST https://api.mnmlai.dev/v1/video-v20-flash \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -F "[email protected]" \
  -F "prompt=morning light sweeps across the facade as the city wakes up" \
  -F "camera_movement=drone-view" \
  -F "motion=balanced" \
  -F "duration=10"
cURL — morph between two frames
curl -X POST https://api.mnmlai.dev/v1/video-v20-flash \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -F "[email protected]" \
  -F "[email protected]" \
  -F "prompt=the building transitions from day to night" \
  -F "duration=15"
JavaScript — submit and poll
const form = new FormData();
form.append("image", fs.createReadStream("render.jpg"));
form.append("prompt", "camera glides through the entrance into the atrium");
form.append("camera_movement", "walkthrough");
form.append("motion", "balanced");
form.append("duration", "15");

const res = await fetch("https://api.mnmlai.dev/v1/video-v20-flash", {
  method: "POST",
  headers: { Authorization: `Bearer ${process.env.MNML_API_KEY}` },
  body: form,
});

const { id, cost } = await res.json();
console.log(`Job ${id} started — ${cost} credits`);

// Poll until the clip is ready.
let result;
do {
  await new Promise((r) => setTimeout(r, 5000));
  result = await fetch(`https://api.mnmlai.dev/v1/status/${id}`, {
    headers: { Authorization: `Bearer ${process.env.MNML_API_KEY}` },
  }).then((r) => r.json());
} while (result.status === "processing");

console.log(result.message[0]); // the video URL
Python — submit and poll
import os, time, requests

API = "https://api.mnmlai.dev/v1"
headers = {"Authorization": f"Bearer {os.environ['MNML_API_KEY']}"}

with open("render.jpg", "rb") as f:
    res = requests.post(
        f"{API}/video-v20-flash",
        headers=headers,
        files={"image": f},
        data={
            "prompt": "late afternoon light rakes across the courtyard",
            "camera_movement": "hero-shot",
            "motion": "subtle",
            "duration": "10",
        },
    ).json()

job_id = res["id"]
print(f"Job {job_id} started — {res['cost']} credits")

while True:
    time.sleep(5)
    status = requests.get(f"{API}/status/{job_id}", headers=headers).json()
    if status["status"] != "processing":
        break

print(status["message"][0])  # the video URL

Checking Processing Status

Status Check Endpoint
GET https://api.mnmlai.dev/v1/status/{id}
Completed job
{
  "status": "success",
  "message": ["https://api.mnmlai.dev/api/v1/images/files/clip.mp4"],
  "seed": 482913
}

Processing time: video takes substantially longer than an image render — expect a few minutes for a 10 second clip, longer for 15. Poll every 5–10 seconds until the status is "success" or "failed". The returned URL serves an MP4 and supports range requests, so it plays directly in a browser or video element.

Refunds

A clip you cannot collect is refunded automatically. If the render fails, or completes without producing a video, the exact number of credits this clip was charged is returned to your balance — once, even if the failure is reported more than once. You do not need to ask for it, and there is nothing to reconcile: a 15 second Cinematic clip refunds 30, a 10 second Flash clip refunds 10.

Best Practices

Choosing a tier

  • Use Flash for iteration, client previews, social and anything that will be watched at small size. Half the price, and the composition is identical.
  • Use Cinematic for final delivery, full-screen playback and pitch material — anywhere the extra resolution will actually be seen.
  • Draft on Flash with a fixed seed, then re-run the same request on Cinematic once you like the shot.

Frame guidelines

  • Start from a clean, sharp render — video amplifies artefacts in the source frame.
  • Keep the aspect between 1:2.5 and 2.5:1. Crop panoramas before sending them.
  • Upload bytes rather than a URL when you can: the aspect check then runs instantly and for free.
  • For a morph, match the two frames' dimensions and keep the same subject in roughly the same place.

Prompt tips

  • Describe visible motion, not adjectives: “clouds drift behind the tower, pedestrians cross the plaza” beats “dynamic and cinematic”.
  • Let one thing direct the camera. If you are describing the move in words, set camera_movement=auto so the preset does not compete with your sentence.
  • Do not ask for a shot the source cannot support — an orbit needs a subject with sides, not a flat elevation.
  • Reach for raw_prompt only when you want full control; you lose the stability and sound direction the composed prompt adds.

Error Handling

CodeStatusMeaning
MISSING_IMAGE400No start frame. Send image or image_url.
INVALID_FRAME_ASPECT_RATIO400A frame is outside 1:2.5–2.5:1. The message names the measured ratio; details.frame says which frame.
INVALID_IMAGE_TYPE400Not a JPEG, PNG or WebP.
IMAGE_TOO_LARGE / IMAGE_TOO_SMALL400A frame is outside 1KB–15MB.
INVALID_MOTION400motion is not one of auto, subtle, balanced, dynamic.
PROMPT_TOO_LONG400Over 2000 characters.
NO_CREDITS400Balance below this clip's price. details.required gives the price and details.duration the length it was quoted for.
INVALID_REQUEST_BODY400The body was not multipart/form-data.
USER_NOT_FOUND400The API key does not resolve to an account.
INTERNAL_SERVER_ERROR500Something failed on our side. Nothing is charged.

Every 400 above is returned before any credits are spent. See Error Handling for the shared error envelope.