REST API v1 · Reference

API documentation

Everything you need to generate motion design videos over HTTP: a key, a brief, a quote, a generation, an MP4.
Create an API keyOpenAPI 3.1 spechttps://motiongist.com/api/v1

Introduction

Overview

The Motiongist REST API generates narrated motion design videos (vertical 1080×1920, 45 to 180 s) over HTTP: you send a brief, get a quote, start a generation and download the MP4, the captions and the storyboard. It is the same engine as the web studio, with the same pricing rules.

Every request goes to https://motiongist.com/api/v1. The API is included in the Studio, Team plans.

Videos are narrated in English (default) or French: set "language": "en" or "language": "fr" in the brief. Every voice speaks both languages. The API itself (errors, descriptions) is in English.

  • GET /api/v1 is a public index, without a key, that describes authentication and every endpoint.
  • The OpenAPI 3.1 spec is published at /openapi.json (client generators, Postman, agents).
  • To drive Motiongist from Claude, ChatGPT or Cursor, use the MCP server instead.

Access

Authentication

Create a REST key in API & AI agents. The full key (prefix mg_live_) is shown only once: keep it in a secrets manager, never in browser-side code. Send it in the X-API-Key header (or Authorization: Bearer) with every request.

Authenticated request
curl https://motiongist.com/api/v1/me \
  -H "X-API-Key: $MOTIONGIST_API_KEY"

Each key carries scopes: jobs:read, jobs:write, videos:read, videos:write, assets:write, webhooks:write. A read-only key can track generations without being able to spend credits. MCP keys (mg_mcp_) are refused on the REST API. Each key is tied to one workspace: it only sees that workspace’s projects, generations and videos.

In 4 calls

Quickstart

1. Estimate the price. Nothing is charged; the response contains the total in credits and a signed quote valid for 1 hour.

1 · Estimate
curl -X POST https://motiongist.com/api/v1/jobs/estimate \
  -H "X-API-Key: $MOTIONGIST_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "brief": {
    "subject": "How a CDN works",
    "length": "short",
    "language": "en",
    "voice": "George",
    "look": { "mode": "preset", "preset": "night" }
  }
}'
# → data.total (credits), data.quote (signed quote), data.allowed

2. Start the generation with the same brief and the quote: the price is guaranteed. Response 202 with the job id.

2 · Generate
curl -X POST https://motiongist.com/api/v1/jobs \
  -H "X-API-Key: $MOTIONGIST_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: cdn-short-001" \
  -d '{
  "brief": {
    "subject": "How a CDN works",
    "length": "short",
    "language": "en",
    "voice": "George",
    "look": { "mode": "preset", "preset": "night" }
  },
  "quote": "<data.quote from step 1>"
}'
# → data.job.id, data.queuePosition

3. Track it every 5 to 10 seconds until status = succeeded (or subscribe to webhooks). A video takes a few minutes.

3 · Track
curl https://motiongist.com/api/v1/jobs/$JOB_ID \
  -H "X-API-Key: $MOTIONGIST_API_KEY"
# → data.status: queued → running → succeeded, data.stage, data.progress, data.videoId

4. Download. Links are signed and valid for 15 minutes; fetch the video again to get new ones.

4 · Download
URL=$(curl -s https://motiongist.com/api/v1/videos/$VIDEO_ID \
  -H "X-API-Key: $MOTIONGIST_API_KEY" | jq -r '.data.files.mp4.url')
curl -L -o video.mp4 "$URL"
Node.js (18+) · full flow
const base = 'https://motiongist.com/api/v1';
const headers = { 'X-API-Key': process.env.MOTIONGIST_API_KEY, 'Content-Type': 'application/json' };
const call = async (path, init = {}) => {
  const res = await fetch(base + path, { headers, ...init });
  const json = await res.json();
  if (!res.ok) throw new Error(`${json.error.code}: ${json.error.message}`);
  return json.data;
};

const brief = { subject: 'How a CDN works', length: 'short', language: 'en', voice: 'George' };
const est = await call('/jobs/estimate', { method: 'POST', body: JSON.stringify({ brief }) });
console.log(`Price: ${est.total} credits`);
const { job } = await call('/jobs', { method: 'POST', body: JSON.stringify({ brief, quote: est.quote }) });

let j = job;
while (!['succeeded', 'failed', 'cancelled'].includes(j.status)) {
  await new Promise((r) => setTimeout(r, 8000));
  j = await call(`/jobs/${job.id}`);
}
if (j.status !== 'succeeded') throw new Error(j.error?.message ?? j.status);
const video = await call(`/videos/${j.videoId}`);
console.log(video.files.mp4.url);

For a French narration, set "language": "fr" in the brief; English ("en") is the default.

Conventions

Response format

Everything is JSON (UTF-8). A successful response wraps the payload in data; an error returns an error object. Both always include requestId.

Success
{
  "data": { /* endpoint payload */ },
  "requestId": "req_3f9a1c2e7b8d4a60"
}
Error
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Invalid request.",
    "details": [
      { "path": "brief.subject", "code": "too_small", "message": "…" }
    ]
  },
  "requestId": "req_3f9a1c2e7b8d4a60"
}

Dates are ISO 8601 (UTC), ids are UUIDs, durations are in seconds, prices in credits. Error messages are in English; code against error.code, which is stable.

Response headerPurpose
X-Request-IdUnique request id (also in the body). Quote it to support.
X-RateLimit-LimitRequests allowed per window (60 per 60 s, per key).
X-RateLimit-RemainingRequests left in the current window.
X-RateLimit-ResetISO 8601 time when the window resets.
X-API-TierPlan resolved for the key (Studio, Team).
X-Idempotent-ReplayPresent and true when the response is replayed from an Idempotency-Key.
Retry-AfterOn a 429 (or IDEMPOTENCY_IN_PROGRESS): seconds to wait before retrying.

Lists are cursor-paginated: pass limit (1 to 100) and, for the next page, the nextCursor you received as cursor. nextCursor: null marks the last page. The API accepts calls from a browser (open CORS), but never put your key in public code.

Failures

Errors

Every error returns the matching HTTP status and a stable error.code.

CodeStatusMeaning
MISSING_API_KEY401No key sent (X-API-Key or Authorization: Bearer header).
INVALID_API_KEY401Unknown or revoked key, or an MCP key (mg_mcp_) used on the REST API.
UNAUTHENTICATED401OAuth token missing or expired.
FORBIDDEN403The key lacks the required scope, or the action is temporarily disabled.
PLAN_REQUIRED403The workspace plan does not include the API or the requested option (final voice, custom look, Studio quality…).
INSUFFICIENT_CREDITS402Not enough credits to start the generation. details.required and details.balance show the gap.
VALIDATION_ERROR400Unreadable JSON body or invalid fields. details[] lists each faulty field (path, message).
QUOTE_INVALID400The quote expired (1 h), was signed for another workspace, or the brief / project changed since. Estimate again.
NOT_FOUND404Resource missing, deleted, or owned by another workspace.
CONFLICT409Action impossible in the current state (e.g. deleting a project whose generation is running).
NOT_CANCELLABLE409The generation is finished or its rendering has started.
CONCURRENCY_LIMIT429Too many simultaneous generations for the plan.
RATE_LIMITED429Too many requests: wait Retry-After seconds.
IDEMPOTENCY_CONFLICT409Idempotency-Key already used with a different request body.
IDEMPOTENCY_IN_PROGRESS409The first request with this Idempotency-Key is still running: retry after Retry-After.
PAYLOAD_TOO_LARGE413File too large (logo 2 MB, document 20 MB).
UNSUPPORTED_MEDIA_TYPE415File format or Content-Type not supported.
INTERNAL_ERROR500Unexpected error on our side. Safe to retry with an Idempotency-Key; quote the requestId to support.

A generation that fails in the engine is not an HTTP error: the job moves to failed with error.code (e.g. render-failed) and its credits are refunded automatically.

Quotas

Rate limits

  • 60 requests per minute per key, fixed window. Beyond that: 429 RATE_LIMITED with Retry-After.
  • 10 generation starts per minute per workspace.
  • 60 AI calls per hour (script writing, ideas, narration previews) and 30 file uploads per hour, per workspace.
  • Parallel generations: 2 on Studio, 4 on Team. Further ones wait in the queue.

Polling calls count toward the quota: poll GET /jobs/{id} every 5 to 10 seconds at most, or use webhooks. Generations consume credits, as in the studio.

Safe retries

Idempotency

Every POST accepts an optional Idempotency-Key header (200 characters max). Replaying the same key with the same body within 24 hours returns the original response (marked X-Idempotent-Replay: true) instead of creating a duplicate: no generation is started, no credit is charged twice. Reusing a key with a different body returns IDEMPOTENCY_CONFLICT; retrying while the first request is still running returns IDEMPOTENCY_IN_PROGRESS with Retry-After. Errors are not stored: you can retry with the same key.

Idempotent creation
curl -X POST https://motiongist.com/api/v1/jobs \
  -H "X-API-Key: $MOTIONGIST_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order-4512" \
  -d '{ "brief": { "subject": "How a CDN works", "language": "en" } }'

Events

Webhooks

Instead of polling GET /jobs/{id}, register an HTTPS URL (POST /webhooks): Motiongist sends it a JSON POST for every subscribed event. Answer 2xx within 10 seconds; otherwise the delivery is retried after 30 s, 5 min, then 30 min, before being dropped. The same event can arrive twice: deduplicate with id.

EventWhen
job.startedA worker picked up the generation (data.attempt gives the attempt number).
job.progressStage change: data.stage (script, narration, storyboard, check, render, upload) and data.progress in %.
job.succeededThe video is ready: data.videoId and data.durationS. Call GET /videos/{id} for the links.
job.failedFinal failure after the automatic retries: data.error.code and message. Credits refunded.
job.cancelledGeneration cancelled (by you or by the system). Credits refunded.
video.deletedA video was deleted (API, studio).
post.publishedA social post went live: data.postId, data.provider, data.externalUrl.
post.failedA social post failed after the automatic retries: data.error (data.reconnectRequired when the account must be reconnected).
Body sent
{
  "id": "evt_Q2x8fK1mZpL0",
  "type": "job.succeeded",
  "created": "2026-10-23T09:19:41.000Z",
  "data": {
    "jobId": "7f3c2a10-5b1e-4c8a-9d2f-0e6b4a1c9d55",
    "projectId": "2b9e4d71-8c0a-4f3e-a6b5-1d2c3e4f5a6b",
    "videoId": "c4d5e6f7-1a2b-4c3d-8e9f-0a1b2c3d4e5f",
    "durationS": 104.2
  }
}

Verifying the signature

Every request carries the header X-Motiongist-Signature: t=<unix timestamp>,v1=<signature>, where the signature is the hex HMAC-SHA256 of the string "<t>.<raw body>" with the webhook secret (whsec_…, shown only once at creation). Compare in constant time and reject timestamps older than 5 minutes to block replays.

Node.js (Express)
import crypto from 'node:crypto';
import express from 'express';

const app = express();
const SECRET = process.env.MOTIONGIST_WEBHOOK_SECRET; // whsec_…

// Keep the raw body: the signature covers the exact bytes received.
app.post('/hooks/motiongist', express.raw({ type: 'application/json' }), (req, res) => {
  const header = req.get('X-Motiongist-Signature') ?? '';
  const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')));
  const expected = crypto.createHmac('sha256', SECRET).update(`${parts.t}.${req.body}`).digest('hex');
  const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) < 300; // 5 min
  const valid = parts.v1 && parts.v1.length === expected.length
    && crypto.timingSafeEqual(Buffer.from(parts.v1), Buffer.from(expected));
  if (!valid || !fresh) return res.status(400).send('invalid signature');

  const event = JSON.parse(req.body);
  if (event.type === 'job.succeeded') {
    // event.data.videoId → GET /api/v1/videos/{id}
  }
  res.sendStatus(200); // answer fast (under 10 s), process in the background
});
Python (Flask)
import hashlib, hmac, json, os, time
from flask import Flask, request, abort

app = Flask(__name__)
SECRET = os.environ["MOTIONGIST_WEBHOOK_SECRET"].encode()  # whsec_…

@app.post("/hooks/motiongist")
def motiongist_hook():
    header = request.headers.get("X-Motiongist-Signature", "")
    parts = dict(p.split("=", 1) for p in header.split(",") if "=" in p)
    body = request.get_data()  # raw bytes
    expected = hmac.new(SECRET, f"{parts.get('t', '')}.".encode() + body, hashlib.sha256).hexdigest()
    if not hmac.compare_digest(parts.get("v1", ""), expected):
        abort(400)
    if abs(time.time() - int(parts.get("t", "0"))) > 300:
        abort(400)
    event = json.loads(body)
    if event["type"] == "job.succeeded":
        video_id = event["data"]["videoId"]  # → GET /api/v1/videos/{id}
    return "", 200

Model

Concepts

Brief

The full description of a video: subject, source (idea, script or document), audience, length, narration language (en or fr), voice, look, brand, notes, mascot, quality. It is the single input of the engine; the studio, the API and MCP validate the same schema (see Estimate a brief for every field).

Project

A draft, like in the studio: settings, editable script, voice preview, then successive generations (versions). Projects created through the API show up in the web studio. POST /jobs with a brief creates a project automatically; the /projects endpoints are for when you want to iterate (write the script, listen to it, adjust it) before paying.

Quote

A signed token returned by the estimate endpoints. It locks the price for this exact brief, in this workspace, for 1 hour. A generation started with a quote never costs more than the announced price; if the brief or the project changes, the quote is refused (QUOTE_INVALID) and you need to estimate again. POST /projects/{id}/generate requires it; POST /jobs accepts it (without a quote, the price is recomputed at start).

Generation (job)

One run of the engine: queued → running → succeeded | failed | cancelled. While running, stage goes through script, narration, storyboard, check, render, upload. Cancelling is possible until rendering starts. Transient failures are retried automatically.

Video

The result of a successful generation: 1080×1920 MP4, SRT captions, contact sheet, poster, JSON storyboard and report. Files are private: the API returns signed links valid for 15 minutes. MP4 files carry metadata marking them as AI-generated content.

Credits

Each generation consumes credits from the workspace balance (GET /me): 400 (short), 700 (standard) or 1,000 (long), plus 1 credit per 10 characters narrated with a final voice, +300 for Studio quality, +300 for a custom look. A new version of a project costs 50% of the base price. Failures and cancellations are refunded. The Studio and Team plans can go past their monthly balance (slower queue) down to −15,000 and −50,000 credits.

Reference

Endpoints

Base URL: https://motiongist.com/api/v1. Every endpoint requires a key, except the index. Examples use the $MOTIONGIST_API_KEY variable.

Account & catalog

Public index

GET/api/v1no key

Describes the API without a key: version, links to this documentation and the OpenAPI spec, authentication and the list of endpoints.

Request
curl https://motiongist.com/api/v1
Response 200
{
  "data": {
    "name": "Motiongist API",
    "version": "1.0.0",
    "docs": "https://motiongist.com/docs/api",
    "openapi": "https://motiongist.com/openapi.json",
    "endpoints": [
      "…"
    ]
  },
  "requestId": "req_3f9a1c2e7b8d4a60"
}

My account

GET/api/v1/me

Workspace linked to the key, plan, credit balance, limits (rate, parallel generations) and the key’s scopes.

Request
curl https://motiongist.com/api/v1/me \
  -H "X-API-Key: $MOTIONGIST_API_KEY"
Response 200
{
  "data": {
    "workspace": {
      "id": "0d1e2f3a-4b5c-4d6e-8f70-81a2b3c4d5e6",
      "name": "Acme’s workspace",
      "plan": "studio",
      "subscriptionStatus": "active"
    },
    "plan": {
      "id": "studio",
      "name": "Studio",
      "monthlyCredits": 30000,
      "softOverdraft": 15000,
      "maxConcurrentJobs": 2
    },
    "credits": {
      "balance": 29153
    },
    "limits": {
      "requestsPerMinute": 60,
      "maxConcurrentJobs": 2,
      "activeJobs": {
        "running": 1,
        "queued": 0
      }
    },
    "key": {
      "id": "b1c2d3e4-…",
      "kind": "rest",
      "scopes": [
        "jobs:write",
        "jobs:read",
        "videos:read",
        "videos:write",
        "assets:write",
        "webhooks:write"
      ]
    }
  },
  "requestId": "req_3f9a1c2e7b8d4a60"
}

Catalog

GET/api/v1/catalog

Everything you can put in a brief: voices (with audio samples), looks, lengths, narration languages (en, fr), audiences, credit costs and plans.

Request
curl https://motiongist.com/api/v1/catalog \
  -H "X-API-Key: $MOTIONGIST_API_KEY"
Response 200
{
  "data": {
    "voices": [
      {
        "id": "George",
        "name": "George",
        "gender": "m",
        "description": "Warm and steady, made for explaining",
        "tags": [
          "narration",
          "calm"
        ]
      }
    ],
    "looks": [
      {
        "id": "clean",
        "label": "Light, product"
      },
      {
        "id": "night",
        "label": "Dark, tech"
      },
      {
        "id": "toon",
        "label": "Illustrated"
      }
    ],
    "lengths": [
      {
        "id": "short",
        "secondsMin": 45,
        "secondsMax": 60,
        "baseCredits": 400
      }
    ],
    "languages": [
      "en",
      "fr"
    ],
    "defaultLanguage": "en",
    "audiences": [
      "beginner",
      "dev",
      "ops",
      "decision-maker"
    ],
    "creditCosts": {
      "base": {
        "short": 400,
        "standard": 700,
        "full": 1000
      },
      "finalVoicePerChars": 10,
      "studioTier": 300,
      "customLook": 300,
      "threeD": 100,
      "revisionFactor": 0.5
    }
  },
  "requestId": "req_3f9a1c2e7b8d4a60"
}

Generations (jobs)

Estimate a brief

POST/api/v1/jobs/estimatescope jobs:read

Computes the exact price in credits and the generation time, and returns a signed quote valid for 1 hour. Nothing is charged. Pass the quote to POST /jobs to lock the price.

JSON body

FieldTypeDescription
briefobjectThe full video briefrequired
brief.subjectstringVideo subject, in one sentence (3 to 200 characters).required
brief.inputobject (variants)Content source: { kind: "idea" } (the script is written for you), { kind: "script", text } (your exact text, 4,000 chars max) or { kind: "document", text, sourceName } (facts to respect, 40,000 chars max).Default: {"kind":"idea"}
brief.audience"beginner" | "dev" | "ops" | "decision-maker"Target audience: beginner, dev, ops or decision-maker.Default: "dev"
brief.length"short" | "standard" | "full"Length: short (45–60 s), standard (90–120 s) or full (150–180 s).Default: "standard"
brief.language"fr" | "en"Narration language: en (English, default) or fr (French).Default: "en"
brief.voicestringNarrator voice (see GET /catalog): George, Sarah, Daniel, Brian, Lily, Alice, Will, Matilda. Every voice speaks both languages.Default: "George"
brief.lookobject (variants)Visual style: { mode: "preset", preset: "clean" | "night" | "toon" }, { mode: "brand" } (brand colors, brand field required) or { mode: "custom", description } (Studio plan).Default: {"mode":"preset"}
brief.brandobjectBrand shown: name (24 chars), site, colors (1 to 3 #RRGGBB colors), logo ("asset:<id>" returned by POST /assets).
brief.brand.namestringBrand name (24 characters max).required
brief.brand.sitestringWebsite shown at the end of the video (40 characters max).
brief.brand.colorsstring[]1 to 3 hex colors #RRGGBB.required
brief.brand.logostringLogo: "asset:<id>" returned by POST /assets.
brief.notesobjectInstructions: mustShow (up to 10 things to show), avoid (things to avoid), keyMoment, free (free text).Default: {"mustShow":[],"avoid":[]}
brief.notes.mustShowstring[]Things that must be shown (10 max, 80 chars each).Default: []
brief.notes.avoidstring[]Things to avoid (10 max).Default: []
brief.notes.keyMomentstringThe key moment to highlight.
brief.notes.freestringFree-form instructions (1,000 characters max).
brief.mascotbooleanAdds an animated presenter.Default: false
brief.tier"standard" | "studio"standard, or studio (higher quality, +300 credits, Studio plan).Default: "standard"
brief.dimension"2d" | "3d"2d (flat motion design, default) or 3d (perspective, volume, depth and camera moves).Default: "2d"
brief.threeD"none" | "signature"Legacy, ignored: use dimension.Default: "none"
voiceMode"draft" | "final"final = licensed voice (paid plans), draft = free draft voice. Default: final on paid plans.
Request
curl -X POST https://motiongist.com/api/v1/jobs/estimate \
  -H "X-API-Key: $MOTIONGIST_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
  "brief": {
    "subject": "How a CDN works",
    "input": {
      "kind": "idea"
    },
    "audience": "dev",
    "length": "standard",
    "language": "en",
    "voice": "George",
    "look": {
      "mode": "preset",
      "preset": "night"
    },
    "notes": {
      "mustShow": [
        "edge cache",
        "latency"
      ],
      "avoid": []
    }
  }
}'
Response 200
{
  "data": {
    "total": 847,
    "lines": [
      {
        "label": "Standard video (90–120 s)",
        "credits": 700
      },
      {
        "label": "Final voice (1,470 characters)",
        "credits": 147
      }
    ],
    "quote": "eyJ3cyI6IjQ…(signed quote, pass it back unchanged)…",
    "quoteExpiresAt": "2026-10-23T10:12:00.000Z",
    "voiceMode": "final",
    "isRevision": false,
    "balance": 29153,
    "allowed": true,
    "blockers": [],
    "overdraft": false,
    "eta": {
      "minutesMin": 4,
      "minutesMax": 8
    }
  },
  "requestId": "req_3f9a1c2e7b8d4a60"
}

Possible errors: VALIDATION_ERROR (400)

Start a generation

POST/api/v1/jobsscope jobs:write

Charges the credits and queues the generation. With a quote, the price is guaranteed (and refused if the brief changed); without a quote, the server recomputes the price. 202 response: then poll GET /jobs/{id} or subscribe to webhooks.

JSON body

FieldTypeDescription
briefobjectVideo brief (one-shot: a project is created for it). Or projectId.
brief.subjectstringVideo subject, in one sentence (3 to 200 characters).required
brief.inputobject (variants)Content source: { kind: "idea" } (the script is written for you), { kind: "script", text } (your exact text, 4,000 chars max) or { kind: "document", text, sourceName } (facts to respect, 40,000 chars max).Default: {"kind":"idea"}
brief.audience"beginner" | "dev" | "ops" | "decision-maker"Target audience: beginner, dev, ops or decision-maker.Default: "dev"
brief.length"short" | "standard" | "full"Length: short (45–60 s), standard (90–120 s) or full (150–180 s).Default: "standard"
brief.language"fr" | "en"Narration language: en (English, default) or fr (French).Default: "en"
brief.voicestringNarrator voice (see GET /catalog): George, Sarah, Daniel, Brian, Lily, Alice, Will, Matilda. Every voice speaks both languages.Default: "George"
brief.lookobject (variants)Visual style: { mode: "preset", preset: "clean" | "night" | "toon" }, { mode: "brand" } (brand colors, brand field required) or { mode: "custom", description } (Studio plan).Default: {"mode":"preset"}
brief.brandobjectBrand shown: name (24 chars), site, colors (1 to 3 #RRGGBB colors), logo ("asset:<id>" returned by POST /assets).
brief.brand.namestringBrand name (24 characters max).required
brief.brand.sitestringWebsite shown at the end of the video (40 characters max).
brief.brand.colorsstring[]1 to 3 hex colors #RRGGBB.required
brief.brand.logostringLogo: "asset:<id>" returned by POST /assets.
brief.notesobjectInstructions: mustShow (up to 10 things to show), avoid (things to avoid), keyMoment, free (free text).Default: {"mustShow":[],"avoid":[]}
brief.notes.mustShowstring[]Things that must be shown (10 max, 80 chars each).Default: []
brief.notes.avoidstring[]Things to avoid (10 max).Default: []
brief.notes.keyMomentstringThe key moment to highlight.
brief.notes.freestringFree-form instructions (1,000 characters max).
brief.mascotbooleanAdds an animated presenter.Default: false
brief.tier"standard" | "studio"standard, or studio (higher quality, +300 credits, Studio plan).Default: "standard"
brief.dimension"2d" | "3d"2d (flat motion design, default) or 3d (perspective, volume, depth and camera moves).Default: "2d"
brief.threeD"none" | "signature"Legacy, ignored: use dimension.Default: "none"
projectIduuidExisting project to generate (alternative to brief)
quotestringSigned quote returned by POST /jobs/estimate. Guarantees the price; without a quote, the server recomputes the price.
titlestringTitle (120 characters max). Default: the subject.
voiceMode"draft" | "final"final = licensed voice (paid plans), draft = free draft voice. Default: final on paid plans.
Request
curl -X POST https://motiongist.com/api/v1/jobs \
  -H "X-API-Key: $MOTIONGIST_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
  "brief": {
    "subject": "How a CDN works",
    "input": {
      "kind": "idea"
    },
    "audience": "dev",
    "length": "standard",
    "language": "en",
    "voice": "George",
    "look": {
      "mode": "preset",
      "preset": "night"
    },
    "notes": {
      "mustShow": [
        "edge cache",
        "latency"
      ],
      "avoid": []
    }
  },
  "quote": "eyJ3cyI6IjQ…(signed quote, pass it back unchanged)…"
}'
Response 202
{
  "data": {
    "job": {
      "id": "7f3c2a10-5b1e-4c8a-9d2f-0e6b4a1c9d55",
      "projectId": "2b9e4d71-8c0a-4f3e-a6b5-1d2c3e4f5a6b",
      "status": "queued",
      "stage": "script",
      "stageLabel": "Script",
      "progress": 0,
      "detail": null,
      "stages": {},
      "voiceMode": "final",
      "isRevision": false,
      "credits": 847,
      "queuePosition": 1,
      "cancellable": true,
      "error": null,
      "videoId": null,
      "attempt": 1,
      "source": "api",
      "queuedAt": "2026-10-23T09:12:04.000Z",
      "startedAt": null,
      "finishedAt": null
    },
    "projectId": "2b9e4d71-8c0a-4f3e-a6b5-1d2c3e4f5a6b",
    "queuePosition": 1
  },
  "requestId": "req_3f9a1c2e7b8d4a60"
}

Possible errors: VALIDATION_ERROR (400), QUOTE_INVALID (400), INSUFFICIENT_CREDITS (402), PLAN_REQUIRED (403), RATE_LIMITED (429)

List generations

GET/api/v1/jobsscope jobs:read

Generations of the workspace, newest first, with cursor pagination.

Parameters

NameInTypeDescription
limitqueryintegerNumber of items (1 to 100, default 20)
cursorquerystringCursor returned in nextCursor, for the next page
statusquerystringFilter, one or more comma-separated values: queued, running, succeeded, failed, cancelled
sincequerydate-timeOnly generations created after this date (ISO 8601)
Request
curl https://motiongist.com/api/v1/jobs \
  -H "X-API-Key: $MOTIONGIST_API_KEY"
Response 200
{
  "data": {
    "items": [
      {
        "id": "7f3c2a10-5b1e-4c8a-9d2f-0e6b4a1c9d55",
        "projectId": "2b9e4d71-8c0a-4f3e-a6b5-1d2c3e4f5a6b",
        "status": "running",
        "stage": "storyboard",
        "stageLabel": "Storyboard",
        "progress": 46,
        "detail": "Scene 4/9",
        "stages": {},
        "voiceMode": "final",
        "isRevision": false,
        "credits": 847,
        "queuePosition": 0,
        "cancellable": true,
        "error": null,
        "videoId": null,
        "attempt": 1,
        "source": "api",
        "queuedAt": "2026-10-23T09:12:04.000Z",
        "startedAt": "2026-10-23T09:12:09.000Z",
        "finishedAt": null
      }
    ],
    "nextCursor": "2026-10-23T09:12:04.000Z"
  },
  "requestId": "req_3f9a1c2e7b8d4a60"
}

Track a generation

GET/api/v1/jobs/{id}scope jobs:read

Status, stage (script, voice, storyboard, review, render), progress in %, queue position, error if any, and videoId once finished. Poll every 5 to 10 seconds at most.

Parameters

NameInTypeDescription
idpathuuidGeneration idrequired
Request
curl https://motiongist.com/api/v1/jobs/7f3c2a10-5b1e-4c8a-9d2f-0e6b4a1c9d55 \
  -H "X-API-Key: $MOTIONGIST_API_KEY"
Response 200
{
  "data": {
    "id": "7f3c2a10-5b1e-4c8a-9d2f-0e6b4a1c9d55",
    "projectId": "2b9e4d71-8c0a-4f3e-a6b5-1d2c3e4f5a6b",
    "status": "succeeded",
    "stage": "done",
    "stageLabel": "Ready",
    "progress": 100,
    "detail": null,
    "stages": {},
    "voiceMode": "final",
    "isRevision": false,
    "credits": 847,
    "queuePosition": 0,
    "cancellable": false,
    "error": null,
    "videoId": "c4d5e6f7-1a2b-4c3d-8e9f-0a1b2c3d4e5f",
    "attempt": 1,
    "source": "api",
    "queuedAt": "2026-10-23T09:12:04.000Z",
    "startedAt": "2026-10-23T09:12:09.000Z",
    "finishedAt": "2026-10-23T09:19:41.000Z"
  },
  "requestId": "req_3f9a1c2e7b8d4a60"
}

Possible errors: NOT_FOUND (404)

Cancel a generation

POST/api/v1/jobs/{id}/cancelscope jobs:write

Cancels a generation as long as rendering has not started. Queued: cancelled immediately and credits refunded. Running: the engine is asked to stop, the refund follows.

Parameters

NameInTypeDescription
idpathuuidGeneration idrequired
Request
curl -X POST https://motiongist.com/api/v1/jobs/7f3c2a10-5b1e-4c8a-9d2f-0e6b4a1c9d55/cancel \
  -H "X-API-Key: $MOTIONGIST_API_KEY"
Response 200
{
  "data": {
    "id": "7f3c2a10-5b1e-4c8a-9d2f-0e6b4a1c9d55",
    "projectId": "2b9e4d71-8c0a-4f3e-a6b5-1d2c3e4f5a6b",
    "status": "cancelled",
    "stage": "storyboard",
    "stageLabel": "Storyboard",
    "progress": 46,
    "detail": "Scene 4/9",
    "stages": {},
    "voiceMode": "final",
    "isRevision": false,
    "credits": 847,
    "queuePosition": 0,
    "cancellable": false,
    "error": null,
    "videoId": null,
    "attempt": 1,
    "source": "api",
    "queuedAt": "2026-10-23T09:12:04.000Z",
    "startedAt": "2026-10-23T09:12:09.000Z",
    "finishedAt": null
  },
  "requestId": "req_3f9a1c2e7b8d4a60"
}

Possible errors: NOT_FOUND (404), NOT_CANCELLABLE (409)

Videos

List videos

GET/api/v1/videosscope videos:read

Finished videos of the workspace, newest first. Use GET /videos/{id} to get the download links.

Parameters

NameInTypeDescription
limitqueryintegerNumber of items (1 to 100, default 20)
cursorquerystringCursor returned in nextCursor, for the next page
projectIdqueryuuidOnly the versions of this project
Request
curl https://motiongist.com/api/v1/videos \
  -H "X-API-Key: $MOTIONGIST_API_KEY"
Response 200
{
  "data": {
    "items": [
      {
        "id": "c4d5e6f7-1a2b-4c3d-8e9f-0a1b2c3d4e5f",
        "projectId": "2b9e4d71-8c0a-4f3e-a6b5-1d2c3e4f5a6b",
        "jobId": "7f3c2a10-5b1e-4c8a-9d2f-0e6b4a1c9d55",
        "title": "How a CDN works",
        "durationS": 104.2,
        "width": 1080,
        "height": 1920,
        "draftVoice": false,
        "watermarked": false,
        "createdAt": "2026-10-23T09:19:41.000Z"
      }
    ],
    "nextCursor": "2026-10-23T09:12:04.000Z"
  },
  "requestId": "req_3f9a1c2e7b8d4a60"
}

Get a video

GET/api/v1/videos/{id}scope videos:read

Metadata and signed links valid for 15 minutes: MP4 (1080×1920, Range requests supported), SRT captions, contact sheet, poster, storyboard. Fetch the video again for fresh links.

Parameters

NameInTypeDescription
idpathuuidVideo idrequired
Request
curl https://motiongist.com/api/v1/videos/c4d5e6f7-1a2b-4c3d-8e9f-0a1b2c3d4e5f \
  -H "X-API-Key: $MOTIONGIST_API_KEY"
Response 200
{
  "data": {
    "id": "c4d5e6f7-1a2b-4c3d-8e9f-0a1b2c3d4e5f",
    "projectId": "2b9e4d71-8c0a-4f3e-a6b5-1d2c3e4f5a6b",
    "jobId": "7f3c2a10-5b1e-4c8a-9d2f-0e6b4a1c9d55",
    "title": "How a CDN works",
    "durationS": 104.2,
    "width": 1080,
    "height": 1920,
    "voice": "George",
    "voiceProvider": "elevenlabs",
    "draftVoice": false,
    "watermarked": false,
    "look": "night",
    "scenes": 11,
    "qaScore": 0.93,
    "createdAt": "2026-10-23T09:19:41.000Z",
    "aiGenerated": true,
    "files": {
      "mp4": {
        "url": "https://motiongist.com/api/files/videos/…/video.mp4?e=1761211161&s=…",
        "download": "https://motiongist.com/api/files/videos/…/video.mp4?e=1761211161&s=…&d=how-a-cdn-works.mp4"
      },
      "srt": {
        "url": "https://motiongist.com/api/files/videos/…/captions.srt?e=1761211161&s=…",
        "download": "https://motiongist.com/api/files/videos/…/captions.srt?e=1761211161&s=…&d=how-a-cdn-works-captions.srt"
      },
      "contact": {
        "url": "https://motiongist.com/api/files/videos/…/contact.jpg?e=1761211161&s=…",
        "download": "https://motiongist.com/api/files/videos/…/contact.jpg?e=1761211161&s=…&d=…"
      },
      "poster": {
        "url": "https://motiongist.com/api/files/videos/…/poster.jpg?e=1761211161&s=…",
        "download": "https://motiongist.com/api/files/videos/…/poster.jpg?e=1761211161&s=…&d=…"
      }
    }
  },
  "requestId": "req_3f9a1c2e7b8d4a60"
}

Possible errors: NOT_FOUND (404)

Delete a video

DELETE/api/v1/videos/{id}scope videos:write

Deletes the video (files are permanently erased after 30 days). Emits the video.deleted event.

Parameters

NameInTypeDescription
idpathuuidVideo idrequired
Request
curl -X DELETE https://motiongist.com/api/v1/videos/c4d5e6f7-1a2b-4c3d-8e9f-0a1b2c3d4e5f \
  -H "X-API-Key: $MOTIONGIST_API_KEY"
Response 200
{
  "data": {
    "id": "c4d5e6f7-1a2b-4c3d-8e9f-0a1b2c3d4e5f",
    "deleted": true
  },
  "requestId": "req_3f9a1c2e7b8d4a60"
}

Possible errors: NOT_FOUND (404)

Post copy, sharing & publishing

Get the post copy

GET/api/v1/videos/{id}/post-copyscope videos:read

Title, description and hashtags for YouTube, TikTok, Instagram and LinkedIn, in the video language and within each platform limit (limits are returned). Generated on the first call, then stored. caption is the text actually posted (description + hashtags).

Parameters

NameInTypeDescription
idpathuuidVideo idrequired
Request
curl https://motiongist.com/api/v1/videos/c4d5e6f7-1a2b-4c3d-8e9f-0a1b2c3d4e5f/post-copy \
  -H "X-API-Key: $MOTIONGIST_API_KEY"
Response 200
{
  "data": {
    "language": "en",
    "generatedAt": "2026-10-23T09:20:00.000Z",
    "platforms": {
      "youtube": {
        "title": "How a CDN works in 60 seconds #shorts",
        "description": "Your files, next to your visitors.\n\nFollow for more explainers.",
        "hashtags": [
          "cdn",
          "webdev",
          "shorts"
        ],
        "caption": "Your files, next to your visitors.\n\nFollow for more explainers.\n\n#cdn #webdev #shorts",
        "limits": {
          "title": 100,
          "caption": 5000,
          "hashtags": 15
        }
      }
    }
  },
  "requestId": "req_3f9a1c2e7b8d4a60"
}

Possible errors: NOT_FOUND (404)

Rewrite the post copy

POST/api/v1/videos/{id}/post-copyscope videos:write

Rewrites the copy (all platforms or some), optionally in another language. Free, limited to 60 AI calls per hour per workspace.

Parameters

NameInTypeDescription
idpathuuidVideo idrequired

JSON body

FieldTypeDescription
language"en" | "fr"Write it in this language (default: the current one, else the video language)
platformsenum[]Only these platforms (the others are kept)
Request
curl -X POST https://motiongist.com/api/v1/videos/c4d5e6f7-1a2b-4c3d-8e9f-0a1b2c3d4e5f/post-copy \
  -H "X-API-Key: $MOTIONGIST_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
  "language": "en",
  "platforms": [
    "tiktok"
  ]
}'
Response 200
{
  "data": {
    "language": "en",
    "generatedAt": "2026-10-23T09:20:00.000Z",
    "platforms": {
      "youtube": {
        "title": "How a CDN works in 60 seconds #shorts",
        "description": "Your files, next to your visitors.\n\nFollow for more explainers.",
        "hashtags": [
          "cdn",
          "webdev",
          "shorts"
        ],
        "caption": "Your files, next to your visitors.\n\nFollow for more explainers.\n\n#cdn #webdev #shorts",
        "limits": {
          "title": 100,
          "caption": 5000,
          "hashtags": 15
        }
      }
    }
  },
  "requestId": "req_3f9a1c2e7b8d4a60"
}

Possible errors: NOT_FOUND (404), RATE_LIMITED (429)

Edit the post copy

PATCH/api/v1/videos/{id}/post-copyscope videos:write

Replaces the given platforms. Text over a platform limit is refused (VALIDATION_ERROR), never cut. Hashtags are given without #.

Parameters

NameInTypeDescription
idpathuuidVideo idrequired

JSON body

FieldTypeDescription
language"en" | "fr"Narration language: en (English, default) or fr (French).
platformsobjectrequired
platforms.youtubeobject
platforms.youtube.titlestringrequired
platforms.youtube.descriptionstringrequired
platforms.youtube.hashtagsstring[]required
platforms.tiktokobject
platforms.tiktok.titlestringrequired
platforms.tiktok.descriptionstringrequired
platforms.tiktok.hashtagsstring[]required
platforms.instagramobject
platforms.instagram.titlestringrequired
platforms.instagram.descriptionstringrequired
platforms.instagram.hashtagsstring[]required
platforms.linkedinobject
platforms.linkedin.titlestringrequired
platforms.linkedin.descriptionstringrequired
platforms.linkedin.hashtagsstring[]required
Request
curl -X PATCH https://motiongist.com/api/v1/videos/c4d5e6f7-1a2b-4c3d-8e9f-0a1b2c3d4e5f/post-copy \
  -H "X-API-Key: $MOTIONGIST_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "platforms": {
    "tiktok": {
      "title": "How a CDN works",
      "description": "Your files, next to your visitors.",
      "hashtags": [
        "webdev",
        "cdn"
      ]
    }
  }
}'
Response 200
{
  "data": {
    "language": "en",
    "generatedAt": "2026-10-23T09:20:00.000Z",
    "platforms": {
      "youtube": {
        "title": "How a CDN works in 60 seconds #shorts",
        "description": "Your files, next to your visitors.\n\nFollow for more explainers.",
        "hashtags": [
          "cdn",
          "webdev",
          "shorts"
        ],
        "caption": "Your files, next to your visitors.\n\nFollow for more explainers.\n\n#cdn #webdev #shorts",
        "limits": {
          "title": 100,
          "caption": 5000,
          "hashtags": 15
        }
      }
    }
  },
  "requestId": "req_3f9a1c2e7b8d4a60"
}

Possible errors: NOT_FOUND (404), VALIDATION_ERROR (400)

Get the share page

GET/api/v1/videos/{id}/sharescope videos:read

Public share page of the video: link (unlisted, noindex), embed code, button, download, branding and view count. enabled=false means no public link.

Parameters

NameInTypeDescription
idpathuuidVideo idrequired
Request
curl https://motiongist.com/api/v1/videos/c4d5e6f7-1a2b-4c3d-8e9f-0a1b2c3d4e5f/share \
  -H "X-API-Key: $MOTIONGIST_API_KEY"
Response 200
{
  "data": {
    "enabled": true,
    "token": "Xq3vR8kP2mWz7tLa",
    "url": "https://motiongist.com/v/Xq3vR8kP2mWz7tLa",
    "embedUrl": "https://motiongist.com/embed/Xq3vR8kP2mWz7tLa",
    "embedCode": "<iframe src=\"https://motiongist.com/embed/Xq3vR8kP2mWz7tLa\" …></iframe>",
    "cta": {
      "label": "Book a demo",
      "url": "https://example.com/demo"
    },
    "allowDownload": false,
    "showBranding": true,
    "brandingForced": false,
    "views": 128,
    "createdAt": "2026-10-23T09:25:00.000Z"
  },
  "requestId": "req_3f9a1c2e7b8d4a60"
}

Possible errors: NOT_FOUND (404)

Update the share page

PATCH/api/v1/videos/{id}/sharescope videos:write

enabled=true creates the link (kept across updates); enabled=false revokes it for good (turning it back on creates a new link). cta adds a button (http/https URL). The free plan always shows the "Made with Motiongist" badge.

Parameters

NameInTypeDescription
idpathuuidVideo idrequired

JSON body

FieldTypeDescription
enabledbooleanrequired
ctaobject (variants)
allowDownloadboolean
showBrandingboolean
Request
curl -X PATCH https://motiongist.com/api/v1/videos/c4d5e6f7-1a2b-4c3d-8e9f-0a1b2c3d4e5f/share \
  -H "X-API-Key: $MOTIONGIST_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "enabled": true,
  "cta": {
    "label": "Book a demo",
    "url": "https://example.com/demo"
  },
  "allowDownload": false
}'
Response 200
{
  "data": {
    "enabled": true,
    "token": "Xq3vR8kP2mWz7tLa",
    "url": "https://motiongist.com/v/Xq3vR8kP2mWz7tLa",
    "embedUrl": "https://motiongist.com/embed/Xq3vR8kP2mWz7tLa",
    "embedCode": "<iframe src=\"https://motiongist.com/embed/Xq3vR8kP2mWz7tLa\" …></iframe>",
    "cta": {
      "label": "Book a demo",
      "url": "https://example.com/demo"
    },
    "allowDownload": false,
    "showBranding": true,
    "brandingForced": false,
    "views": 128,
    "createdAt": "2026-10-23T09:25:00.000Z"
  },
  "requestId": "req_3f9a1c2e7b8d4a60"
}

Possible errors: NOT_FOUND (404), VALIDATION_ERROR (400)

List connected accounts

GET/api/v1/social-accountsscope videos:read

YouTube, TikTok, Instagram and LinkedIn accounts connected by the workspace members in the web app (/app/publishing; connecting needs a browser). platforms tells which platforms this server can publish to.

Request
curl https://motiongist.com/api/v1/social-accounts \
  -H "X-API-Key: $MOTIONGIST_API_KEY"
Response 200
{
  "data": {
    "items": [
      {
        "id": "01970d2c-55aa-7c1e-9d0b-3e4f5a6b7c8d",
        "provider": "youtube",
        "name": "Motiongist Demo",
        "avatarUrl": null,
        "status": "active",
        "simulated": false,
        "connectedAt": "2026-10-20T14:02:00.000Z",
        "updatedAt": "2026-10-20T14:02:00.000Z"
      }
    ],
    "platforms": [
      {
        "id": "youtube",
        "available": true,
        "mode": "live"
      }
    ]
  },
  "requestId": "req_3f9a1c2e7b8d4a60"
}

List posts

GET/api/v1/postsscope videos:read

Posts of the workspace, filtered by scheduled date (from, to), status or video.

Parameters

NameInTypeDescription
fromquerydate-timeScheduled at or after (ISO 8601 with offset)
toquerydate-timeScheduled at or before
statusquerystringscheduled, publishing, published, failed or cancelled
videoIdqueryuuidOnly the posts of this video
limitqueryintegerNumber of items (1 to 500, default 100)
Request
curl https://motiongist.com/api/v1/posts \
  -H "X-API-Key: $MOTIONGIST_API_KEY"
Response 200
{
  "data": {
    "items": [
      {
        "id": "01970d2c-77cc-7e3a-8f1b-5a6b7c8d9e0f",
        "videoId": "c4d5e6f7-1a2b-4c3d-8e9f-0a1b2c3d4e5f",
        "videoTitle": "How a CDN works",
        "projectId": "2b9e4d71-8c0a-4f3e-a6b5-1d2c3e4f5a6b",
        "accountId": "01970d2c-55aa-7c1e-9d0b-3e4f5a6b7c8d",
        "accountName": "Motiongist Demo",
        "provider": "youtube",
        "status": "scheduled",
        "scheduledAt": "2026-10-30T16:30:00.000Z",
        "publishedAt": null,
        "copy": {
          "title": "How a CDN works in 60 seconds #shorts",
          "description": "Your files, next to your visitors.\n\nFollow for more explainers.",
          "hashtags": [
            "cdn",
            "webdev",
            "shorts"
          ]
        },
        "caption": "Your files, next to your visitors.\n\nFollow for more explainers.\n\n#cdn #webdev #shorts",
        "options": {},
        "externalId": null,
        "externalUrl": null,
        "error": null,
        "attempts": 0,
        "createdAt": "2026-10-23T09:30:00.000Z"
      }
    ]
  },
  "requestId": "req_3f9a1c2e7b8d4a60"
}

Publish or schedule

POST/api/v1/postsscope videos:write

Queues a video for a connected account, now (scheduledAt omitted) or at scheduledAt (up to 1 year ahead). Uses the video post copy for the platform unless copy is given. Costs no credits. The worker publishes within seconds of the date, retries temporary errors (after 1 min, 5 min, 30 min) and emits post.published or post.failed.

JSON body

FieldTypeDescription
videoIduuidrequired
accountIduuidrequired
scheduledAtobject (variants)
copyobject
copy.titlestringrequired
copy.descriptionstringrequired
copy.hashtagsstring[]required
optionsobject
options.privacy"public" | "unlisted" | "private"
options.simulate"transient" | "permanent" | "auth"
Request
curl -X POST https://motiongist.com/api/v1/posts \
  -H "X-API-Key: $MOTIONGIST_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
  "videoId": "c4d5e6f7-1a2b-4c3d-8e9f-0a1b2c3d4e5f",
  "accountId": "01970d2c-55aa-7c1e-9d0b-3e4f5a6b7c8d",
  "scheduledAt": "2026-10-30T17:30:00+01:00"
}'
Response 201
{
  "data": {
    "id": "01970d2c-77cc-7e3a-8f1b-5a6b7c8d9e0f",
    "videoId": "c4d5e6f7-1a2b-4c3d-8e9f-0a1b2c3d4e5f",
    "videoTitle": "How a CDN works",
    "projectId": "2b9e4d71-8c0a-4f3e-a6b5-1d2c3e4f5a6b",
    "accountId": "01970d2c-55aa-7c1e-9d0b-3e4f5a6b7c8d",
    "accountName": "Motiongist Demo",
    "provider": "youtube",
    "status": "scheduled",
    "scheduledAt": "2026-10-30T16:30:00.000Z",
    "publishedAt": null,
    "copy": {
      "title": "How a CDN works in 60 seconds #shorts",
      "description": "Your files, next to your visitors.\n\nFollow for more explainers.",
      "hashtags": [
        "cdn",
        "webdev",
        "shorts"
      ]
    },
    "caption": "Your files, next to your visitors.\n\nFollow for more explainers.\n\n#cdn #webdev #shorts",
    "options": {},
    "externalId": null,
    "externalUrl": null,
    "error": null,
    "attempts": 0,
    "createdAt": "2026-10-23T09:30:00.000Z"
  },
  "requestId": "req_3f9a1c2e7b8d4a60"
}

Possible errors: NOT_FOUND (404), VALIDATION_ERROR (400)

Get a post

GET/api/v1/posts/{id}scope videos:read

Status of a post; externalUrl once published, error when it failed.

Parameters

NameInTypeDescription
idpathuuidPost idrequired
Request
curl https://motiongist.com/api/v1/posts/01970d2c-77cc-7e3a-8f1b-5a6b7c8d9e0f \
  -H "X-API-Key: $MOTIONGIST_API_KEY"
Response 200
{
  "data": {
    "id": "01970d2c-77cc-7e3a-8f1b-5a6b7c8d9e0f",
    "videoId": "c4d5e6f7-1a2b-4c3d-8e9f-0a1b2c3d4e5f",
    "videoTitle": "How a CDN works",
    "projectId": "2b9e4d71-8c0a-4f3e-a6b5-1d2c3e4f5a6b",
    "accountId": "01970d2c-55aa-7c1e-9d0b-3e4f5a6b7c8d",
    "accountName": "Motiongist Demo",
    "provider": "youtube",
    "status": "published",
    "scheduledAt": "2026-10-30T16:30:00.000Z",
    "publishedAt": "2026-10-30T16:30:08.000Z",
    "copy": {
      "title": "How a CDN works in 60 seconds #shorts",
      "description": "Your files, next to your visitors.\n\nFollow for more explainers.",
      "hashtags": [
        "cdn",
        "webdev",
        "shorts"
      ]
    },
    "caption": "Your files, next to your visitors.\n\nFollow for more explainers.\n\n#cdn #webdev #shorts",
    "options": {},
    "externalId": "dQw4w9WgXcQ",
    "externalUrl": "https://www.youtube.com/shorts/dQw4w9WgXcQ",
    "error": null,
    "attempts": 0,
    "createdAt": "2026-10-23T09:30:00.000Z"
  },
  "requestId": "req_3f9a1c2e7b8d4a60"
}

Possible errors: NOT_FOUND (404)

Reschedule a post

PATCH/api/v1/posts/{id}scope videos:write

Moves a scheduled post, or re-queues a failed or cancelled one (attempts reset).

Parameters

NameInTypeDescription
idpathuuidPost idrequired

JSON body

FieldTypeDescription
scheduledAtobject (variants)New time (ISO 8601 with offset); null = nowrequired
Request
curl -X PATCH https://motiongist.com/api/v1/posts/01970d2c-77cc-7e3a-8f1b-5a6b7c8d9e0f \
  -H "X-API-Key: $MOTIONGIST_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "scheduledAt": "2026-10-31T09:00:00+01:00"
}'
Response 200
{
  "data": {
    "id": "01970d2c-77cc-7e3a-8f1b-5a6b7c8d9e0f",
    "videoId": "c4d5e6f7-1a2b-4c3d-8e9f-0a1b2c3d4e5f",
    "videoTitle": "How a CDN works",
    "projectId": "2b9e4d71-8c0a-4f3e-a6b5-1d2c3e4f5a6b",
    "accountId": "01970d2c-55aa-7c1e-9d0b-3e4f5a6b7c8d",
    "accountName": "Motiongist Demo",
    "provider": "youtube",
    "status": "scheduled",
    "scheduledAt": "2026-10-30T16:30:00.000Z",
    "publishedAt": null,
    "copy": {
      "title": "How a CDN works in 60 seconds #shorts",
      "description": "Your files, next to your visitors.\n\nFollow for more explainers.",
      "hashtags": [
        "cdn",
        "webdev",
        "shorts"
      ]
    },
    "caption": "Your files, next to your visitors.\n\nFollow for more explainers.\n\n#cdn #webdev #shorts",
    "options": {},
    "externalId": null,
    "externalUrl": null,
    "error": null,
    "attempts": 0,
    "createdAt": "2026-10-23T09:30:00.000Z"
  },
  "requestId": "req_3f9a1c2e7b8d4a60"
}

Possible errors: NOT_FOUND (404), CONFLICT (409), VALIDATION_ERROR (400)

Cancel a post

DELETE/api/v1/posts/{id}scope videos:write

Cancels a post that has not started publishing (CONFLICT otherwise).

Parameters

NameInTypeDescription
idpathuuidPost idrequired
Request
curl -X DELETE https://motiongist.com/api/v1/posts/01970d2c-77cc-7e3a-8f1b-5a6b7c8d9e0f \
  -H "X-API-Key: $MOTIONGIST_API_KEY"
Response 200
{
  "data": {
    "id": "01970d2c-77cc-7e3a-8f1b-5a6b7c8d9e0f",
    "videoId": "c4d5e6f7-1a2b-4c3d-8e9f-0a1b2c3d4e5f",
    "videoTitle": "How a CDN works",
    "projectId": "2b9e4d71-8c0a-4f3e-a6b5-1d2c3e4f5a6b",
    "accountId": "01970d2c-55aa-7c1e-9d0b-3e4f5a6b7c8d",
    "accountName": "Motiongist Demo",
    "provider": "youtube",
    "status": "cancelled",
    "scheduledAt": "2026-10-30T16:30:00.000Z",
    "publishedAt": null,
    "copy": {
      "title": "How a CDN works in 60 seconds #shorts",
      "description": "Your files, next to your visitors.\n\nFollow for more explainers.",
      "hashtags": [
        "cdn",
        "webdev",
        "shorts"
      ]
    },
    "caption": "Your files, next to your visitors.\n\nFollow for more explainers.\n\n#cdn #webdev #shorts",
    "options": {},
    "externalId": null,
    "externalUrl": null,
    "error": null,
    "attempts": 0,
    "createdAt": "2026-10-23T09:30:00.000Z"
  },
  "requestId": "req_3f9a1c2e7b8d4a60"
}

Possible errors: NOT_FOUND (404), CONFLICT (409)

Projects

List projects

GET/api/v1/projectsscope jobs:read

Projects (studio drafts) of the workspace, most recently updated first.

Parameters

NameInTypeDescription
limitqueryintegerNumber of items (1 to 100, default 20)
cursorquerystringCursor returned in nextCursor, for the next page
statusquerystringdraft, queued, generating, ready or failed
qquerystringSearch in the title
Request
curl https://motiongist.com/api/v1/projects \
  -H "X-API-Key: $MOTIONGIST_API_KEY"
Response 200
{
  "data": {
    "items": [
      {
        "id": "2b9e4d71-8c0a-4f3e-a6b5-1d2c3e4f5a6b",
        "title": "How a CDN works",
        "status": "draft",
        "draft": {
          "subject": "How a CDN works",
          "inputKind": "script",
          "audience": "dev",
          "length": "standard",
          "language": "en",
          "voice": "George",
          "look": {
            "mode": "preset",
            "preset": "night"
          },
          "notes": {
            "mustShow": [],
            "avoid": []
          },
          "mascot": false,
          "tier": "standard",
          "format": "9:16",
          "captions": true,
          "music": true
        },
        "script": "Your site takes 3 seconds to load in Tokyo? The problem isn’t your server. It’s the distance…",
        "narration": null,
        "latestJobId": null,
        "latestVideoId": null,
        "source": "api",
        "createdAt": "2026-10-23T09:02:11.000Z",
        "updatedAt": "2026-10-23T09:04:37.000Z",
        "url": "https://motiongist.com/app/projects/2b9e4d71-8c0a-4f3e-a6b5-1d2c3e4f5a6b"
      }
    ],
    "nextCursor": "2026-10-23T09:12:04.000Z"
  },
  "requestId": "req_3f9a1c2e7b8d4a60"
}

Create a project

POST/api/v1/projectsscope jobs:write

Creates a draft, like in the studio: settings, script, voice preview, then estimate and generate. Handy for an agent that iterates with the user before paying. The project also shows up in the web studio.

JSON body

FieldTypeDescription
titlestringTitle (120 characters max). Default: the subject.
scriptstringExact text to narrate. Without a script, POST /projects/{id}/script writes one.
draftobjectProject settings: subject, audience, length, language (en or fr), voice, look, brand, notes…
draft.subjectstringVideo subject.
draft.inputKind"idea" | "script" | "url" | "document"idea, script, url or document.
draft.sourceTextstringSource text (document, web page) whose facts must be used.
draft.sourceNamestring
draft.sourceUrlstring
draft.audience"beginner" | "dev" | "ops" | "decision-maker"Target audience: beginner, dev, ops or decision-maker.
draft.length"short" | "standard" | "full"Length: short (45–60 s), standard (90–120 s) or full (150–180 s).
draft.language"fr" | "en"Narration language: en (English, default) or fr (French).
draft.voicestringNarrator voice (see GET /catalog): George, Sarah, Daniel, Brian, Lily, Alice, Will, Matilda. Every voice speaks both languages.
draft.lookobject (variants)Visual style: { mode: "preset", preset: "clean" | "night" | "toon" }, { mode: "brand" } (brand colors, brand field required) or { mode: "custom", description } (Studio plan).
draft.brandobjectBrand shown: name (24 chars), site, colors (1 to 3 #RRGGBB colors), logo ("asset:<id>" returned by POST /assets).
draft.brand.namestringBrand name (24 characters max).required
draft.brand.sitestringWebsite shown at the end of the video (40 characters max).
draft.brand.colorsstring[]1 to 3 hex colors #RRGGBB.required
draft.brand.logostringLogo: "asset:<id>" returned by POST /assets.
draft.notesobjectInstructions: mustShow (up to 10 things to show), avoid (things to avoid), keyMoment, free (free text).
draft.notes.mustShowstring[]Things that must be shown (10 max, 80 chars each).
draft.notes.avoidstring[]Things to avoid (10 max).
draft.notes.keyMomentstringThe key moment to highlight.
draft.notes.freestringFree-form instructions (1,000 characters max).
draft.mascotbooleanAdds an animated presenter.
draft.tier"standard" | "studio"standard, or studio (higher quality, +300 credits, Studio plan).
draft.dimension"2d" | "3d"2d (flat motion design, default) or 3d (perspective, volume, depth and camera moves).
draft.format"9:16" | "16:9"Format: 9:16 (vertical) or 16:9.
draft.captionsbooleanBurned-in captions.
draft.musicbooleanBackground music.
Request
curl -X POST https://motiongist.com/api/v1/projects \
  -H "X-API-Key: $MOTIONGIST_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
  "draft": {
    "subject": "How a CDN works",
    "length": "standard",
    "language": "en",
    "voice": "George",
    "look": {
      "mode": "preset",
      "preset": "night"
    }
  }
}'
Response 201
{
  "data": {
    "id": "2b9e4d71-8c0a-4f3e-a6b5-1d2c3e4f5a6b",
    "title": "How a CDN works",
    "status": "draft",
    "draft": {
      "subject": "How a CDN works",
      "inputKind": "script",
      "audience": "dev",
      "length": "standard",
      "language": "en",
      "voice": "George",
      "look": {
        "mode": "preset",
        "preset": "night"
      },
      "notes": {
        "mustShow": [],
        "avoid": []
      },
      "mascot": false,
      "tier": "standard",
      "format": "9:16",
      "captions": true,
      "music": true
    },
    "script": "Your site takes 3 seconds to load in Tokyo? The problem isn’t your server. It’s the distance…",
    "narration": null,
    "latestJobId": null,
    "latestVideoId": null,
    "source": "api",
    "createdAt": "2026-10-23T09:02:11.000Z",
    "updatedAt": "2026-10-23T09:04:37.000Z",
    "url": "https://motiongist.com/app/projects/2b9e4d71-8c0a-4f3e-a6b5-1d2c3e4f5a6b"
  },
  "requestId": "req_3f9a1c2e7b8d4a60"
}

Possible errors: VALIDATION_ERROR (400)

Get a project

GET/api/v1/projects/{id}scope jobs:read

Settings, script, narration preview, latest generation and latest video of the project.

Parameters

NameInTypeDescription
idpathuuidProject idrequired
Request
curl https://motiongist.com/api/v1/projects/2b9e4d71-8c0a-4f3e-a6b5-1d2c3e4f5a6b \
  -H "X-API-Key: $MOTIONGIST_API_KEY"
Response 200
{
  "data": {
    "id": "2b9e4d71-8c0a-4f3e-a6b5-1d2c3e4f5a6b",
    "title": "How a CDN works",
    "status": "draft",
    "draft": {
      "subject": "How a CDN works",
      "inputKind": "script",
      "audience": "dev",
      "length": "standard",
      "language": "en",
      "voice": "George",
      "look": {
        "mode": "preset",
        "preset": "night"
      },
      "notes": {
        "mustShow": [],
        "avoid": []
      },
      "mascot": false,
      "tier": "standard",
      "format": "9:16",
      "captions": true,
      "music": true
    },
    "script": "Your site takes 3 seconds to load in Tokyo? The problem isn’t your server. It’s the distance…",
    "narration": null,
    "latestJobId": null,
    "latestVideoId": null,
    "source": "api",
    "createdAt": "2026-10-23T09:02:11.000Z",
    "updatedAt": "2026-10-23T09:04:37.000Z",
    "url": "https://motiongist.com/app/projects/2b9e4d71-8c0a-4f3e-a6b5-1d2c3e4f5a6b"
  },
  "requestId": "req_3f9a1c2e7b8d4a60"
}

Possible errors: NOT_FOUND (404)

Update a project

PATCH/api/v1/projects/{id}scope jobs:write

Updates the title, the script (null to clear it) or part of the settings (merged with the existing ones). Any change invalidates the previous quote.

Parameters

NameInTypeDescription
idpathuuidProject idrequired

JSON body

FieldTypeDescription
titlestringTitle (120 characters max). Default: the subject.
scriptobject (variants)
draftobject
draft.subjectstringVideo subject.
draft.inputKind"idea" | "script" | "url" | "document"idea, script, url or document.
draft.sourceTextstringSource text (document, web page) whose facts must be used.
draft.sourceNamestring
draft.sourceUrlstring
draft.audience"beginner" | "dev" | "ops" | "decision-maker"Target audience: beginner, dev, ops or decision-maker.
draft.length"short" | "standard" | "full"Length: short (45–60 s), standard (90–120 s) or full (150–180 s).
draft.language"fr" | "en"Narration language: en (English, default) or fr (French).
draft.voicestringNarrator voice (see GET /catalog): George, Sarah, Daniel, Brian, Lily, Alice, Will, Matilda. Every voice speaks both languages.
draft.lookobject (variants)Visual style: { mode: "preset", preset: "clean" | "night" | "toon" }, { mode: "brand" } (brand colors, brand field required) or { mode: "custom", description } (Studio plan).
draft.brandobjectBrand shown: name (24 chars), site, colors (1 to 3 #RRGGBB colors), logo ("asset:<id>" returned by POST /assets).
draft.brand.namestringBrand name (24 characters max).required
draft.brand.sitestringWebsite shown at the end of the video (40 characters max).
draft.brand.colorsstring[]1 to 3 hex colors #RRGGBB.required
draft.brand.logostringLogo: "asset:<id>" returned by POST /assets.
draft.notesobjectInstructions: mustShow (up to 10 things to show), avoid (things to avoid), keyMoment, free (free text).
draft.notes.mustShowstring[]Things that must be shown (10 max, 80 chars each).
draft.notes.avoidstring[]Things to avoid (10 max).
draft.notes.keyMomentstringThe key moment to highlight.
draft.notes.freestringFree-form instructions (1,000 characters max).
draft.mascotbooleanAdds an animated presenter.
draft.tier"standard" | "studio"standard, or studio (higher quality, +300 credits, Studio plan).
draft.dimension"2d" | "3d"2d (flat motion design, default) or 3d (perspective, volume, depth and camera moves).
draft.format"9:16" | "16:9"Format: 9:16 (vertical) or 16:9.
draft.captionsbooleanBurned-in captions.
draft.musicbooleanBackground music.
Request
curl -X PATCH https://motiongist.com/api/v1/projects/2b9e4d71-8c0a-4f3e-a6b5-1d2c3e4f5a6b \
  -H "X-API-Key: $MOTIONGIST_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "draft": {
    "voice": "Sarah",
    "length": "short"
  }
}'
Response 200
{
  "data": {
    "id": "2b9e4d71-8c0a-4f3e-a6b5-1d2c3e4f5a6b",
    "title": "How a CDN works",
    "status": "draft",
    "draft": {
      "subject": "How a CDN works",
      "inputKind": "script",
      "audience": "dev",
      "length": "standard",
      "language": "en",
      "voice": "George",
      "look": {
        "mode": "preset",
        "preset": "night"
      },
      "notes": {
        "mustShow": [],
        "avoid": []
      },
      "mascot": false,
      "tier": "standard",
      "format": "9:16",
      "captions": true,
      "music": true
    },
    "script": "Your site takes 3 seconds to load in Tokyo? The problem isn’t your server. It’s the distance…",
    "narration": null,
    "latestJobId": null,
    "latestVideoId": null,
    "source": "api",
    "createdAt": "2026-10-23T09:02:11.000Z",
    "updatedAt": "2026-10-23T09:04:37.000Z",
    "url": "https://motiongist.com/app/projects/2b9e4d71-8c0a-4f3e-a6b5-1d2c3e4f5a6b"
  },
  "requestId": "req_3f9a1c2e7b8d4a60"
}

Possible errors: NOT_FOUND (404), VALIDATION_ERROR (400)

Delete a project

DELETE/api/v1/projects/{id}scope jobs:write

Deletes the project and its videos. Refused (409) while a generation is running.

Parameters

NameInTypeDescription
idpathuuidProject idrequired
Request
curl -X DELETE https://motiongist.com/api/v1/projects/2b9e4d71-8c0a-4f3e-a6b5-1d2c3e4f5a6b \
  -H "X-API-Key: $MOTIONGIST_API_KEY"
Response 200
{
  "data": {
    "id": "2b9e4d71-8c0a-4f3e-a6b5-1d2c3e4f5a6b",
    "deleted": true
  },
  "requestId": "req_3f9a1c2e7b8d4a60"
}

Possible errors: NOT_FOUND (404), CONFLICT (409)

Write the script

POST/api/v1/projects/{id}/scriptscope jobs:write

Writes a narrated script (hook, one idea per sentence, recap) from the project subject or source document, in the project language, and saves it to the project. Free, limited to 60 AI calls per hour per workspace.

Parameters

NameInTypeDescription
idpathuuidProject idrequired

JSON body

FieldTypeDescription
topicstringTopic to cover (default: the project subject)
tonestringDesired tone, e.g. "funny and direct"
Request
curl -X POST https://motiongist.com/api/v1/projects/2b9e4d71-8c0a-4f3e-a6b5-1d2c3e4f5a6b/script \
  -H "X-API-Key: $MOTIONGIST_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
  "tone": "direct, with a touch of humor"
}'
Response 200
{
  "data": {
    "title": "How a CDN works",
    "script": "Your site takes 3 seconds to load in Tokyo? The problem isn’t your server. It’s the distance…",
    "words": 246,
    "seconds": 100,
    "source": "llm",
    "project": {
      "id": "2b9e4d71-8c0a-4f3e-a6b5-1d2c3e4f5a6b",
      "title": "How a CDN works",
      "status": "draft",
      "draft": {
        "subject": "How a CDN works",
        "inputKind": "script",
        "audience": "dev",
        "length": "standard",
        "language": "en",
        "voice": "George",
        "look": {
          "mode": "preset",
          "preset": "night"
        },
        "notes": {
          "mustShow": [],
          "avoid": []
        },
        "mascot": false,
        "tier": "standard",
        "format": "9:16",
        "captions": true,
        "music": true
      },
      "script": "Your site takes 3 seconds to load in Tokyo? The problem isn’t your server. It’s the distance…",
      "narration": null,
      "latestJobId": null,
      "latestVideoId": null,
      "source": "api",
      "createdAt": "2026-10-23T09:02:11.000Z",
      "updatedAt": "2026-10-23T09:04:37.000Z",
      "url": "https://motiongist.com/app/projects/2b9e4d71-8c0a-4f3e-a6b5-1d2c3e4f5a6b"
    }
  },
  "requestId": "req_3f9a1c2e7b8d4a60"
}

Possible errors: NOT_FOUND (404), VALIDATION_ERROR (400), RATE_LIMITED (429)

Preview the narration

POST/api/v1/projects/{id}/narrationscope jobs:write

Synthesizes the script with the chosen voice so you can listen before generating. Free, cached as long as the text and the voice do not change. Returns an audio URL valid for 15 minutes.

Parameters

NameInTypeDescription
idpathuuidProject idrequired
Request
curl -X POST https://motiongist.com/api/v1/projects/2b9e4d71-8c0a-4f3e-a6b5-1d2c3e4f5a6b/narration \
  -H "X-API-Key: $MOTIONGIST_API_KEY"
Response 200
{
  "data": {
    "narration": {
      "assetId": "5e6f7a8b-9c0d-4e1f-a2b3-c4d5e6f7a8b9",
      "voice": "George",
      "provider": "edge",
      "seconds": 98.4,
      "words": 246
    },
    "audioUrl": "https://motiongist.com/api/files/narrations/…"
  },
  "requestId": "req_3f9a1c2e7b8d4a60"
}

Possible errors: NOT_FOUND (404), VALIDATION_ERROR (400), RATE_LIMITED (429)

Read the storyboard

GET/api/v1/projects/{id}/storyboardscope jobs:read

The scene plan of the project: status (none, planning, ready, failed) and scenes (id, stage, narration, intent). After a render it is the storyboard of the latest video; dirty lists the scenes the next render rebuilds.

Parameters

NameInTypeDescription
idpathuuidProject idrequired
Request
curl https://motiongist.com/api/v1/projects/2b9e4d71-8c0a-4f3e-a6b5-1d2c3e4f5a6b/storyboard \
  -H "X-API-Key: $MOTIONGIST_API_KEY"
Response 200
{
  "data": {
    "supported": true,
    "status": "ready",
    "source": "engine",
    "basedOnVideoId": null,
    "dirty": [],
    "planJob": null,
    "error": null,
    "updatedAt": "2026-10-23T09:14:00.000Z",
    "plan": {
      "title": "How a CDN works",
      "look": "night",
      "presenter": false,
      "scenes": [
        {
          "id": "s1",
          "stage": "hero",
          "narration": "Your site lives on one server. Your visitors live everywhere.",
          "intent": "A single server in Virginia, dots of visitors light up around the globe."
        },
        {
          "id": "s2",
          "stage": "graph",
          "narration": "A CDN keeps copies of it close to every visitor.",
          "intent": "Edge servers pop up on each continent on “copies”, lines shorten on “close”."
        },
        {
          "id": "s3",
          "stage": "split",
          "narration": "Pages load faster, and your server breathes.",
          "intent": "Left: stopwatch 800 → 40 ms. Right: CPU gauge drops."
        }
      ]
    }
  },
  "requestId": "req_3f9a1c2e7b8d4a60"
}

Possible errors: NOT_FOUND (404)

Write the storyboard

POST/api/v1/projects/{id}/storyboardscope jobs:write

Optional and free: the engine plans the video scene by scene in about a minute, so the user can review it before paying for a render. Poll GET until status is ready. The render then follows the storyboard exactly.

Parameters

NameInTypeDescription
idpathuuidProject idrequired
Request
curl -X POST https://motiongist.com/api/v1/projects/2b9e4d71-8c0a-4f3e-a6b5-1d2c3e4f5a6b/storyboard \
  -H "X-API-Key: $MOTIONGIST_API_KEY"
Response 202
{
  "data": {
    "jobId": "7f3c2a10-5b1e-4c8a-9d2f-0e6b4a1c9d55",
    "reused": false,
    "storyboard": {
      "supported": true,
      "status": "planning",
      "source": "engine",
      "basedOnVideoId": null,
      "dirty": [],
      "planJob": null,
      "error": null,
      "updatedAt": "2026-10-23T09:14:00.000Z",
      "plan": null
    }
  },
  "requestId": "req_3f9a1c2e7b8d4a60"
}

Possible errors: NOT_FOUND (404), CONFLICT (409), RATE_LIMITED (429)

Edit the storyboard

PATCH/api/v1/projects/{id}/storyboardscope jobs:write

Replaces the ordered scene list (3 to 24 scenes). Keep the id of existing scenes, omit it for new ones. Edited scenes are rebuilt on the next render; untouched scenes of a rendered video keep their visuals. Estimate again afterwards.

Parameters

NameInTypeDescription
idpathuuidProject idrequired

JSON body

FieldTypeDescription
titlestringTitle (120 characters max). Default: the subject.
scenesobject[]required
Request
curl -X PATCH https://motiongist.com/api/v1/projects/2b9e4d71-8c0a-4f3e-a6b5-1d2c3e4f5a6b/storyboard \
  -H "X-API-Key: $MOTIONGIST_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "scenes": [
    {
      "id": "s1",
      "narration": "A CDN keeps copies of your site close to every visitor.",
      "intent": "World map, servers light up on “close”."
    },
    {
      "narration": "New scene without id.",
      "intent": "Stopwatch drops from 800 to 40 ms."
    },
    {
      "id": "s3",
      "narration": "Faster pages, lighter servers.",
      "intent": "Two gauges move in opposite directions."
    }
  ]
}'
Response 200
{
  "data": {
    "supported": true,
    "status": "ready",
    "source": "engine",
    "basedOnVideoId": null,
    "dirty": [],
    "planJob": null,
    "error": null,
    "updatedAt": "2026-10-23T09:14:00.000Z",
    "plan": {
      "title": "How a CDN works",
      "look": "night",
      "presenter": false,
      "scenes": [
        {
          "id": "s1",
          "stage": "hero",
          "narration": "Your site lives on one server. Your visitors live everywhere.",
          "intent": "A single server in Virginia, dots of visitors light up around the globe."
        },
        {
          "id": "s2",
          "stage": "graph",
          "narration": "A CDN keeps copies of it close to every visitor.",
          "intent": "Edge servers pop up on each continent on “copies”, lines shorten on “close”."
        },
        {
          "id": "s3",
          "stage": "split",
          "narration": "Pages load faster, and your server breathes.",
          "intent": "Left: stopwatch 800 → 40 ms. Right: CPU gauge drops."
        }
      ]
    }
  },
  "requestId": "req_3f9a1c2e7b8d4a60"
}

Possible errors: NOT_FOUND (404), CONFLICT (409), VALIDATION_ERROR (400)

Rebuild scenes

POST/api/v1/projects/{id}/storyboard/regeneratescope jobs:write

Marks scenes to be rebuilt on the next render even though their text did not change (new take on the visuals). The other scenes are reused as they are.

Parameters

NameInTypeDescription
idpathuuidProject idrequired

JSON body

FieldTypeDescription
sceneIdsstring[]required
Request
curl -X POST https://motiongist.com/api/v1/projects/2b9e4d71-8c0a-4f3e-a6b5-1d2c3e4f5a6b/storyboard/regenerate \
  -H "X-API-Key: $MOTIONGIST_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
  "sceneIds": [
    "s2"
  ]
}'
Response 200
{
  "data": {
    "supported": true,
    "status": "ready",
    "source": "engine",
    "basedOnVideoId": null,
    "dirty": [
      "s2"
    ],
    "planJob": null,
    "error": null,
    "updatedAt": "2026-10-23T09:14:00.000Z",
    "plan": {
      "title": "How a CDN works",
      "look": "night",
      "presenter": false,
      "scenes": [
        {
          "id": "s1",
          "stage": "hero",
          "narration": "Your site lives on one server. Your visitors live everywhere.",
          "intent": "A single server in Virginia, dots of visitors light up around the globe."
        },
        {
          "id": "s2",
          "stage": "graph",
          "narration": "A CDN keeps copies of it close to every visitor.",
          "intent": "Edge servers pop up on each continent on “copies”, lines shorten on “close”."
        },
        {
          "id": "s3",
          "stage": "split",
          "narration": "Pages load faster, and your server breathes.",
          "intent": "Left: stopwatch 800 → 40 ms. Right: CPU gauge drops."
        }
      ]
    }
  },
  "requestId": "req_3f9a1c2e7b8d4a60"
}

Possible errors: NOT_FOUND (404), CONFLICT (409), VALIDATION_ERROR (400)

Remove the storyboard

DELETE/api/v1/projects/{id}/storyboardscope jobs:write

Removes the storyboard: the engine plans the scenes itself during the next render.

Parameters

NameInTypeDescription
idpathuuidProject idrequired
Request
curl -X DELETE https://motiongist.com/api/v1/projects/2b9e4d71-8c0a-4f3e-a6b5-1d2c3e4f5a6b/storyboard \
  -H "X-API-Key: $MOTIONGIST_API_KEY"
Response 200
{
  "data": {
    "supported": true,
    "status": "none",
    "source": "engine",
    "basedOnVideoId": null,
    "dirty": [],
    "planJob": null,
    "error": null,
    "updatedAt": "2026-10-23T09:14:00.000Z",
    "plan": null
  },
  "requestId": "req_3f9a1c2e7b8d4a60"
}

Possible errors: NOT_FOUND (404)

Estimate a project

POST/api/v1/projects/{id}/estimatescope jobs:read

Exact price in credits for the current state of the project, and a signed quote valid for 1 hour. A new version of an already generated project is billed as a revision (50% of the base price).

Parameters

NameInTypeDescription
idpathuuidProject idrequired

JSON body

FieldTypeDescription
voiceMode"draft" | "final"final = licensed voice (paid plans), draft = free draft voice. Default: final on paid plans.
Request
curl -X POST https://motiongist.com/api/v1/projects/2b9e4d71-8c0a-4f3e-a6b5-1d2c3e4f5a6b/estimate \
  -H "X-API-Key: $MOTIONGIST_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
  "voiceMode": "final"
}'
Response 200
{
  "data": {
    "total": 847,
    "lines": [
      {
        "label": "Standard video (90–120 s)",
        "credits": 700
      },
      {
        "label": "Final voice (1,470 characters)",
        "credits": 147
      }
    ],
    "quote": "eyJ3cyI6IjQ…(signed quote, pass it back unchanged)…",
    "quoteExpiresAt": "2026-10-23T10:12:00.000Z",
    "voiceMode": "final",
    "isRevision": false,
    "balance": 29153,
    "allowed": true,
    "blockers": [],
    "overdraft": false,
    "eta": {
      "minutesMin": 4,
      "minutesMax": 8
    }
  },
  "requestId": "req_3f9a1c2e7b8d4a60"
}

Possible errors: NOT_FOUND (404), VALIDATION_ERROR (400)

Generate a project

POST/api/v1/projects/{id}/generatescope jobs:write

Starts the generation at the quoted price. The quote is required: if it expired or the project changed since the estimate, the response is QUOTE_INVALID and you need to estimate again.

Parameters

NameInTypeDescription
idpathuuidProject idrequired

JSON body

FieldTypeDescription
quotestringSigned quote returned by POST /projects/{id}/estimate (valid 1 h)required
Request
curl -X POST https://motiongist.com/api/v1/projects/2b9e4d71-8c0a-4f3e-a6b5-1d2c3e4f5a6b/generate \
  -H "X-API-Key: $MOTIONGIST_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
  "quote": "eyJ3cyI6IjQ…(signed quote, pass it back unchanged)…"
}'
Response 202
{
  "data": {
    "job": {
      "id": "7f3c2a10-5b1e-4c8a-9d2f-0e6b4a1c9d55",
      "projectId": "2b9e4d71-8c0a-4f3e-a6b5-1d2c3e4f5a6b",
      "status": "queued",
      "stage": "storyboard",
      "stageLabel": "Storyboard",
      "progress": 0,
      "detail": "Scene 4/9",
      "stages": {},
      "voiceMode": "final",
      "isRevision": false,
      "credits": 847,
      "queuePosition": 0,
      "cancellable": true,
      "error": null,
      "videoId": null,
      "attempt": 1,
      "source": "api",
      "queuedAt": "2026-10-23T09:12:04.000Z",
      "startedAt": "2026-10-23T09:12:09.000Z",
      "finishedAt": null
    },
    "projectId": "2b9e4d71-8c0a-4f3e-a6b5-1d2c3e4f5a6b",
    "queuePosition": 1
  },
  "requestId": "req_3f9a1c2e7b8d4a60"
}

Possible errors: NOT_FOUND (404), QUOTE_INVALID (400), INSUFFICIENT_CREDITS (402), PLAN_REQUIRED (403)

Files

Upload a file

POST/api/v1/assetsscope assets:write

multipart/form-data upload (file field). Logo as PNG, SVG, JPEG or WebP up to 2 MB: use ref ("asset:<id>") in brand.logo. Document as PDF, DOCX, TXT or MD up to 20 MB: the extracted text (40,000 characters max) is returned, to pass in brief.input (kind document) or draft.sourceText. The type is detected from the content, not the extension.

Body (multipart/form-data)

FieldTypeDescription
filefileThe file to upload. required
kind"logo" | "document"Optional: rejects the file if it does not match the expected type.
Request
curl -X POST https://motiongist.com/api/v1/assets \
  -H "X-API-Key: $MOTIONGIST_API_KEY" \
  -F "file=@whitepaper.pdf" \
  -F "kind=document"
Response 201
{
  "data": {
    "id": "5e6f7a8b-9c0d-4e1f-a2b3-c4d5e6f7a8b9",
    "ref": "asset:5e6f7a8b-9c0d-4e1f-a2b3-c4d5e6f7a8b9",
    "kind": "document",
    "name": "whitepaper.pdf",
    "mime": "application/pdf",
    "bytes": 482113,
    "sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
    "createdAt": "2026-10-23T09:00:00.000Z",
    "url": "https://motiongist.com/api/files/assets/…",
    "text": "Whitepaper — CDNs in 2026…",
    "textLength": 18342,
    "truncated": false,
    "title": "whitepaper"
  },
  "requestId": "req_3f9a1c2e7b8d4a60"
}

Possible errors: VALIDATION_ERROR (400), PAYLOAD_TOO_LARGE (413), UNSUPPORTED_MEDIA_TYPE (415), RATE_LIMITED (429)

Webhooks

List webhooks

GET/api/v1/webhooksscope webhooks:write

Webhooks of the workspace with the status of the last delivery. The secret is only shown at creation.

Request
curl https://motiongist.com/api/v1/webhooks \
  -H "X-API-Key: $MOTIONGIST_API_KEY"
Response 200
{
  "data": [
    {
      "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
      "url": "https://example.com/hooks/motiongist",
      "events": [
        "job.succeeded",
        "job.failed"
      ],
      "active": true,
      "lastStatus": null,
      "lastDeliveredAt": null,
      "createdAt": "2026-10-23T08:55:00.000Z",
      "secretPreview": "whsec_k3Jd…"
    }
  ],
  "requestId": "req_3f9a1c2e7b8d4a60"
}

Create a webhook

POST/api/v1/webhooksscope webhooks:write

Registers a URL that receives the chosen events. The response contains the signing secret (whsec_…), shown only once.

JSON body

FieldTypeDescription
urlstringPublic HTTPS URL that receives the events (signed JSON POST)required
eventsenum[]Subscribed events (all by default)
Request
curl -X POST https://motiongist.com/api/v1/webhooks \
  -H "X-API-Key: $MOTIONGIST_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
  "url": "https://example.com/hooks/motiongist",
  "events": [
    "job.succeeded",
    "job.failed"
  ]
}'
Response 201
{
  "data": {
    "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
    "url": "https://example.com/hooks/motiongist",
    "events": [
      "job.succeeded",
      "job.failed"
    ],
    "active": true,
    "lastStatus": null,
    "lastDeliveredAt": null,
    "createdAt": "2026-10-23T08:55:00.000Z",
    "secret": "whsec_k3JdQ9…"
  },
  "requestId": "req_3f9a1c2e7b8d4a60"
}

Possible errors: VALIDATION_ERROR (400)

Delete a webhook

DELETE/api/v1/webhooks/{id}scope webhooks:write

Deletes the webhook; pending deliveries are dropped.

Parameters

NameInTypeDescription
idpathuuidWebhook idrequired
Request
curl -X DELETE https://motiongist.com/api/v1/webhooks/9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d \
  -H "X-API-Key: $MOTIONGIST_API_KEY"
Response 200
{
  "data": {
    "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
    "deleted": true
  },
  "requestId": "req_3f9a1c2e7b8d4a60"
}

Possible errors: NOT_FOUND (404)

Scripts & ideas

Write a script

POST/api/v1/scripts/generatescope jobs:write

Writes a narrated script for a vertical video, in English (default) or French (language), without creating a project. Then pass it in brief.input ({kind: "script", text}). Free, limited to 60 AI calls per hour per workspace.

JSON body

FieldTypeDescription
topicstringTopic to cover (3 to 500 characters).required
audience"beginner" | "dev" | "ops" | "decision-maker"Target audience: beginner, dev, ops or decision-maker.
length"short" | "standard" | "full"Length: short (45–60 s), standard (90–120 s) or full (150–180 s).
language"fr" | "en"Narration language: en (English, default) or fr (French).
tonestringDesired tone (120 characters max).
sourcestringSource text whose facts must be respected
Request
curl -X POST https://motiongist.com/api/v1/scripts/generate \
  -H "X-API-Key: $MOTIONGIST_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
  "topic": "How a CDN works",
  "audience": "dev",
  "length": "standard",
  "language": "en"
}'
Response 200
{
  "data": {
    "title": "How a CDN works",
    "script": "Your site takes 3 seconds to load in Tokyo? The problem isn’t your server. It’s the distance…",
    "words": 246,
    "seconds": 100,
    "source": "llm"
  },
  "requestId": "req_3f9a1c2e7b8d4a60"
}

Possible errors: VALIDATION_ERROR (400), RATE_LIMITED (429)

Video ideas

POST/api/v1/scripts/ideasscope jobs:write

Suggests video ideas (catchy title and visual angle) for a niche. Free, limited to 60 AI calls per hour per workspace.

JSON body

FieldTypeDescription
nichestringTopic or niche (2 to 200 characters).required
language"fr" | "en"Narration language: en (English, default) or fr (French).
countintegerNumber of ideas (1 to 12, default 8).
Request
curl -X POST https://motiongist.com/api/v1/scripts/ideas \
  -H "X-API-Key: $MOTIONGIST_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
  "niche": "cybersecurity for small businesses",
  "language": "en",
  "count": 3
}'
Response 200
{
  "data": {
    "ideas": [
      {
        "title": "Your password lasts 3 seconds",
        "angle": "A stopwatch cracking longer and longer passwords"
      }
    ]
  },
  "requestId": "req_3f9a1c2e7b8d4a60"
}

Possible errors: VALIDATION_ERROR (400), RATE_LIMITED (429)