Video generation
Turn an image and a prompt, with optional audio, into an mp4 of up to 15 seconds. No avatar, persona or live session needed.
POST /v1/generation/clips renders a clip of the
person or character in your image, moving the way your prompt describes, and speaking your audio
if you send some. It runs on the Atmee V2 renderer, and you get a downloadable mp4 back in the
same request, or after a retry when the renderer is starting cold.
| Input | Output | Price | Plans |
|---|---|---|---|
| An image and a prompt, audio optional | An mp4 of 1 to 15 seconds at 25 fps | 1 credit per clip | Growth and up, with Atmee V2 access |
Use it for UGC-style ads, product explainers, social posts, spokesperson intros, and character shots for games and stories. Clips work best when a person or character is the subject of the shot; a scene without one, such as a place or a product on its own, can work too, but results vary. For the images that animate badly, see Avatar 101: Negative examples.
Before you start
- Atmee V2 access on a plan with clips. Without V2 access the call answers
v2_access_required; there is no Atmee V1 clip. - A key with
generation:writeandavatars:write. See what every generation call has in common. - A server. Call the API from your backend only; the key never goes to a browser.
- Permission. If the image shows a real person other than you, get that person's permission first.
Make a clip
Get the image and the audio to Atmee
A public https link works: a jpg, png or webp of up to 10 MB, and audio as wav, mp3, webm or
another common format. Otherwise upload them first with
POST /v1/uploads (kind portrait or voice) and
pass upload:<uploadId>. A portrait upload stays usable after the clip, and a generated portrait
works too.
Send the request
curl -X POST https://api.atmanity.us/v1/generation/clips \
-H "X-Api-Key: $ATMEE_API_KEY" \
-H "Idempotency-Key: 6f1c2a9e-3b7d-4c11-9a52-0d8e4f7b2c61" \
-H "Content-Type: application/json" \
-d '{
"image": "https://example.com/host.png",
"prompt": "she holds up the bottle and smiles, the camera slowly pushing in",
"audio": "https://example.com/line.mp3",
"durationSeconds": 12
}'Mint a new UUID for each new clip. The fields are in Request fields.
Retry while it renders
A warm renderer answers within the request. A cold one can take a few minutes, and the request
doesn't wait that long: past about 45 seconds it answers 409 request_in_progress with a
Retry-After header, and the render keeps going. Wait that many seconds and send the same
request with the same key until the clip comes back. You're charged once.
Never mint a new key for a request that is still in progress: the first render may still finish and charge, so a new key can charge twice.
Download the clip
{
"clip": {
"upload": "upload:3b2f6e1a-9c4d-4f8e-a1b2-7c6d5e4f3a2b",
"url": "https://…/clip.mp4",
"expiresAt": "2026-11-05T09:12:44Z",
"durationSeconds": 12,
"width": 512,
"height": 512,
"unavailable": false
},
"replayed": false
}Download clip.url right away and store it yourself: the link is short-lived, and the clip
expires 24 hours after it was made. The answer also reports microcreditsCharged, what the clip cost. A retry with the
same key returns the same result with replayed: true and charges nothing; once the clip has expired, it comes back with
unavailable: true and no url.
Request fields
| Field | Notes |
|---|---|
image | Required. Who or what the clip shows: a public https link or upload:<uploadId> of a portrait upload. |
prompt | Required. The shot and the motion, up to 2,000 characters, such as "she walks along the beach at sunset, the camera following". |
audio | Optional. Speech or sound: a public https link or upload:<uploadId> of a voice upload. It plays once from the start of the clip; without it the clip is silent. |
durationSeconds | 1 to 15. Defaults to 8 for a silent clip. Required with audio, since the length isn't read from the file: longer audio is cut at the end of the clip, shorter audio is padded with silence. |
resolution | Optional. { "width", "height" } in pixels, square 512 × 512 by default. See Sizes. |
seed | Optional. A whole number, 0 or more, default 0. Another seed gives other motion from the same inputs. |
Sizes
Set the clip's size with resolution. Leave it out for a square clip.
| Shape | resolution | Good for |
|---|---|---|
| Square | { "width": 512, "height": 512 } | The default, on every account |
| Portrait | { "width": 384, "height": 672 } | Stories, Reels and Shorts |
| Landscape | { "width": 672, "height": 384 } | Web pages and YouTube |
Not every account can use every size yet. To see which ones yours can:
- Call
GET /v1/accountwith your key. - Look at
pricing.generation.clipResolutions: it lists the sizes you can ask for. If it isn't there at all, every size is open to you.
Asking for a size that isn't on that list fails with 400 invalid_input, and the error message
names the sizes you can use instead.
Longer videos
A clip is at most 15 seconds. For a longer video, make several clips and join them:
- Split the script or audio into parts of 15 seconds or less, at a pause between sentences so the joins don't land mid-word.
- Make one clip per part, with the same image and size for each so the person looks the same
throughout, and a new
Idempotency-Keyfor each. - Join the mp4s in order. Every clip is 25 fps at the size you asked for, so they join without re-encoding:
# parts.txt lists the clips in order, one per line: file 'part1.mp4'
ffmpeg -f concat -safe 0 -i parts.txt -c copy video.mp4Each part is billed as its own clip, so a 60-second video made of four 15-second clips costs 4 credits.
Cost
1 credit per clip, whatever its length, with or without sound, on Growth and up. To check
what a call cost, read microcreditsCharged in its answer: it counts in hundredths of a credit,
so 100 is 1 credit, and a replay shows 0.
You pay only for a clip you get: flagged, failed and refused calls cost nothing, and so does a replay. Your balance and the key's spend cap are checked before the render starts.
Errors
Clips answer the errors every generation call shares, plus:
error | What to do |
|---|---|
invalid_input naming input_failure image or audio | The renderer couldn't use the file (no face, unreadable audio). Send another file, with a new key. |
invalid_input naming input_failure resolution | That size isn't being served right now. Pick another size, not another image. |
invalid_input for a missing durationSeconds | Set it whenever you send audio. |
v2_access_required | The account has no Atmee V2 access yet. |
Node example
Make a clip, retry while it renders, and save the mp4. Node 18 or later.
import { randomUUID } from "node:crypto";
import { writeFile } from "node:fs/promises";
const BASE = "https://api.atmanity.us";
export async function makeClip(input) {
const key = randomUUID(); // one key for this clip, reused on every retry
for (;;) {
const res = await fetch(`${BASE}/v1/generation/clips`, {
method: "POST",
headers: {
"X-Api-Key": process.env.ATMEE_API_KEY,
"Idempotency-Key": key,
"Content-Type": "application/json",
},
body: JSON.stringify(input),
});
// A wrong key answers 401 as plain text or no body, so never assume JSON.
const text = await res.text();
let body = {};
try {
body = text ? JSON.parse(text) : {};
} catch {
body = { message: text };
}
if (res.ok) return body.clip;
const code =
typeof body.error === "string" ? body.error : `http_${res.status}`;
const retry = ["request_in_progress", "rate_limited"].includes(code);
if (!retry) throw new Error(`${res.status} ${code}: ${body.message ?? ""}`);
const wait = Number(res.headers.get("Retry-After") ?? 10);
await new Promise((resolve) => setTimeout(resolve, wait * 1000));
}
}
const clip = await makeClip({
image: "https://example.com/host.png",
prompt: "he waves at the camera and laughs",
durationSeconds: 8,
});
const mp4 = await fetch(clip.url);
await writeFile("clip.mp4", Buffer.from(await mp4.arrayBuffer()));Next steps
Generation
Make what an avatar or a video needs: portraits from a description, an enhanced or restyled photo, a designed voice, or a short video clip. Paid per call, charged only when something comes back.
Portraits and enhancement
Generate portraits of an invented character from a description, or enhance and restyle a real photo. Up to four options per call, each ready to build an avatar or a clip.

