DX.GL

Developer Docs

HTML
# Developer Docs

DX.GL renders GLB and OBJ-scan models into turntable videos and multi-view datasets (a raw `.gltf` must be packed into a `.glb` first). There are two ways to integrate — a REST API for backend pipelines and an MCP server for AI agents — and both run on one pipeline and one credit system.

---

## Authentication & API Keys

You use a single API key (`dxgl_sk_...`) created in [Studio](https://dx.gl/studio) → avatar menu → **API** → **Keys**. By default a key is personal: renders, uploads, and downloads bill your account. You handle user mapping and billing on your side however you want.

A key can also be **bound to a team workspace** at creation (the workspace picker in the Create-key form; requires the editor role or above). A bound key operates entirely on that workspace — the whole team's models and renders, billed to the workspace owner's credit pool — and your membership is re-checked on every request, so a removed member's key stops working immediately. `GET /v1/account` tells you which workspace a key is bound to.

The REST base URL is `https://api.dx.gl/v1`. Every request sends `Authorization: Bearer dxgl_sk_...` in the header.

### Two Integration Paths

| Path | Best for | How it works |
|---|---|---|
| **REST API** | Production pipelines, backend services | Direct HTTP calls from your server. Deterministic, testable, fully scriptable. |
| **MCP (Model Context Protocol)** | Prototyping, ad-hoc tasks, AI-powered workflows | AI agents call DX.GL tools via natural language. Great for internal tooling and rapid iteration. |

Both paths use the same underlying API and the same credit system. Many integrators start with MCP for prototyping and add direct REST calls for production.

---

## REST Quick Start

Base URL: `https://api.dx.gl/v1`

All requests require `Authorization: Bearer dxgl_sk_...` in the header.

### 1. Upload a model

```bash
curl -X POST https://api.dx.gl/v1/models \
  -H "Authorization: Bearer dxgl_sk_..." \
  -F "[email protected]" \
  -F 'renderSettings={"aspect":"16:9","bgColor":"#ffffff","length":6}'
```

Response:
```json
{ "data": { "modelId": "Ab3kF9x2qL1m", "renderId": "Xz7pQ4w8nR2k" } }
```

The upload triggers a free system preview render automatically. If you pass `renderSettings`, a paid render is also queued.

### 2. Ingest from URL (no file upload)

```bash
curl -X POST https://api.dx.gl/v1/models/ingest \
  -H "Authorization: Bearer dxgl_sk_..." \
  -H "Content-Type: application/json" \
  -d '{"url": "https://your-cdn.com/model.glb", "renderSettings": {"aspect":"16:9"}}'
```

Same response format. The server downloads the file directly (max 1 GB; the download aborts after 45 seconds without progress, 10-minute ceiling). The URL can be a presigned S3/R2 GET URL — pull directly from your own bucket, no credentials shared. SHA-256 deduplication prevents re-uploading identical files.

### 2b. Staged upload (presigned PUT)

For large files, flaky links, or bucket-to-bucket pipelines, upload straight to storage instead of through the API:

```bash
# 1. mint a ticket + presigned PUT URL
curl -X POST https://api.dx.gl/v1/models/create-upload-url \
  -H "Authorization: Bearer dxgl_sk_..."
# → { "data": { "url": "https://…", "ticket": "aB3kF9x2qL1m", "expiresIn": 3600, "maxBytes": 1073741824 } }

# 2. PUT the GLB (resumable with your own s3 tooling)
curl -T product.glb "<url>"

# 3. register it — same validation, dedup, and free preview as a direct upload
curl -X POST https://api.dx.gl/v1/models/finalize \
  -H "Authorization: Bearer dxgl_sk_..." \
  -H "Content-Type: application/json" \
  -d '{"ticket": "aB3kF9x2qL1m", "filename": "product.glb"}'
```

The response is identical to a direct upload (`{ modelId, renderId }`). GLB only.

### 3. Create additional renders

```bash
curl -X POST https://api.dx.gl/v1/renders \
  -H "Authorization: Bearer dxgl_sk_..." \
  -H "Content-Type: application/json" \
  -d '{
    "modelId": "Ab3kF9x2qL1m",
    "renderSettings": {
      "quality": "standard",
      "aspect": "1:1",
      "bgColor": "#000000",
      "length": 9,
      "shadows": true,
      "easing": true
    }
  }'
```

Retries are safe: send an `Idempotency-Key` header (any stable string per submit intent) and a timed-out-and-retried POST replays the original render with `deduped: true` instead of charging twice. Works on batch too — one key covers the whole batch.

### 4. Batch render (multiple variants at once)

```bash
curl -X POST https://api.dx.gl/v1/renders/batch \
  -H "Authorization: Bearer dxgl_sk_..." \
  -H "Content-Type: application/json" \
  -d '{
    "renders": [
      { "modelId": "Ab3kF9x2qL1m", "renderSettings": { "aspect": "16:9", "bgColor": "#ffffff" } },
      { "modelId": "Ab3kF9x2qL1m", "renderSettings": { "aspect": "1:1", "bgColor": "#000000" } },
      { "modelId": "Ab3kF9x2qL1m", "renderSettings": { "aspect": "9:16", "bgColor": "#ffffff" } }
    ]
  }'
```

Maximum 100 renders per batch. Credits deducted atomically. Same-model variants that share dimensions and scene settings (e.g. background-color variants at one aspect) render their GPU frames once and encode in parallel; variants that change the framing render separately.

### 5. Poll for completion

```bash
curl https://api.dx.gl/v1/renders/Xz7pQ4w8nR2k \
  -H "Authorization: Bearer dxgl_sk_..."
```

Status progression: `pending` → `poster-processing` → `video-processing` → `done` (any status can move to `error`; `poster-done` is a rare recovery state, not part of the happy path — poll for `done` or `error` and treat the rest as opaque "in progress"). Errored renders carry `failureCode` (machine-readable) alongside `errorMessage`.

### 6. Download assets

```bash
# Full video
curl -o video.mp4 https://api.dx.gl/v1/renders/Xz7pQ4w8nR2k/video \
  -H "Authorization: Bearer dxgl_sk_..."

# Web-optimized variant (lower bitrate, faster loading)
curl -o web.mp4 "https://api.dx.gl/v1/renders/Xz7pQ4w8nR2k/video?quality=web" \
  -H "Authorization: Bearer dxgl_sk_..."

# Poster image (full resolution)
curl -o poster.png "https://api.dx.gl/v1/renders/Xz7pQ4w8nR2k/poster?quality=full" \
  -H "Authorization: Bearer dxgl_sk_..."

# All assets as ZIP
curl -o assets.zip https://api.dx.gl/v1/renders/Xz7pQ4w8nR2k/bundle \
  -H "Authorization: Bearer dxgl_sk_..."
```

All asset endpoints return `Cache-Control: immutable` — renders never change after completion. Safe to cache aggressively.

---

## Python Example

```python
import requests, time, json

API = "https://api.dx.gl/v1"
HEADERS = {"Authorization": "Bearer dxgl_sk_..."}

def render_model(glb_path, settings):
    """Upload a model, render it, return the video URL."""
    with open(glb_path, "rb") as f:
        r = requests.post(f"{API}/models", headers=HEADERS,
            files={"file": f},
            data={"renderSettings": json.dumps(settings)})
    data = r.json()["data"]
    render_id = data["renderId"]

    # Poll until done
    while True:
        r = requests.get(f"{API}/renders/{render_id}", headers=HEADERS)
        status = r.json()["data"]["status"]
        if status == "done":
            return f"{API}/renders/{render_id}/video"
        if status == "error":
            raise Exception(f"Render failed: {r.json()['data'].get('errorMessage')}")
        time.sleep(3)

# Usage
url = render_model("chair.glb", {"aspect": "16:9", "bgColor": "#ffffff", "length": 6})
```

## Node.js Example

```javascript
const API = 'https://api.dx.gl/v1';
const headers = { 'Authorization': 'Bearer dxgl_sk_...' };

async function renderModel(filePath, settings) {
  // Node 20+: openAsBlob pairs with the built-in fetch/FormData
  // (a ReadStream would be stringified, not streamed)
  const form = new FormData();
  form.append('file', await require('fs').openAsBlob(filePath), 'model.glb');
  form.append('renderSettings', JSON.stringify(settings));

  const upload = await fetch(`${API}/models`, { method: 'POST', headers, body: form });
  const { renderId } = (await upload.json()).data;

  // Poll until done
  while (true) {
    await new Promise(r => setTimeout(r, 3000));
    const res = await fetch(`${API}/renders/${renderId}`, { headers });
    const { status, errorMessage } = (await res.json()).data;
    if (status === 'done') return `${API}/renders/${renderId}/video`;
    if (status === 'error') throw new Error(`Render failed: ${errorMessage}`);
  }
}
```

---

## Common Patterns

### Catalog Sync

Upload new SKUs nightly from your product database. Tag each upload with a client or campaign identifier for easy filtering.

```python
for sku in new_skus:
    r = requests.post(f"{API}/models/ingest", headers=HEADERS, json={
        "url": sku["glb_url"],
        "renderSettings": {"aspect": "16:9", "bgColor": "#ffffff", "length": 6}
    })
    model_id = r.json()["data"]["modelId"]
    # Tag the model
    requests.patch(f"{API}/models/{model_id}", headers=HEADERS, json={
        "title": sku["name"],
        "sku": sku["sku_code"],
        "tags": ["client-acme", "fall-2026"]
    })
```

### Variant Matrix

Generate every combination of aspect ratio, background, and quality for each model:

```python
variants = [
    {"aspect": "16:9", "bgColor": "#ffffff", "shadows": True},
    {"aspect": "1:1", "bgColor": "#000000", "shadows": True},
    {"aspect": "9:16", "bgColor": "#ffffff", "reflector": True},
]

renders = []
for model_id in model_ids:
    for v in variants:
        renders.append({"modelId": model_id, "renderSettings": v})

# Submit all at once (max 100 per batch)
for batch in chunks(renders, 100):
    r = requests.post(f"{API}/renders/batch", headers=HEADERS, json={"renders": batch})
```

### Background Image Branding

Upload your client's branded background once, then reference it across all renders:

```bash
# Upload the background image
curl -X POST https://api.dx.gl/v1/overlays \
  -H "Authorization: Bearer dxgl_sk_..." \
  -F "[email protected]" \
  -F "layer=background" \
  -F "name=Acme Brand Gradient"

# Use it in renders
curl -X POST https://api.dx.gl/v1/renders \
  -H "Authorization: Bearer dxgl_sk_..." \
  -H "Content-Type: application/json" \
  -d '{"modelId": "...", "renderSettings": {"bgImageId": "overlay_id_here", "aspect": "16:9"}}'
```

The image is scaled to cover the output dimensions and cropped from the `bgAlign` anchor (nine positions, default `top-left`), keeping logos safe across all aspect ratios. Before re-uploading an asset you already have, `POST /v1/overlays/resolve` with the file's SHA-256 returns the existing id.

### Custom Materials & Environments

Two more asset kinds slot into render settings the same way. **Textures** replace a material's base-color map — mint one, then key it to a material by name:

```bash
curl -X POST https://api.dx.gl/v1/assets/textures \
  -H "Authorization: Bearer dxgl_sk_..." \
  -F "[email protected]" -F "name=Red colorway"
# → { "data": { "id": "tex_id" } }   (re-POSTing identical bytes returns the same id)

curl -X POST https://api.dx.gl/v1/renders \
  -H "Authorization: Bearer dxgl_sk_..." \
  -H "Content-Type: application/json" \
  -d '{"modelId": "...", "renderSettings": {"materialEdits": {"woven": {"mapAssetId": "tex_id"}}}}'
```

Material and mesh names come from `GET /v1/models/{id}` — it returns `materialNames` (the keys `materialEdits` matches), `meshNames` (for `hiddenMeshNames`, which hides named meshes per render), and `animations`. Names are exact-match; a wrong `mapAssetId` silently keeps the original map, so verify against the inventory.

**Environments** are custom HDRIs for `envId`, uploaded as the pre-encoded gainmap triple ([gainmap-js](https://github.com/MONOGRID/gainmap-js) `encodeAndCompress` output — or upload through Studio's Environment panel and reuse the id). A bad `envId` fails the render loudly with a refund. `POST /v1/assets/envs`, `GET /v1/assets/envs`, quotas apply — full details in the [API reference](/docs/api).

### Tags for Multi-Tenant Organization

Tags organize models across clients, projects, and campaigns. Every model can have up to 20 tags (lowercased, trimmed).

```
client-{name}     → per-client isolation (client-acme, client-widgets-inc)
campaign-{name}   → per-campaign grouping (campaign-fall-2026)
batch-{id}        → per-import-batch tracking
status-{state}    → workflow state (status-pending-review, status-approved)
```

```bash
# List all models for a specific client
curl "https://api.dx.gl/v1/models?tags=client-acme&limit=100" \
  -H "Authorization: Bearer dxgl_sk_..."

# List models matching any of multiple tags (OR filter)
curl "https://api.dx.gl/v1/models?tags=client-acme,campaign-fall-2026" \
  -H "Authorization: Bearer dxgl_sk_..."
```

Lists also take `status=active|archived|any`, keyset `cursor` pagination for large catalogs, and `updatedSince=<ISO 8601>` for cheap delta polling — poll renders with `updatedSince` + `status=done` to sweep completions across a big batch in one call.

To calculate spend per client, list their models, count their renders, and total each render's credit cost — video is billed per second by quality tier, datasets are flat per set. The `quote` endpoint can help estimate future costs.

---

## Credit Management

### Check Balance

```bash
curl https://api.dx.gl/v1/account \
  -H "Authorization: Bearer dxgl_sk_..."

# Response: { "data": { "credits": 847, "paid": 847, "free": 0, "total": 847,
#             "scopes": ["read", "render"], "workspace": null } }
```

For a workspace-bound key, `workspace` names the bound workspace and the balances are its owner's pool — the one that funds this key's renders.

### Quote Before Committing

```bash
curl -X POST https://api.dx.gl/v1/quote \
  -H "Authorization: Bearer dxgl_sk_..." \
  -H "Content-Type: application/json" \
  -d '{"renders": [{"quality": "standard"}, {"quality": "standard"}, {"quality": "4k"}]}'

# Response: { "data": { "creditsRequired": 144, "creditsAvailable": 847, "sufficient": true } }
```

Always quote large batches before submitting. The quote is free and returns per-item breakdown, total cost, and whether you have enough credits. Batch renders deduct atomically — if you don't have enough credits, nothing is charged.

Credit packs are $9.90, $79, and $490. Build a simple dashboard that checks `GET /v1/account` periodically and alerts when credits drop below a threshold. Contact us for volume pricing at scale.

---

## Asset Delivery

Every completed render produces four assets: the full video, a web-optimized variant, a thumbnail, and a poster.

### Public Share URLs

Every completed render has a public share page at `https://dx.gl/portal/v?id={renderId}`. No authentication required for viewing. Useful for sharing with stakeholders or embedding in emails.

### Direct Download

All asset endpoints support `Range` headers for streaming and return `Cache-Control: immutable`. Assets per render:

| Endpoint | Format | Use case |
|---|---|---|
| `GET /v1/renders/:id/video` | MP4 or MOV (Pro) | Full-quality download |
| `GET /v1/renders/:id/video?quality=web` | MP4 (lower bitrate) | Web embedding, streaming |
| `GET /v1/renders/:id/poster?quality=full` | PNG | Product page hero, social sharing |
| `GET /v1/renders/:id/poster` | PNG (thumbnail) | Grid previews, hover states |
| `GET /v1/renders/:id/thumb` | MP4 (quarter res) | Hover video preview |
| `GET /v1/renders/:id/bundle` | ZIP | Bulk download of all assets |
| `GET /v1/renders/:id/download-url` | Presigned URL | Large files (ProRes, dataset ZIPs) — no auth header on the download |
| `GET /v1/models/:id/file-url` | Presigned URL | Source GLB, direct from storage |

### Embedding

The web video variant is optimized for `<video>` tags:

```html
<video autoplay loop muted playsinline>
  <source src="https://api.dx.gl/v1/renders/{id}/video?quality=web" type="video/mp4">
</video>
```

---

## MCP for AI Agents

The MCP server lets AI agents interact with DX.GL through tool calls. Add this to your MCP client configuration (Windsurf, Claude Desktop, Cursor, or any MCP-compatible host):

```json
{
  "mcpServers": {
    "dxgl": {
      "serverUrl": "https://mcp.dx.gl/",
      "headers": {
        "Authorization": "Bearer dxgl_sk_..."
      }
    }
  }
}
```

Replace `dxgl_sk_...` with your API key from [Studio](https://dx.gl/studio) → avatar menu → **API** → **Keys**.

### Your First Prompt

Paste this into your AI agent to verify everything works:

> List my models and tell me how many I have.

The agent will call `list_models` and return a summary. If this works, you're connected.

### Prompt Examples

**Render a single model in multiple formats (video):**

> Render model Ab3kF9x2qL1m in all three aspect ratios (16:9, 1:1, 9:16) with a white background, 9 seconds, shadows enabled.

The agent calls `create_batch_renders` with 3 variants and polls until all complete.

**Full catalog in multiple variants (video):**

> I just uploaded 30 shoe models tagged "ss26". Render each one in 16:9 white, 1:1 black, and 9:16 with shadows. Quote the total cost first.

The agent will:
1. Call `list_models` with `tags: "ss26"` to find the 30 models
2. Call `quote` with 90 renders (30 models × 3 variants) to show total cost
3. Wait for your confirmation
4. Call `create_batch_renders` to submit all 90 at once
5. Credits are deducted atomically — if you don't have enough, nothing is charged
6. Poll and return video URLs when complete

**Generate a dataset for one model:**

> Generate a 196×1024 hemisphere dataset for model Ab3kF9x2qL1m.

The agent calls `create_render` with `output: "dataset"`, `datasetQuality: "196x1024"`, `coverage: "hemisphere"`, then polls `get_render` until done, and returns the download URL.

**Generate datasets for all models with a tag:**

> Generate 100×800 hemisphere datasets for every model tagged "validation-set". Quote the cost first and wait for my approval before proceeding.

The agent will:
1. Call `list_models` with `tags: "validation-set"` to find matching models
2. Call `quote` with the dataset configuration to show the total credit cost
3. Wait for your confirmation
4. Call `create_batch_renders` to submit all datasets at once
5. Poll `get_render` for each until complete
6. Return download URLs for all ZIPs

### Available Tools

37 tools covering the full API surface.

**Models**

| Tool | Description |
|---|---|
| `ingest_model` | Import a 3D model from a URL (public or presigned S3/R2) |
| `upload_model` | Upload a GLB from bytes — local file path (stdio) or base64 |
| `list_models` | List models — tag filter, `updatedSince` delta polling, cursor pagination |
| `get_model` | Get model details and all its renders |
| `update_model` | Update a model's title, SKU, and/or tags |
| `archive_model` / `unarchive_model` | Hide / restore a model in the library |
| `delete_model` | Soft-delete a model |
| `check_hash` | Check whether a SHA-256 already exists (dedup pre-flight) |
| `get_model_file` | Download URL for a model's original GLB |
| `get_model_file_url` | Presigned GLB URL — downloads direct from storage, no auth header |

**Renders**

| Tool | Description |
|---|---|
| `create_render` | Create a single render (video or dataset) |
| `create_batch_renders` | Create multiple renders atomically (max 100) |
| `list_renders` | List render jobs — status filter, `updatedSince` delta polling |
| `get_render` | Check render status (poll until done) |
| `cancel_render` | Cancel a still-queued render and refund its credits |
| `dismiss_render` | Acknowledge an errored render |
| `delete_render` | Permanently delete a finished render and its assets |
| `quote` | Estimate credit cost before committing |

**Billing**

| Tool | Description |
|---|---|
| `get_account` | Check credit balance |
| `list_products` | List purchasable credit packs |
| `get_purchases` | Credit ledger — purchases, spends, refunds |

**Downloads**

| Tool | Description |
|---|---|
| `download_render` | Get download URL for a completed render |
| `get_download_url` | Presigned URL for any render asset — downloads direct from storage, no auth header |
| `get_poster` | Poster image (PNG) URL |
| `get_thumb` | Thumbnail loop video (MP4) URL |
| `download_bundle` | ZIP of all assets for a completed render |
| `download_dataset` | ZIP of a dataset render's multi-view images |
| `get_hls` | HLS streaming status and playback URLs |

**Overlays**

| Tool | Description |
|---|---|
| `upload_overlay` | Upload a PNG/JPEG/WebP as a reusable overlay asset (background, foreground, decal, or texture layer) |
| `list_overlays` | List overlay assets — ids feed backgrounds, decals, material edits |
| `delete_overlay` | Delete an overlay asset you own |

**Textures & Environments**

| Tool | Description |
|---|---|
| `upload_texture` | Mint a base-color replacement texture — id feeds `materialEdits.<material>.mapAssetId` |
| `upload_env` | Upload a custom HDRI (pre-encoded gainmap triple) — id feeds `envId` |
| `list_assets` | List the workspace's texture or environment assets |
| `delete_asset` | Delete an owned texture/environment asset |
| `resolve_asset_sha` | Reuse an existing asset by content SHA-256 instead of re-uploading |

**Team workspaces:** there's no MCP workspace configuration — the key implies the workspace. A workspace-bound key (minted in Studio) makes every tool operate on that workspace, billing its owner.

### Tips

**Tag everything.** Tags are the key to efficient batch operations. Tag models on upload or in Studio, then target them in prompts:

> Render all models tagged "new-arrivals" in 16:9 white with shadows.

**Quote first on large batches.** The `quote` tool prevents surprises. Ask the agent to quote before any batch:

> Quote 16:9 + 1:1 + 9:16 renders for my 80 shoe models, then proceed only if it's under 250 credits.

**Atomic credit deduction.** Batch renders deduct all credits at once. If you don't have enough for the full batch, nothing is charged — the batch fails cleanly and you can adjust.

**Presigned URLs for large files.** `get_download_url` (render assets) and `get_model_file_url` (source GLBs) return short-lived URLs that download directly from storage with no `Authorization` header — use them for ProRes videos, dataset ZIPs, or handing a download to another system.

---

## Tiers & Render Settings

### Video Quality Tiers

| Tier (API value) | Label | Resolution | Codec | Alpha | Credits/sec | Best for |
|---|---|---|---|---|---|---|
| `share` | Standard | 960×540 | H.264 MP4 | No | 1 | Quick shares, web, messaging, social |
| `standard` | HD | 1920×1080 | H.264 MP4 | No | 4 | Web, social media, product pages |
| `4k` | 4K | 3840×2160 | H.264 MP4 | No | 16 | In-store displays, kiosks, high-DPI screens |
| `pro` | ProRes 4444 | 3840×2160 | ProRes 4444 | Yes | 64 | Video editing, compositing, broadcast |

Video is charged per second of output based on quality tier.

### Dataset Tiers

| Tier | Views | Resolution | Credits | Best for |
|---|---|---|---|---|
| `100x800` | 100 | 800×800 | 40 | Quick experiments, proof-of-concept |
| `196x1024` | 196 | 1024×1024 | 160 | Production training, best quality-to-cost ratio |
| `400x2048` | 400 | 2048×2048 | 640 | Maximum fidelity, large-scale reconstruction |

Datasets are a flat cost per set.

**Coverage**

- **Hemisphere** (default) — upper hemisphere views, suitable for objects typically seen from above
- **Full sphere** — ±80° elevation range, captures undersides and all angles

**Output ZIP contents**

Every dataset ZIP contains:

- `images/` — RGB PNG frames (composited on white background)
- `depth/` — 8-bit grayscale depth PNGs
- `depth_16bit/` — 16-bit grayscale depth PNGs (65,536 levels)
- `normals/` — world-space normal map PNGs
- `masks/` — foreground/background alpha masks
- `transforms.json` — camera intrinsics + per-frame 4×4 transform matrices (nerfstudio / instant-ngp format)
- `overview.webp` — 4-quadrant contact sheet

After downloading a dataset ZIP, you can train directly with nerfstudio (the `--pipeline.model.background-color white` flag is required, since images are composited on white):

```bash
unzip dataset.zip -d mymodel
ns-train splatfacto --data ./mymodel \
  --max-num-iterations 15000 \
  --pipeline.model.sh-degree 3 \
  --pipeline.model.background-color white
```

### Render Settings

| Field | Type | Default | Description |
|---|---|---|---|
| `quality` | string | `"standard"` | `"share"` (540p, 1 credit/sec), `"standard"` (HD 1080p, 4 credits/sec — **the default when omitted**: a default 6s render with no `quality` set costs 24 credits), `"4k"` (16/sec), `"pro"` (4K ProRes with alpha, 64/sec) |
| `aspect` | string | `"16:9"` | `"16:9"`, `"1:1"`, `"9:16"` |
| `bgColor` | string | `"#ffffff"` | Any 6-digit hex (ignored when `bgImageId` is set) |
| `bgImageId` | string | — | Overlay asset ID for background image |
| `length` | number | `6` | Duration in seconds — anything above 0 up to 120 (ProRes: 60), decimal OK e.g. `4.7`. Billed per second, 1-credit minimum. |
| `animation` | string | — | Animation clip name (or `"*"` for first clip) — discover clips via `GET /v1/models/{id}` → `animations` |
| `animationSpeed` | number | `1` | Animation speed multiplier (−10 to 10; negative plays in reverse) |
| `shadows` | boolean | `true` | Ground shadows |
| `reflector` | boolean | `false` | Reflective ground plane |
| `easing` | boolean | `true` | Smooth rotation start/stop |
| `rotateY` | number | `0` | Camera orbit start angle (−180° to 180°) — moves the camera, not the model |
| `panX` | number | `0` | Model yaw (−180° to 180°), applied first |
| `tiltX` | number | `0` | Model pitch (−180° to 180°), applied after yaw |
| `rollZ` | number | `0` | Model roll (−180° to 180°), applied last |
| `zoom` | number | `1.0` | Camera zoom (0.5–2.0; above 1 = closer) |
| `bgAlign` | string | `"top-left"` | Background-image crop anchor (nine positions) |
| `hiddenMeshNames` | string[] | — | Meshes to hide, by exact runtime name (`GET /v1/models/{id}` → `meshNames`) |
| `materialEdits` | object | — | Per-material overrides keyed by material name — color, roughness, metalness, `mapAssetId` texture swaps, and more |
| `envId` | string | — | Custom HDRI environment (see Custom Materials & Environments) |
| `effect` | string | `"turntable"` | Camera motion: `"turntable"`, `"hero-spin"`, `"showcase"`, `"zoom-orbit"`, `"reveal"`, `"keyframes"` (custom fly-through) |
| `output` | string | `"video"` | `"video"` or `"dataset"` |
| `datasetQuality` | string | — | `"100x800"`, `"196x1024"`, `"400x2048"` (when output is dataset) |
| `coverage` | string | `"hemisphere"` | `"hemisphere"` or `"sphere"` (dataset only) |

This is the working set — the full surface (environment and lighting knobs, decals, keyframe camera paths, filters) is in the [API reference](/docs/api), and everything the Studio editor can author is accepted here.

---

## Error Handling

All errors return a consistent format:

```json
{
  "error": {
    "code": "no_credits",
    "message": "No render credits remaining",
    "status": 402
  }
}
```

Key error codes for integrators:

| Code | Status | Action |
|---|---|---|
| `unauthorized` | 401 | Check API key |
| `no_credits` | 402 | Purchase more credits or contact for volume pricing |
| `file_too_large` | 400 | File exceeds 1 GB — reduce before upload |
| `invalid_format` | 400 | Only `.glb` and `.zip` (OBJ+MTL) are supported — pack `.gltf` as `.glb` |
| `model_not_found` | 404 | Model was deleted or ID is wrong |
| `too_many` | 400 | Batch exceeds 100 renders — split into smaller batches |
| `upload_limit` | 429 | Free upload limit reached — purchase credits to unlock |

---

## Further Reading

- [API Documentation](https://dx.gl/docs/api) — Full REST API reference with all endpoints
- [Use Cases](https://dx.gl/use-cases) — Product video production and other workflows at scale
- [Datasets](https://dx.gl/datasets) — Multi-view datasets for 3D reconstruction, plus free CC0 samples
- [Showcase](https://dx.gl/showcase) — Browse example videos
- [Pricing](https://dx.gl/pricing) — Credit packs and volume pricing