Skip to content

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.

bash
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.

bash
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:

json
{
  "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.

bash
curl https://api.cinevva.com/v1/jobs/8f14e45f-ceea-467a-9c1a-1f0d0e6b7a21 \
  -H "Authorization: Bearer $CINEVVA_API_KEY"

status moves through queuedprocessingsucceeded or failed. Those four values are the whole set; write your state machine against them and nothing else.

json
{
  "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.

bash
curl -o goblin-rigged.glb "https://cdn.cinevva.com/rigs/8f14e45f.glb"

The whole loop in one script

python
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"))
javascript
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.