Build with GoMotion
Turn a script, or your own voiceover, into a finished documentary-style video from your code, from n8n or Make, or from an AI agent like Claude or Cursor. Same videos, same credits as the studio.
- Plans
- Included in every paid plan (Creator, Studio and Agency)
- REST API
https://gomotionai.com/v1- MCP server
https://gomotionai.com/mcp
Quickstart
Your first video in three steps. Each program creates a short video, waits for it, exports the MP4 and prints the download link. These exact files run in our test suite before every release.
- Create an API key
In the app, open API & MCP and click Create API key. It is shown once: store it somewhere safe.
Open API & MCP - Put it in your environment
export GOMOTION_API_KEY="gmk_live_..." - Run one of these
#!/usr/bin/env bash # GoMotion API quickstart (curl + jq): script -> video -> MP4 download link set -euo pipefail API=https://gomotionai.com/v1 AUTH="Authorization: Bearer $GOMOTION_API_KEY" # 1. Create the video (the Idempotency-Key makes a retry safe: it never charges twice) VIDEO=$(curl --fail-with-body -sS -X POST "$API/videos" -H "$AUTH" -H "Content-Type: application/json" \ -H "Idempotency-Key: quickstart-$(date +%s)" \ -d '{"script": "In 1969, three astronauts left Earth on a journey nobody had made before. Four days later, two of them walked on the Moon while the whole planet watched.", "title": "Apollo 11 in 30 seconds"}') ID=$(echo "$VIDEO" | jq -r .id) echo "video $ID: $(echo "$VIDEO" | jq -r .status), $(echo "$VIDEO" | jq -r .cost_credits) credits" # 2. Wait until it is ready while true; do STATUS=$(curl --fail-with-body -sS "$API/videos/$ID" -H "$AUTH" | jq -r .status) echo "status: $STATUS" case "$STATUS" in succeeded) break;; failed|cancelled|interrupted) exit 1;; esac sleep 10 done # 3. Export it to MP4 (the first export of a video is free) and wait for the link EXPORT=$(curl --fail-with-body -sS -X POST "$API/videos/$ID/export" -H "$AUTH" -H "Content-Type: application/json" \ -H "Idempotency-Key: quickstart-export-$ID" -d '{"quality": "medium"}') EID=$(echo "$EXPORT" | jq -r .id) while true; do E=$(curl --fail-with-body -sS "$API/exports/$EID" -H "$AUTH") case "$(echo "$E" | jq -r .status)" in succeeded) break;; failed|cancelled|interrupted) exit 1;; esac sleep 10 done echo "download: $(echo "$E" | jq -r .download_url)"# GoMotion API quickstart (Python 3 + requests): script -> video -> MP4 download link import os, time, uuid import requests API = "https://gomotionai.com/v1" s = requests.Session() s.headers["Authorization"] = "Bearer " + os.environ["GOMOTION_API_KEY"] def call(method, path, **kw): """One request, retrying politely on 429/503 (the API tells you how long to wait).""" for _ in range(6): r = s.request(method, API + path, timeout=60, **kw) if r.status_code in (429, 503) and "Retry-After" in r.headers: time.sleep(min(int(r.headers["Retry-After"]), 120)) continue if r.status_code >= 400: raise SystemExit("API error %s: %s" % (r.status_code, r.json().get("error"))) return r.json() raise SystemExit("API still busy (%s): try again later" % r.status_code) def wait(path): while True: obj = call("GET", path) print(obj["object"], obj["id"], obj["status"]) if obj["status"] == "succeeded": return obj if obj["status"] in ("failed", "cancelled", "interrupted"): raise SystemExit("%s %s: %s" % (obj["object"], obj["status"], obj["error"])) time.sleep(10) video = call("POST", "/videos", headers={"Idempotency-Key": str(uuid.uuid4())}, json={ "script": "In 1969, three astronauts left Earth on a journey nobody had made before. " "Four days later, two of them walked on the Moon while the whole planet watched.", "title": "Apollo 11 in 30 seconds", }) print("video", video["id"], "costs", video["cost_credits"], "credits") wait("/videos/" + video["id"]) export = call("POST", "/videos/%s/export" % video["id"], headers={"Idempotency-Key": "export-" + video["id"]}, json={"quality": "medium"}) done = wait("/exports/" + export["id"]) print("download:", done["download_url"])// GoMotion API quickstart (Node.js 18+, no dependencies): script -> video -> MP4 download link import { randomUUID } from "node:crypto"; const API = "https://gomotionai.com/v1"; const headers = { Authorization: `Bearer ${process.env.GOMOTION_API_KEY}`, "Content-Type": "application/json" }; const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); async function call(method, path, body, idempotencyKey) { for (let i = 0; i < 6; i++) { const h = idempotencyKey ? { ...headers, "Idempotency-Key": idempotencyKey } : headers; const r = await fetch(API + path, { method, headers: h, body: body ? JSON.stringify(body) : undefined }); if ((r.status === 429 || r.status === 503) && r.headers.get("retry-after")) { await sleep(Math.min(Number(r.headers.get("retry-after")), 120) * 1000); continue; } const data = await r.json(); if (!r.ok) throw new Error(`${r.status}: ${data.error}`); return data; } throw new Error("still busy, try again later"); } async function wait(path) { for (;;) { const obj = await call("GET", path); console.log(obj.object, obj.id, obj.status); if (obj.status === "succeeded") return obj; if (["failed", "cancelled", "interrupted"].includes(obj.status)) throw new Error(obj.error); await sleep(10000); } } const video = await call("POST", "/videos", { script: "In 1969, three astronauts left Earth on a journey nobody had made before. " + "Four days later, two of them walked on the Moon while the whole planet watched.", title: "Apollo 11 in 30 seconds", }, randomUUID()); console.log("video", video.id, "costs", video.cost_credits, "credits"); await wait(`/videos/${video.id}`); const exp = await call("POST", `/videos/${video.id}/export`, { quality: "medium" }, `export-${video.id}`); const done = await wait(`/exports/${exp.id}`); console.log("download:", done.download_url);
AI agents (MCP)
GoMotion has an MCP server, so AI agents such as Claude Code, Claude Desktop and Cursor can make videos for you. Tell your agent "make me a 10-minute history video about the fall of Rome" and it writes or fetches the script, creates the video, waits for it and hands you the download link.
- Remote server:
https://gomotionai.com/mcp(Streamable HTTP). Nothing to install. - Same account, same prices. The agent uses your API key, so it gets the same credits, plan limits and permissions as the web app. Nothing extra is charged for using MCP.
1. Get an API key
Open gomotionai.com/app → API & MCP in the sidebar → Create API key. Copy it (it starts with gmk_live_); it is shown only once. API keys need a paid plan. You can give a key a daily credit cap to limit what an agent can spend per day.
2. Connect your agent
Replace gmk_live_YOUR_KEY with your key.
Claude Code
One command:
claude mcp add --transport http gomotion https://gomotionai.com/mcp \
--header "Authorization: Bearer gmk_live_YOUR_KEY"
claude mcp list should say Connected (that only checks the connection, not the key): to check the key, ask Claude "How many GoMotion credits do I have?". Add --scope user to use it in every project.
Claude Desktop
Settings → Developer → Edit Config, then put this in claude_desktop_config.json (macOS: ~/Library/Application Support/Claude/claude_desktop_config.json, Windows: %APPDATA%\Claude\claude_desktop_config.json) and restart Claude Desktop. It needs Node.js 20 or newer.
{
"mcpServers": {
"gomotion": {
"command": "npx",
"args": [
"-y",
"mcp-remote",
"https://gomotionai.com/mcp",
"--header",
"Authorization:${GOMOTION_AUTH}"
],
"env": {
"GOMOTION_AUTH": "Bearer gmk_live_YOUR_KEY"
}
}
}
}
Claude Desktop runs local servers, so this uses mcp-remote (it needs Node.js 20 or newer) to connect to the remote server with your key. Restart Claude Desktop after saving.
Cursor
Put this in ~/.cursor/mcp.json (all projects) or .cursor/mcp.json (one project):
{
"mcpServers": {
"gomotion": {
"url": "https://gomotionai.com/mcp",
"headers": { "Authorization": "Bearer gmk_live_YOUR_KEY" }
}
}
}
Other MCP clients
Any client that lets you set an HTTP header for a remote MCP server: URL https://gomotionai.com/mcp, header Authorization: Bearer gmk_live_YOUR_KEY. For apps that only run local servers, use mcp-remote as in Claude Desktop above.
Quick check from a terminal (asks for your credit balance, so it also checks the key):
curl -s https://gomotionai.com/mcp -H "Authorization: Bearer gmk_live_YOUR_KEY" -H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"get_credits","arguments":{}}}'
3. Ask for a video
Example prompts:
- "Make me a 2-minute video in English about the history of the Roman aqueducts. Write the narration yourself, tell me the price first, then give me the download link."
- "Write a 10-minute script about the fall of Constantinople in the style of https://www.youtube.com/@MyFavouriteChannel, show it to me, and when I say OK turn it into a video with an epic soundtrack and the cinematic background."
- "List Spanish voices, pick a deep male narrator, and make a 5-minute video in Spanish from this script: …"
- "How many credits do I have left, and what did my last videos cost?"
- "Make a YouTube thumbnail for my video 'The Last Days of Pompeii' like the ones on https://www.youtube.com/@MyChannel."
Videos take several minutes to render. The agent gets a video id at once and then checks on it (about once a minute, or by waiting up to 50 seconds per check). If your agent cannot wait, it gives you the video id: ask again later, or find the video in the web app.
Tools
| Tool | What it does | Spends credits |
|---|---|---|
get_credits |
Balance, credits held by running jobs, this key's daily cap | no |
get_credit_history |
Recent credit movements | no |
get_account |
Plan, this key's limits and today's usage | no |
estimate_cost |
Price of a video by length, language and voice | no |
list_languages |
Narration languages | no |
list_voices |
Narrator voices with audio samples | no |
list_music_tracks |
Background music catalog by mood | no |
find_reference_videos |
Videos of a YouTube channel to imitate | no |
generate_script / get_script |
AI script writer (imitates 1-3 reference videos) | plan allowance, then a few credits |
create_video / get_video |
Create a video from a script, a generated script or your own voice-over; follow its progress | yes |
list_videos |
Your videos (web and API) | no |
cancel_video |
Cancel a queued or running video or export (refunded) | no |
export_video / get_export |
1080p MP4 and its download link (the first export of a video is normally free) | re-exports |
list_exports |
Your exports with fresh download links | no |
create_upload |
Upload link for your own voice-over or music | no |
upload_file |
Upload a local audio file (local package only) | no |
generate_thumbnail / get_thumbnail |
AI YouTube thumbnail | plan allowance, then a few credits |
The agent also gets a ready-made prompt, make_video, with the whole flow.
Billing, limits and safety
- Everything is billed exactly like the web app, to the account that owns the key. Creating a video reserves its credits; a failed or cancelled video is refunded automatically.
- Repeating the same request within 24 hours returns the same video instead of charging again, so an agent that retries after a timeout never pays twice. If that earlier video failed or was cancelled, a new one is made. To make a second identical video on purpose (or re-export after editing the project in the web editor), the agent passes
force_new. max_cost_creditsoncreate_videois a hard ceiling for one video. A key's daily credit cap is checked before each new video; one video that turns out longer than estimated can still finish a little over it.- Limits are your plan's API limits (requests per minute, videos in progress). When one is hit, the agent is told how long to wait.
- The remote server keeps nothing: it does not store your key, scripts or videos, and its logs never contain keys. It only calls GoMotion's public API (
/v1) with your key (and reads the public music catalog). Keys used through the remote server show GoMotion's server as their last-used address. - Revoke a key at any time in the web app; the agent loses access at once.
- Not available through MCP, on purpose: billing and plans, API keys, account settings, YouTube channel connection and Autopilot publishing. These stay in the web app.
Keys & authentication
Send the key on every request as Authorization: Bearer gmk_live_... (or X-API-Key: gmk_live_...).
All calls are HTTPS to https://gomotionai.com/v1 with JSON bodies.
- Create, name and revoke keys in the app (API & MCP in the sidebar). The full key is shown once; we only keep a hash and show the last 4 characters.
- Give each integration its own key, with only the permissions it needs:
videos:write,videos:read,scripts:write,thumbnails:write,credits:read. Optionally set a daily credit limit per key (a good idea for AI agents). - Never put a key in a web page or a mobile app: call the API from your server or automation tool.
- If a key leaks, revoke it: it stops working at once.
How a video is made
POST /v1/videoswith your script (oraudio_key). The price is reserved from your credits; you get an id.- Poll
GET /v1/videos/{id}every 20–30 s.status:queued→running(withprogress.percent) →succeeded, orfailed/cancelled(credits come back:refunded: true). A 1-minute video usually takes a few minutes. POST /v1/videos/{id}/exportrenders the 1080p MP4 (the first export of each video is free).- Poll
GET /v1/exports/{id}untilsucceeded:download_urlis a signed link valid 12 h (it is re-signed on every GET); the file is kept 48 h.
Instead of polling you can get a webhook when each step ends. Polling is free, but every call counts toward your requests-per-minute limit, so poll every 20–30 seconds.
Your own voiceover
Ask for an upload URL, PUT your audio file there, then create the video with audio_key and the length in seconds. Do the same with kind: "music" and music_key to use your own background track.
#!/usr/bin/env bash
# Your own voiceover (any common audio file) -> video
set -euo pipefail
API=https://gomotionai.com/v1
AUTH="Authorization: Bearer $GOMOTION_API_KEY"
FILE=${1:-voiceover.mp3}
# 1. Ask for an upload URL and PUT the file there (the URL is valid for 1 hour)
UP=$(curl --fail-with-body -sS -X POST "$API/uploads" -H "$AUTH" -H "Content-Type: application/json" -d '{"kind": "audio"}')
curl --fail-with-body -sS -X PUT --upload-file "$FILE" "$(echo "$UP" | jq -r .put_url)"
# 2. Create the video: duration_sec = length of your audio (you are billed on the real length)
SECONDS_LONG=$(ffprobe -v error -show_entries format=duration -of csv=p=0 "$FILE" | cut -d. -f1)
curl --fail-with-body -sS -X POST "$API/videos" -H "$AUTH" -H "Content-Type: application/json" \
-H "Idempotency-Key: voice-$(date +%s)" \
-d "{\"audio_key\": \"$(echo "$UP" | jq -r .key)\", \"duration_sec\": $SECONDS_LONG, \"title\": \"My narrated video\"}" | jq '{id, status, cost_credits}'
AI scripts & thumbnails
Scripts and thumbnails are written in the style of a YouTube channel you pick (channel_url, or up to 3 video_ids). With only a topic, we search YouTube for it and use 3 videos as references (same price; references_source: "topic_search"). They run in the background: POST returns 202 with an id; poll GET /v1/scripts/{id}.
# Write a script with AI in the style of a YouTube channel, then turn it into a video
import os, time, uuid
import requests
API = "https://gomotionai.com/v1"
H = {"Authorization": "Bearer " + os.environ["GOMOTION_API_KEY"]}
def ok(r):
if r.status_code >= 400:
raise SystemExit("API error %s: %s" % (r.status_code, r.json().get("error")))
return r.json()
task = ok(requests.post(API + "/scripts", headers=dict(H, **{"Idempotency-Key": str(uuid.uuid4())}), json={
"channel_url": "https://www.youtube.com/@NASA",
"topic": "How the James Webb telescope unfolded in space",
"minutes": 1,
}, timeout=60))
while task["status"] == "running":
time.sleep(10)
task = ok(requests.get(API + "/scripts/" + task["id"], headers=H, timeout=60))
if task["status"] != "succeeded":
raise SystemExit(task["error"])
print(task["result"]["title"], "-", task["result"]["words"], "words")
video = ok(requests.post(API + "/videos", headers=dict(H, **{"Idempotency-Key": str(uuid.uuid4())}),
json={"script": task["result"]["script"], "title": task["result"]["title"]}, timeout=60))
print("video", video["id"], video["status"])
n8n & Make templates
Ready-to-import workflows for topic in a Google Sheet → GoMotion video → download link back in the sheet.
Download the n8n workflow Download the Make blueprint
- n8n: Workflows → Import from file. Create a Header Auth credential named
GoMotion API keywith nameAuthorizationand valueBearer gmk_live_..., and connect your Google Sheets credential. - Make: Create a new scenario → … → Import Blueprint. Put your key in the Authorization header of the HTTP modules and pick your sheet.
- Zapier: use Webhooks by Zapier with the same four calls (create, poll, export, poll).
The sheet needs columns topic, channel_url, script, title, status, video_id, download_url, error and (optional) voice — a voice id from GET /v1/voices.
Rows with a script go straight to a video; rows with only a topic get an AI script first, written in the style of the YouTube channel in channel_url (required for topic rows).
If a row fails, the template writes failed and the reason in error; clear status to try that row again.
Webhooks
Get a POST on your server when a video, export, script or thumbnail created by your key ends. Add an endpoint with
POST /v1/webhooks (up to 3 per key). The answer includes the signing secret: it is shown only there and on
rotate-secret.
curl --fail-with-body -sS -X POST "https://gomotionai.com/v1/webhooks" \
-H "Authorization: Bearer $GOMOTION_API_KEY" \
-H "Content-Type: application/json" -d '{"url": "https://yourshop.com/gomotion/webhook", "events": ["video.succeeded", "video.failed", "export.succeeded", "export.failed"]}'Events: video.succeeded, video.failed, video.cancelled, export.succeeded, export.failed, export.cancelled, script.succeeded, script.failed, thumbnail.succeeded, thumbnail.failed (default: all). Each POST body is an event whose data.object is the same object
GET /v1/videos/{id}, /v1/exports/{id}, /v1/scripts/{id} or /v1/thumbnails/{id} returns:
{
"api_version": "2026-11-01",
"created_at": 1793535293,
"data": {
"object": {
"cost_credits": 0,
"created_at": 1793535290,
"download_filename": "Acme Films - order 0.mp4",
"download_url": "https://<account>.r2.cloudflarestorage.com/gomotion/t/t_5f3a91c2e4b7/downloads/j_73776a5038734181.mp4?X-Amz-Signature=...&response-content-disposition=attachment%3B%20filename%3D%22Acme%20Films%20-%20order%200.mp4%22",
"download_url_expires_in_s": 43200,
"error": null,
"external_id": "ord-0",
"id": "j_73776a5038734181",
"metadata": {
"n": "0",
"order_id": "o0",
"shop": "acme"
},
"object": "export",
"progress": {
"eta_s": 0,
"percent": 100,
"phase": "Completed"
},
"project_id": "p_d47e376ac26f",
"queue_position": null,
"refunded": false,
"status": "succeeded",
"updated_at": 1793535291
}
},
"id": "evt_5f3a91c2e4b74e64916c",
"object": "event",
"type": "export.succeeded"
}Verify the signature
Header GoMotion-Signature: t=<unix>,v1=<hex>, where v1 = HMAC-SHA256 of "<t>.<raw body>" with your secret.
Use the raw body bytes, compare in constant time and reject t older than 5 minutes. This exact code runs in our test suite.
import crypto from "node:crypto";
// rawBody: the request bytes exactly as received (e.g. express.raw({ type: "application/json" })).
// header: the GoMotion-Signature header.
export function verifyGoMotion(rawBody, header, secret, toleranceSec = 300) {
const parts = Object.fromEntries(header.split(",").map((p) => p.split("=", 2)));
const t = Number(parts.t);
if (!t || Math.abs(Date.now() / 1000 - t) > toleranceSec) return false;
const expected = crypto.createHmac("sha256", secret).update(`${t}.`).update(rawBody).digest("hex");
const a = Buffer.from(expected), b = Buffer.from(parts.v1 || "");
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
import hashlib, hmac, time
def verify_gomotion(raw_body: bytes, header: str, secret: str, tolerance: int = 300) -> bool:
"""raw_body: the request bytes exactly as received. header: GoMotion-Signature."""
parts = dict(p.split("=", 1) for p in header.split(",") if "=" in p)
t = parts.get("t", "")
if not t.isdigit() or abs(time.time() - int(t)) > tolerance:
return False
expected = hmac.new(secret.encode(), t.encode() + b"." + raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, parts.get("v1", ""))
- Answer 2xx within 10 s and do the work afterwards. Otherwise we retry at +30 s, 2 min, 10 min, 30 min, 2 h, 6 h and 12 h (8 attempts).
- Dedupe on the event id (
id, also inGoMotion-Event-Id): retries keep the same id. Order is not guaranteed. - Webhooks are a hint:
GETis the truth, and polling keeps working as before. - 25 events in a row that use all attempts disable the endpoint (
status: "disabled"); turn it back on withPOST /v1/webhooks/{id}and{"enabled": true}. POST /v1/webhooks/{id}/testsends a signedwebhook.testnow;GET /v1/webhooks/{id}/deliveriesshows the last attempts;POST /v1/webhooks/{id}/deliveries/{evt}/retrysends one again.- URL:
https://on port 443 with a public host name. No redirects are followed.
Build on GoMotion / resell
Sell videos under your own brand. Your customer talks to your site; only your backend talks to GoMotion.
- Your site takes the order. Your backend (it holds the API key, never the browser) calls
POST /v1/videoswithexternal_id(your order id) andmetadata(up to 20 keys). Both come back on every object and webhook. - Wait for the
video.succeededwebhook, or pollGET /v1/videos/{id}. POST /v1/videos/{id}/exportwithdownload_filename(e.g."Acme Films - order 1001"): the MP4 downloads with your name, not ours.- On
export.succeeded, download the MP4 fromdownload_urland store it on your own storage. The link lasts 12 h and the file 48 h: never give our link to your customer.
Find orders later with or GET /v1/videos?external_id=order-1001?metadata[shop]=acme (also on GET /v1/exports).
Page with starting_after = next_starting_after of the previous page.
Reseller credit pack
0.0125 USD per credit, minimum 20,000 credits (250 USD). A video costs the same credits as in the app: voice quality, length,
thumbnails and AI scripts change the total, so quote each order with GET /v1/quote before you set your price.
Questions and bigger limits: support@gomotionai.com.
Good to know
- Music catalog (for
music_track="<mood>/<file>"): gomotionai.com/music/catalog.json, public. - Cancel works on queued or running jobs; on a finished one it returns the object unchanged. A cancel shows in
GET /v1/credits/historyas aholdand then arefundwith the samejob_id. - Upload with a plain
PUTof the file body toput_urlfrom your server (no extra headers). Ascript_keycan be reused for 60 h. - Script and thumbnail tasks can't be cancelled; a failed one refunds its credits (
credits_refunded). - Idempotency-Key is 8-64 characters: for longer ids (e.g. Stripe session ids) send a hash.
- Webhook signatures are over the RAW request body: in no-code tools, keep the raw body (don't re-serialise the JSON) before you check
GoMotion-Signature.
- Download gomotion-reseller-kit.zip.
- Put your API key in the server environment (
GOMOTION_API_KEY), never in the page. - Set your brand and price per credit in
kit.config.js. - Deploy it (Vercel, Netlify or Cloudflare) and add its webhook URL with
POST /v1/webhooks.
Credits per call
| Call | Credits |
|---|---|
| Create a video | Same price as the studio: by length, voice quality and language. Ask first with GET /v1/quote. Refunded if it fails or you cancel it. |
| Export to MP4 | First export of a video: free. Re-exports: 4. |
| AI script / thumbnail | 4 each, or free while your plan's monthly allowance lasts. |
| Everything else (status, lists, voices, quote, uploads, references) | Free |
Your own voiceover is billed on its real length, measured by our server. Using the API or MCP costs nothing extra.
Limits per plan
Generous by design: these are safety rails, not a paywall.
| Limit | Creator | Studio | Agency |
|---|---|---|---|
| Videos per day per key | 100 | 300 | 1,000 |
| API videos per day (all keys) | 150 | 400 | 1,500 |
| Rendering at once (shared with the studio) | 5 | 10 | 20 |
| Active keys | 3 | 5 | 10 |
| Requests per minute per key | 60 | 60 | 120 |
| Requests per minute (all keys) | 120 | 180 | 300 |
| Paid requests per minute | 10 | 20 | 30 |
| Scripts and thumbnails at once | 1 | 2 | 3 |
When you hit one you get 429 with a Retry-After header. GET /v1/account shows your current limits and today's usage of the key.
Need higher limits? Email support@gomotionai.com with what you are building and the volume you need, and we will raise them. Then custom_limits is true in GET /v1/account.
Idempotency
Never pay twice for the same request. Every call that spends credits (POST /v1/videos, /export, /v1/scripts, /v1/thumbnails)
needs an Idempotency-Key header: any unique string (8–64 letters, digits, - or _), e.g. a UUID or your own row id.
If a request times out, send it again with the same key and body: you get the same video back (200) instead of a new charge.
The same key with a different body is a 409. Keys are remembered 24 h. If your own id is longer than 64 characters, send a hash of it.
Errors
Errors are JSON: {"error": "message", ...}. Newer errors also carry a stable code.
| Status | Meaning |
|---|---|
| 400 | Bad request: a field is missing or wrong. The message says which. |
| 401 | Missing, wrong or revoked API key. |
402 plan_required | Your plan doesn't include the API (required_plan says which does). |
| 402 | Not enough credits (needed, available). Nothing was charged. |
| 403 | The key doesn't have the scope this call needs. |
403 api_not_enabled | The API is not enabled for this account. |
| 404 | Unknown id (or it belongs to another account). |
400 dns / blocked_address | Webhook URL: the host doesn't resolve, or resolves to a private address. |
| 409 | Idempotency-Key reused with a different body, or the video isn't ready to export. |
409 webhook_limit | This key already has 3 webhooks. |
| 410 | The project expired (videos are kept 48 h): create the video again. |
| 413 | Too long (over 90 minutes). |
| 422 | Valid request we can't do: above max_cost_credits, channel without videos. |
422 topic_no_references | Topic-only script: no YouTube videos found for the topic. Nothing charged. |
| 429 | Rate limit (per key or account) or daily credit cap of the key. Wait Retry-After seconds. |
429 key_videos_in_flight | This key already has its maximum of videos in progress. |
429 key_videos_per_day | This key reached its videos per day (resets 00:00 UTC). |
429 account_videos_per_day | Your account reached its API videos per day across all keys (resets 00:00 UTC). |
429 api_capacity | The API share of our render capacity is full right now. Nothing charged; retry after Retry-After. |
| 502 | YouTube didn't let us read a channel (references). Nothing charged. |
502 topic_search_failed | Topic-only script: YouTube search failed. Nothing charged; retry in a minute. |
503 api_paused | The API is paused for maintenance. Nothing charged. |
503 webhooks_paused | Webhooks are paused; nothing is lost and polling works as usual. |
| 503 | An update or a provider outage: new videos are paused for a few minutes. Nothing charged. |
{
"error": "daily credit cap reached for this key",
"cap": 170,
"spent": 75,
"needed": 207,
"retry_after": 41319
}
Endpoint reference
Base URL https://gomotionai.com. Machine-readable: /openapi.json (OpenAPI 3.1).
Videos
/v1/videosCreate a video. Charges the video price when accepted (refunded if it fails). Poll GET /v1/videos/{id}.
| Field | In | Type | Description |
|---|---|---|---|
Idempotency-Key | header | string · required | Any unique string per new request. Retrying with the same key and body returns the same object (never charges twice). |
script | body | string · maxLength 50000 | The narration text (TTS voice). One of script, script_key, audio_key. |
script_key | body | string | Key from POST /v1/uploads/script (for long scripts). |
audio_key | body | string | Key from POST /v1/uploads with kind=audio: your own voiceover (no TTS). |
duration_sec | body | integer · minimum 1, maximum 5400 | Length of your audio in seconds (required with audio_key). You are billed on the real length the server measures. |
lang | body | string | Language code from GET /v1/languages, or auto. Default en (script) / auto (audio). |
voice | body | string | Voice id from GET /v1/voices (script videos only). |
title | body | string · maxLength 150 | |
subtitles | body | boolean · default true | |
animations | body | boolean · default true | |
background | body | string | blue (default), cinematic, vintage, light or grid |
no_music | body | boolean · default false | |
music_volume | body | number · minimum 0, maximum 1, default 0.03 | |
music_style | body | one of: epic, documentary, tension, sad, chill, upbeat | |
music_track | body | string | '<mood>/<file>.mp3' from the music catalog |
music_key | body | string | Key from POST /v1/uploads with kind=music: your own background track. |
max_cost_credits | body | integer · minimum 1, maximum 100000 | Refuse (422) if the video would cost more; never charged above it. Not with audio_key. |
options | body | object | Same fields nested (accepted for web-body compatibility). |
external_id | body | string | Your own order or customer id: 1-128 printable ASCII characters, no spaces. Filter lists with ?external_id=. |
metadata | body | object | Your own data, echoed on the object and on webhooks: up to 20 keys [A-Za-z0-9_.-]{1,40}, string values up to 500 characters (numbers become strings), 8 KB in total. |
curl --fail-with-body -sS -X POST "https://gomotionai.com/v1/videos" \
-H "Authorization: Bearer $GOMOTION_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" -d '{"script": "In 1969, three astronauts left Earth...", "title": "Apollo 11", "voice": "aurora", "background": "cinematic", "music_style": "documentary"}'
{
"cost_credits": 75,
"created_at": 1793535286,
"error": null,
"estimated_duration_sec": 32.0,
"external_id": "ord-0",
"id": "j_a384eca193e1458f",
"metadata": {
"n": "0",
"order_id": "o0",
"shop": "acme"
},
"object": "video",
"progress": null,
"project_id": null,
"queue_position": null,
"refunded": false,
"status": "running",
"updated_at": 1793535286
}
/v1/videosList videos (newest first)
| Field | In | Type | Description |
|---|---|---|---|
limit | query | integer | |
status | query | one of: queued, running, succeeded, failed, cancelled, interrupted | |
starting_after | query | string | Cursor: the last id of the previous page (next_starting_after). |
external_id | query | string | Only objects created with this external_id. |
metadata | query | object | metadata[key]=value: only objects whose metadata has that value (up to 5 keys; metadata.key=value also works). |
curl --fail-with-body -sS "https://gomotionai.com/v1/videos?limit=10&status=succeeded" \
-H "Authorization: Bearer $GOMOTION_API_KEY"
{
"data": [
{
"cost_credits": 75,
"created_at": 1793535286,
"error": null,
"estimated_duration_sec": 32.0,
"external_id": "ord-0",
"id": "j_a384eca193e1458f",
"metadata": {
"n": "0",
"order_id": "o0",
"shop": "acme"
},
"object": "video",
"progress": {
"eta_s": 0,
"percent": 100,
"phase": "Completed"
},
"project_id": "p_d47e376ac26f",
"queue_position": null,
"refunded": false,
"status": "succeeded",
"updated_at": 1793535287
},
{
"cost_credits": 75,
"created_at": 1793535286,
"error": null,
"estimated_duration_sec": 32.0,
"external_id": "ord-1",
"id": "j_a92d9fc8e6ab428c",
"metadata": {
"n": "1",
"order_id": "o1",
"shop": "acme"
},
"object": "video",
"progress": {
"eta_s": 0,
"percent": 100,
"phase": "Completed"
},
"project_id": "p_28f68f8f6ba1",
"queue_position": null,
"refunded": false,
"status": "succeeded",
"updated_at": 1793535288
}
],
"has_more": true,
"next_starting_after": "j_a92d9fc8e6ab428c",
"object": "list"
}
/v1/videos/{id}Get a video
| Field | In | Type | Description |
|---|---|---|---|
id | path | string · required |
curl --fail-with-body -sS "https://gomotionai.com/v1/videos/$VIDEO_ID" \
-H "Authorization: Bearer $GOMOTION_API_KEY"
{
"cost_credits": 75,
"created_at": 1793535286,
"error": null,
"estimated_duration_sec": 32.0,
"external_id": "ord-0",
"id": "j_a384eca193e1458f",
"metadata": {
"n": "0",
"order_id": "o0",
"shop": "acme"
},
"object": "video",
"progress": null,
"project_id": null,
"queue_position": null,
"refunded": false,
"status": "running",
"updated_at": 1793535286
}
/v1/jobs/{id}/cancelCancel a video or export (credits come back)
| Field | In | Type | Description |
|---|---|---|---|
id | path | string · required |
curl --fail-with-body -sS -X POST "https://gomotionai.com/v1/jobs/$VIDEO_ID/cancel" \
-H "Authorization: Bearer $GOMOTION_API_KEY"
{
"cost_credits": 75,
"created_at": 1793535298,
"error": null,
"estimated_duration_sec": 33.0,
"external_id": null,
"id": "j_89ece89df61c47aa",
"metadata": {},
"object": "video",
"progress": null,
"project_id": null,
"queue_position": null,
"refunded": true,
"status": "cancelled",
"updated_at": 1793535298
}
Exports
/v1/videos/{id}/exportExport the video to MP4 (1080p)
| Field | In | Type | Description |
|---|---|---|---|
id | path | string · required | |
Idempotency-Key | header | string · required | Any unique string per new request. Retrying with the same key and body returns the same object (never charges twice). |
quality | body | one of: high, medium, fast · default "medium" | |
download_filename | body | string | File name of the download (your brand, not GoMotion's); .mp4 is added. Default gomotion_video.mp4. |
external_id | body | string | Default: the video's. |
metadata | body | object | Default: the video's metadata. |
curl --fail-with-body -sS -X POST "https://gomotionai.com/v1/videos/$VIDEO_ID/export" \
-H "Authorization: Bearer $GOMOTION_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" -d '{"quality": "high", "download_filename": "Acme Films - order 1001"}'
{
"cost_credits": 0,
"created_at": 1793535290,
"download_filename": null,
"download_url": null,
"download_url_expires_in_s": null,
"error": null,
"external_id": "ord-0",
"id": "j_73776a5038734181",
"metadata": {
"n": "0",
"order_id": "o0",
"shop": "acme"
},
"object": "export",
"progress": null,
"project_id": "p_d47e376ac26f",
"queue_position": null,
"refunded": false,
"status": "running",
"updated_at": 1793535290
}
/v1/exportsList exports (newest first)
| Field | In | Type | Description |
|---|---|---|---|
limit | query | integer | |
starting_after | query | string | Cursor: the last id of the previous page (next_starting_after). |
external_id | query | string | Only objects created with this external_id. |
metadata | query | object | metadata[key]=value: only objects whose metadata has that value (up to 5 keys; metadata.key=value also works). |
curl --fail-with-body -sS "https://gomotionai.com/v1/exports?limit=10&external_id=order-1001" \
-H "Authorization: Bearer $GOMOTION_API_KEY"
{
"data": [
{
"cost_credits": 0,
"created_at": 1793535290,
"download_filename": "Acme Films - order 0.mp4",
"download_url": "https://<account>.r2.cloudflarestorage.com/gomotion/t/t_5f3a91c2e4b7/downloads/j_73776a5038734181.mp4?X-Amz-Signature=...&response-content-disposition=attachment%3B%20filename%3D%22Acme%20Films%20-%20order%200.mp4%22",
"download_url_expires_in_s": 43200,
"error": null,
"external_id": "ord-0",
"id": "j_73776a5038734181",
"metadata": {
"n": "0",
"order_id": "o0",
"shop": "acme"
},
"object": "export",
"progress": {
"eta_s": 0,
"percent": 100,
"phase": "Completed"
},
"project_id": "p_d47e376ac26f",
"queue_position": null,
"refunded": false,
"status": "succeeded",
"updated_at": 1793535291
}
],
"has_more": true,
"next_starting_after": "j_73776a5038734181",
"object": "list"
}
/v1/exports/{id}Get an export (download_url when succeeded)
| Field | In | Type | Description |
|---|---|---|---|
id | path | string · required |
curl --fail-with-body -sS "https://gomotionai.com/v1/exports/$EXPORT_ID" \
-H "Authorization: Bearer $GOMOTION_API_KEY"
{
"cost_credits": 0,
"created_at": 1793535290,
"download_filename": null,
"download_url": null,
"download_url_expires_in_s": null,
"error": null,
"external_id": "ord-0",
"id": "j_73776a5038734181",
"metadata": {
"n": "0",
"order_id": "o0",
"shop": "acme"
},
"object": "export",
"progress": null,
"project_id": "p_d47e376ac26f",
"queue_position": null,
"refunded": false,
"status": "running",
"updated_at": 1793535290
}
Uploads
/v1/uploads/scriptUpload a long script
| Field | In | Type | Description |
|---|---|---|---|
text | body | string · required · maxLength 50000 |
curl --fail-with-body -sS -X POST "https://gomotionai.com/v1/uploads/script" \
-H "Authorization: Bearer $GOMOTION_API_KEY" \
-H "Content-Type: application/json" -d '{"text": "In 1969 three astronauts left Earth on a journey nobody had made before. Four days later two of them walked on the Moon while the whole planet watched on television.In 1969 three astronauts left Earth on a journey nobody had made before. Four days later two of them walked on the Moon while the whole planet watched on television.In 1969 three astronauts left Earth on a journey nobody had made before. Four days later two of them walked on the Moon while the whole planet watched on television.In 1969 three astronauts left Earth on a journey nobody had made before. Four days later two of them walked on the Moon while the whole planet watched on television.In 1969 three astronauts left Earth on a journey nobody had made before. Four days later two of them walked on the Moon while the whole planet watched on television."}'
{
"key": "t/t_5f3a91c2e4b7/uploads/136ae2dbcc2a/script.txt",
"chars": 825,
"expires_in_hours": 60
}
/v1/uploadsGet a signed URL to upload your own voiceover or music. Then PUT the file bytes to put_url (any common audio format) within 1 hour, and pass key as audio_key or music_key to POST /v1/videos.
| Field | In | Type | Description |
|---|---|---|---|
kind | body | one of: audio, music · required |
curl --fail-with-body -sS -X POST "https://gomotionai.com/v1/uploads" \
-H "Authorization: Bearer $GOMOTION_API_KEY" \
-H "Content-Type: application/json" -d '{"kind": "audio"}'
{
"object": "upload",
"kind": "audio",
"key": "t/t_5f3a91c2e4b7/uploads/40a30b320493/audio.mp3",
"put_url": "https://<account>.r2.cloudflarestorage.com/gomotion/t/t_5f3a91c2e4b7/uploads/40a30b320493/audio.mp3?X-Amz-Signature=...",
"method": "PUT",
"expires_in_s": 3600,
"max_bytes": 100663296
}
Voices
/v1/languagesVoice languages
curl --fail-with-body -sS "https://gomotionai.com/v1/languages" \
-H "Authorization: Bearer $GOMOTION_API_KEY"
{
"languages": [
{
"code": "en",
"name": "English",
"model": "vega",
"chars_per_min": 998,
"quality": "standard"
},
{
"code": "nl",
"name": "Dutch",
"model": "nova",
"chars_per_min": 774,
"quality": "high"
},
{
"code": "fr",
"name": "French",
"model": "nova",
"chars_per_min": 812,
"quality": "high"
}
]
}
/v1/voicesVoices of a language
| Field | In | Type | Description |
|---|---|---|---|
lang | query | string | Language code (default en). |
quality | query | one of: standard, high |
curl --fail-with-body -sS "https://gomotionai.com/v1/voices?lang=en&quality=standard" \
-H "Authorization: Bearer $GOMOTION_API_KEY"
{
"lang": "en",
"voices": [
{
"id": "aurora",
"name": "Aurora",
"gender": "female",
"age": "",
"style": "",
"region": "US",
"model": "vega",
"sample_url": "https://static-files.genaipro.io/vega-samples/aurora.mp3",
"quality": "standard"
},
{
"id": "luna",
"name": "Luna",
"gender": "female",
"age": "",
"style": "",
"region": "US",
"model": "vega",
"sample_url": "https://static-files.genaipro.io/vega-samples/luna.mp3",
"quality": "standard"
},
{
"id": "stella",
"name": "Stella",
"gender": "female",
"age": "",
"style": "",
"region": "UK",
"model": "vega",
"sample_url": "https://static-files.genaipro.io/vega-samples/stella.mp3",
"quality": "standard"
}
]
}
Scripts & thumbnails
/v1/referencesVideos of a YouTube channel (references for scripts and thumbnails)
| Field | In | Type | Description |
|---|---|---|---|
channel_url | query | string · required | |
sort | query | one of: recent, popular |
curl --fail-with-body -sS "https://gomotionai.com/v1/references?channel_url=https://www.youtube.com/@NASA" \
-H "Authorization: Bearer $GOMOTION_API_KEY"
{
"object": "references",
"type": "channel",
"sort": "recent",
"videos": [
{
"id": "dQw4w9WgXcA",
"title": "Reference video 0",
"url": "https://www.youtube.com/watch?v=dQw4w9WgXcA",
"thumbnail_url": null,
"duration_sec": 600,
"views": 0
},
{
"id": "dQw4w9WgXcB",
"title": "Reference video 1",
"url": "https://www.youtube.com/watch?v=dQw4w9WgXcB",
"thumbnail_url": null,
"duration_sec": 601,
"views": 1000
},
{
"id": "dQw4w9WgXcC",
"title": "Reference video 2",
"url": "https://www.youtube.com/watch?v=dQw4w9WgXcC",
"thumbnail_url": null,
"duration_sec": 602,
"views": 2000
}
],
"credits": {
"script": 4,
"thumbnail": 4
},
"included_in_plan_left": {
"scripts": 0,
"thumbnails": 0
}
}
/v1/scriptsWrite a script with AI (async). Give channel_url, video_ids or only topic (then GoMotion finds 3 references on YouTube: 422 topic_no_references / 502 topic_search_failed, nothing charged).
| Field | In | Type | Description |
|---|---|---|---|
Idempotency-Key | header | string · required | Any unique string per new request. Retrying with the same key and body returns the same object (never charges twice). |
channel_url | body | string · maxLength 500 | YouTube channel link; the first 3 videos are the references. Or pass video_ids. |
video_ids | body | array · maxItems 3 | |
reference_titles | body | array · maxItems 3 | |
topic | body | string · maxLength 400, minLength 8 | What the script is about. Alone (no channel_url / video_ids) GoMotion searches YouTube for the topic and uses 3 videos as style references (same price). |
minutes | body | integer · minimum 1, maximum 90, default 8 | |
lang | body | string | Force the script language (e.g. en, es). |
sort | body | one of: recent, popular · default "recent" | |
external_id | body | string | Your own order or customer id: 1-128 printable ASCII characters, no spaces. Filter lists with ?external_id=. |
metadata | body | object | Your own data, echoed on the object and on webhooks: up to 20 keys [A-Za-z0-9_.-]{1,40}, string values up to 500 characters (numbers become strings), 8 KB in total. |
curl --fail-with-body -sS -X POST "https://gomotionai.com/v1/scripts" \
-H "Authorization: Bearer $GOMOTION_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" -d '{"channel_url": "https://www.youtube.com/@NASA", "topic": "How Webb unfolded in space", "minutes": 1}'
{
"created_at": 1793535294,
"error": null,
"external_id": "scr-1",
"id": "scr_8c6265cf04fa45fd",
"metadata": {
"o": "1"
},
"object": "script",
"references_source": "topic_search",
"result": null,
"status": "running",
"updated_at": 1793535294
}
/v1/scripts/{id}Get a script task
| Field | In | Type | Description |
|---|---|---|---|
id | path | string · required |
curl --fail-with-body -sS "https://gomotionai.com/v1/scripts/$SCRIPT_ID" \
-H "Authorization: Bearer $GOMOTION_API_KEY"
{
"created_at": 1793535294,
"error": null,
"external_id": "scr-1",
"id": "scr_8c6265cf04fa45fd",
"metadata": {
"o": "1"
},
"object": "script",
"references_source": "topic_search",
"result": null,
"status": "running",
"updated_at": 1793535294
}
/v1/thumbnailsMake a thumbnail with AI (async)
| Field | In | Type | Description |
|---|---|---|---|
Idempotency-Key | header | string · required | Any unique string per new request. Retrying with the same key and body returns the same object (never charges twice). |
title | body | string · required · maxLength 300 | |
text | body | string · maxLength 120 | |
subject | body | string · maxLength 300 | |
script | body | string · maxLength 50000 | |
with_text | body | boolean · default true | |
channel_url | body | string · maxLength 500 | |
video_ids | body | array · maxItems 3 | |
sort | body | one of: recent, popular · default "recent" | |
external_id | body | string | Your own order or customer id: 1-128 printable ASCII characters, no spaces. Filter lists with ?external_id=. |
metadata | body | object | Your own data, echoed on the object and on webhooks: up to 20 keys [A-Za-z0-9_.-]{1,40}, string values up to 500 characters (numbers become strings), 8 KB in total. |
curl --fail-with-body -sS -X POST "https://gomotionai.com/v1/thumbnails" \
-H "Authorization: Bearer $GOMOTION_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" -d '{"title": "Webb unfolds", "video_ids": ["dQw4w9WgXcQ"]}'
{
"created_at": 1793535297,
"error": null,
"external_id": "thm-1",
"id": "thm_09a9e88bc6094f11",
"metadata": {
"o": "2"
},
"object": "thumbnail",
"result": null,
"status": "running",
"updated_at": 1793535297
}
/v1/thumbnails/{id}Get a thumbnail task
| Field | In | Type | Description |
|---|---|---|---|
id | path | string · required |
curl --fail-with-body -sS "https://gomotionai.com/v1/thumbnails/$THUMBNAIL_ID" \
-H "Authorization: Bearer $GOMOTION_API_KEY"
{
"created_at": 1793535297,
"error": null,
"external_id": "thm-1",
"id": "thm_09a9e88bc6094f11",
"metadata": {
"o": "2"
},
"object": "thumbnail",
"result": null,
"status": "running",
"updated_at": 1793535297
}
Webhooks
/v1/webhooksAdd a webhook endpoint (max 3 per key). Events for videos, exports, scripts and thumbnails created by THIS key, sent when they end. The secret is shown only here and on rotate-secret.
| Field | In | Type | Description |
|---|---|---|---|
url | body | string · required · maxLength 2000 | https, port 443, a public host name (no IP, no localhost/.local/.internal); every address it resolves to must be public. 10 s timeout, no redirects. |
events | body | array | Default (or ["*"]): all events. |
description | body | string · maxLength 200 |
curl --fail-with-body -sS -X POST "https://gomotionai.com/v1/webhooks" \
-H "Authorization: Bearer $GOMOTION_API_KEY" \
-H "Content-Type: application/json" -d '{"url": "https://yourshop.com/gomotion/webhook", "events": ["video.succeeded", "video.failed", "export.succeeded", "export.failed"]}'
{
"created_at": 1793535285,
"description": "drift",
"disabled_reason": null,
"events": [
"video.succeeded",
"video.failed",
"video.cancelled",
"export.succeeded",
"export.failed",
"export.cancelled",
"script.succeeded",
"script.failed",
"thumbnail.succeeded",
"thumbnail.failed"
],
"failed_in_a_row": 0,
"id": "wh_a0f047599f69410c",
"last_failure_at": null,
"last_success_at": null,
"object": "webhook",
"secret": "whsec_...",
"status": "enabled",
"url": "https://hooks.yourshop.com/drift/hook"
}
/v1/webhooksList this key's webhooks
curl --fail-with-body -sS "https://gomotionai.com/v1/webhooks" \
-H "Authorization: Bearer $GOMOTION_API_KEY"
{
"data": [
{
"created_at": 1793535285,
"description": "drift",
"disabled_reason": null,
"events": [
"video.succeeded",
"video.failed",
"video.cancelled",
"export.succeeded",
"export.failed",
"export.cancelled",
"script.succeeded",
"script.failed",
"thumbnail.succeeded",
"thumbnail.failed"
],
"failed_in_a_row": 0,
"id": "wh_a0f047599f69410c",
"last_failure_at": null,
"last_success_at": null,
"object": "webhook",
"status": "enabled",
"url": "https://hooks.yourshop.com/drift/hook"
}
],
"object": "list"
}
/v1/webhooks/{id}Get a webhook
| Field | In | Type | Description |
|---|---|---|---|
id | path | string · required |
curl --fail-with-body -sS "https://gomotionai.com/v1/webhooks/$WEBHOOK_ID" \
-H "Authorization: Bearer $GOMOTION_API_KEY"
{
"created_at": 1793535285,
"description": "drift",
"disabled_reason": null,
"events": [
"video.succeeded",
"video.failed",
"video.cancelled",
"export.succeeded",
"export.failed",
"export.cancelled",
"script.succeeded",
"script.failed",
"thumbnail.succeeded",
"thumbnail.failed"
],
"failed_in_a_row": 0,
"id": "wh_a0f047599f69410c",
"last_failure_at": null,
"last_success_at": null,
"object": "webhook",
"status": "enabled",
"url": "https://hooks.yourshop.com/drift/hook"
}
/v1/webhooks/{id}Update a webhook (url, events, description, enabled)
| Field | In | Type | Description |
|---|---|---|---|
id | path | string · required | |
url | body | string · maxLength 2000 | https, port 443, a public host name (no IP, no localhost/.local/.internal); every address it resolves to must be public. 10 s timeout, no redirects. |
events | body | array | Default (or ["*"]): all events. |
description | body | string · maxLength 200 | |
enabled | body | boolean | true re-enables an endpoint disabled after failures. |
curl --fail-with-body -sS -X POST "https://gomotionai.com/v1/webhooks/$WEBHOOK_ID" \
-H "Authorization: Bearer $GOMOTION_API_KEY" \
-H "Content-Type: application/json" -d '{"enabled": true}'
{
"created_at": 1793535285,
"description": "drift 2",
"disabled_reason": null,
"events": [
"video.succeeded",
"video.failed",
"video.cancelled",
"export.succeeded",
"export.failed",
"export.cancelled",
"script.succeeded",
"script.failed",
"thumbnail.succeeded",
"thumbnail.failed"
],
"failed_in_a_row": 0,
"id": "wh_a0f047599f69410c",
"last_failure_at": null,
"last_success_at": null,
"object": "webhook",
"status": "enabled",
"url": "https://hooks.yourshop.com/drift/hook"
}
/v1/webhooks/{id}Delete a webhook
| Field | In | Type | Description |
|---|---|---|---|
id | path | string · required |
curl --fail-with-body -sS -X DELETE "https://gomotionai.com/v1/webhooks/$WEBHOOK_ID" \
-H "Authorization: Bearer $GOMOTION_API_KEY"
{
"deleted": true,
"id": "wh_120f20b244eb403b",
"object": "webhook"
}
/v1/webhooks/{id}/testSend one signed webhook.test event now
| Field | In | Type | Description |
|---|---|---|---|
id | path | string · required |
curl --fail-with-body -sS -X POST "https://gomotionai.com/v1/webhooks/$WEBHOOK_ID/test" \
-H "Authorization: Bearer $GOMOTION_API_KEY"
{
"delivered": true,
"error": null,
"event_id": "evt_5f3a91c2e4b74206a566",
"object": "webhook_test",
"status_code": 200
}
/v1/webhooks/{id}/rotate-secretNew signing secret (the old one stops at once)
| Field | In | Type | Description |
|---|---|---|---|
id | path | string · required |
curl --fail-with-body -sS -X POST "https://gomotionai.com/v1/webhooks/$WEBHOOK_ID/rotate-secret" \
-H "Authorization: Bearer $GOMOTION_API_KEY"
{
"created_at": 1793535285,
"description": "drift 2",
"disabled_reason": null,
"events": [
"video.succeeded",
"video.failed",
"video.cancelled",
"export.succeeded",
"export.failed",
"export.cancelled",
"script.succeeded",
"script.failed",
"thumbnail.succeeded",
"thumbnail.failed"
],
"failed_in_a_row": 0,
"id": "wh_a0f047599f69410c",
"last_failure_at": null,
"last_success_at": null,
"object": "webhook",
"secret": "whsec_...",
"status": "enabled",
"url": "https://hooks.yourshop.com/drift/hook"
}
/v1/webhooks/{id}/deliveriesRecent deliveries (newest first, kept 7 days)
| Field | In | Type | Description |
|---|---|---|---|
id | path | string · required | |
limit | query | integer |
curl --fail-with-body -sS "https://gomotionai.com/v1/webhooks/$WEBHOOK_ID/deliveries?limit=10" \
-H "Authorization: Bearer $GOMOTION_API_KEY"
{
"data": [
{
"attempts": 1,
"created_at": 1793535285,
"delivered_at": 1793535285,
"id": "evt_5f3a91c2e4b74206a566",
"last_error": null,
"last_status_code": 200,
"next_attempt_at": null,
"object": "webhook_delivery",
"object_id": null,
"status": "delivered",
"type": "webhook.test"
}
],
"object": "list"
}
/v1/webhooks/{id}/deliveries/{evt}/retrySend an event again (same event id)
| Field | In | Type | Description |
|---|---|---|---|
id | path | string · required | |
evt | path | string · required |
curl --fail-with-body -sS -X POST "https://gomotionai.com/v1/webhooks/$WEBHOOK_ID/deliveries/$EVENT_ID/retry" \
-H "Authorization: Bearer $GOMOTION_API_KEY"
{
"id": "evt_5f3a91c2e4b74206a566",
"object": "webhook_delivery",
"status": "pending"
}
Account
/v1/accountYour plan, API limits and this key's usage today
curl --fail-with-body -sS "https://gomotionai.com/v1/account" \
-H "Authorization: Bearer $GOMOTION_API_KEY"
{
"custom_limits": false,
"key": {
"credits_spent_today": 0,
"id": "k_ae85e6baae2b",
"max_credits_day": 0,
"scopes": [
"videos:read",
"videos:write",
"scripts:write"
],
"videos_in_progress": 0,
"videos_today": 0
},
"limits": {
"active_keys_per_account": 10,
"paid_requests_per_minute_per_account": 30,
"requests_per_minute_per_account": 300,
"requests_per_minute_per_key": 120,
"scripts_and_thumbnails_in_progress_per_account": 3,
"videos_in_progress_per_account": 3,
"videos_in_progress_per_key": 5,
"videos_per_day_per_account": 300,
"videos_per_day_per_key": 200
},
"object": "account",
"plan": "agency"
}
/v1/creditsCredit balance
curl --fail-with-body -sS "https://gomotionai.com/v1/credits" \
-H "Authorization: Bearer $GOMOTION_API_KEY"
{
"balance": 2000,
"held": 0,
"available": 2000,
"expires_at": 0,
"topup_credits": 0,
"renews_at": 0,
"key": {
"id": "k_0d5e1907611a",
"max_credits_day": 0,
"spent_today": 0
}
}
/v1/credits/historyCredit movements (newest first)
| Field | In | Type | Description |
|---|---|---|---|
limit | query | integer |
curl --fail-with-body -sS "https://gomotionai.com/v1/credits/history?limit=10" \
-H "Authorization: Bearer $GOMOTION_API_KEY"
{
"object": "list",
"data": [
{
"at": 1793457476,
"type": "credit",
"credits": 5000,
"balance_after": 5000,
"job_id": null
}
]
}
/v1/quotePrice of a video in credits
| Field | In | Type | Description |
|---|---|---|---|
chars | query | integer | Script length in characters (or pass duration_sec). |
duration_sec | query | integer | Length in seconds (for your own audio). |
lang | query | string | |
voice | query | string |
curl --fail-with-body -sS "https://gomotionai.com/v1/quote?chars=1200&lang=en" \
-H "Authorization: Bearer $GOMOTION_API_KEY"
{
"duration_sec": 67,
"lang": "en",
"voice": null,
"quality": "standard",
"cost_credits": 75
}
OpenAPI
OpenAPI 3.1, API version 1.2.0. Import it in Postman, Insomnia or your code generator.