DX.GL

Developer Docs

Markdown API Reference

REST API and MCP for turntable videos and multi-view datasets

Contents
Authentication & API KeysREST Quick StartPython ExampleNode.js ExampleCommon PatternsCredit ManagementAsset DeliveryMCP for AI AgentsTiers & Render SettingsError HandlingFurther Reading

Authentication & API Keys

You use a single API key (dxgl_sk_...) created in Studio → avatar menu → APIKeys. 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

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:

{ "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)

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:

# 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

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)

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

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

Status progression: pendingposter-processingvideo-processingdone (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

# 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

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

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.

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:

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:

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

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

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)
# 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

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

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:

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

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

Replace dxgl_sk_... with your API key from Studio → avatar menu → APIKeys.

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

Output ZIP contents

Every dataset ZIP contains:

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

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, and everything the Studio editor can author is accepted here.


Error Handling

All errors return a consistent format:

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