Mocai API v1

Capture, programmatically.

The Mocai API turns a video into a 3D take (mesh, skeleton, and exports) with a few HTTP calls. Uploads count toward your plan's monthly footage allowance and appear in your library next to portal uploads. Grab the Postman collection to start in minutes, or skip the HTTP entirely with the Blender add-on, the Unity plugin, the Maya module, or the Unreal plugin.

Authentication

Send your API key as a bearer token on every request. Keys start with mk_live_ and are shown once at creation. Create and revoke them under App → Developers. API access is included on every plan, Free included. Requests are rate-limited per account (Free 10/min, Basic 20/min, Pro 60/min; all your keys share the budget); every response carries RateLimit-Limit / -Remaining / -Reset headers, and going over returns 429 rate_limited with Retry-After.

curl {BASE}/api/v1/takes \
  -H "Authorization: Bearer mk_live_…"

Upload a take

Uploads are two steps so video bytes go straight to storage: POST /api/v1/uploads returns a list of parts (byte ranges with their own upload URLs; send them concurrently for roughly double the throughput), you PUT each range, then create the take from the uploadId; the parts are stitched together server-side. Options: hands toggles finger tracking, maxPersons is capped by your plan, an optional selection of normalized first-frame boxes pins exactly which people to reconstruct, and smoothing (off | standard | strong, default standard) applies zero-lag temporal smoothing to the skeleton and mesh exports and portal playback; keypoint JSON/CSV always stay raw for analytics. An optional exports array (fbx | glb | usd | bvh, default ["fbx"]) picks which native formats are baked up front; every other kind can be generated on a ready take with one request, so select what you will actually open. mesh (default true) attaches the performer's skinned body mesh to the FBX/GLB/USD skeleton exports; set it false for armature-only files. An optional rigs array (mixamo | ue5 | metahuman) adds FBX exports retargeted onto those skeletons, and rootMotion picks how every skeleton export travels: drop-in (default) scales motion to each skeleton's canonical proportions so it applies straight to a stock character, while performer keeps the subject's true scale and trajectory for measurement or in-engine retargeting. For clips under ~30 MB you can still POST multipart/form-data with a file field directly to /api/v1/takes in one call. If you are building an integration, send an X-Mocai-Client header naming it (name/version, like mocai-blender/1.4.0); the value is echoed back as client on the take resource. Our DCC plugins do this automatically.

POST /api/v1/uploads
POST /api/v1/takes
# 1) Create an upload session
curl -X POST {BASE}/api/v1/uploads \
  -H "Authorization: Bearer mk_live_…" \
  -H "Content-Type: application/json" \
  -d '{ "contentType": "video/mp4", "sizeBytes": 48211234 }'
# → 201  { "uploadId": "9dK1…", "maxBytes": 2147483648, "parts": [
#           { "url": "https://storage.googleapis.com/…", "offset": 0,        "size": 16070412 },
#           { "url": "https://storage.googleapis.com/…", "offset": 16070412, "size": 16070411 },
#           { "url": "https://storage.googleapis.com/…", "offset": 32140823, "size": 16070411 } ] }

# 2) PUT each part's byte range to its URL, in parallel for ~2× throughput
#    (no auth header; each URL is its own credential; ≤8 MB files get one part)
curl -X PUT --data-binary @part0.bin "https://storage.googleapis.com/…" &
curl -X PUT --data-binary @part1.bin "https://storage.googleapis.com/…" &
curl -X PUT --data-binary @part2.bin "https://storage.googleapis.com/…" &
wait

# 3) Create the take from the finished upload
curl -X POST {BASE}/api/v1/takes \
  -H "Authorization: Bearer mk_live_…" \
  -H "Content-Type: application/json" \
  -d '{ "uploadId": "9dK1…", "title": "Golf swing", "hands": true, "maxPersons": 1, "smoothing": "standard", "exports": ["fbx", "glb"] }'

# → 201
{
  "id": "8Fk2…",
  "status": "queued",
  "via": "api",
  "client": null,
  "options": { "hands": true, "maxPersons": 1, "smoothing": "standard",
               "exports": ["fbx", "glb"], "mesh": true },
  "exports": []
}

Status & exports

Status moves through queued → processing → baking → ready. When ready, exports[] lists the assets you selected at upload (default: FBX) plus any requested rig retargets. Ready no longer means every kind exists; it means your selection does. The full menu: FBX, GLB, BVH, USD on the native skeleton, usd-mesh (per-frame mesh animation; generated from the rigged character on newer takes), raw keypoints as JSON/CSV, and the rig retargets (fbx-mixamo, fbx-ue5, fbx-metahuman). Native skeleton exports carry the solver's full 127-joint body rig — 4-joint spine, arm/leg twist chains, articulated feet, and full fingers — with a true standing bind pose (zero rotation = rest), so auto-retargeting tools calibrate correctly, and by default FBX/GLB/USD also include the performer's skinned body mesh bound to that rig (a richer file that imports exactly the same; opt out with mesh: false); the fbx-<rig> kinds are the same motion on simpler standard skeletons, armature-only. Any kind can be added to a take that is already ready: POST /api/v1/takes/{id}/exports with { "kinds": ["glb", "fbx-mixamo"] } queues a background job (202) and the new kinds appear on the resource when done (a take.exported webhook fires too). The pre-selection spelling { "rigs": ["mixamo"] } still works. The export endpoint answers with a 302 to a short-lived download URL — follow the redirect (standard clients like curl -L handle this, and correctly drop the Authorization header on the way). Takes older than your plan's retention window become expired (files removed; expiresAt on the resource warns 7 days ahead). A clip is refused up front if your monthly allowance is already spent; the take that crosses the cap still completes.

GET /api/v1/takes
GET /api/v1/takes/{id}
GET /api/v1/takes/{id}/exports/{kind}
POST /api/v1/takes/{id}/exports
DELETE /api/v1/takes/{id}
curl {BASE}/api/v1/takes/8Fk2… \
  -H "Authorization: Bearer mk_live_…"

{
  "id": "8Fk2…",
  "status": "ready",
  "frames": 240,
  "persons": 1,
  "exports": [
    { "kind": "fbx",  "url": "/api/v1/takes/8Fk2…/exports/fbx" },
    { "kind": "glb",  "url": "/api/v1/takes/8Fk2…/exports/glb" }
  ]
}

# any other kind is one request away
curl -X POST {BASE}/api/v1/takes/8Fk2…/exports \
  -H "Authorization: Bearer mk_live_…" \
  -d '{ "kinds": ["bvh", "keypoints-json"] }'   # → 202, poll the take

Webhooks

Register an endpoint and we POST a signed event when a take finishes. No polling. Events: take.ready, take.failed, and take.exported (rig exports added to a ready take). Verify X-Mocai-Signature by computing HMAC-SHA256 of the raw request body with your signing secret.

# register once
curl -X PUT {BASE}/api/v1/webhook \
  -H "Authorization: Bearer mk_live_…" \
  -d '{ "url": "https://your-app.com/hooks/mocai" }'

# then we POST you signed events, no polling
POST https://your-app.com/hooks/mocai
X-Mocai-Event: take.ready
X-Mocai-Signature: sha256=5f2b…   # HMAC-SHA256(secret, raw body)

{
  "event": "take.ready",
  "data": { "take": { "id": "8Fk2…", "status": "ready", "exports": [ … ] } }
}

MCP server

AI agents can drive the whole flow through the Model Context Protocol: connect to https://api.mocai.ai/v1/mcp (streamable HTTP) with the same bearer key. The tools mirror the REST API: get_started (works without a key), get_account, request_upload, create_take, get_take, list_takes, get_export_url, and delete_take. Point an agent at the server keyless and get_started explains plans and the workflow, so agents can onboard themselves.

# Claude Code
claude mcp add --transport http mocai https://api.mocai.ai/v1/mcp \
  --header "Authorization: Bearer mk_live_…"

# Any MCP client (JSON config)
{
  "mcpServers": {
    "mocai": {
      "type": "http",
      "url": "https://api.mocai.ai/v1/mcp",
      "headers": { "Authorization": "Bearer mk_live_…" }
    }
  }
}

Errors & limits

Errors use standard HTTP status codes with a typed body: { "error": { "code", "message" } }. Two-step uploads take files up to 2 GB (inline multipart stays under ~30 MB), and a 403 usage_limit_reached means the monthly footage allowance is spent. List endpoints paginate with limit and the nextBefore cursor.