Videos from templates
The template you already render as an image becomes a video when its layers move. Same layers, same variables, same fonts; one call returns an MP4, WebM or GIF.
Motion is a layer property
Add animate to any layer. Images ignore it and show every layer at rest; videos play it:
{
"width": 1200, "height": 630, "background": "#0b1220",
"layers": [
{ "name": "title", "type": "text", "x": 72, "y": 200, "width": 1056, "height": 200,
"text": "{{title}}", "font": "Inter", "weight": 800, "size": 72, "color": "#ffffff",
"animate": { "in": "slide-up", "in_at": 0.2, "easing": "spring" } },
{ "name": "accent", "type": "rect", "x": 0, "y": 612, "width": 1200, "height": 18, "fill": "#ffd60a",
"animate": { "in": "wipe-right", "in_at": 0.6, "in_duration": 0.8 } }
]
}
The title slides up 0.2 seconds in, overshooting a little, and the accent bar wipes across from 0.6 s to 1.4 s. The video runs until the last movement ends plus a second to read it (2.4 s here), unless the template or the request sets duration.
Every option
| Key | Values | Default |
|---|---|---|
in |
fade, slide-up, slide-down, slide-left, slide-right, zoom-in, zoom-out, pop, blur, wipe-right, wipe-left, wipe-up, wipe-down, type |
none |
in_at, in_duration |
seconds | 0, 0.6 |
loop |
pulse, float, bob, spin, shake, blink; starts when the entrance ends |
none |
loop_duration |
seconds per cycle | 1.6 (spin 3) |
out |
any entrance, played backwards | none |
out_at, out_duration |
seconds | ends with the video, 0.5 |
easing |
ease-out, ease-in-out, ease-in, ease, linear, spring |
ease-out |
distance |
px a slide travels | 60 |
type reveals text a character at a time. Wipes reveal the layer's own box, so a wipe always takes its full duration however wide the layer is. Text that shrinks to fit is fitted before anything moves, so a long headline animates at the size it will end up.
Render it
curl -X POST https://api.imageapis.com/v1/videos \
-H 'X-API-Key: YOUR_KEY' -H 'Content-Type: application/json' \
-d '{"template":"tpl_…","variables":{"title":"Launch week starts Monday"},"format":"mp4","wait":true}'
With "wait": true the request holds until the video is done, for up to about a minute, and answers 201 with the finished record. Without it the answer is 202 at once, with the final url already assigned. That URL answers 404 until the file exists. Then poll GET /v1/videos/{id}, which reports progress from 0 to 1, or pass webhook_url to get one POST with video.completed or video.failed:
import os, time, requests
API, H = "https://api.imageapis.com", {"X-API-Key": os.environ["IMAGEAPIS_KEY"]}
v = requests.post(f"{API}/v1/videos", headers=H, json={"template": "tpl_…", "format": "gif"}).json()
while v["status"] in ("pending", "running"):
time.sleep(2)
v = requests.get(f"{API}/v1/videos/{v['id']}", headers=H).json()
print(v["status"], v["url"])
Formats and sizes
format |
What you get | Good for |
|---|---|---|
mp4 (default) |
H.264, plays everywhere | social posts, the web, Slack |
webm |
VP9, smaller than MP4 at the same quality | the web |
gif |
loops, 256 colours a frame, larger files | email, docs, READMEs |
fpsis 30 by default, 15 for GIF, and anything from 1 to 60.scalesets the output size relative to the template. A GIF defaults to whatever fits 800 px.- Videos go up to 1080p. GIFs go up to 1200 px on the long side.
- A video is at most 30 seconds and 900 frames.
bitratein kbit/s overrides the default, which scales with size and frame rate.
Files are served with byte ranges, so a <video> tag streams them in every browser, Safari included.
Your own HTML, with CSS animations
Send html instead of a template and its CSS animations play. Keyframes, transitions and the Web Animations API all work, because every frame is rendered by pausing each animation at that moment:
{ "html": "<style>@keyframes spin{to{transform:rotate(1turn)}} .logo{animation:spin 2s linear infinite}</style><img class='logo' src='https://example.com/logo.svg'>",
"width": 600, "height": 600, "duration": 2, "format": "gif" }
Animation driven by JavaScript timers (setInterval, a requestAnimationFrame loop) does not play. Use CSS or element.animate() instead.
Cost and limits
- A video costs one image per second, rounded up, whatever the format, frame rate or size. A 2.4-second clip is 3 images.
- The whole video is reserved against the plan before it renders. If the plan does not have that many images left, the answer is
429withneededandremaining, and nothing is charged. - Videos are kept like images: 30 days on the free plan, a year on paid plans.
The free animated card maker shows the motion styles on the starters. The editor has an Animation section on every layer and a play button, so you can tune timings before you render.