Guide · 2026-10-11

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
  • fps is 30 by default, 15 for GIF, and anything from 1 to 60.
  • scale sets 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.
  • bitrate in 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 429 with needed and remaining, 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.

Get a free key: 150 images a month API reference