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 length | Flash (this endpoint) | Cinematic |
|---|---|---|
10 seconds (default) | 10 credits | 20 credits |
15 seconds | 15 credits | 30 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
POST https://api.mnmlai.dev/v1/video-v20-flashRequest
Send a POST request with multipart/form-data. A start frame is the only required field — everything else has a working default.
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
imageRequired | File | - | The start frame (JPEG, PNG, WebP). Max 15MB, min 1KB. Send this or image_url. |
image_url | String | - | 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. |
promptRecommended | String | empty | What should happen in the clip. Optional — with no prompt, the camera and motion settings carry the direction on their own. Max 2000 characters. |
raw_prompt | String | - | Bypasses prompt composition entirely and sends your text to the model verbatim. prompt, camera_movement and motion are ignored when this is set. |
duration | Integer | 10 | Clip length in seconds. 10 or 15. Any other value renders — and is billed as — a 10 second clip. |
camera_movement | String | static | How the camera moves. A preset name, a raw move token, or two tokens comma-joined. See Camera Movement. |
motion | String | subtle | How much happens in the scene, independent of the camera. One of auto, subtle, balanced, dynamic. |
end_image | File | - | 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_url | String | - | A publicly reachable URL for the end frame, instead of uploading bytes. |
seed | Integer | random | Reuse 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:
| Preset | The shot |
|---|---|
auto | No camera instruction at all — your prompt decides. Falls back to static if you sent no prompt. |
static | Locked-off camera; only the scene moves. The default. |
walkthrough | Tracks right while panning left — moving through a space. |
room-showcase | Clockwise orbit around the subject, holding centre focus. |
drone-view | Rises vertically, then tilts down toward the ground plane. |
pull-back | Pulls back to reveal the full scene, then tilts up. |
hero-shot | Pushes in on the subject, then tilts up the elevation. |
detail-focus | Slow 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.
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
staticMigrating 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
balancedordynamic, 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. - •
autoon either field suppresses that field's direction so your prompt has an empty stage — but only if you actually sent a prompt.
Frame Requirements
| Rule | What happens |
|---|---|
| Aspect ratio between 1:2.5 and 2.5:1 | Enforced. 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 frames | Handled 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 size | JPEG, PNG or WebP; 1KB to 15MB per frame. |
| Morph frames | When 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
{
"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 -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 -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"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 URLimport 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 URLChecking Processing Status
GET https://api.mnmlai.dev/v1/status/{id}{
"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=autoso 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_promptonly when you want full control; you lose the stability and sound direction the composed prompt adds.
Error Handling
| Code | Status | Meaning |
|---|---|---|
MISSING_IMAGE | 400 | No start frame. Send image or image_url. |
INVALID_FRAME_ASPECT_RATIO | 400 | A frame is outside 1:2.5–2.5:1. The message names the measured ratio; details.frame says which frame. |
INVALID_IMAGE_TYPE | 400 | Not a JPEG, PNG or WebP. |
IMAGE_TOO_LARGE / IMAGE_TOO_SMALL | 400 | A frame is outside 1KB–15MB. |
INVALID_MOTION | 400 | motion is not one of auto, subtle, balanced, dynamic. |
PROMPT_TOO_LONG | 400 | Over 2000 characters. |
NO_CREDITS | 400 | Balance below this clip's price. details.required gives the price and details.duration the length it was quoted for. |
INVALID_REQUEST_BODY | 400 | The body was not multipart/form-data. |
USER_NOT_FOUND | 400 | The API key does not resolve to an account. |
INTERNAL_SERVER_ERROR | 500 | Something 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.