Video v2.0 Cinematic
Turn a still render into architectural footage at 1080p. v2.0 Cinematic is the full-resolution tier of our v2.0 video engine — the same directed camera moves, scene control and synchronized ambient sound as v2.0 Flash, rendered at twice the size.
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.
Cinematic vs Flash:
- • Output size: Cinematic renders 1080p — the full-resolution refinement of the same source Flash renders at 768p
- • Cost: 2 credits per second, against Flash's 1
- • 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. A request written for one tier runs unchanged on the other.
Pricing
Video is billed per second, so a shorter clip costs less. Cinematic charges 2 credits per second of finished footage.
| Clip length | Cinematic (this endpoint) | Flash |
|---|---|---|
10 seconds (default) | 20 credits | 10 credits |
15 seconds | 30 credits | 15 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-cinematicRequest
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. 20 renders 15 seconds (and is billed as 15); any other value renders and is billed as 10. |
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 — 1080p here, 768p on Flash. - • 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 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": "the building transitions from day to night",
"parameters": {
"tier": "cinematic",
"duration": 15,
"resolution": "1080P",
"creditsPerSecond": 2,
"cameraMovement": "static",
"motion": "subtle",
"shape": "first-to-last-frame",
"audio": "ambient"
},
"cost": 30,
"credits": 1210
}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-cinematic \
-H "Authorization: Bearer YOUR_API_KEY" \
-F "[email protected]" \
-F "prompt=golden hour light rakes across the facade" \
-F "camera_movement=hero-shot" \
-F "motion=balanced" \
-F "duration=15"curl -X POST https://api.mnmlai.dev/v1/video-v20-cinematic \
-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-cinematic", {
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']}"}
# Draft on Flash, then re-run the SAME request on Cinematic with the
# seed it returned once the shot is right.
def animate(endpoint, seed=None):
with open("render.jpg", "rb") as f:
data = {
"prompt": "late afternoon light rakes across the courtyard",
"camera_movement": "hero-shot",
"motion": "subtle",
"duration": "15",
}
if seed:
data["seed"] = str(seed)
return requests.post(
f"{API}/{endpoint}", headers=headers, files={"image": f}, data=data
).json()
draft = animate("video-v20-flash") # 15 credits
final = animate("video-v20-cinematic", draft["seed"]) # 30 creditsChecking 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, and 1080p takes longer than 768p — expect several minutes for a 15 second Cinematic clip. 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 Cinematic for final delivery, full-screen playback and pitch material — anywhere the extra resolution will actually be seen.
- Use Flash for iteration, client previews and social. Half the price, and the composition is identical.
- Draft on Flash with a fixed
seed, then re-run the same request here once you like the shot — see the Python example above.
Frame guidelines
- Start from a clean, sharp render — 1080p video amplifies artefacts in the source frame more than 768p does.
- 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.