API Documentation

Markdown Portal Guide

Programmatic access to the DXGL rendering pipeline

Contents
Authentication Models Renders Batch Renders Assets Overlays Account Health Check Render Settings Render Status Errors Quick Start

Authentication

All requests require a Bearer token in the Authorization header:

Authorization: Bearer dxgl_sk_...

Create tokens in the Portal under the API tab. The raw token is shown once on creation — store it securely.

Response Format

Success:

{
  "data": { ... }
}

Paginated list:

{
  "data": [ ... ],
  "meta": { "total": 142, "offset": 0, "limit": 50 }
}

Error:

{
  "error": {
    "code": "model_not_found",
    "message": "Model not found",
    "status": 404
  }
}

Models

A model is an uploaded 3D file (GLB/glTF) or a converted 3D scan. Uploading automatically creates a render job.

POST /v1/models

Upload a model file. Multipart form data.

FieldTypeRequiredDescription
fileFileYes.glb, .gltf, or .zip (OBJ+MTL+textures)
renderSettingsJSON stringNoRender configuration
# Upload a GLB
curl -X POST https://dx.gl/v1/models \
  -H "Authorization: Bearer dxgl_sk_..." \
  -F "[email protected]" \
  -F 'renderSettings={"aspect":"16:9","bgColor":"#ffffff","length":6}'

# Upload a 3D scan (OBJ+MTL+textures in a ZIP)
curl -X POST https://dx.gl/v1/models \
  -H "Authorization: Bearer dxgl_sk_..." \
  -F "[email protected]" \
  -F 'renderSettings={"aspect":"1:1","bgColor":"#ffffff","length":9}'

Response 201:

{
  "data": {
    "modelId": "Ab3kF9x2qL1m",
    "renderId": "Xz7pQ4w8nR2k"
  }
}

If the file's SHA-256 matches an existing upload, a new render is created for the existing model and duplicate: true is returned.

3D scan support: Upload a .zip containing one OBJ file with its MTL and texture files (JPG/PNG). One model per ZIP. The server converts to GLB automatically, with materials set to roughness 1.0 for a natural matte finish. The converted GLB is stored as your model and can be downloaded via GET /v1/models/:id/file. Ideal for photogrammetry and structured-light scan exports (Artec Studio, RealityCapture, Metashape, etc.).

Instant preview: Every upload automatically triggers a free system preview render (no credits deducted). The preview generates a lightweight thumbnail video within seconds, which also serves as a model integrity check — if the preview fails, the model likely has issues (broken geometry, missing textures, invalid glTF). Check the model's renders list for a render with is_system_preview: true.

POST /v1/models/ingest

Ingest a model from a URL. Max 100 MB, 2-minute timeout. URL ingest currently supports GLB/glTF only. For OBJ scans, use the file upload endpoint with a ZIP.

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

Response 201:

{
  "data": {
    "modelId": "Ab3kF9x2qL1m",
    "renderId": "Xz7pQ4w8nR2k"
  }
}

GET /v1/models

List all models. Paginated.

ParamTypeDefaultDescription
limitinteger50Max 100
offsetinteger0Pagination offset
tagsstringComma-separated tags (OR filter)
curl "https://dx.gl/v1/models?limit=10&tags=furniture" \
  -H "Authorization: Bearer dxgl_sk_..."

GET /v1/models/:id

Get model detail with all its renders.

curl https://dx.gl/v1/models/Ab3kF9x2qL1m \
  -H "Authorization: Bearer dxgl_sk_..."

Response:

{
  "data": {
    "id": "Ab3kF9x2qL1m",
    "originalName": "chair.glb",
    "title": "Ergonomic Chair",
    "sku": "EC-1001",
    "tags": ["furniture", "office"],
    "fileSize": 4521984,
    "createdAt": "2026-02-15T20:00:00.000Z",
    "renders": [
      { "id": "Xz7pQ4w8nR2k", "status": "done", "renderSettings": { ... } }
    ]
  }
}

PATCH /v1/models/:id

Update model metadata. All fields are optional — only provided fields are changed.

curl -X PATCH https://dx.gl/v1/models/Ab3kF9x2qL1m \
  -H "Authorization: Bearer dxgl_sk_..." \
  -H "Content-Type: application/json" \
  -d '{"title": "Ergonomic Chair Pro", "sku": "ECP-2001", "tags": ["furniture", "office"]}'
FieldTypeDescription
titlestringDisplay title
skustringProduct code
tagsstring[]Up to 20 tags (lowercased, trimmed)

PATCH /v1/models/:id/archive

Archive a model. Archived models are hidden from default list queries but can be restored.

PATCH /v1/models/:id/unarchive

Restore an archived model back to active status.

GET /v1/models/:id/file

Download the original GLB file. Returns Content-Type: model/gltf-binary with a Content-Disposition: attachment header.

curl -o chair.glb https://dx.gl/v1/models/Ab3kF9x2qL1m/file \
  -H "Authorization: Bearer dxgl_sk_..."

POST /v1/models/check-hash

SHA-256 dedup pre-flight. Compute your file's hash locally, then check before uploading.

{ "sha256": "a1b2c3d4..." }

Response 200:

{ "data": { "exists": true, "modelId": "Ab3kF9x2qL1m" } }

DELETE /v1/models/:id

Soft-delete a model. Existing renders remain accessible until independently deleted.

Renders

A render is a video generated from a model. Creating a render queues it for processing.

POST /v1/renders

Create a render for an existing model.

{
  "modelId": "Ab3kF9x2qL1m",
  "renderSettings": {
    "quality": "standard",
    "aspect": "16:9",
    "bgColor": "#ffffff",
    "length": 6,
    "shadows": true,
    "reflector": false,
    "easing": true
  }
}

The quality field is optional (defaults to "standard"). Set to "4k" for 4K H.264 (4 credits) or "pro" for 4K ProRes 4444 with alpha (12 credits). See Render Settings for all options.

GET /v1/renders

List renders. Filterable.

ParamTypeDescription
modelstringFilter by model ID
statusstringFilter by status (e.g. done)
limitintegerMax 100, default 50
offsetintegerPagination offset

GET /v1/renders/:id

Get render status and metadata.

{
  "data": {
    "id": "Xz7pQ4w8nR2k",
    "modelId": "Ab3kF9x2qL1m",
    "status": "done",
    "renderSettings": { ... },
    "fileSize": 2457600,
    "errorMessage": null,
    "hasWebVariant": true,
    "createdAt": "2026-02-15T20:00:01.000Z",
    "updatedAt": "2026-02-15T20:01:30.000Z"
  }
}
FieldTypeDescription
fileSizeintegerVideo file size in bytes (null until done)
errorMessagestringError detail when status is error
hasWebVariantbooleanWhether a web-optimized MP4 is available

PATCH /v1/renders/:id/dismiss

Dismiss an errored render (transitions errorfailed). Useful for acknowledging errors programmatically.

DELETE /v1/renders/:id

Permanently delete a render and its files (video, poster, thumbnail).

Batch Renders

Create multiple renders in a single atomic request. All credits are deducted at once — if there aren't enough credits, the entire batch fails.

POST /v1/renders/batch

{
  "renders": [
    { "modelId": "Ab3kF9x2qL1m", "renderSettings": { "aspect": "16:9", "bgColor": "#ffffff" } },
    { "modelId": "Ab3kF9x2qL1m", "renderSettings": { "aspect": "1:1", "bgColor": "#000000" } },
    { "modelId": "Yz9mK3v7pN4j", "renderSettings": { "aspect": "16:9" } }
  ]
}

Maximum 100 renders per batch. Each render in the array requires a modelId and optional renderSettings.

Response 201:

{
  "data": [
    { "id": "Xz7pQ4w8nR2k", "modelId": "Ab3kF9x2qL1m", "status": "pending" },
    { "id": "Bc5nL2x9mQ3r", "modelId": "Ab3kF9x2qL1m", "status": "pending" },
    { "id": "Wv8jR6t4kP1s", "modelId": "Yz9mK3v7pN4j", "status": "pending" }
  ]
}

Renders for the same model are automatically grouped into a batch — the worker renders shared frames once and encodes variants in parallel.

Assets

Download output files of a completed render.

GET /v1/renders/:id/video

Stream the MP4 video. Supports Range headers. Pass ?quality=web to get the lighter web-optimized variant (when available, see hasWebVariant).

GET /v1/renders/:id/poster

Download the poster image (first frame, WebP).

GET /v1/renders/:id/thumb

Stream a quarter-resolution thumbnail MP4. Supports Range headers.

Overlays

Overlay assets are reusable background (or foreground) images for renders. Upload once, reference by ID in render settings. System overlays (published by admins) are available to all users.

POST /v1/overlays

Upload an overlay image. Accepts multipart/form-data with a file field (PNG, JPEG, or WebP, max 10 MB).

FieldTypeDefaultDescription
filefileImage file (required)
layerstring"background""background" or "foreground"
namestringDisplay name (max 100 chars)
descriptionstringShort description (max 500 chars)
alignmentstring"top-left"Crop alignment when image doesn't match output aspect ratio

Returns { "data": { "id": "...", "layer": "background", ... } }

GET /v1/overlays

List your overlay assets plus all published system overlays. Filter by layer with ?layer=background.

DELETE /v1/overlays/:id

Delete an overlay asset you own. Removes the image from storage.

Using overlays in renders

Pass the overlay's id as bgImageId in your render settings:

{ "renderSettings": { "bgImageId": "abc123DEF456", "aspect": "16:9", "length": 6 } }

When bgImageId is set, bgColor is ignored. The image is scaled to cover the output dimensions and cropped from the bgAlign anchor (default: top-left), keeping logos safe across all aspect ratios.

Account

GET /v1/account

Returns the authenticated user's account info and credit balance.

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

Response 200:

{
  "data": {
    "id": "Ab3kF9x2qL1m",
    "email": "[email protected]",
    "credits": 47
  }
}

Health Check

GET /v1/health

No authentication required. Returns database connectivity status.

Render Settings

FieldTypeDefaultOptions
qualitystring"standard""standard" (1080p H.264) · "4k" (4K H.264) · "pro" (4K ProRes 4444 with alpha)
effectstring"turntable""turntable" (360°) · "hero-spin" (fast spin to front) · "showcase" (look-around) · "zoom-orbit" (360° + zoom) · "reveal" (scale-up entrance) · "dataset" (vision training, 1 credit)
sweepAnglenumberOverride sweep angle in degrees (hero-spin default 540, reveal default 180). Range: 10–1080.
easeOutRationumberHero-spin deceleration fraction (default 0.6). Range: 0–1.
amplitudenumberShowcase swing angle in degrees (default 60). Range: 5–180.
zoomStartnumberZoom-orbit starting radius multiplier (default 1.0). Range: 0.1–3.
zoomEndnumberZoom-orbit ending radius multiplier (default 0.7). Range: 0.1–3.
scaleFromnumberReveal starting model scale (default 0). Range: 0–1.
aspectstring"16:9""16:9" "1:1" "9:16"
bgColorstring"#ffffff"Any 6-digit hex color, e.g. "#ff8800" (must include #). Ignored when bgImageId is set.
bgImageIdstringShort ID of an overlay asset (background layer). Replaces bgColor with the uploaded image.
bgAlignstring"top-left"Image alignment when cropping to fit aspect ratio. "top-left" keeps logos anchored in the corner.
lengthinteger6315 (seconds)
shadowsbooleantrueGround shadows
reflectorbooleanfalseReflective ground plane
easingbooleantrueEase in/out on rotation
rotateYinteger0Starting angle in degrees (−180 to 180). Rotates the model so a different side faces camera first.
tiltXinteger0Forward/back tilt in degrees (−90 to 90). Useful for angling products in portrait aspect. Bypasses shadow caching.

Quality Tiers

TierResolutionCodecAlphaCreditsOutput
standard1920×1080H.264No1.mp4
4k3840×2160H.264No4.mp4
pro3840×2160ProRes 4444Yes16.mov

Pro renders always include an alpha channel — the bgColor is applied only to web/thumbnail/poster variants. The .mov file has a transparent background for compositing in professional editors (DaVinci Resolve, After Effects, Final Cut Pro). All tiers render with GPU-accelerated 2× supersampling (SSAA).

Note: The PREVIEW watermark is automatically applied when using free render credits (standard quality only). Free credits cannot be used for 4k or pro renders.

Dataset Export (Vision Training)

Set output: "dataset" to generate a NeRF/3DGS-ready training dataset instead of a video. Use datasetQuality to select a tier and coverage for hemisphere or full sphere viewpoints.

Tier (datasetQuality)ViewsResolutionCredits
100x800100800×8001
196x10241961024×10244
400x20484002048×204816

Output is a ZIP containing:

POST /v1/renders
{ "modelId": "Ab3kF9x2qL1m", "renderSettings": { "effect": "dataset" } }

Quick start: Unzip the dataset and train a 3D Gaussian Splat with nerfstudio:

unzip dataset.zip -d mymodel

ns-train splatfacto --data ./mymodel
--max-num-iterations 15000
--pipeline.model.sh-degree 3
--pipeline.model.background-color white

The background-color white flag is required because images are composited on a white background.

Render Status

pending poster-processing poster-done video-processing done

Any status can transition to error if the render fails.

StatusDescription
pendingQueued, waiting for a worker
poster-processingRendering frames, poster extracted
poster-donePoster uploaded, video encoding starting
video-processingVideo being encoded
doneAll assets ready (video, poster, thumbnail)
errorRender failed

Errors

CodeStatusDescription
unauthorized401Missing, invalid, or revoked token
token_expired401Token has expired
forbidden403Account suspended
insufficient_scope403Token lacks required scope
no_file400No file in upload request
invalid_format400File is not .glb, .gltf, or .zip
invalid_url400Malformed URL
no_credits402No render credits remaining
model_not_found404Model does not exist
render_not_found404Render does not exist
video_not_found404Video not yet available
timeout408URL download timed out
internal500Internal server error

Quick Start

1. Upload a model

# GLB file
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}'

# Or a 3D scan ZIP
curl -X POST https://api.dx.gl/v1/models \
  -H "Authorization: Bearer dxgl_sk_..." \
  -F "[email protected]"

# Response: { "data": { "modelId": "Ab3k...", "renderId": "Xz7p..." } }

2. Create a 4K or Pro render

# 4K render (4 credits)
curl -X POST https://api.dx.gl/v1/renders \
  -H "Authorization: Bearer dxgl_sk_..." \
  -H "Content-Type: application/json" \
  -d '{"modelId":"Ab3k...","renderSettings":{"quality":"4k","aspect":"16:9"}}'

# Pro render — ProRes 4444 with alpha (16 credits)
curl -X POST https://api.dx.gl/v1/renders \
  -H "Authorization: Bearer dxgl_sk_..." \
  -H "Content-Type: application/json" \
  -d '{"modelId":"Ab3k...","renderSettings":{"quality":"pro","aspect":"16:9"}}'

3. Poll for completion

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

# Response: { "data": { "status": "done", ... } }

4. Download assets

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

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

Python Example

import requests, time

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

# Upload
with open("chair.glb", "rb") as f:
    r = requests.post(f"{API}/models", headers=HEADERS,
        files={"file": f},
        data={"renderSettings": '{"aspect":"16:9","length":6}'})
render_id = r.json()["data"]["renderId"]

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

# Download
r = requests.get(f"{API}/renders/{render_id}/video", headers=HEADERS)
with open("chair.mp4", "wb") as f:
    f.write(r.content)

Node.js Example

const fs = require('fs');

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

// Upload
const form = new FormData();
form.append('file', fs.createReadStream('chair.glb'));
form.append('renderSettings', JSON.stringify({ aspect: '16:9', length: 6 }));

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

// Poll
let status;
do {
  await new Promise(r => setTimeout(r, 5000));
  const res = await fetch(API + '/renders/' + renderId, { headers });
  status = (await res.json()).data.status;
} while (status !== 'done' && status !== 'error');

// Download
const video = await fetch(API + '/renders/' + renderId + '/video', { headers });
fs.writeFileSync('chair.mp4', Buffer.from(await video.arrayBuffer()));