Download
Unified download endpoint and async format conversion.
GLB is produced when a submission completes. Other formats are converted on-demand:
either inline via the unified /download endpoint (which waits for the result), or
asynchronously via the /convert job API.
Download a submission asset
GET /api/v1/{submission_id}/download
Public.
Unified endpoint for all downloadable artifacts. GLB is always ready; FBX/OBJ/STL/USDZ trigger an on-demand conversion if not yet cached.
Path Parameters
| Name | Type | Description |
|---|---|---|
submission_id | string (required) | UUID of the submission. |
Query Parameters
| Name | Type | Description |
|---|---|---|
format | string | Asset format. Values: glb, fbx, obj, stl, usdz, thumbnail, reference_image. Default: glb. |
textures | string | GLB only. png (default) or webp. |
draco | boolean | GLB only. Draco geometry compression. Default: false. |
quality | string | GLB only. auto (default), full or lite. See Lite and Full. |
target | string | The engine the file is for. With format=fbx: generic (default), roblox, unity or ue5, see FBX for an engine. With format=glb: roblox, see GLB for Roblox. |
texture_size | integer | 512, 1024 or 2048: cap every texture's longest side at that many pixels. Any format with textures; ignored for stl. See Texture size. |
skeleton | boolean | FBX only. false drops a rigged model's skeleton and skin weights, keeping its rest pose. Default: true. |
index | integer | Index for reference_image format (0–5). Default: 0. |
Example Request
GLB is served straight from storage:
curl -L -o model.glb https://api.thrixel.com/api/v1/a1b2c3d4-.../download?format=glbAny other format triggers a conversion, so the first request for it may take a few seconds:
curl -L -o model.fbx https://api.thrixel.com/api/v1/a1b2c3d4-.../download?format=fbxLite and Full
Some models are ready to look at before their full surface detail is: the job
completes with a lightweight build first, and the full one follows a few minutes
later. While it is on its way, GET /{submission_id}/info reports
full_build_status: "pending"; once it is ready (or same, when the two builds
are identical) the full model is what every download gets.
quality | What you get |
|---|---|
auto (default) | The full model when it is ready, the lightweight one until then. |
full | The full model. While it is still being prepared the response is 202 with a Retry-After header; retry after that many seconds. |
lite | A lightweight file with simplified surface colors, available as soon as the job completes. |
Formats other than GLB, and any texture_size, are always made from the full
model: while it is still being prepared they answer 202 with a Retry-After
header too, whatever quality says. To be sure you save the final
file, ask for quality=full and retry on 202: right after a job completes,
auto can still hand you the lightweight file.
Treat the lightweight file as a placeholder. Swap it for the full download once that is ready, and do not change its geometry on your side: the full build cannot be laid over those edits. Make shape changes through the API instead (Edit, Transform, Decimate); each result gets its own full build.
You do not have to wait before building on the model. Edits, Detail, Texture,
Decimate and Transform start straight away. Rig and Group Parts keep the
model's surface as it is, so when submitted while its full build is on the way
they are accepted as usual and start by themselves once it is ready: their
info shows waiting_for_full_build: true until then. Keep polling; do not
resubmit. To start them at once instead, send wait_for_full_build: false: they
run on the lightweight build, and their result then has a full build of its own
on the way (full_build_status: "pending"), which lands by itself shortly after
the model's.
Texture size
Pick the texture resolution when you download: texture_size=512, 1024 or
2048 caps the longest side of every texture at that many pixels, in GLB, FBX,
OBJ and USDZ. Geometry, rigs and animations are unchanged. It only ever
shrinks, so asking for a size at or above the model's own returns the model as
it is. STL carries no textures and ignores it.
curl -L -o model.fbx https://api.thrixel.com/api/v1/a1b2c3d4-.../download?format=fbx&texture_size=1024The first request for a size builds it and answers 202 with the conversion
job; retry and the file is served from then on. It is free.
This replaces Rebake (POST /api/v1/texture/rebake), which is retired.
Shipping a GLB to a browser
If the model is going into a web page or a browser game, ask for WebP textures:
curl -L -o model.glb https://api.thrixel.com/api/v1/a1b2c3d4-.../download?format=glb&textures=webpMeasured at export on real submissions:
| Model | Default (png) | textures=webp | + draco=true |
|---|---|---|---|
| Geometry-heavy (15% textures) | 7.00 MB | 6.28 MB | 1.18 MB |
| Texture-heavy (100% textures) | 18.33 MB | 0.95 MB | 0.91 MB |
Textures are where most of the weight is on most models, and WebP is a plain
image format the browser decodes natively - nothing extra to install, and a
reader that cannot handle it fails visibly. This is the setting to reach for
first. Thrixel's own web viewer serves exactly this: textures=webp, no Draco.
When to add Draco
draco=true compresses the geometry rather than the textures, so it only pays
off on models that are mostly geometry - the first row above, where it turns
6.28 MB into 1.18 MB. On a texture-heavy model it buys about 4%.
Draco needs a decoder on the other end
KHR_draco_mesh_compression is read by three.js (with DRACOLoader), Babylon,
model-viewer and Blender 4.x, but three.js fetches a ~200KB WASM decoder at
runtime and you have to wire it up. A reader without a Draco decoder
usually does not raise an error - it returns the mesh with every vertex at the
origin. Do not hand a Draco file to an unknown tool, and do not feed one back
into a pipeline of your own.
Both flags are independent, and neither changes the model: same parts, same triangle count, same bounds.
GLB for Roblox
Roblox gives a MeshPart one mesh and one texture and ignores a material's flat
colour, so a model made of many single-colour parts imports grey. Add
target=roblox to a GLB download for a file Roblox imports in colour:
- Each top-level part group (a door, a wheel, a rotor) becomes one mesh, so moving parts stay separate; loose parts merge into the body.
- Every part's colour, and any texture, is painted into one colour texture per mesh, at most 1024 px.
- See-through glass is removed: Roblox would draw it as a solid panel.
- No mesh is over 20,000 triangles; a bigger one is split into pieces. If the whole model is over your
budget, reduce its triangles first (Reduce in the app,
thrixel_reduce_trianglesover MCP).
It carries colour only, with no normal, roughness or glow maps. Import the .glb
with Studio's 3D Importer as-is. For a humanoid that needs an R15 rig, use the
FBX instead.
curl -L -o model-roblox.glb https://api.thrixel.com/api/v1/a1b2c3d4-.../download?format=glb&target=robloxFBX for an engine
Every FBX embeds its textures, carries metallic and roughness maps, and writes no
extra end bones. Any mesh over 20,000 triangles is split into pieces under that, so
a high-detail model still imports into engines with a per-mesh limit. Add target
to shape the file for one engine:
target | What changes |
|---|---|
generic | Nothing beyond the above. |
roblox | A .zip: the FBX as a Roblox R15 rig, textures at 1024 px, and a textures/ folder with each material's ColorMap, NormalMap, RoughnessMap and MetalnessMap PNGs for a SurfaceAppearance. See Roblox R15. |
unity | Bones renamed for Unity's Humanoid avatar (Hips, LeftUpperLeg...). |
ue5 | Bones renamed to the UE5 Mannequin (pelvis, thigh_l...), with a root bone above pelvis. |
Bone names only change on a humanoid Rig; any other model gets the same file
as generic, except that roblox still applies its texture size and maps.
curl -L -o model-roblox.zip https://api.thrixel.com/api/v1/a1b2c3d4-.../download?format=fbx&target=robloxRoblox R15
On a humanoid Rig, target=roblox rebuilds the skeleton to Roblox's
R15 layout:
- 15 bones (
LowerTorso,UpperTorso,Head, andUpper/Lower/HandandUpper/Lower/Footon each side) underRoot>HumanoidRootNode. - Joints R15 does not have fold into the bone they sit on, weights included: the
spine and clavicles into
UpperTorso, the neck intoHead, fingers into the hand, toes into the foot. LowerTorsoandRootat the origin, at most 4 bones per vertex, none onRoot.- One mesh per body part, named after its bone (
LeftUpperArm_Geo...). A part over 20,000 triangles is still split, so it arrives as two meshes.
The advanced R15 bones (fingers, extra spine joints) and the avatar attachments are not included.
Importing into Roblox Studio. Roblox does not build materials from an FBX, so a
model imported on its own shows grey. For a model without a rig, the
GLB for Roblox imports in colour with no setup. Import the .fbx with the 3D Importer, then
for each MeshPart add a SurfaceAppearance and set its ColorMap, NormalMap,
RoughnessMap and MetalnessMap from the matching PNGs in the zip's textures/
folder (each file is named after its material). SurfaceAppearance.txt in the zip
lists, per material, the MeshParts that use it and the files to set.
Glowing parts. A part that glows in the model (a lamp, a headlight, a neon sign)
comes with an EmissiveMask PNG, a flat ColorMap, and an EmissiveStrength and
EmissiveTint in SurfaceAppearance.txt: set EmissiveMaskContent,
EmissiveStrength and EmissiveTint on that part's SurfaceAppearance. This covers
models made with Create and Edit; Sculptor and Detailer results do not carry a glow
yet.