Generate a model
Turns a reference image into a 3D model. Returns a job to poll, like every other long-running operation.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
image_url | string | one of | Publicly reachable URL of the reference image |
image_base64 | string | one of | The image inline, base64-encoded |
quality | string | no | standard (default), rapid, or pro |
face_count | integer | no | Target polygon budget for the result |
pbr | boolean | no | Generate PBR material maps |
Image in, not text in
This endpoint takes an image, not a prompt. Text-to-3D internally means generating a reference image first and choosing between candidates, which is a separate operation with its own cost and its own judgement call about which candidate is right. Folding that silently into one call would spend your credits on a decision you did not get to make.
If you are starting from words, generate the reference image with whatever you already use, then pass it here. Sending a prompt returns invalid_request saying exactly this rather than failing somewhere downstream.
Response
202 Accepted
{
"id": "caf57cd7-ca65-430e-9cea-9c2deba9a723",
"object": "job",
"operation": "generate_model",
"status": "queued",
"output": null,
"error": null,
"poll_url": "/v1/jobs/caf57cd7-ca65-430e-9cea-9c2deba9a723",
"estimated_seconds": 60,
"created_at": "2026-08-30T20:56:06.251Z"
}Poll GET /v1/jobs/{id} until succeeded. Generation typically finishes in under a minute.
Output format
A successful generation returns output.model_url pointing at a ZIP archive containing the mesh and its textures, rather than a single file. That is the generator's native output and unpacking it server-side would lose the texture set.
You do not need to unpack it to keep going: POST /v1/rigs accepts ZIP directly, so chaining with from_job works without you touching the archive.
Example: image to rigged character
import time, requests
API = "https://api.cinevva.com/v1"
H = {"Authorization": f"Bearer {KEY}", "Content-Type": "application/json"}
def wait(job_id, interval=6, timeout=300):
deadline = time.time() + timeout
while time.time() < deadline:
job = requests.get(f"{API}/jobs/{job_id}", headers=H, timeout=30).json()
if job["status"] == "succeeded":
return job
if job["status"] == "failed":
raise RuntimeError(job["error"])
time.sleep(interval)
raise TimeoutError(job_id)
# 1. Reference image to 3D model
gen = requests.post(f"{API}/models", headers=H,
json={"image_url": "https://example.com/knight.png"}).json()
wait(gen["id"])
# 2. Rig it straight from the job — no download, no re-upload
rig = requests.post(f"{API}/rigs", headers=H,
json={"from_job": gen["id"], "rig_type": "biped"}).json()
print(wait(rig["id"])["output"]["model_url"])Errors
| Status | Code | Cause |
|---|---|---|
| 400 | invalid_request | No image supplied, or an unknown quality |
| 401 | unauthorized | Missing or invalid API key |
| 402 | subscription_required | No active Standard or Pro subscription |
| 402 | insufficient_credits | Balance will not cover the generation |
| 429 | rate_limited | Over 60 requests per minute |
| 503 | backend_unavailable | Generator at capacity. Retry with backoff |