Back to Portal
# DXGL Public API
Base URL: `https://api.dx.gl/v1`
## Authentication
All requests require a Bearer token in the `Authorization` header:
```
Authorization: Bearer dxgl_sk_...
```
Tokens are created in the [Client Portal](https://dx.gl/portal) under the **API** tab. The raw token is shown once on creation — store it securely.
---
## Response Format
**Success:**
```json
{
"data": { ... }
}
```
**List (paginated):**
```json
{
"data": [ ... ],
"meta": { "total": 142, "offset": 0, "limit": 50 }
}
```
**Error:**
```json
{
"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 a model automatically creates a render job.
### Upload Model
```
POST /v1/models
Content-Type: multipart/form-data
```
| Field | Type | Required | Description |
|---|---|---|---|
| `file` | File | Yes | `.glb`, `.gltf`, or `.zip` (OBJ+MTL+textures) |
| `renderSettings` | JSON string | No | Render configuration (see below) |
**Response** `201`:
```json
{
"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.).
### Ingest from URL
```
POST /v1/models/ingest
Content-Type: application/json
```
```bash
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`: Same as upload.
Downloads the file from the URL (max 100 MB, 2-minute timeout). SHA-256 deduplication applies. URL ingest currently supports GLB/glTF only. For OBJ scans, use the file upload endpoint with a ZIP.
### List Models
```
GET /v1/models?limit=50&offset=0&tags=furniture
```
| Param | Type | Default | Description |
|---|---|---|---|
| `limit` | integer | 50 | Max 100 |
| `offset` | integer | 0 | Pagination offset |
| `tags` | string | — | Comma-separated tags (OR filter) |
```bash
curl "https://dx.gl/v1/models?limit=10&tags=furniture" \
-H "Authorization: Bearer dxgl_sk_..."
```
**Response** `200`:
```json
{
"data": [
{
"id": "Ab3kF9x2qL1m",
"originalName": "chair.glb",
"title": "Ergonomic Chair",
"sku": "EC-1001",
"tags": ["furniture", "office"],
"fileSize": 4521984,
"sha256": "a1b2c3...",
"createdAt": "2026-02-15T20:00:00.000Z"
}
],
"meta": { "total": 42, "offset": 0, "limit": 50 }
}
```
### Get Model
```
GET /v1/models/:id
```
Returns the model detail with all its renders:
```bash
curl https://dx.gl/v1/models/Ab3kF9x2qL1m \
-H "Authorization: Bearer dxgl_sk_..."
```
```json
{
"data": {
"id": "Ab3kF9x2qL1m",
"originalName": "chair.glb",
"title": "Ergonomic Chair",
"sku": "EC-1001",
"tags": ["furniture", "office"],
"fileSize": 4521984,
"sha256": "a1b2c3...",
"createdAt": "2026-02-15T20:00:00.000Z",
"renders": [
{
"id": "Xz7pQ4w8nR2k",
"status": "done",
"renderSettings": { ... },
"createdAt": "2026-02-15T20:00:01.000Z",
"updatedAt": "2026-02-15T20:01:30.000Z"
}
]
}
}
```
### Update Model
```
PATCH /v1/models/:id
Content-Type: application/json
```
| Field | Type | Description |
|---|---|---|
| `title` | string | Display title |
| `sku` | string | Product code |
| `tags` | string[] | Up to 20 tags (lowercased, trimmed) |
All fields are optional — only provided fields are changed.
```bash
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"]}'
```
### Archive Model
```
PATCH /v1/models/:id/archive
```
Archives the model. Archived models are hidden from default list queries but can be restored.
```bash
curl -X PATCH https://dx.gl/v1/models/Ab3kF9x2qL1m/archive \
-H "Authorization: Bearer dxgl_sk_..."
```
### Unarchive Model
```
PATCH /v1/models/:id/unarchive
```
Restores an archived model back to active status.
### Download Model File
```
GET /v1/models/:id/file
```
Downloads the original GLB file. Returns `Content-Type: model/gltf-binary` with a `Content-Disposition: attachment` header.
```bash
curl -o chair.glb https://dx.gl/v1/models/Ab3kF9x2qL1m/file \
-H "Authorization: Bearer dxgl_sk_..."
```
### Check Hash (Dedup Pre-flight)
```
POST /v1/models/check-hash
Content-Type: application/json
```
```json
{ "sha256": "a1b2c3d4..." }
```
**Response** `200`:
```json
{
"data": {
"exists": true,
"modelId": "Ab3kF9x2qL1m"
}
}
```
Pre-flight check to avoid uploading duplicate files. Compute the SHA-256 of your file locally, then call this endpoint before uploading.
### Delete Model
```
DELETE /v1/models/:id
```
Soft-deletes the 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 by the render pipeline.
### Create Render
```
POST /v1/renders
Content-Type: application/json
```
```json
{
"modelId": "Ab3kF9x2qL1m",
"renderSettings": {
"aspect": "16:9",
"bgColor": "#ffffff",
"length": 6,
"shadows": true,
"reflector": false,
"easing": true
}
}
```
**Response** `201`:
```json
{
"data": {
"id": "Xz7pQ4w8nR2k",
"modelId": "Ab3kF9x2qL1m",
"status": "pending",
"renderSettings": { ... }
}
}
```
### List Renders
```
GET /v1/renders?model=Ab3kF9x2qL1m&status=done&limit=50&offset=0
```
| Param | Type | Description |
|---|---|---|
| `model` | string | Filter by model ID |
| `status` | string | Filter by status |
| `limit` | integer | Max 100, default 50 |
| `offset` | integer | Pagination offset |
### Get Render
```
GET /v1/renders/:id
```
```json
{
"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"
}
}
```
| Field | Type | Description |
|---|---|---|
| `fileSize` | integer | Video file size in bytes (null until done) |
| `errorMessage` | string | Error detail when status is `error` |
| `hasWebVariant` | boolean | Whether a web-optimized MP4 is available |
### Dismiss Render
```
PATCH /v1/renders/:id/dismiss
```
Dismisses an errored render (transitions `error` → `failed`). Useful for acknowledging errors programmatically.
### Delete Render
```
DELETE /v1/renders/:id
```
Permanently deletes the render and its files (video, poster, thumbnail).
### Batch Render
```
POST /v1/renders/batch
Content-Type: application/json
```
```json
{
"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 item requires a `modelId` and optional `renderSettings`. All credits are deducted atomically — if there aren't enough credits, the entire batch fails.
**Response** `201`:
```json
{
"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 the output files of a completed render.
### Video
```
GET /v1/renders/:id/video
GET /v1/renders/:id/video?quality=web
```
Returns the video file. Supports `Range` headers for streaming.
Pass `?quality=web` to get the lighter web-optimized variant (when available, see `hasWebVariant`).
**Content-Type:** `video/mp4` (standard/4k) or `video/quicktime` (pro — ProRes .mov)
### Poster
```
GET /v1/renders/:id/poster
GET /v1/renders/:id/poster?quality=full
```
Returns the poster image (first frame). By default returns a small thumbnail suitable for previews. Pass `?quality=full` to get the full-resolution PNG at the video's native dimensions (e.g. 1920×1080).
**Content-Type:** `image/png`
### Thumbnail
```
GET /v1/renders/:id/thumb
```
Returns a quarter-resolution MP4 thumbnail video. Supports `Range` headers.
**Content-Type:** `video/mp4`
### Bundle (Zip Download)
```
GET /v1/renders/:id/bundle
```
Downloads all render assets as a single ZIP file. The zip is streamed directly — no server-side buffering, suitable for large ProRes files. Contains:
- `video.mp4` or `video.mov` (main video)
- `web.mp4` (web-optimized variant)
- `poster.png` (full-resolution poster)
- `thumb.mp4` (thumbnail video)
The response includes `Cache-Control: immutable` — renders never change after completion.
```bash
curl -o assets.zip https://api.dx.gl/v1/renders/Xz7pQ4w8nR2k/bundle \
-H "Authorization: Bearer dxgl_sk_..."
```
---
## Render Settings
| Field | Type | Default | Description |
|---|---|---|---|
| `quality` | string | `"standard"` | Quality tier: `"standard"` (1080p H.264), `"4k"` (4K H.264), `"pro"` (4K ProRes 4444 with alpha) |
| `effect` | string | `"turntable"` | Camera motion: `"turntable"` (360°), `"hero-spin"` (fast spin to front), `"showcase"` (look-around), `"zoom-orbit"` (360° + zoom), `"reveal"` (scale-up entrance), `"dataset"` (vision training, 1 credit) |
| `sweepAngle` | number | — | Override sweep angle in degrees (hero-spin default 540, reveal default 180). Range: 10–1080. |
| `easeOutRatio` | number | — | Hero-spin deceleration fraction (default 0.6). Range: 0–1. |
| `amplitude` | number | — | Showcase swing angle in degrees (default 60). Range: 5–180. |
| `zoomStart` | number | — | Zoom-orbit starting radius multiplier (default 1.0). Range: 0.1–3. |
| `zoomEnd` | number | — | Zoom-orbit ending radius multiplier (default 0.7). Range: 0.1–3. |
| `scaleFrom` | number | — | Reveal starting model scale (default 0). Range: 0–1. |
| `aspect` | string | `"16:9"` | Video aspect ratio: `"16:9"`, `"1:1"`, `"9:16"` |
| `bgColor` | string | `"#ffffff"` | Background hex color, e.g. `"#ff8800"` (6-digit hex, must include `#`) |
| `length` | integer | `6` | Video duration in seconds: `3` – `15` (integer) |
| `shadows` | boolean | `true` | Enable ground shadows |
| `reflector` | boolean | `false` | Enable reflective ground plane |
| `easing` | boolean | `true` | Ease in/out on turntable rotation |
| `rotateY` | integer | `0` | Starting angle in degrees (-180 to 180). Rotates the model so a different side faces camera first. Negative = left, positive = right. |
| `tiltX` | integer | `0` | Forward/back tilt in degrees (-90 to 90). Useful for angling products in portrait aspect. Bypasses shadow caching. |
### Quality Tiers
| Tier | Resolution | Codec | Alpha | Credits | Output |
|---|---|---|---|---|---|
| `standard` | 1920×1080 | H.264 MP4 | No | 1 | `.mp4` |
| `4k` | 3840×2160 | H.264 MP4 | No | 4 | `.mp4` |
| `pro` | 3840×2160 | ProRes 4444 | Yes | 16 | `.mov` |
Pro renders always include an alpha channel — the `bgColor` is applied only to the 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) for anti-aliased edges.
> **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"` with `datasetQuality` to generate a NeRF/3DGS-ready training dataset. Three tiers:
| Tier (`datasetQuality`) | Views | Resolution | Credits |
|---|---|---|---|
| `100x800` | 100 | 800×800 | 1 |
| `196x1024` | 196 | 1024×1024 | 4 |
| `400x2048` | 400 | 2048×2048 | 16 |
Use `coverage: "hemisphere"` (default) or `coverage: "sphere"` for full sphere viewpoints.
Output ZIP contains:
- `images/` — RGB PNG frames (composited on white background)
- `depth/` — 100 8-bit grayscale depth PNGs (closer = darker, farther = lighter). Tight near/far planes maximize precision.
- `depth_16bit/` — 100 16-bit grayscale depth PNGs (65,536 levels of precision for surface reconstruction)
- `normals/` — 100 world-space normal map PNGs
- `masks/` — 100 foreground/background alpha mask PNGs
- `transforms.json` — Camera intrinsics + per-frame 4×4 transform matrices (instant-ngp / nerfstudio format). Includes `depth_near` and `depth_far` for decoding: `depth = pixel_value/255 * (depth_far - depth_near) + depth_near`
- `overview.webp` — 4-quadrant contact sheet (10×10 grid of all views across RGB, depth, normals, masks)
```
POST /v1/renders
{ "modelId": "Ab3kF9x2qL1m", "renderSettings": { "effect": "dataset" } }
```
---
## Render Status
Renders progress through these statuses:
```
pending → poster-processing → poster-done → video-processing → done
```
Any status can transition to `error` if the render fails.
| Status | Description |
|---|---|
| `pending` | Queued, waiting for a worker to pick it up |
| `poster-processing` | Worker is rendering frames, poster extracted |
| `poster-done` | Poster uploaded, video encoding in progress |
| `video-processing` | Video being encoded (ffmpeg) |
| `done` | All assets ready (video, poster, thumbnail) |
| `error` | Render failed |
---
## 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.
### Upload Overlay
```
POST /v1/overlays
Content-Type: multipart/form-data
```
| Field | Type | Default | Description |
|-------|------|---------|-------------|
| `file` | file | — | Image file (PNG, JPEG, WebP; max 10 MB) |
| `layer` | string | `"background"` | `"background"` or `"foreground"` |
| `name` | string | — | Display name (max 100 chars) |
| `description` | string | — | Short description (max 500 chars) |
| `alignment` | string | `"top-left"` | Crop anchor when image doesn't match output aspect ratio |
**Response `200`:**
```json
{ "data": { "id": "abc123DEF456", "layer": "background", "alignment": "top-left", "name": "My Gradient" } }
```
### List Overlays
```
GET /v1/overlays
GET /v1/overlays?layer=background
```
Returns your overlay assets plus all published system overlays.
### Delete Overlay
```
DELETE /v1/overlays/:id
```
Deletes an overlay you own. Removes the image from storage.
### Using Overlays in Renders
Pass the overlay `id` as `bgImageId` in render settings:
```json
{ "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.
```bash
curl https://dx.gl/v1/account \
-H "Authorization: Bearer dxgl_sk_..."
```
```json
{
"data": {
"id": "Ab3kF9x2qL1m",
"email": "[email protected]",
"credits": 47
}
}
```
---
## Health Check
```
GET /v1/health
```
No authentication required. Returns database connectivity status.
---
## Render Credits
Render cost depends on quality tier:
| Quality | Credits |
|---|---|
| `standard` | 1 |
| `4k` | 4 |
| `pro` | 16 |
New accounts receive **2 free credits** — renders using free credits produce a "PREVIEW" watermark. Free credits only cover `standard` quality.
Batch renders deduct credits atomically based on the sum of all items' quality costs. If there aren't enough credits, the entire batch fails.
When credits are exhausted, render requests return `402` with error code `no_credits`.
---
## Rate Limits
Not currently enforced. Planned for a future release.
---
## Errors
| Code | Status | Description |
|---|---|---|
| `unauthorized` | 401 | Missing, invalid, or revoked token |
| `token_expired` | 401 | Token has expired |
| `forbidden` | 403 | Account suspended |
| `insufficient_scope` | 403 | Token lacks required scope |
| `no_file` | 400 | No file in upload request |
| `invalid_format` | 400 | File is not .glb, .gltf, or .zip |
| `invalid_url` | 400 | Malformed URL |
| `url_required` | 400 | Missing `url` field |
| `invalid_settings` | 400 | Invalid renderSettings value |
| `model_id_required` | 400 | Missing `modelId` field |
| `file_too_large` | 400 | File exceeds 100 MB limit |
| `no_credits` | 402 | No render credits remaining |
| `model_not_found` | 404 | Model does not exist |
| `render_not_found` | 404 | Render does not exist |
| `renders_required` | 400 | Missing or empty renders array |
| `too_many` | 400 | Batch exceeds 100 renders |
| `upload_limit` | 429 | Free upload limit reached |
| `video_not_found` | 404 | Video not yet available |
| `poster_not_found` | 404 | Poster not yet available |
| `thumb_not_found` | 404 | Thumbnail not yet available |
| `file_not_found` | 404 | Model file not found in storage |
| `sha256_required` | 400 | Missing `sha256` field |
| `timeout` | 408 | URL download timed out |
| `internal` | 500 | Internal server error |
---
## Quick Start
```bash
# Upload a GLB model and start rendering
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}'
# Upload a 3D scan (OBJ+MTL+textures in a ZIP)
curl -X POST https://api.dx.gl/v1/models \
-H "Authorization: Bearer dxgl_sk_..." \
-F "[email protected]" \
-F 'renderSettings={"aspect":"1:1","bgColor":"#ffffff","length":9}'
# Create a 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":"Ab3kF9x2qL1m","renderSettings":{"quality":"4k","aspect":"16:9","bgColor":"#ffffff"}}'
# Create a 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":"Ab3kF9x2qL1m","renderSettings":{"quality":"pro","aspect":"16:9","bgColor":"#000000"}}'
# Check render status
curl https://api.dx.gl/v1/renders/Xz7pQ4w8nR2k \
-H "Authorization: Bearer dxgl_sk_..."
# Download the video when done
curl -o chair.mp4 https://api.dx.gl/v1/renders/Xz7pQ4w8nR2k/video \
-H "Authorization: Bearer dxgl_sk_..."
# Download all assets as a zip
curl -o chair.zip https://api.dx.gl/v1/renders/Xz7pQ4w8nR2k/bundle \
-H "Authorization: Bearer dxgl_sk_..."
```