# 3D mesh decimation with measured quality loss (Blender) (`ernestx/mesh-decimate-qc`) Actor

Reduce polygon count on glTF/GLB and FBX, and see what it cost: silhouette deviation measured both ways, UV integrity (islands, overlap, texel density), skin-weight survival. Outputs GLB, FBX, USD, USDZ. For Unreal, Unity, three.js and AR pipelines needing LODs without eyeballing each asset.

- **URL**: https://apify.com/ernestx/mesh-decimate-qc.md
- **Developed by:** [Jean-Baptiste Couche](https://apify.com/ernestx) (community)
- **Categories:** AI, Automation, Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $80.00 / 1,000 decimation completeds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

In JavaScript/TypeScript projects, use official [JavaScript/TypeScript client](https://docs.apify.com/api/client/js/docs.md):

```bash
npm install apify-client
```

In Python projects, use official [Python client library](https://docs.apify.com/api/client/python/docs.md):

```bash
pip install apify-client
```

In shell scripts, use [Apify CLI](https://docs.apify.com/cli/docs.md):

````bash
# MacOS / Linux
curl -fsSL https://apify.com/install-cli.sh | bash
# Windows
irm https://apify.com/install-cli.ps1 | iex
```bash

In AI frameworks, you might use the [Apify MCP server](https://docs.apify.com/integrations/mcp.md).

If your project is in a different language, use the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).


# README

## Decimate a mesh, and find out what it cost you

Point it at a `.glb` or `.fbx`, say how many triangles you want to keep, get
the reduced asset back — plus the number that actually decides whether you
can ship it: **how far the surface moved.**

Headless Blender 4.5 LTS. Outputs GLB, FBX, USD, USDC, USDA or USDZ. The
source format is detected from the file's bytes, not its extension. No UI,
no login, callable by agents over MCP.

### Hitting a triangle target is the easy part

Every decimator does that. What none of them tell you is whether the result
still looks like the thing you started with — so you end up opening each
asset and eyeballing it, which is exactly what does not scale to a few
thousand of them.

So every run measures the distance between the original surface and the
reduced one, **in both directions**, and normalises it to the bounding-box
diagonal so the number means the same thing on a teacup and on a cathedral:

````

DamagedHelmet, 15 452 → 7 725 triangles
original → decimated   p95 0.080 %   max 0.166 %
decimated → original   p95 0.070 %   max 0.122 %

```

Half the triangles, and the surface never moved more than 0.17 % of the
model's own size. Ship it without looking.

Both directions matter. A reduced surface can sit perfectly on the original
while whole features have quietly disappeared — only measuring
original → decimated catches that.

Cross a threshold and you get told:

```

Fox, 576 → 288 triangles
warning: HIGH\_SILHOUETTE\_DEVIATION   p95 1.21 %   max 4.14 %

```

That fox was already low-poly. Halving it genuinely deforms the silhouette,
and the report says so instead of quietly handing you a broken asset.

### UVs are verified, not assumed

A reduction that hits its triangle target and quietly wrecks the texture
mapping is worse than no reduction at all, so three separate things get
measured per UV layer, by layer **name** — losing the second set is how a
lightmap or an AO bake silently disappears.

**Texel density.** UV area per unit of surface. If it drifts, the same
texture now covers a different amount of the model and reads as stretched.

**Island count.** Islands are found by union-find over shared UV edges. This
catches what an average cannot: a density figure can sit perfectly flat while
the layout underneath has been rearranged.

**Overlap.** UV triangles are rasterised into a grid and cells carrying more
than one face are counted. Only overlap the decimator *added* is flagged —
mirrored and tiled UVs overlap on purpose, and a naive detector screams at
them. On a test asset whose second UV set is 100 % overlapping by design, the
report correctly records it as pre-existing and stays quiet.

```

DamagedHelmet, half the triangles
UVMap   islands 486 → 452 (−7 %)   overlap +0 pt   density −0.4 %

MorphStressTest, half the triangles, two UV sets
UVMap       density 5.20482 → 5.21877   drift +0.268 %
UVMap.001   density 0.03631 → 0.03638   drift +0.200 %

```

#### A warning about `planar`

Planar reduction preserves the silhouette almost perfectly — 74 % of the
triangles off a chess set for a geometric deviation of 0.01 %. Measure the
UVs, though, and the picture reverses: it merged **56 % of the UV islands**
and introduced overlap.

**Use `planar` on untextured geometry, trim-sheet assets or collision
proxies. Do not use it on a uniquely-unwrapped textured asset.** The report
will tell you either way, which is the whole point — this limitation was
found by the tool measuring itself, not by reading the manual.

### What a reduction destroys, stated plainly

Vertex order does **not** survive. Decimation rebuilds topology, so anything
keyed to vertex indices breaks: morph targets, point and vertex caches,
Alembic streams, external per-vertex data, any downstream vertex ID. Skin
weights are the exception — they are interpolated onto the new vertices, and
the report verifies that rather than assuming it.

Meshes carrying **morph targets are refused by default**, because Blender
cannot apply a decimate modifier to them at all: the asset would come back
completely unreduced while the run reported success. You get
`HAS_SHAPE_KEYS`, the list of blendshape names, no output and no charge. Set
`shapeKeyPolicy` to `strip` to drop them deliberately — never silently.

### Instanced scenes work properly

A chess set, a kitbash, a forest — production scenes share mesh data between
objects, and Blender flatly refuses to apply a modifier to multi-user data.
Reduce object by object and you get *"Modifiers cannot be applied to
multi-user data"* on most of the scene.

This Actor reduces each **unique mesh** once and re-points the instances at
the result. On a 49-object chess set that is 15 reductions instead of 49
failures, instancing survives, and it finishes in half the time:

```

ABeautifulGame   49 objects / 15 unique meshes
1 497 912 → 748 944 triangles   (exactly 50 %)
43.0 MB → 24.9 MB               (−42 %)
deviation p95 0.0007 %

```

### Fewer triangles does not mean a smaller file

This one surprises people, and it is worth stating plainly: on a textured
asset the textures dominate, and Blender **re-encodes them on export**. Left
on Blender's own default, halving that helmet produced a file **118 % larger
than the original**.

So the default here inspects the materials and picks the smallest encoding
the asset can safely tolerate — JPEG when nothing is driving the shader's
alpha input, Blender's AUTO when something is. Same helmet, same geometry,
same deviation:

| Texture setting | Output | vs source |
|---|---|---|
| Blender AUTO | 8.23 MB | +118 % |
| **Smart (default)** | **2.57 MB** | **−32 %** |
| JPEG + Draco | 2.36 MB | −37 % |
| WEBP + Draco | 1.51 MB | −60 % |

And when the file still grows — small, already-optimised textures where
re-encoding cannot help — you get an `OUTPUT_LARGER_THAN_INPUT` warning
rather than a silent regression.

### Rigged assets keep their skinning

Vertex weights are interpolated through the reduction, and the report
verifies it rather than assuming it: groups before and after, any groups
lost, and how many vertices came out with **no** bone influence — those stay
pinned at the bind pose while everything around them deforms, which reads as
a torn character in motion.

```

Fox         24 groups → 24 groups, 0 lost, 0 unweighted vertices
Samba.fbx   Mixamo character, 2 skinned meshes, 97 vertex groups —
all intact through fbx→fbx, fbx→glb and glb→fbx

```

### FBX, in and out, at studio fidelity

FBX input is read without rewriting the rig: no automatic bone
reorientation, no baked-in transforms, Maya pre/post rotations preserved.
FBX output overrides every Blender default that loses data — smoothing
groups on (`FACE`), tangents exported, custom properties kept, no invented
leaf bones, animation curves unsimplified, textures embedded, and every
animation take in the one file.

One consequence stated up front: an FBX written this way can come out
*larger* than its source, because embedded textures and lossless curves are
the point. The `OUTPUT_LARGER_THAN_INPUT` warning says so explicitly instead
of letting you think the reduction failed — the triangle counts are where
the reduction shows.

### Input

| Field | Type | Default | |
|---|---|---|---|
| `glbUrl` | string | — | Public URL to a self-contained `.glb` or `.fbx` (embedded media), up to 200 MB |
| `targetRatio` | string | `0.5` | Fraction of triangles to keep, 0–1 |
| `targetTriangles` | integer | `0` | Absolute budget; overrides the ratio when > 0 |
| `method` | string | `collapse` | `collapse`, `planar` (great silhouette, damages UVs — see above), `unsubdivide` |
| `preserveUvSeams` | boolean | `true` | Stops collapses crossing UV islands |
| `preserveBoundaries` | boolean | `true` | Protects sharp edges and open borders |
| `cleanupFirst` | boolean | `true` | Weld duplicates and drop zero-area faces first |
| `textureFormat` | string | `smart` | `smart`, `AUTO`, `JPEG`, `WEBP`, `keep` |
| `imageQuality` | integer | `85` | For JPEG/WEBP |
| `dracoCompression` | boolean | `false` | Compresses geometry inside the GLB; needs a Draco-capable viewer |
| `shapeKeyPolicy` | string | `reject` | `reject` (refuse, no charge) or `strip` (delete morph targets) |
| `outputFormat` | string | `glb` | `glb`, `fbx`, `usdc`, `usda`, `usd`, `usdz` |

Set `preserveUvSeams` off only on untextured assets. A decimator that welds
across a UV seam shreds the mapping, and no triangle count is worth that.

### Output

The asset lands under `OUTPUT` in the run's key-value store — a ZIP when the
export wrote sidecar textures, the bare file otherwise, with `outputKind`
telling you which.

The dataset record carries triangle counts before and after, the achieved
ratio, the deviation block in both directions, UV integrity, skinning
survival, instancing stats, file-size change and per-phase timings. The
unabridged report is in the store under `REPORT_FULL`.

### Speed

Per-phase, measured:

```

DamagedHelmet   15k tris            1.3 s
Fox             rigged, 3 clips     0.8 s
ABeautifulGame  1.5M tris, 49 objs  13.5 s

````

Quality checking is deliberately bounded — deviation is sampled at up to
4 000 points regardless of mesh size, so a two-million-triangle asset does
not cost a hundred times more to verify than a twenty-thousand one.

### When not to use it

- **Multi-file sources.** A `.gltf` with external `.bin`, or an `.fbx`
  referencing loose texture files, will not find its siblings. Use
  self-contained files.
- **You need hand-authored LODs.** This is automatic reduction; a character
  hero mesh usually still wants an artist.
- **You want retopology.** Decimation reduces existing topology, it does not
  rebuild clean quad flow.

### Limits worth knowing

Deviation is sampled from vertices, so a reduction that moves face interiors
without moving vertices is under-measured. Texel density and overlap are
sampled over at most 6 000 faces, so a very localised distortion on one small
island can slip past. Island counting is exact but skipped above 300 000
faces per mesh, because connectivity cannot be sampled without giving a wrong
answer — the report says when it was skipped rather than guessing.

Normal maps are not re-baked: one baked against the original density stays
geometrically valid but loses fidelity at aggressive reductions. `unsubdivide`
has had less real-asset mileage than `collapse`. And no production character
— heavy rig, many materials, UDIM tiles — has been through this yet; the
verification is against Khronos reference assets.

# Actor input Schema

## `glbUrl` (type: `string`):

Publicly reachable URL to a .glb, .gltf with embedded buffers, or .fbx. The format is detected from the file header, not the extension. FBX referencing external texture files will not resolve them - use an FBX with embedded media.
## `targetRatio` (type: `string`):

Fraction of the original triangle count to keep, between 0 and 1. 0.5 halves the mesh, 0.1 keeps a tenth. Ignored when targetTriangles is set.
## `targetTriangles` (type: `integer`):

Absolute triangle budget for the whole asset. Overrides targetRatio when greater than 0. Use this for LOD tiers with a fixed budget.
## `method` (type: `string`):

collapse is the general-purpose edge-collapse decimator and the right default. planar merges coplanar faces only, which is ideal for hard-surface and architectural meshes because it removes no silhouette detail at all. unsubdivide reverses one subdivision level on cleanly subdivided quad meshes.
## `planarAngle` (type: `string`):

Only used by the planar method: faces meeting at less than this angle are merged. 1-5 degrees is conservative, 15+ starts flattening curvature.
## `preserveUvSeams` (type: `boolean`):

Stops collapses from crossing UV island borders. Leave on unless the asset is untextured: a decimator that welds across a seam shreds the texture mapping, and no triangle count is worth that.
## `preserveBoundaries` (type: `boolean`):

Keeps hard edges and open mesh borders from being collapsed. Costs some reduction on the target, which the report tells you about.
## `cleanupFirst` (type: `boolean`):

Merges coincident vertices and dissolves zero-area faces first. Recommended: decimating a mesh that still carries duplicate vertices spends the budget collapsing edges that should never have existed.
## `outputFormat` (type: `string`):

FBX is written with studio-fidelity settings rather than Blender's defaults: smoothing groups on, tangents exported, custom properties kept, no invented leaf bones, animation curves unsimplified, and every take in the one file. GLB for web and engine ingest, USD for Omniverse, USDZ for AR.
## `textureFormat` (type: `string`):

Blender re-encodes embedded textures on export, and on a textured asset that dominates the file: leaving it on AUTO can more than DOUBLE the size even after the mesh was halved. Smart inspects the materials and picks JPEG when nothing relies on transparency, which on a typical PBR asset cuts the file by ~2/3.
## `imageQuality` (type: `integer`):

1-100. Only applies when textures are re-encoded. 85 is a good default; below 70 shows artifacts on normal maps.
## `dracoCompression` (type: `boolean`):

Compresses vertex data inside the GLB, often shrinking geometry several times over on top of the polygon reduction. The viewer must support Draco - three.js and most web engines do, some DCC importers do not.
## `shapeKeyPolicy` (type: `string`):

Blender cannot apply a decimate modifier to a mesh carrying morph targets, so a facial-animation character would come back completely unreduced while the run looked successful. Reject refuses the asset and charges nothing. Strip drops the morph targets on purpose - only pick it if you know the blendshapes are expendable.

## Actor input object example

```json
{
  "glbUrl": "https://raw.githubusercontent.com/KhronosGroup/glTF-Sample-Assets/main/Models/DamagedHelmet/glTF-Binary/DamagedHelmet.glb",
  "targetRatio": "0.5",
  "targetTriangles": 0,
  "method": "collapse",
  "planarAngle": "5.0",
  "preserveUvSeams": true,
  "preserveBoundaries": true,
  "cleanupFirst": true,
  "outputFormat": "glb",
  "textureFormat": "smart",
  "imageQuality": 85,
  "dracoCompression": false,
  "shapeKeyPolicy": "reject"
}
````

# Actor output Schema

## `decimatedAsset` (type: `string`):

The reduced mesh, stored under the key OUTPUT. When the export wrote textures alongside the model this is a ZIP preserving the relative layout so material references resolve; otherwise it is the bare model file. Check outputKind in the dataset record.

## `qualityReport` (type: `string`):

Triangle counts before and after, achieved reduction, surface deviation measured in both directions and normalised to the bounding box, UV integrity, and skin-weight survival.

## `runInConsole` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "glbUrl": "https://raw.githubusercontent.com/KhronosGroup/glTF-Sample-Assets/main/Models/DamagedHelmet/glTF-Binary/DamagedHelmet.glb"
};

// Run the Actor and wait for it to finish
const run = await client.actor("ernestx/mesh-decimate-qc").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "glbUrl": "https://raw.githubusercontent.com/KhronosGroup/glTF-Sample-Assets/main/Models/DamagedHelmet/glTF-Binary/DamagedHelmet.glb" }

# Run the Actor and wait for it to finish
run = client.actor("ernestx/mesh-decimate-qc").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "glbUrl": "https://raw.githubusercontent.com/KhronosGroup/glTF-Sample-Assets/main/Models/DamagedHelmet/glTF-Binary/DamagedHelmet.glb"
}' |
apify call ernestx/mesh-decimate-qc --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=ernestx/mesh-decimate-qc",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

```json
{
    "openapi": "3.0.1",
    "info": {
        "title": "3D mesh decimation with measured quality loss (Blender)",
        "description": "Reduce polygon count on glTF/GLB and FBX, and see what it cost: silhouette deviation measured both ways, UV integrity (islands, overlap, texel density), skin-weight survival. Outputs GLB, FBX, USD, USDZ. For Unreal, Unity, three.js and AR pipelines needing LODs without eyeballing each asset.",
        "version": "0.1",
        "x-build-id": "tAUes5nMqPxNRBinT"
    },
    "servers": [
        {
            "url": "https://api.apify.com/v2"
        }
    ],
    "paths": {
        "/acts/ernestx~mesh-decimate-qc/run-sync-get-dataset-items": {
            "post": {
                "operationId": "run-sync-get-dataset-items-ernestx-mesh-decimate-qc",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor, waits for its completion, and returns Actor's dataset items in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        },
        "/acts/ernestx~mesh-decimate-qc/runs": {
            "post": {
                "operationId": "runs-sync-ernestx-mesh-decimate-qc",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor and returns information about the initiated run in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/runsResponseSchema"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/acts/ernestx~mesh-decimate-qc/run-sync": {
            "post": {
                "operationId": "run-sync-ernestx-mesh-decimate-qc",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor, waits for completion, and returns the OUTPUT from Key-value store in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        }
    },
    "components": {
        "schemas": {
            "inputSchema": {
                "type": "object",
                "required": [
                    "glbUrl"
                ],
                "properties": {
                    "glbUrl": {
                        "title": "Source model URL (glTF/GLB or FBX)",
                        "type": "string",
                        "description": "Publicly reachable URL to a .glb, .gltf with embedded buffers, or .fbx. The format is detected from the file header, not the extension. FBX referencing external texture files will not resolve them - use an FBX with embedded media."
                    },
                    "targetRatio": {
                        "title": "Target ratio of triangles to keep",
                        "type": "string",
                        "description": "Fraction of the original triangle count to keep, between 0 and 1. 0.5 halves the mesh, 0.1 keeps a tenth. Ignored when targetTriangles is set.",
                        "default": "0.5"
                    },
                    "targetTriangles": {
                        "title": "Target triangle count",
                        "minimum": 0,
                        "type": "integer",
                        "description": "Absolute triangle budget for the whole asset. Overrides targetRatio when greater than 0. Use this for LOD tiers with a fixed budget.",
                        "default": 0
                    },
                    "method": {
                        "title": "Reduction method",
                        "enum": [
                            "collapse",
                            "planar",
                            "unsubdivide"
                        ],
                        "type": "string",
                        "description": "collapse is the general-purpose edge-collapse decimator and the right default. planar merges coplanar faces only, which is ideal for hard-surface and architectural meshes because it removes no silhouette detail at all. unsubdivide reverses one subdivision level on cleanly subdivided quad meshes.",
                        "default": "collapse"
                    },
                    "planarAngle": {
                        "title": "Planar angle limit (degrees)",
                        "type": "string",
                        "description": "Only used by the planar method: faces meeting at less than this angle are merged. 1-5 degrees is conservative, 15+ starts flattening curvature.",
                        "default": "5.0"
                    },
                    "preserveUvSeams": {
                        "title": "Protect UV seams",
                        "type": "boolean",
                        "description": "Stops collapses from crossing UV island borders. Leave on unless the asset is untextured: a decimator that welds across a seam shreds the texture mapping, and no triangle count is worth that.",
                        "default": true
                    },
                    "preserveBoundaries": {
                        "title": "Protect sharp edges and open borders",
                        "type": "boolean",
                        "description": "Keeps hard edges and open mesh borders from being collapsed. Costs some reduction on the target, which the report tells you about.",
                        "default": true
                    },
                    "cleanupFirst": {
                        "title": "Clean up before reducing",
                        "type": "boolean",
                        "description": "Merges coincident vertices and dissolves zero-area faces first. Recommended: decimating a mesh that still carries duplicate vertices spends the budget collapsing edges that should never have existed.",
                        "default": true
                    },
                    "outputFormat": {
                        "title": "Output format",
                        "enum": [
                            "glb",
                            "fbx",
                            "usdc",
                            "usda",
                            "usd",
                            "usdz"
                        ],
                        "type": "string",
                        "description": "FBX is written with studio-fidelity settings rather than Blender's defaults: smoothing groups on, tangents exported, custom properties kept, no invented leaf bones, animation curves unsimplified, and every take in the one file. GLB for web and engine ingest, USD for Omniverse, USDZ for AR.",
                        "default": "glb"
                    },
                    "textureFormat": {
                        "title": "Texture handling on export",
                        "enum": [
                            "smart",
                            "AUTO",
                            "JPEG",
                            "WEBP",
                            "keep"
                        ],
                        "type": "string",
                        "description": "Blender re-encodes embedded textures on export, and on a textured asset that dominates the file: leaving it on AUTO can more than DOUBLE the size even after the mesh was halved. Smart inspects the materials and picks JPEG when nothing relies on transparency, which on a typical PBR asset cuts the file by ~2/3.",
                        "default": "smart"
                    },
                    "imageQuality": {
                        "title": "Texture quality (JPEG/WEBP)",
                        "minimum": 1,
                        "maximum": 100,
                        "type": "integer",
                        "description": "1-100. Only applies when textures are re-encoded. 85 is a good default; below 70 shows artifacts on normal maps.",
                        "default": 85
                    },
                    "dracoCompression": {
                        "title": "Draco mesh compression (GLB only)",
                        "type": "boolean",
                        "description": "Compresses vertex data inside the GLB, often shrinking geometry several times over on top of the polygon reduction. The viewer must support Draco - three.js and most web engines do, some DCC importers do not.",
                        "default": false
                    },
                    "shapeKeyPolicy": {
                        "title": "Meshes with morph targets (shape keys)",
                        "enum": [
                            "reject",
                            "strip"
                        ],
                        "type": "string",
                        "description": "Blender cannot apply a decimate modifier to a mesh carrying morph targets, so a facial-animation character would come back completely unreduced while the run looked successful. Reject refuses the asset and charges nothing. Strip drops the morph targets on purpose - only pick it if you know the blendshapes are expendable.",
                        "default": "reject"
                    }
                }
            },
            "runsResponseSchema": {
                "type": "object",
                "properties": {
                    "data": {
                        "type": "object",
                        "properties": {
                            "id": {
                                "type": "string"
                            },
                            "actId": {
                                "type": "string"
                            },
                            "userId": {
                                "type": "string"
                            },
                            "startedAt": {
                                "type": "string",
                                "format": "date-time",
                                "example": "2025-01-08T00:00:00.000Z"
                            },
                            "finishedAt": {
                                "type": "string",
                                "format": "date-time",
                                "example": "2025-01-08T00:00:00.000Z"
                            },
                            "status": {
                                "type": "string",
                                "example": "READY"
                            },
                            "meta": {
                                "type": "object",
                                "properties": {
                                    "origin": {
                                        "type": "string",
                                        "example": "API"
                                    },
                                    "userAgent": {
                                        "type": "string"
                                    }
                                }
                            },
                            "stats": {
                                "type": "object",
                                "properties": {
                                    "inputBodyLen": {
                                        "type": "integer",
                                        "example": 2000
                                    },
                                    "rebootCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "restartCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "resurrectCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "computeUnits": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            },
                            "options": {
                                "type": "object",
                                "properties": {
                                    "build": {
                                        "type": "string",
                                        "example": "latest"
                                    },
                                    "timeoutSecs": {
                                        "type": "integer",
                                        "example": 300
                                    },
                                    "memoryMbytes": {
                                        "type": "integer",
                                        "example": 1024
                                    },
                                    "diskMbytes": {
                                        "type": "integer",
                                        "example": 2048
                                    }
                                }
                            },
                            "buildId": {
                                "type": "string"
                            },
                            "defaultKeyValueStoreId": {
                                "type": "string"
                            },
                            "defaultDatasetId": {
                                "type": "string"
                            },
                            "defaultRequestQueueId": {
                                "type": "string"
                            },
                            "buildNumber": {
                                "type": "string",
                                "example": "1.0.0"
                            },
                            "containerUrl": {
                                "type": "string"
                            },
                            "usage": {
                                "type": "object",
                                "properties": {
                                    "ACTOR_COMPUTE_UNITS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_WRITES": {
                                        "type": "integer",
                                        "example": 1
                                    },
                                    "KEY_VALUE_STORE_LISTS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_INTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_EXTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_SERPS": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            },
                            "usageTotalUsd": {
                                "type": "number",
                                "example": 0.00005
                            },
                            "usageUsd": {
                                "type": "object",
                                "properties": {
                                    "ACTOR_COMPUTE_UNITS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_WRITES": {
                                        "type": "number",
                                        "example": 0.00005
                                    },
                                    "KEY_VALUE_STORE_LISTS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_INTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_EXTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_SERPS": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}
```
