Make motion graphics videos from your own code. Send a prompt, and Easymotion writes the motion and renders the video.
Last updated: October 8, 2026 · Use Easymotion in Claude and ChatGPT
The Easymotion API makes motion graphics videos from a prompt. You send a prompt, Easymotion writes the motion, then renders it as a video file. This takes a few minutes, so you start a generation, check on it, and download the video when it is ready.
Everything you make with the API also appears in Easymotion Studio, where you can watch it, change it, and keep working on it.
https://studio-be.easymotion.io/v1The API is part of the paid plans. Anyone can read these docs.
Keep your key secret, like a password: anyone who has it can use your credits. Call the API from your server, never from a website or app that runs in the browser. You can have up to 5 keys. Delete a key in the same place; it stops working within a minute.
Send your key in the Authorization header with every request:
Authorization: Bearer em_live_…If your tool can't set that header, send X-API-Key: em_live_… instead. You can also send X-EM-Email with your account email. When you do, a request whose email does not match the key's account is refused.
1. Create a generation. It answers in a few seconds with an id.
curl -X POST https://studio-be.easymotion.io/v1/generations \
-H "Authorization: Bearer $EASYMOTION_API_KEY" \
-H "Content-Type: application/json" \
-d '{"prompt": "Bar chart of Q3 revenue: EU 40, US 35, APAC 25", "aspect_ratio": "16:9"}'2. Check it every 10–15 seconds until status is completed. Writing the motion takes 2–4 minutes, then the video renders for 1–3 minutes.
curl https://studio-be.easymotion.io/v1/generations/gen_… \
-H "Authorization: Bearer $EASYMOTION_API_KEY"3. Download the video. video.url in the response works for one hour. This link always works with your key:
curl -L -o motion.mp4 https://studio-be.easymotion.io/v1/generations/gen_…/video \
-H "Authorization: Bearer $EASYMOTION_API_KEY"const API = "https://studio-be.easymotion.io/v1";
const headers = {
Authorization: `Bearer ${process.env.EASYMOTION_API_KEY}`,
"Content-Type": "application/json",
};
// 1. Start a generation
let generation = await fetch(`${API}/generations`, {
method: "POST",
headers,
body: JSON.stringify({ prompt: "Bar chart of Q3 revenue: EU 40, US 35, APAC 25" }),
}).then((res) => res.json());
if (!generation.id) throw new Error(generation.error.message);
// 2. Check every 15 seconds until it is done
while (["generating", "rendering"].includes(generation.status)) {
await new Promise((resolve) => setTimeout(resolve, 15000));
generation = await fetch(`${API}/generations/${generation.id}`, { headers }).then((res) => res.json());
}
// 3. Use the result
if (generation.status === "completed") console.log(generation.video.url);
else console.log(generation.status, generation.question || generation.error);Every generation has one of these statuses:
| Status | Meaning |
|---|---|
generating | Easymotion is writing the motion. progress shows how far it is. |
needs_input | Easymotion asked a question (question). No credits were used. See Change a motion or answer a question. |
rendering | The motion is ready and the video file is rendering. |
completed | The video is ready in video. With "export": false, the motion is ready in Studio and there is no file. |
failed | See error.code and error.message. Credits held for a failed generation are returned. |
POST /generations
| Field | Type | What it does |
|---|---|---|
prompt | string, required | What to make, or what to change. Up to 2,000 characters. Put data in attachments, not in the prompt. |
session_id | string | Continue an earlier motion, or answer its question. Leave it out for a new motion. |
aspect_ratio | 16:9 9:16 1:1 4:5 | Default 16:9 for new motions. Follow-ups keep the motion's shape unless you set it. |
export | object or false | The video file to render. Default {"format": "mp4", "fps": 30, "resolution": "fhd"}. Formats mp4 mov gif, fps 30 60, resolution fhd 4k (4K and 60 fps need Plus or Pro). false makes the motion without a video file. |
attachments | array | Up to 6 files: images, logos, data, PDFs, video references. See Attachments. |
metadata | object | Up to 20 of your own text values, for example your order ID. Returned as they are. |
{
"prompt": "Bar chart of Q3 revenue: EU 40, US 35, APAC 25",
"aspect_ratio": "9:16",
"export": { "format": "mp4", "fps": 30, "resolution": "fhd" },
"metadata": { "order_id": "A-1042" }
}{
"id": "gen_6f1c2a9b8e7d4c3b2a1f0e9d",
"object": "generation",
"status": "generating",
"session_id": "6702f1c2a9b8e7d4c3b2a1f0",
"version": null,
"title": "Q3 revenue by region",
"prompt": "Bar chart of Q3 revenue: EU 40, US 35, APAC 25",
"aspect_ratio": "9:16",
"export": { "format": "mp4", "fps": 30, "resolution": "fhd" },
"studio_url": "https://studio.easymotion.io/s/6702f1c2a9b8e7d4c3b2a1f0",
"progress": { "phase": "generating", "percent": 35, "message": "Generating motion...", "estimated_seconds": 180 },
"metadata": { "order_id": "A-1042" },
"created_at": "2026-10-08T10:00:00.000Z",
"updated_at": "2026-10-08T10:00:04.000Z",
"completed_at": null
}Add up to 6 files to a generation. Each attachment has one of these:
| Field | What it is |
|---|---|
url | A public link to the file. |
data_base64 | The file itself, base64-encoded. Also send filename and content_type. Up to 25 MB. |
csv | CSV text for a chart or table, up to 200 KB. |
svg | SVG markup of a logo or icon, up to 512 KB. |
Tell Easymotion how to use a file with use_as:
| use_as | Meaning |
|---|---|
style_reference | Copy the look of this image. |
image_asset | Place this image in the motion. Add a description, like "our logo, top-left". |
logo_svg | Animate this SVG logo. |
data | Use this sheet (CSV or XLSX) as chart data. |
document | Use this PDF as a brief. |
video_reference | Copy the motion style of this clip. Needs duration_seconds (60 or less). |
PNG, JPG and WebP images need use_as. For other files it is worked out from the file type. Your plan's file limits are the same as in Studio.
{
"prompt": "Animated sales report in our brand style, our logo top-left",
"attachments": [
{ "url": "https://example.com/brand-style.png", "use_as": "style_reference" },
{ "url": "https://example.com/logo.png", "use_as": "image_asset", "description": "our logo" },
{ "csv": "month,sales\nJan,120\nFeb,180\nMar,240" }
]
}Each generation is one message in a motion's conversation, like a chat message in Studio. To change a motion, create a new generation with its session_id and the change as the prompt. You get a new version and a new video. Earlier versions stay in Studio.
{
"session_id": "6702f1c2a9b8e7d4c3b2a1f0",
"prompt": "Make the bars blue and the animation slower"
}When the status is needs_input, Easymotion asked a question instead of making the motion — for example, which years a chart should show. No credits were used. Answer the same way: a new generation with the same session_id and your answer as the prompt.
GET /generations/{id} returns the generation. When status is completed, it has a video:
"video": {
"format": "mp4",
"fps": 30,
"resolution": "fhd",
"url": "https://…",
"url_expires_at": "2026-10-08T11:05:00.000Z",
"download_url": "https://studio-be.easymotion.io/v1/generations/gen_6f1c2a9b8e7d4c3b2a1f0e9d/video",
"file_size_bytes": 4823910,
"duration_seconds": 12.5
}| Field | Meaning |
|---|---|
url | Download link for the file. Works for one hour (url_expires_at). |
download_url | A permanent link. With your key it always redirects to a fresh download link, so you can store it. |
deleted_after | MOV files only: when the file is deleted. MP4 and GIF files are kept. |
GET /generations/{id}/video is the same as download_url: it redirects to the file. Use curl -L or follow redirects in your HTTP client.
| Endpoint | What it does |
|---|---|
GET /generations | Your generations, newest first. limit (up to 100), before, session_id. |
GET /account | Your plan, credits left, generations running, and plan limits. |
For the next page of a list, pass the created_at of the last item as before. Lists don't include download links; get one generation for those.
Errors use normal HTTP status codes and this shape:
{ "error": { "code": "INSUFFICIENT_CREDITS", "message": "Not enough credits for this motion." } }| Status | Code | What to do |
|---|---|---|
| 400 | INVALID_REQUEST, INVALID_JSON | Fix the request. error.details lists each field. |
| 401 | INVALID_API_KEY | Check the key. It may have been deleted. |
| 402 | INSUFFICIENT_CREDITS | Add credits or upgrade your plan. |
| 403 | PAID_PLAN_REQUIRED, PLAN_EXPORT_UPGRADE_REQUIRED | Your plan doesn't include this. Upgrade on the pricing page. |
| 404 | NOT_FOUND | The generation or session_id is not in your account. |
| 409 | ALREADY_GENERATING, NOT_READY | Wait for the motion's current generation, or for the video to finish. |
| 422 | ATTACHMENT_FAILED | A file could not be used. error.details says why. Nothing was generated. |
| 429 | TOO_MANY_IN_FLIGHT, RATE_LIMITED | Wait and try again. Retry-After says how many seconds. |
Safe retries: send an Idempotency-Key header with a unique value, like your order ID. If the same key comes again, you get the first generation back instead of a new one, so a retry after a timeout never charges you twice.
A machine-readable description of the API is at https://studio-be.easymotion.io/v1/openapi.json. Import it into Postman or Insomnia, or give it to an AI coding assistant.
Prefer chatting? You can also make motions from Claude and ChatGPT.