REST API and MCP for turntable videos and multi-view datasets
You use a single API key (dxgl_sk_...) created in the Portal → API Keys. All renders, uploads, and downloads are billed to your account. You handle user mapping and billing on your side however you want.
The REST base URL is https://api.dx.gl/v1. Every request sends Authorization: Bearer dxgl_sk_... in the header.
| 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.
Base URL: https://api.dx.gl/v1
All requests require Authorization: Bearer dxgl_sk_... in the header.
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.
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, 2-minute timeout). SHA-256 deduplication prevents re-uploading identical files.
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
}
}'
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. Renders for the same model share GPU frames — encoding multiple variants from one render pass.
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").
# 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.
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})
const API = 'https://api.dx.gl/v1';
const headers = { 'Authorization': 'Bearer dxgl_sk_...' };
async function renderModel(filePath, settings) {
const form = new FormData();
form.append('file', fs.createReadStream(filePath));
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}`);
}
}
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"]
})
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})
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 top-left anchor, keeping logos safe across all aspect ratios.
Tags organize models across clients, projects, and campaigns. Every model can have up to 20 tags (lowercased, trimmed).
# 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_..."
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.
curl https://api.dx.gl/v1/account \
-H "Authorization: Bearer dxgl_sk_..."
# Response: { "data": { "credits": 847 } }
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.
Every completed render produces four assets: the full video, a web-optimized variant, a thumbnail, and a poster.
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.
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 |
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>
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 the Portal → API Keys.
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.
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:
list_models with tags: "ss26" to find the 30 modelsquote with 90 renders (30 models × 3 variants) to show total costcreate_batch_renders to submit all 90 at onceGenerate 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:
list_models with tags: "validation-set" to find matching modelsquote with the dataset configuration to show the total credit costcreate_batch_renders to submit all datasets at onceget_render for each until complete| Tool | Description |
|---|---|
ingest_model |
Import a 3D model from a URL |
list_models |
List models with tag and pagination filters |
get_model |
Get model details and all its renders |
create_render |
Create a single render (video or dataset) |
create_batch_renders |
Create multiple renders atomically (max 100) |
get_render |
Check render status (poll until done) |
download_render |
Get download URL for a completed render |
get_account |
Check credit balance |
quote |
Estimate credit cost before committing |
Tag everything. Tags are the key to efficient batch operations. Tag models on upload or via the portal, 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.
| 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.
| 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:
images/ — RGB PNG frames (composited on white background)depth/ — 8-bit grayscale depth PNGsdepth_16bit/ — 16-bit grayscale depth PNGs (65,536 levels)normals/ — world-space normal map PNGsmasks/ — foreground/background alpha maskstransforms.json — camera intrinsics + per-frame 4×4 transform matrices (nerfstudio / instant-ngp format)overview.webp — 4-quadrant contact sheetAfter 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
| 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 (3–120, decimal OK e.g. 4.7) |
animation |
string | — | Animation clip name (or "*" for first clip) |
animationSpeed |
number | 1 |
Animation speed multiplier (e.g. 0.5, 2) |
shadows |
boolean | true |
Ground shadows |
reflector |
boolean | false |
Reflective ground plane |
easing |
boolean | true |
Smooth rotation start/stop |
rotateY |
integer | 0 |
Starting angle (−180° to 180°) |
tiltX |
integer | 0 |
Forward/back tilt (−90° to 90°) |
effect |
string | "turntable" |
Camera motion: "turntable", "hero-spin", "showcase", "zoom-orbit", "reveal" |
output |
string | "video" |
"video" or "dataset" |
datasetQuality |
string | — | "100x800", "196x1024", "400x2048" (when output is dataset) |
coverage |
string | "hemisphere" |
"hemisphere" or "sphere" (dataset only) |
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 |