Meo Studio

Home / Developers

APIMCPWebhooks

Build with Meo Studio

Generate AI videos and images from your own app, scripts or AI assistant. One API key gives you a REST API and a remote MCP server for Claude, Cursor and other agents, on the same credits as the studio.

Get an API keyOpenAPI spec
REST APIhttps://www.mymeo.app/v1
MCP serverhttps://www.mymeo.app/mcp
AuthBearer API key
BillingYour plan's credits, refunds on failure

1. Get an API key

Sign in, open the account menu (your initial, top right) and choose API & MCP. Create a key and copy it: it starts with meo_live_ and is shown once, with a webhook signing secret. The API works on any active plan and spends that plan's credits, exactly like the studio.

2. Quickstart

Make a 5-second Kling 3.0 video and wait up to 55 seconds for it:

curl https://www.mymeo.app/v1/generations \
  -H "Authorization: Bearer $MEO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "kling-3",
    "prompt": "A red vintage sports car drifts around a wet mountain curve at dusk, low tracking shot",
    "params": { "resolution": "720p", "duration": 5, "aspect_ratio": "16:9" },
    "wait_seconds": 55
  }'

If it isn't finished yet you get status queued or processing. Poll GET /v1/generations/{id}?wait=55 or pass a webhook_url. When it completes, outputs[0].url is your file.

// Node 18+ / Deno / Bun
const res = await fetch("https://www.mymeo.app/v1/generations", {
  method: "POST",
  headers: { Authorization: `Bearer ${process.env.MEO_API_KEY}`, "Content-Type": "application/json", "Idempotency-Key": crypto.randomUUID() },
  body: JSON.stringify({ model: "soul-2", prompt: "Editorial portrait in soft window light", params: { aspect_ratio: "4:5" }, wait_seconds: 30 }),
});
const gen = await res.json();
console.log(gen.status, gen.outputs?.[0]?.url);
import os, requests
H = {"Authorization": f"Bearer {os.environ['MEO_API_KEY']}"}
gen = requests.post("https://www.mymeo.app/v1/generations", headers=H, json={
    "model": "seedance-2", "prompt": "A lone surfer at golden hour, slow tracking shot",
    "params": {"resolution": "720p", "duration": 5}}).json()
while gen["status"] in ("queued", "processing"):
    gen = requests.get(f"https://www.mymeo.app/v1/generations/{gen['id']}?wait=55", headers=H).json()
print(gen["status"], gen["outputs"][0]["url"] if gen["outputs"] else gen["error"])

Animate your own photo: upload it first, then pass the returned URL as image_url.

curl https://www.mymeo.app/v1/uploads -H "Authorization: Bearer $MEO_API_KEY" -H "Content-Type: image/jpeg" --data-binary @photo.jpg
# → {"url":"https://www.mymeo.app/uploads/…/abc.jpg"}

3. MCP server for AI assistants

Let Claude, Cursor or any MCP client make images and videos for you. The server speaks Streamable HTTP at https://www.mymeo.app/mcp and authenticates with your API key. Tools: list_models, get_model, estimate_cost, create_generation, get_generation, list_generations, cancel_generation, list_templates, get_account.

Claude Code

claude mcp add --transport http meo https://www.mymeo.app/mcp --header "Authorization: Bearer meo_live_…"

Claude Desktop (Settings → Developer → Edit config)

{
  "mcpServers": {
    "meo": {
      "command": "npx",
      "args": ["-y", "mcp-remote", "https://www.mymeo.app/mcp", "--header", "Authorization: Bearer meo_live_…"]
    }
  }
}

Cursor, Windsurf, VS Code (mcp.json)

{
  "mcpServers": {
    "meo": { "url": "https://www.mymeo.app/mcp", "headers": { "Authorization": "Bearer meo_live_…" } }
  }
}

Apps that can't send headers can use https://www.mymeo.app/mcp/k/meo_live_…. Treat that URL like a password.

Then just ask: "Make a 5-second cinematic drone shot of Tokyo at night with Kling 3.0 and give me the link."

4. Endpoints

EndpointWhat it does
GET /v1/accountPlan and credits left
GET /v1/modelsEvery model with modes, settings (params) and credits per quality. Filter with ?kind=video|image
GET /v1/models/{id}One model
POST /v1/estimateCredits a request would cost: {"model","params","video_seconds"}
GET /v1/templatesMotion templates (need image_url) and clip templates (need image_urls). ?kind=motion|clip&search=
POST /v1/generationsStart a generation. Optional Idempotency-Key header, webhook_url, wait_seconds (max 55)
GET /v1/generationsYour generations, newest first. ?limit=&starting_after=
GET /v1/generations/{id}Status and outputs. ?wait=55 long-polls
POST /v1/generations/{id}/cancelCancel and refund
POST /v1/uploadsUpload an image or video (raw body, up to 200 MB). Uploads are deleted after 72 hours

Request body for POST /v1/generations: model (video: genjutsu, seedance-2, seedance-2-5, kling-3, minimax-h3, wan-3-prime…; image: soul-2, soul-standard, marketing-studio, grok-imagine-2, ideogram-4…), prompt, params (keys from the model's params list, such as resolution, duration, aspect_ratio), and optionally image_url, image_urls, video_url + video_seconds, template_id. The mode (text, image or video to video) is picked from the inputs; set mode to override. Statuses: queued, processing, completed, failed, canceled. Download outputs soon: provider links can expire.

5. Webhooks

Pass webhook_url (public https) and we POST the finished generation there as {"type":"generation.completed"|"generation.failed"|"generation.canceled","data":{…}}. Failed deliveries are retried 6 times with backoff. Verify the Meo-Signature: t=…,v1=… header with your key's webhook secret:

import crypto from "node:crypto";
function verify(rawBody, header, secret) {
  const { t, v1 } = Object.fromEntries(header.split(",").map((p) => p.split("=")));
  const expected = crypto.createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
  const fresh = Math.abs(Date.now() / 1000 - Number(t)) < 300;
  return fresh && crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(v1));
}

6. Limits and errors

Questions or need higher limits? Contact us.