Quickstart
This walks the full loop: get a key, submit a mesh, poll for the result, download the rigged file. Budget about five minutes.
1. Get an API key
Sign in with your Cinevva account and create a key on the API keys page. The secret is shown once, at creation, and is hashed on save. If you lose it, revoke the key and make a new one.
export CINEVVA_API_KEY="ck_live_..."Two things gate access, and it is worth getting both right before you write code. The API needs an active Standard or Pro subscription — on a free plan every call returns 402subscription_required. And work is billed against your credit balance, which is the same balance the web tool uses. Both are managed at cinevva.com/pricing, and you can confirm both with GET /v1/account.
2. Submit a model
The API needs to fetch your mesh, so model_url must be reachable from the public internet. If your asset lives somewhere private, use a pre-signed URL, or send the bytes inline with model_base64 for files under about 10 MB.
curl -X POST https://api.cinevva.com/v1/rigs \
-H "Authorization: Bearer $CINEVVA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model_url": "https://example.com/goblin.glb",
"rig_type": "biped",
"engine": "fast",
"output_format": "glb"
}'You get a 202 Accepted back with a job id:
{
"id": "8f14e45f-ceea-467a-9c1a-1f0d0e6b7a21",
"object": "job",
"operation": "rig",
"status": "queued",
"engine": "fast",
"rig_type": "biped",
"output_format": "glb",
"poll_url": "/v1/jobs/8f14e45f-ceea-467a-9c1a-1f0d0e6b7a21",
"estimated_seconds": 30,
"created_at": "2026-08-30T19:44:02.113Z"
}Credits are reserved when the job is accepted, not when it finishes. See pricing for what happens if a job fails.
3. Poll until it finishes
Rigging is asynchronous because it takes tens of seconds. Poll every 5 seconds for the Fast engine; every 15 to 20 seconds is plenty for Pro.
curl https://api.cinevva.com/v1/jobs/8f14e45f-ceea-467a-9c1a-1f0d0e6b7a21 \
-H "Authorization: Bearer $CINEVVA_API_KEY"status moves through queued → processing → succeeded or failed. Those four values are the whole set; write your state machine against them and nothing else.
{
"id": "8f14e45f-ceea-467a-9c1a-1f0d0e6b7a21",
"object": "job",
"operation": "rig",
"status": "succeeded",
"output": {
"model_url": "https://cdn.cinevva.com/rigs/8f14e45f.glb",
"thumbnail_url": null
},
"error": null,
"created_at": "2026-08-30T19:44:02.113Z",
"completed_at": "2026-08-30T19:44:29.780Z"
}4. Download the result
output.model_url is a direct CDN link. Fetch it with any HTTP client; no auth header is needed on the CDN itself.
curl -o goblin-rigged.glb "https://cdn.cinevva.com/rigs/8f14e45f.glb"The whole loop in one script
import os, time, requests
API = "https://api.cinevva.com/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['CINEVVA_API_KEY']}"}
def rig(model_url, rig_type="biped", engine="fast"):
created = requests.post(
f"{API}/rigs",
headers=HEADERS,
json={"model_url": model_url, "rig_type": rig_type, "engine": engine},
timeout=30,
)
created.raise_for_status()
rig_id = created.json()["id"]
# Rigs finish in 15-150s depending on engine and mesh size. Give up well
# past the slow end rather than at the average, or you will abandon jobs
# that were about to succeed.
deadline = time.time() + 300
while time.time() < deadline:
time.sleep(5)
job = requests.get(f"{API}/jobs/{rig_id}", headers=HEADERS, timeout=30).json()
if job["status"] == "succeeded":
return job["output"]["model_url"]
if job["status"] == "failed":
raise RuntimeError(f"Rig failed: {job['error']}")
raise TimeoutError(f"Rig {rig_id} did not finish within 300s")
print(rig("https://example.com/goblin.glb"))const API = 'https://api.cinevva.com/v1'
const headers = {
Authorization: `Bearer ${process.env.CINEVVA_API_KEY}`,
'Content-Type': 'application/json',
}
const sleep = (ms) => new Promise((r) => setTimeout(r, ms))
export async function rig(modelUrl, { rigType = 'biped', engine = 'fast' } = {}) {
const created = await fetch(`${API}/rigs`, {
method: 'POST',
headers,
body: JSON.stringify({ model_url: modelUrl, rig_type: rigType, engine }),
})
if (!created.ok) throw new Error((await created.json()).error.message)
const { id } = await created.json()
const deadline = Date.now() + 300_000
while (Date.now() < deadline) {
await sleep(5000)
const job = await (await fetch(`${API}/jobs/${id}`, { headers })).json()
if (job.status === 'succeeded') return job.output.model_url
if (job.status === 'failed') throw new Error(`Rig failed: ${job.error}`)
}
throw new Error(`Rig ${id} did not finish within 300s`)
}Next steps
Add animations to the rigged character with the clip catalog, or read model requirements before you push production assets through: most rig failures are input problems, and the common ones are avoidable.