# Architect Source: https://docs.thrixel.com/architect import { Callout } from 'fumadocs-ui/components/callout'; **When to use Architect:** hard-surface and structured objects - props, furniture, buildings, mechanical parts - where you want an **editable, multi-part** result. The output is a part hierarchy you can later [Edit](/edit) part-by-part. For organic or photoreal forms (characters, creatures), use [Sculptor](/sculptor) instead. Start a new generation from a text prompt, a reference image, or up to six images. The call returns a `submission_id` you track via [Job Status](/progress). ## Create a model Requires API key. At least one of `task`, `image`, or `images` is required. ### Request Body | Name | Type | Description | | --- | --- | --- | | `task` | string | Natural-language description of the model to create. | | `image` | string | Single reference image: base64 data URL or HTTPS URL. | | `images` | string[] | Up to 6 reference images (base64 or HTTPS URLs). Mutually exclusive with `image`. | | `style_reference_submission_id` | string | UUID of a **completed** submission whose look (palette, materials, finish, level of wear) this one should match. Contributes appearance only - the subject still comes from `task` / `image`. Pass the same id across a set to keep it visually consistent. | | `project_id` | string | Group the submission under a project. | | `target_size_m` | number | Real-world size: the exported model's longest side, in metres (0.01-1000; glTF units are metres). Edits of this model keep the same size. Omit to normalize the longest side to 2. | | `auto_route_id` | string | Route id from [Auto](/auto) (`POST /route`), when Auto chose this engine. Single-use, expires after 30 minutes; charged 1 Cube when used. | | `model` | string | Architect tier: `architect-plus` (most capable, default) or `architect-balanced`. Omit to use the default. `architect-lite` was retired on 2026-08-04 and now returns `400`. | | `adaptive_thinking` | boolean | Enable extended thinking. Slower but higher quality. Default: `true`. | | `effort` | string | Thinking effort: `high`, `medium`, or `low`. Only applies when `adaptive_thinking` is on. Default: `high`. | `style_reference_submission_id` contributes **appearance only** - materials, palette, finish, how worn the object looks. The subject always comes from your `task` or your `image`; pointing at a wooden boat and asking for a barrel gives you a barrel, finished like that boat. It must be a **completed** submission of yours that still has its artefacts. Otherwise: `404` unknown id, `400` not completed or no artefacts, `403` it belongs to another account. For rules rather than looks - polycount budgets, naming, real-world sizes - attach a [project source](/organize) instead. Text states constraints well, a finished model shows appearance well, and they combine. ### Example Request ### Example Response ```json { "submission_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "status": "queued", "created_at": "2026-04-20T14:32:11Z" } ``` ## Next steps Once the job completes, chain refinement onto its `submission_id`: [Edit](/edit) to change specific parts, or [Detailer & Texture](/detailer) for a high-resolution textured pass. --- # Auto Source: https://docs.thrixel.com/auto import { Callout } from 'fumadocs-ui/components/callout'; **When to use Auto:** you have a prompt or an image and do not want to decide the engine yourself. Auto reads the request and suggests [Architect](/architect) (editable, multi-part) or [Sculptor](/sculptor) (one dense, organic mesh). You then submit to that engine as usual, passing the route along. Auto is two calls: **route**, then **submit**. Routing only suggests an engine. Nothing is generated until you submit, and nothing is charged until a submit uses the route. ## Choose an engine Requires API key. ### Request Body | Name | Type | Description | | --- | --- | --- | | `task` | string | The prompt you are about to submit. Up to 8,000 characters. | | `images` | string[] | Reference images as data URLs. Up to 6; the first 3 are looked at. | | `image` | string | Single-image alias for `images`. | Provide `task`, an image, or both. Send the same inputs you will submit, so the suggestion describes the request you actually make. ### Example Request ### Example Response ```json { "route_id": "0b1c2d3e-...", "engine": "architect", "confidence": 0.93, "reason_code": "moving_parts", "ask_user": false, "cubes": 1 } ``` ### Response Fields | Name | Type | Description | | --- | --- | --- | | `route_id` | string \| null | Pass it back as `auto_route_id` on the submit. Null when no suggestion could be made. | | `engine` | string \| null | `architect` or `sculptor`. Null when no suggestion could be made. | | `confidence` | number | 0 to 1. How sure the suggestion is. | | `reason_code` | string \| null | Why this engine, from the list below. | | `ask_user` | boolean | `true` when the suggestion is not confident enough to follow on its own. Show the user both engines and let them pick. | | `cubes` | integer | What the route costs when a submit uses it. 0 when there is no route. | When `engine` is null, `ask_user` is `true` and nothing is owed: let the user pick the engine and submit without `auto_route_id`. ### Reason codes | Code | Suggests | Meaning | | --- | --- | --- | | `moving_parts` | Architect | Part of it needs to move on its own (a lid, a wheel, a door). | | `multi_part` | Architect | Separate, editable parts were asked for. | | `scene` | Architect | Several objects laid out together. | | `hard_surface` | Architect | A man-made object with clean edges and exact proportions. | | `furniture_vehicle_building` | Architect | Furniture, vehicles and buildings build best from separate parts. | | `lowpoly_style` | Architect | A low-poly, voxel or blocky style. | | `product_photo` | Architect | The image shows a man-made object. | | `organic_single` | Sculptor | An organic subject in one piece: a creature, character, plant or rock. | | `organic_photo` | Sculptor | The image shows an organic subject. | | `one_piece_request` | Sculptor | A single fused piece was asked for. | New codes may be added. Treat an unknown code as "no reason given" rather than as an error. ## Submit with the route Submit to the engine you settled on, [Architect](/architect) or [Sculptor](/sculptor), exactly as you would without Auto, and add one field: | Name | Type | Description | | --- | --- | --- | | `auto_route_id` | string | The `route_id` from `/route`. Records that Auto made the choice and charges the route. | You may submit to the **other** engine than the one suggested. The route is still used and charged, and the submission records both the suggestion and what you chose: [`GET /{submission_id}/info`](/progress) returns them to you as `auto_route.engine` and `auto_route.chosen_engine`. A route id is single-use, belongs to the account that asked for it, and expires after **30 minutes**. A submit carrying one that is unknown, already used or expired is refused with `400` before anything is created or charged. Ask `/route` again with the current prompt. ## Cost A route costs **1 Cube** (the exact amount is in the `cubes` field), charged only when a submit uses it. A route you never submit costs nothing. The submission itself is priced as usual for its engine. The route's Cube shows in your billing activity under **Auto**. ## Errors | Status | When | | --- | --- | | `402` | Your Cube balance is empty. Top up before routing, since you could not submit either. | | `422` | Neither `task` nor an image was sent, or more than 6 images. | | `429` | Too many routes requested without a submit (30 unused within 30 minutes). Submit one, or wait a few minutes. | A routing outage never fails the call: it returns `engine: null` and `ask_user: true`, so you can still let the user choose. --- # Build World Source: https://docs.thrixel.com/build-world import { Tab, Tabs } from 'fumadocs-ui/components/tabs'; [Build World](https://github.com/thrixel/build-world) turns one prompt into a playable Unity or Three.js game: your coding agent writes the game, Thrixel generates the 3D assets. It ships as two pieces: - **The skill** - a workflow the agent follows (asset planning, engine setup, iteration loop). Lives in the [`build-world`](https://github.com/thrixel/build-world) repository. - **The MCP server** - [`thrixel-mcp`](/mcp), the tools the agent calls to actually generate assets. On **Claude Code** and **Codex** one plugin installs both. Pick your agent below. Using a different coding agent? The [MCP server](/mcp) works in any MCP-compatible client on its own, and the skill is a folder of markdown that most agents can read from their own skills directory. ## Quick start ### Sign in {/* Windows blocks are fenced as `bash`, not `powershell`: shiki's PowerShell grammar reads the slashes in a URL as division operators and paints them red, which makes every install line look broken. The bash grammar renders these same commands correctly. */} Same for every agent. This opens a page with a code, click Approve: ```bash uvx thrixel-mcp@latest login ``` Needs [uv](https://docs.astral.sh/uv/). Not installed yet? ```bash # macOS, Linux, WSL curl -LsSf https://astral.sh/uv/install.sh | sh ``` ```bash # Windows PowerShell irm https://astral.sh/uv/install.ps1 | iex ``` ### Install The recommended way. One Claude Code plugin carries the skill and the MCP server, machine-wide: ```bash claude plugin marketplace add thrixel/build-world claude plugin install thrixel@thrixel ``` Confirm both pieces landed: ```bash claude plugin list # thrixel@thrixel -> enabled claude mcp list # plugin:thrixel:thrixel -> Connected ``` Then start a session. Run this wherever you keep projects - Claude makes the project folder itself: ```bash claude --permission-mode auto ``` Into Claude Code, not the terminal:
```bash title="In Claude Code" # [!code word:/thrixel\:build-world] /thrixel:build-world build a submarine exploration game in three.js set in a bright, vibrant tropical sea with coral and fish ```
Updates are off by default for third-party plugins. Turn them on once: type `/plugin`, open **Marketplaces** > **thrixel**, choose **Enable auto-update**. If you skip this, the skill notices a newer version and tells you to run `/plugin update thrixel@thrixel`.
Prefer to install the two pieces yourself? Skip the plugin and register each half directly. The start command is the same either way. ```bash # macOS, Linux, WSL # 1. The connector. --scope user covers EVERY project, not just this folder. claude mcp add --scope user thrixel -- uvx thrixel-mcp@latest # 2. The skill. Clone rather than download, so it can update itself later. git clone https://github.com/thrixel/build-world ~/.claude/skills/thrixel ``` ```bash # Windows PowerShell # The path is quoted because ~ is not expanded when PowerShell hands it to git, # and a skill that lands anywhere else is invisible to Claude. claude mcp add --scope user thrixel -- uvx thrixel-mcp@latest git clone https://github.com/thrixel/build-world "$HOME\.claude\skills\thrixel" ``` Confirm: ```bash claude mcp list # thrixel -> Connected ls ~/.claude/skills/ # thrixel listed ``` Update the skill later with: ```bash git -C ~/.claude/skills/thrixel pull ```
Switching from a manual install? Remove the old copies The plugin carries both pieces, so an earlier manual install is now a duplicate and the two collide. Remove it once. ```bash # macOS, Linux, WSL claude mcp remove thrixel rm -rf ~/.claude/skills/build-world ~/.claude/skills/goal-to-game ~/.claude/skills/thrixel ``` ```bash # Windows PowerShell claude mcp remove thrixel Remove-Item -Recurse -Force "$HOME\.claude\skills\build-world","$HOME\.claude\skills\goal-to-game","$HOME\.claude\skills\thrixel" ```
Codex reads Claude Code plugins natively, so the install mirrors Claude Code: ```bash codex plugin marketplace add thrixel/build-world codex plugin add thrixel@thrixel ``` Confirm both pieces landed. `codex plugin list` also prints OpenAI's curated catalog, so read the `Marketplace thrixel` block at the top rather than the long list under it: ```bash codex plugin list # thrixel@thrixel -> installed, enabled codex mcp list # thrixel -> enabled ``` The plugin reports its version as `local`, which is expected: it is versioned by commit rather than by a version number. Start a **new** Codex session - a plugin installed mid-session is not picked up. Run this wherever you keep projects: ```bash codex --sandbox workspace-write --ask-for-approval never ``` That lets Codex work without a prompt on every step while keeping its sandbox on. Plain `codex` prompts per action, which stalls a long build. Then start the line with `$build-world` and describe the game:
```bash title="In Codex" # [!code word:$build-world] $build-world build a submarine exploration game in three.js set in a bright, vibrant tropical sea with coral and fish ```
Insert the skill, do not paste it Type `$build` and press **Enter** to pick `build-world` from the list, then write the description after it. Pasting the whole line at once does not run the skill: Codex only treats it as one when it is inserted from that picker, and the giveaway is the colour - an inserted skill turns blue, pasted text stays plain.
Codex asks before every Thrixel call? Approve the server once Codex gates MCP tools separately from the commands it runs itself, so the launch flags above do not reach them. **Always allow** approves one tool at a time, and Thrixel ships dozens. This covers the whole server in one go: ```bash grep -q 'thrixel@thrixel".mcp_servers' ~/.codex/config.toml || printf '\n[plugins."thrixel@thrixel".mcp_servers.thrixel]\ndefault_tools_approval_mode = "approve"\n' >> ~/.codex/config.toml ``` The `grep` guard matters: appending the same table twice is a TOML duplicate key, and Codex then refuses to start at all rather than ignoring the second copy. Check it landed with `codex mcp get thrixel`, and run it again after any `codex plugin remove` / `add` cycle - reinstalling the plugin rewrites that part of the config and drops the setting. Skip the setting and a headless `codex exec` run does not even prompt: the call is cancelled and the agent carries on without Thrixel.
Prefer to install the two pieces yourself? Also the path to take on a Codex build without plugin support. The command is the same either way: `$build-world`. ```bash # 1. The connector, then two settings on it: a startup timeout that survives a # uvx cold start (Codex allows 10s), and no approval prompt per tool call. codex mcp add thrixel -- uvx thrixel-mcp@latest grep -q startup_timeout_sec ~/.codex/config.toml || printf '\nstartup_timeout_sec = 60\ndefault_tools_approval_mode = "approve"\n' >> ~/.codex/config.toml # 2. The skill, linked where Codex looks for it. git clone https://github.com/thrixel/build-world ~/.thrixel/build-world mkdir -p ~/.agents/skills ln -s ~/.thrixel/build-world/skills/build-world ~/.agents/skills/build-world ``` Confirm: ```bash codex mcp list # thrixel listed ls ~/.agents/skills/ # build-world listed ``` Update the skill later with: ```bash git -C ~/.thrixel/build-world pull ```
Cursor has no CLI for either piece, so both are files you write once. It reads the same `SKILL.md` the agents above use. ```bash # 1. The connector. This writes the file only if you do not have one. If you # already have ~/.cursor/mcp.json, add the "thrixel" entry to your own # "mcpServers" instead - the guard below is silent, not an error. mkdir -p ~/.cursor [ -f ~/.cursor/mcp.json ] || cat > ~/.cursor/mcp.json <<'JSON' { "mcpServers": { "thrixel": { "type": "stdio", "command": "uvx", "args": ["thrixel-mcp@latest"] } } } JSON # 2. The skill. git clone https://github.com/thrixel/build-world ~/.thrixel/build-world mkdir -p ~/.cursor/skills ln -s ~/.thrixel/build-world/skills/build-world ~/.cursor/skills/build-world ``` Restart Cursor. Type `/` in the Agent chat and `build-world` should be in the list; the connector appears under **Settings** > **MCP** as `thrixel`. Then pick `build-world` and describe the game:
```bash title="In Cursor" # [!code word:/build-world] /build-world build a submarine exploration game in three.js set in a bright, vibrant tropical sea with coral and fish ```
On Windows, without WSL Same two steps in PowerShell. Step 2 uses a junction rather than a symlink because symlinks need Developer Mode and junctions do not. ```powershell # 1. The connector. Merge the entry by hand if you already have this file. New-Item -ItemType Directory -Force "$HOME\.cursor" | Out-Null if (-not (Test-Path "$HOME\.cursor\mcp.json")) { '{"mcpServers":{"thrixel":{"type":"stdio","command":"uvx","args":["thrixel-mcp@latest"]}}}' | Set-Content "$HOME\.cursor\mcp.json" } # 2. The skill. git clone https://github.com/thrixel/build-world "$HOME\.thrixel\build-world" New-Item -ItemType Directory -Force "$HOME\.cursor\skills" | Out-Null New-Item -ItemType Junction -Path "$HOME\.cursor\skills\build-world" -Target "$HOME\.thrixel\build-world\skills\build-world" ```
Already set up for Claude Code or Codex? Cursor also reads `~/.claude/skills/` and `~/.agents/skills/`, so a skill you installed for either agent is already visible here. Only step 1 is left to do.
Cloud Agents and remote SSH need a project install Cursor does not carry user-level skills into a Cloud Agent or a remote SSH session, so an install under your home directory is invisible there. Put the skill in the repository instead, and commit it: ```bash mkdir -p .cursor/skills git clone --depth 1 https://github.com/thrixel/build-world /tmp/build-world cp -R /tmp/build-world/skills/build-world .cursor/skills/build-world rm -rf /tmp/build-world ``` The connector goes in `.cursor/mcp.json` in that same repository, with the JSON from step 1.
Updating the skill ```bash git -C ~/.thrixel/build-world pull ```
Gemini CLI reads the same skill format and can install it straight from the repository: ```bash # 1. Register the MCP server for every project. --trust means Gemini runs the # Thrixel tools without asking for confirmation on every call. gemini mcp add -s user --trust thrixel uvx -- thrixel-mcp@latest # 2. Install the skill from the repo. gemini skills install https://github.com/thrixel/build-world --path skills/build-world ``` Start a new session wherever you keep projects: ```bash gemini --approval-mode=yolo ``` Gemini asks whether to trust the folder the first time it opens one. Say yes: in an untrusted folder it quietly downgrades `--approval-mode=yolo` back to prompting on every tool call. That auto-approves tool calls for the whole session. `--approval-mode=auto_edit` is the middle ground: file edits go through, shell commands still ask. Confirm both pieces from inside it: ```text /mcp # thrixel listed /skills list # build-world listed ``` An installed skill registers as its own slash command, so start with `/build-world` and approve it when Gemini asks:
```bash title="In Gemini CLI" # [!code word:/build-world] /build-world build a submarine exploration game in three.js set in a bright, vibrant tropical sea with coral and fish ```
Gemini also activates skills from their description, so if the command does not appear, describe the game and name `build-world` in the sentence instead.
Any agent that speaks MCP can run it. The connector is a plain stdio server and the skill is a folder with a `SKILL.md`, so both go in by hand. First the connector. Add it to your agent's MCP settings; most agents take this JSON as it is. If you already have servers, merge the `thrixel` entry into them. ```json { "mcpServers": { "thrixel": { "type": "stdio", "command": "uvx", "args": ["thrixel-mcp@latest"] } } } ``` Then the skill. `~/.agents/skills` is the shared folder many agents read. ```bash git clone https://github.com/thrixel/build-world ~/.thrixel/build-world mkdir -p ~/.agents/skills ln -s ~/.thrixel/build-world/skills/build-world ~/.agents/skills/build-world ``` Restart the agent so it loads both. If it lists skills, start with `build-world`. If it has no skill support, name the file instead; that works in any agent:
```bash title="In your agent" Read ~/.thrixel/build-world/skills/build-world/SKILL.md and follow it to build a submarine exploration game in three.js set in a bright, vibrant tropical sea with coral and fish ```
Update the skill later with: ```bash git -C ~/.thrixel/build-world pull ```
### The command | agent | command | |---|---| | Claude Code | `/thrixel:build-world` | | Codex | `$build-world` | | Cursor | `/build-world` | | Gemini CLI | `/build-world` | ## What happens next Your agent checks your Thrixel account, plans the assets the game needs, and starts building. Keep talking to it in plain English to change things. Give it the most capable model you have access to, with reasoning effort set to high or above. In Claude Code that is `/model` set to Opus 5 or better; every other agent has the same picker in its chat panel. Planning a coherent asset list and wiring a scene together is where a weaker model shows. ## Manage your assets Assets appear in your [Thrixel workspace](https://thrixel.com/create) as they generate, so you can view, edit, and reuse them across projects and engines. ## Usage and credits You can test this workflow using the free Thrixel Cubes included with your Starter account. However, building a full-scale game generally requires a wider variety of assets and rapid iteration that usually exceeds Starter limits. Upgrading to a [Paid Plan](https://thrixel.com/create/#upgrade) unlocks higher parallel job processing capacity and higher generation limits, allowing you to bring your most ambitious ideas to life. You can keep track of your remaining Cubes anytime in [Account Settings](https://thrixel.com/create/#settings/billing). --- # CLI Source: https://docs.thrixel.com/cli import { Callout } from 'fumadocs-ui/components/callout'; import { Accordion, Accordions } from 'fumadocs-ui/components/accordion'; `thrixel` makes 3D models from a terminal. One command takes a description to a finished model on disk, and every tool the [MCP server](/mcp) gives an agent is one `thrixel run` away. It ships in the same package as the MCP server and shares its sign-in. ```bash thrixel make "a mossy stone frog statue, squat, cracked grey stone" -o frog.glb ``` ## Install Requires [uv](https://docs.astral.sh/uv/getting-started/installation/). ```bash uv tool install thrixel-mcp ``` | Task | Command | |---|---| | Update | `uv tool upgrade thrixel-mcp` | | Uninstall | `uv tool uninstall thrixel-mcp` | | Check the version | `thrixel --version` | | Run once without installing | `uvx --from thrixel-mcp@latest thrixel ` | ## Sign in ```bash thrixel login ``` This prints a link and a short code. Open the link, check that the code matches, and approve. It works on a machine with no browser: open the link on your phone. You can create an account on the same page. The key is saved in `~/.thrixel/credentials.json` and does not expire. `thrixel status` shows which key is in use and `thrixel logout` removes it. In CI, set `THRIXEL_API_KEY` instead; it takes precedence over the saved key. Create one at [thrixel.com/create](https://thrixel.com/create/#settings/api-keys). ## Quick start ```bash thrixel balance # Cubes, plan, running jobs thrixel make "a red oil barrel" --dry-run # steps and cost, nothing submitted thrixel make "a red oil barrel" --tris 2000 -o barrel.glb thrixel tools # everything else you can run ``` ## make Make a 3D model from a description or an image, and save it. ``` thrixel make [PROMPT] [options] ``` Waits for every step, shows progress, and saves only the final file. The default engine, **sculpt**, returns one textured, high-detail mesh: the right choice for creatures, characters, plants, rocks and props that do not move. When parts must move on their own (wheels, doors, turrets, limbs), use `--engine create`, which keeps each part as a separate named node. | Option | Description | |---|---| | `PROMPT` | What the object is, in a sentence or two. Optional when `--image` is given. | | `-i, --image PATH` | A reference picture, as a path or URL. `create` accepts it more than once. | | `-o, --output PATH` | Where to save. Default: named after the prompt, in the current folder. | | `--engine sculpt\|create` | `sculpt` (default): textured, one piece. `create`: editable, separate parts. | | `--quality plus\|balanced` | `create` only. `plus` is the default; `balanced` costs half and suits simple props. | | `--detail` | `create` only. Add high-resolution detail and a PBR texture. | | `--tris N` | Reduce to at most N triangles. Free. | | `--size M` | Real-world size: the longest side in metres. | | `--format FORMAT` | `glb` (default), `fbx`, `obj`, `stl` or `usdz`. | | `--target ENGINE` | With `--format fbx`: `unity` or `ue5` name a rigged humanoid's bones for that engine; `roblox` writes an R15 `.zip`. | | `--webp` | GLB with WebP textures, up to 20x smaller on texture-heavy models. Use it for the web. | | `--project ID` | File the model under a project. `thrixel run list_projects` lists them. | | `--dry-run` | Print the steps, the estimated Cubes and your balance. Submits nothing. | | `--json` | Print one JSON object instead of text. See [Scripting](#scripting). | Examples: ```bash # A textured prop from a photo thrixel make -i photo.jpg -o lamp.glb # A cart with wheels that can turn, detailed, for Unity thrixel make "a wooden farm cart with two spoked wheels" \ --engine create --detail --format fbx --target unity -o cart.fbx # A light, small model for a browser game thrixel make "a treasure chest" --tris 3000 --webp -o public/models/chest.glb ``` What a run costs: ```bash $ thrixel make "a mossy stone frog statue" --tris 5000 --format fbx --target unity --dry-run Plan (nothing submitted): sculpt Sculpt a textured single-piece model - 11 cubes reduce Reduce to at most 5,000 triangles - free convert Convert to FBX for unity - free Estimate: 11 cubes Balance: 518 cubes ``` On the Free plan the dry run also says how many runs of each step are left this month, and warns when one is used up. `create` is metered on actual usage, so its estimate is a range. On the Free plan, sculpt, detail, retexture and rig each run up to 4 times per calendar month (UTC), per account. The CLI, your agents and the web app share the same count, and a failed run does not use one. `--dry-run` and `thrixel balance` show what is left. Past the limit `make` stops with exit code 4 until the 1st, even with Cubes left; `--engine create`, `--tris` and the format conversions are not limited. Paid plans have no monthly limit. ## Other commands Click a command for its options and examples. ``` thrixel balance [--json] ``` ```bash $ thrixel balance Plan: pro Cubes: 518 Jobs: 1 running of 5 allowed ``` On the Free plan it also lists each GPU operation's runs this month, for example `sculptor: 3 of 4 this month`. ``` thrixel tools [NAME] [--json] ``` With a name, prints what the tool does, what it costs, and every parameter with its type and default. It reads the installed version, so it is always current. `--json` includes the parameter schema. The tools are listed by group on the [MCP Server](/mcp#tools) page. ```bash thrixel tools reduce_triangles ``` ``` thrixel run NAME [--arg KEY=VALUE ...] [--args JSON] [--json] ``` `NAME` works with or without the `thrixel_` prefix. Each `--arg` is read as JSON when it parses (`5`, `true`, `["a","b"]`) and as text otherwise; `--args` takes every parameter as one JSON object. Unknown parameters are refused before anything is submitted. Files that `run` downloads go to `./thrixel_assets`, or to `THRIXEL_OUTPUT_DIR`. ```bash thrixel run inspect_model --arg submission_id=50d876b9-... thrixel run retexture_model --arg submission_id=50d876b9-... --arg prompt="weathered bronze" thrixel run group_parts --args '{"submission_id": "...", "keep_groups": ["wheels"]}' thrixel run download --arg submission_id=... --arg format=fbx --arg target=unity ``` See [Sign in](#sign-in). ``` thrixel login [--force] [--api-base URL] ``` | Option | Description | |---|---| | `--force` | Sign in again even if a key is already saved. | | `--api-base URL` | Sign in against another Thrixel deployment. | ``` thrixel status ``` A key set in `THRIXEL_API_KEY` is not affected. ``` thrixel logout ``` Run the MCP server over streamable HTTP, for an MCP client that cannot start a local process. Most clients do not need this; see [MCP Server](/mcp). ``` thrixel serve [--host 127.0.0.1] [--port 8765] ``` ## Scripting Progress goes to stderr and the result to stdout, so `path=$(thrixel make ...)` captures the saved file. With `--json`, stdout is a single object: ```json {"ok": true, "file": "/work/frog.glb", "bytes": 880572, "submission_id": "50d876b9-...", "steps": [{"op": "sculpt", "submission_id": "...", "seconds": 64}, {"op": "reduce", "submission_id": "50d876b9-...", "seconds": 17}]} ``` A failure is `{"ok": false, "error": "...", "exit_code": 4}`. **Exit codes** | Code | Meaning | What to do | |---|---|---| | 0 | Done | | | 1 | Other error | Read the message | | 2 | Bad arguments | Fix the command. Nothing was submitted | | 3 | Not signed in, or the key was refused | `thrixel login`, or set `THRIXEL_API_KEY` | | 4 | Out of Cubes, or this month's runs of that operation are used up (Free plan) | `thrixel balance` shows which | | 5 | Too many jobs running at once | Wait for one to finish, then retry | | 6 | The job failed | Read the message; some prompts are refused | | 7 | Still running when the wait ended | It keeps running and is charged when it finishes. `thrixel run job_status --arg submission_id=...`. Do not resubmit | ## Configuration **Environment variables** | Variable | Default | Description | |---|---|---| | `THRIXEL_API_KEY` | | API key. Takes precedence over the saved key. | | `THRIXEL_API_BASE` | `https://api.thrixel.com` | The API to call. | | `THRIXEL_OUTPUT_DIR` | `./thrixel_assets` | Where `run` saves downloaded files. | | `THRIXEL_TIMEOUT_S` | `3600` | How long a command waits for a job, in seconds. | | `THRIXEL_CONFIG_DIR` | `~/.thrixel` | Where the saved key lives. | | `THRIXEL_LOG_LEVEL` | `WARNING` | Set `INFO` or `DEBUG` to see requests. | **Files** | Path | Contents | |---|---| | `~/.thrixel/credentials.json` | The saved API key and the API it belongs to, readable only by you. | | `./thrixel_assets/` | Files that `thrixel run` downloads. `make` writes only to `-o`. | The CLI and the MCP server are the same package with the same sign-in. An agent that has the MCP server should use its tools; the CLI is for shells, scripts and CI, and for trying a tool by hand. --- # Sign in with Thrixel Source: https://docs.thrixel.com/connect import { Callout } from 'fumadocs-ui/components/callout'; Add a "Sign in with Thrixel" button to your app. Your user approves once in a browser, you get their API key, and you call the API as them. They never see or paste a key. Three requests. No app registration, no client secret, nothing to wait for. ## 1. Ask for a code Call this from your server when the user clicks your button. ```json { "device_code": "kL9x...", "user_code": "WXYZ-4821", "verification_uri_complete": "https://thrixel.com/create/cli-auth?code=WXYZ-4821", "expires_in": 600, "interval": 5 } ``` ## 2. Send the user to approve Open `verification_uri_complete` in a popup or new tab, and show them `user_code` so they can check it matches. They sign in, confirm, approve. If they have no Thrixel account yet, they can sign up on that same page. Keep `device_code` on your server. It is the secret that collects the key. ## 3. Poll for the key Every `interval` seconds, until it returns the key. ```json { "api_key": "sk-thrixel-...", "key_id": "8f3c1a2b-...", "email": "user@example.com" } ``` Store `api_key` against that user. Show them `email` so they know which account they connected. Done. While you wait, poll returns **400** with one of these in `detail.error`: | Error | What to do | | --- | --- | | `authorization_pending` | Not approved yet. Keep polling. | | `slow_down` | You polled too fast. Wait longer, then continue. | | `access_denied` | They declined. Stop. | | `expired_token` | Expired or already collected. Start again at step 1. | ## Complete example ```js // Server side. Returns the user's API key, or throws. async function connectThrixel() { const API = "https://api.thrixel.com/api/v1/cli/device"; const auth = await ( await fetch(`${API}/start`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ client_name: "Your App Name" }), }) ).json(); // Show auth.user_code, and open auth.verification_uri_complete for the user. const deadline = Date.now() + auth.expires_in * 1000; let wait = auth.interval * 1000; while (Date.now() < deadline) { await new Promise((r) => setTimeout(r, wait)); const res = await fetch(`${API}/poll`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ device_code: auth.device_code }), }); if (res.ok) return await res.json(); // { api_key, key_id, email } const { error } = (await res.json()).detail; if (error === "authorization_pending") continue; if (error === "slow_down") { wait += 5000; continue; } throw new Error(error); } throw new Error("expired_token"); } ``` It has full access to that user's account, including their cubes. Never put it in the browser or in a URL. ## Before you ship - **Codes expire in 10 minutes.** If nobody approves in time, start over. - **The key is delivered once.** Store it on the first successful poll. - **Jobs bill to the user**, on their plan and their limits. Not yours. - **On a `401`, run the flow again.** The user can revoke your key from their account, and reconnecting your app replaces the key you were holding. - **Set `client_name` to your app's name.** Your user sees it on the approval screen and in their account afterwards. --- # Detailer & Texture Source: https://docs.thrixel.com/detailer import { Callout } from 'fumadocs-ui/components/callout'; The Detailer takes a completed submission as the source mesh and produces a high-resolution textured model. You can also retexture an existing result without regenerating from scratch. [Remesh](/remesh) the result you already have - it re-decimates the existing mesh and keeps its texture, so it's quicker and cheaper than a second Detailer pass, and you get the same model rather than a new one. This page covers the detail + texture endpoints: - `/api/v1/detailer/submit`: full detail + texture pass. - `/api/v1/texture/*`: texture-only operations on an existing mesh (generate, rebake). --- ## Which one should I use? The Detailer rebuilds the mesh, so it can change the shape you gave it. The texture endpoints never touch geometry. Pick by what you need to keep: | What you need | Use | What you get | |---|---|---| | The geometry exactly as it is, every part and name intact | `POST /api/v1/texture/generate` | **Guaranteed.** Geometry is untouched; only materials are regenerated | | Added geometric detail on a precision or hard-surface model | `POST /api/v1/detailer/submit` with `adherence_level: 12` | **Best effort.** Far more parts survive than at the default, but very small ones can still be lost | | A genuine re-sculpt of an organic or sculptural subject | `POST /api/v1/detailer/submit` (default `adherence_level: 9`) | The silhouette is expected to be reinterpreted | Retexturing costs the same as a detail pass and cannot damage what you have. The Detailer is for when you want the *shape itself* to gain detail. Measured on one 78-part quadcopter blockout with `preserve_parts: true`, same seed and same reference image: at the default `adherence_level: 9`, 28 parts came back and one of the four propellers was missing entirely; at `adherence_level: 12`, 35 parts came back and all four propellers survived at full size. The Detailer is strongest on organic and sculptural subjects (creatures, characters, cloth, plants, rock). It is weaker on precise manufactured geometry: an exactly round cylinder, torus or wheel is re-approximated rather than refined, so it returns measurably less round while carrying many times the triangles. Proportions matter too. A model whose bounding box is far from a cube (a building, a roof, a floor plane, anything long and thin) comes back noticeably worse from both the Detailer and the Sculptor, because the object occupies only a small part of the working volume. For buildings, prefer the texture endpoints. --- ## Detail and texture a model Requires API key. Generate a high-resolution textured mesh from a parent submission. A reference image and prompt guide the texture style; if omitted, they are auto-generated from the parent. ### Request Body | Name | Type | Description | | --- | --- | --- | | `parent_submission_id` | string (required) | ID of the submission to detail. | | `image` | string | Reference image (base64 or HTTPS URL). Auto-generated if omitted. | | `mesh` | string | Override source mesh (base64 or URL). Defaults to the parent's GLB. | | `prompt` | string | Text description of the desired appearance. | | `seed` | integer | RNG seed. 0-999,999. **Omit for a random seed each run** (the default). Pin it only to reproduce a previous result: the same seed with the same inputs reproduces it. | | `texture_size` | integer | Output texture resolution. Values: `2048`, `4096`. Default: `2048`. | | `decimation_target` | integer | Target triangle count after decimation. 10,000–250,000. Default: `80000`. To lighten a model you already have, [Remesh](/remesh) is quicker and cheaper. | | `adherence_level` | integer | How closely the output adheres to the source mesh geometry. `0`–`12`. `0` follows the reference image most freely (most creative); `12` returns the source mesh geometry itself, so the pass only adds texture. Default: `9`, which stays close to the mesh while still letting the reference image inform detail. Previously named `coherence_level`, which is still accepted. | | `preserve_parts` | boolean | Experimental. Keep the input mesh's separate parts in the output. When `false`, returns a single merged mesh. **Omit to let the server choose:** on at `adherence_level` `9` or above, off below it, where keeping the parts split through a reshaped silhouette produces artifacts. | | `project_id` | string | Assign to a project. | ### Example Request ### Example Response ```json { "submission_id": "e5f6a7b8-...", "status": "queued", "created_at": "2026-04-20T14:45:30Z", "reference_image_url": "https://api.thrixel.com/api/v1/detailer/reference/e5f6a7b8-..." } ``` --- ## Generate textures for an existing mesh Requires API key. Produce fresh PBR textures from a reference image. Geometry is preserved; only materials change. Works on any completed submission, not just Detailer output. Image priority: user-supplied → cached parent input → auto-generated from the GLB. ### Request Body | Name | Type | Description | | --- | --- | --- | | `parent_submission_id` | string (required) | ID of a completed submission to retexture. | | `texture_size` | integer | Output texture resolution. Values: `2048`, `4096`. Default: `2048`. | | `image` | string | Reference image (base64 data URI). Auto-generated from the GLB if omitted. | | `seed` | integer | RNG seed. 0-999,999. **Omit for a random seed each run** (the default). Pin it only to reproduce a previous result: the same seed with the same inputs reproduces it. | | `apply_to_node_names` | string[] | Restrict texturing to specific mesh nodes. Empty = whole mesh. | | `project_id` | string | Assign to a project. | ### Example Request --- ## Rebake textures at a new resolution Requires API key. Re-bake the existing texture at a different resolution. Picks the fastest available method automatically. ### Request Body | Name | Type | Description | | --- | --- | --- | | `parent_submission_id` | string (required) | ID of a completed submission. | | `texture_size` | integer | Output texture resolution. Values: `1024`, `2048`, `4096`. Default: `2048`. | | `project_id` | string | Assign to a project. | ### Example Request --- ## Fetch reference image Public. Returns the user-provided or auto-generated reference image used to guide the detailing. PNG. ### Path Parameters | Name | Type | Description | | --- | --- | --- | | `submission_id` | string (required) | UUID of a Detailer submission. | ### Example Request --- # Download Source: https://docs.thrixel.com/download import { Callout } from 'fumadocs-ui/components/callout'; 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 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`. | | `target` | string | FBX only. The engine the file is for: `generic` (default), `roblox`, `unity` or `ue5`. See [FBX for an engine](#fbx-for-an-engine). | | `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: Any other format triggers a conversion, so the first request for it may take a few seconds: ## Shipping a GLB to a browser If the model is going into a web page or a browser game, ask for WebP textures: Measured 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%. `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. ## FBX 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](#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](/rig); any other model gets the same file as `generic`, except that `roblox` still applies its texture size and maps. ### Roblox R15 On a humanoid [Rig](/rig), `target=roblox` rebuilds the skeleton to Roblox's [R15 layout](https://create.roblox.com/docs/en-us/avatar/character-bodies/specifications#rigging): - 15 bones (`LowerTorso`, `UpperTorso`, `Head`, and `Upper`/`Lower`/`Hand` and `Upper`/`Lower`/`Foot` on each side) under `Root` > `HumanoidRootNode`. - Joints R15 does not have fold into the bone they sit on, weights included: the spine and clavicles into `UpperTorso`, the neck into `Head`, fingers into the hand, toes into the foot. - `LowerTorso` and `Root` at the origin, at most 4 bones per vertex, none on `Root`. - 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. 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. --- # Edit Source: https://docs.thrixel.com/edit import { Tab, Tabs } from 'fumadocs-ui/components/tabs'; import { Callout } from 'fumadocs-ui/components/callout'; Apply a natural-language edit to an existing model. Example: "make the lantern glow blue". Part-scoped edits work best on [Architect](/architect) results, which carry an editable part hierarchy. ## Modify a model Requires API key. ### Request Body | Name | Type | Description | | --- | --- | --- | | `parent_submission_id` | string (required) | ID of the submission to modify. | | `modification_request` | string (required) | Description of the change to apply. | | `project_id` | string | Inherit or override the parent's project. | | `adaptive_thinking` | boolean | Enable extended thinking for higher quality. Default: `false`. | | `focus_on_node_names` | string[] | Part names from the parent mesh to scope the edit to. Every part outside the list is held bit-identical. Empty/omitted = whole-mesh edit. Get part names from `/api/v1/{submission_id}/hierarchy`. | ### Example Request Every part not named in `focus_on_node_names` is left bit-identical to the input. ### Example Response ```json { "submission_id": "c3d4e5f6-...", "status": "queued", "created_at": "2026-04-20T14:38:01Z" } ``` --- # Getting Started Source: https://docs.thrixel.com/getting-started import { Callout } from 'fumadocs-ui/components/callout'; If Claude Code, Codex, or Gemini is the one making the calls, install the [MCP server](/mcp) instead of writing a poll loop by hand. It wraps these same endpoints and handles the waiting. ## What is Thrixel? Thrixel turns text prompts and reference images into production-ready 3D models through a simple REST API. You submit a request, poll (or stream) until it's ready, then download the result in GLB, FBX, OBJ, STL, or USDZ. ## Base URL ```bash https://api.thrixel.com/api/v1 ``` ## Authentication Authenticated endpoints require an API key passed as a Bearer token, in the format `sk-thrixel-...`. To create one, sign in at [thrixel.com/create](https://thrixel.com/create), then open **Profile > API Keys > Create new Key**. Copy the key right away, it is only shown once. ```bash Authorization: Bearer sk-thrixel- ``` Public endpoints (info, stream, downloads, convert, gallery) do not require authentication. Do not ask your users to paste a key. [Sign in with Thrixel](/connect) lets them approve your app in a browser and hands you their key directly. ## Rate Limits The main limit is per account: how many jobs may be active (queued or processing) at the same time. It depends on your plan. A submit past it returns `429` with a message naming the limit; wait for a job to finish and submit again. On the Free plan, Detailer, Sculptor, Texture and Rig also have a monthly allowance: 4 runs of each per calendar month (UTC). Queued, running and completed runs count; a failed or cancelled run does not. A run past it returns `402`. Paid plans have no allowance. `GET /api/v1/usage` returns your current cap (`concurrent_job_cap`), how many jobs are in flight (`concurrent_in_flight`), your Cubes balance and, on the Free plan, this month's runs (`monthly_runs`: `limit`, `used` per operation, `resets_at`). The live allowance per plan is `monthly_gpu_runs` on `GET /api/v1/tiers`. ## Quickstart Submit a text prompt, wait for the job to complete, and download the GLB: **1. Submit a prompt.** The response carries a `submission_id`. **2. Poll until it is done.** Repeat every few seconds until `status` is `completed` or `failed`. No key needed. For a push feed instead of a loop, see [streaming progress](/progress). **3. Download the GLB.** --- # Image Hub Source: https://docs.thrixel.com/imagehub import { Callout } from 'fumadocs-ui/components/callout'; Generate images from a text prompt and reuse them as the reference for a 3D model. Generate once, then feed the same image into as many [Sculptor](/sculptor) or [Detailer](/detailer) jobs as you like. --- ## Generate images Requires API key. Queue one or more text-to-image generations. Each returns an `id` you can poll for completion, then download or reuse. ### Request Body | Name | Type | Description | | --- | --- | --- | | `prompt` | string | Natural-language description of the image. Required. | | `n` | integer | Number of images to generate, 1–4. Default: `1`. | | `size` | string | `1024x1024` (default), `1536x1024`, or `1024x1536`. | | `reference_image` | string | Optional image to guide generation: base64 data URL or HTTPS URL. | ### Example Request ### Example Response ```json [ { "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "status": "queued", "size": "1024x1024", "url": null } ] ``` --- ## Retrieve your images Requires API key. Lists your images, newest first. Poll until `status` is `completed`; the `url` is then the image file (also served directly at `GET /api/v1/imagehub/{image_id}/file`). ```json [ { "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "status": "completed", "size": "1024x1024", "url": "/api/v1/imagehub/a1b2c3d4-e5f6-7890-abcd-ef1234567890/file" } ] ``` --- ## Use an image to create 3D Pass an image's `id` as `reference_image_id` to [`/sculptor/submit`](/sculptor) or [`/detailer/submit`](/detailer) to turn it into a 3D model. The stored image is reused as the reference; no new image is generated. --- # Thrixel API Source: https://docs.thrixel.com/ The Thrixel API turns text prompts and reference images into production-ready 3D models through a simple REST API. You submit a request, poll (or stream) until it's ready, then download the result in GLB, FBX, OBJ, STL, or USDZ. ## Where to start - [Getting Started](/getting-started): base URL, authentication, rate limits, and a quickstart. - [MCP Server](/mcp): drive all of this from Claude Code, Codex, or Gemini. **Generate 3D**: pick the engine that fits your object: - [Auto](/auto) - let Thrixel pick Architect or Sculptor for your prompt. - [Architect](/architect): editable, multi-part models (hard-surface, props, structured objects). - [Sculptor](/sculptor): dense, organic meshes (characters, creatures, photoreal forms). - [Upload a mesh](/upload): bring your own GLB into the pipeline. - [Image Hub](/imagehub): generate 2D reference images, then feed them to either engine. **Refine**: improve any result: - [Edit](/edit): natural-language edits, optionally scoped to specific parts. - [Detailer & Texture](/detailer): high-resolution detailing and texture generate/rebake. - [Remesh](/remesh): re-decimate to a new triangle count. - [Rig](/rig): add a skeleton to a character, creature or robot, with Mixamo, Unity or UE5 bone names. **Operate & guides:** - [Writing Prompts](/prompts): what works and what doesn't, for users with no 3D background. - [Job Status](/progress): polling, streaming (SSE), and submission listing. - [Download](/download): get the model out in GLB, FBX, OBJ, STL, or USDZ. - [Projects & Sources](/organize): group submissions, attach style references, rename/archive/pin. ## Base URL ```bash https://api.thrixel.com/api/v1 ``` --- # MCP Server Source: https://docs.thrixel.com/mcp import { Callout } from 'fumadocs-ui/components/callout'; import { Tab, Tabs } from 'fumadocs-ui/components/tabs'; import { Accordion, Accordions } from 'fumadocs-ui/components/accordion'; Thrixel is built agent-first. `thrixel-mcp` gives coding agents a native way to create, edit, and work with 3D assets, in Claude Code, Codex, Gemini, and any other [MCP](https://modelcontextprotocol.io)-compatible client. It exposes Thrixel as tools an agent can discover and use as part of a larger build, and it runs on the same REST API documented here. What the server adds is the lifecycle. It holds the operation open while the work runs, turns HTTP errors into a next action, writes the GLB to disk, and hands back a thumbnail. An agent can generate an asset, see what it made, modify it, bring it into a scene, and keep building, all in one pass. - [**PyPI**](https://pypi.org/project/thrixel-mcp/) - `thrixel-mcp` - [**Example**](https://github.com/thrixel/build-world) - Build World, one prompt to a playable Unity or Three.js game ## Install Nothing to clone or build. [`uvx`](https://docs.astral.sh/uv/) fetches the published package on each launch, so `@latest` keeps itself current. ```bash uvx thrixel-mcp@latest login ``` That prints a link and a short code. Open it, confirm the code matches, approve. The key lands in `~/.thrixel/credentials.json`. Sign-up happens on that page too if you do not have an account yet, so this works from a cold start. Then register the server with your agent: ```bash claude mcp add --scope user thrixel -- uvx thrixel-mcp@latest ``` `claude mcp add` defaults to `--scope local`, which registers the server for the current directory only. Every new project then starts with no Thrixel tools. User scope registers it once for the whole machine. An MCP client loads its servers once, at session start. Register before you open the agent, or restart the session afterwards. ## Hosted connector Claude (web, desktop and mobile), ChatGPT and any client that accepts a remote MCP server can connect without installing anything. Add a custom connector with this URL and sign in when asked: ``` https://api.thrixel.com/mcp ``` The hosted connector has the same tools as the local server, with three differences: it returns download links instead of writing files, it accepts images as URLs or data URIs rather than local paths, and it leaves out the image-generation and billing tools. ## Tools The agent decides which tool to call, so you normally just describe what you want. Each tool is intent-level rather than endpoint-level: asked to cut a triangle count, `thrixel_reduce_triangles` reads the model and routes it to remesh or decimate itself. Costs are in Cubes. **Metered** means charged after the run from actual usage, typically tens of Cubes. Given no image, a job first generates its reference image: about 3 Cubes more for a sculpt, and about 7 for a detail or retexture, which composes it from the existing model on a higher-quality image model. On the Free plan, **Sculpt**, **Detail**, **Retexture** and **Rig** each run up to 4 times per calendar month (UTC), counted per account across all its keys and apps. A failed or cancelled run does not count. Past the limit that operation is refused until the 1st, even with Cubes left; Create, Edit and every free operation keep working. Paid plans have no monthly limit. `thrixel_account_status` shows what is left. | Tool | What it does | Cost | |---|---|---| | `thrixel_create_model` | An editable model with separate, named parts, from text and optional reference images | Metered | | `thrixel_sculpt_model` | A textured, single-piece organic model from text or one image | 8; Free plan: 4 a month | | `thrixel_generate_image` | 2D concept art, reusable as the reference for later 3D jobs (local server only) | About 3 per image | | Tool | What it does | Cost | |---|---|---| | `thrixel_edit_model` | Change a model with a plain-language instruction, keeping the rest | Metered | | `thrixel_detail_model` | High-resolution geometry and a PBR texture | 14; Free plan: 4 a month | | `thrixel_retexture_model` | New textures, geometry untouched | 5; Free plan: 4 a month | | `thrixel_rig_model` | Skeleton and skin weights for a character | 5; Free plan: 4 a month | | `thrixel_reduce_triangles` | Cut to a triangle budget | Free | | `thrixel_group_parts` | Weld parts that do not move, for fewer draw calls | Free | | `thrixel_transform_model` | Resize, turn or move a whole model | Free | | Tool | What it does | Cost | |---|---|---| | `thrixel_inspect_model` | Part names, triangle counts, bounds and a thumbnail | Free | | `thrixel_job_status` | Whether a job has finished | Free | | `thrixel_list_assets` | Models already in the account | Free | | `thrixel_download` | GLB, FBX (Unity, UE5, Roblox R15), OBJ, STL or USDZ | Free | | `thrixel_list_images`, `thrixel_download_image` | Images in the account (local server only) | Free | | `thrixel_cancel_job` | Cancel a job that has not started | Free | | Tool | What it does | Cost | |---|---|---| | `thrixel_start_project`, `thrixel_list_projects` | Group a game's assets; resume one later | Free | | `thrixel_add_project_source`, `thrixel_list_project_sources` | Rules every generation in the project follows | Free | | `thrixel_rename_asset`, `thrixel_move_asset` | Name or refile a model | Free | | Tool | What it does | Cost | |---|---|---| | `thrixel_publish_game` | Put a game online at `https://.thrixel.world` | Free | | `thrixel_list_games`, `thrixel_update_game`, `thrixel_unpublish_game` | Manage published games | Free | | Tool | What it does | Cost | |---|---|---| | `thrixel_account_status` | Cubes, plan, concurrency cap and running jobs | Free | | `thrixel_pricing` | Plans, caps and prices (local server only) | Free | | `thrixel_upgrade_plan`, `thrixel_buy_cubes`, `thrixel_billing_portal` | Checkout and billing links; nothing is charged until you pay (local server only) | Free | Waiting uses [SSE](/progress) with polling as a fallback, so a long detail pass reports progress instead of going silent. ## Calling a tool yourself Tools are written for agents, but nothing stops you calling one directly. The same package installs a [`thrixel` command](/cli): ```bash thrixel tools # every tool thrixel tools reduce_triangles # what it does, what it costs, its parameters thrixel run reduce_triangles --arg submission_id=... --arg target_triangles=5000 ``` `thrixel tools ` prints the full description and every parameter, read from the installed server, so it is always the current version. ## Example prompts - "Start a project called Harbor Defense, then make a wooden watchtower and a rowing boat with oars that can move." - "Sculpt a mossy stone frog statue and reduce it to 5,000 triangles." - "Detail the cart, then give me an FBX for Unity." - "Inspect the windmill and group everything except the sails." - "Build a small three.js game where the boat collects crates, and publish it." - "How many Cubes do I have left, and what is running?" ## FAQ Yes. Every tool calls the same endpoints at the same prices. Free operations stay free. Only the Thrixel account it is signed in as: its models, projects, images and published games. The local server also writes the files it downloads into `./thrixel_assets`, or `THRIXEL_OUTPUT_DIR`, and refuses paths outside it. Each plan caps how many jobs can run at once, and every running job counts, free ones included. Past the cap a call fails with a message naming the limit. `thrixel_account_status` shows the cap and what is running. On the Free plan, Sculpt, Detail, Retexture and Rig also run up to 4 times a month each. See [Tools](#tools). support@thrixel.com, with the `submission_id` the tool returned. ## Using the API directly The MCP server is the agent-native path, not a gate. Every capability is a plain HTTP call: see [Getting Started](/getting-started) for authentication and a quickstart, and [Job Status](/progress) for the polling and streaming contracts the server implements internally. --- # Projects & Sources Source: https://docs.thrixel.com/organize import { Callout } from 'fumadocs-ui/components/callout'; Group related submissions into projects, give them nicknames, pin favorites, and archive older work. All endpoints below require authentication and act on resources owned by your API key. ## Projects --- ### List projects Requires API key. Returns all projects owned by the authenticated API key. #### Query Parameters | Name | Type | Description | | --- | --- | --- | | `include_archived` | boolean | Include archived projects. Default: `false`. | #### Example Request #### Example Response ```json { "projects": [ { "id": "p1q2r3s4-...", "name": "Gargoyle variations", "description": "Stylistic experiments", "is_archived": false, "created_at": "2026-04-18T09:00:00Z", "updated_at": "2026-04-20T14:00:00Z", "submission_count": 7 } ], "count": 1 } ``` --- ### Create a project Requires API key. #### Request Body | Name | Type | Description | | --- | --- | --- | | `name` | string (required) | Project name. 1–255 characters. | | `description` | string | Optional description. Max 2000 characters. | #### Example Request --- ### Get a project Requires API key. Returns project metadata plus an embedded list of its submissions (soft-deleted excluded). #### Path Parameters | Name | Type | Description | | --- | --- | --- | | `project_id` | string (required) | UUID of the project. | --- ### Update a project Requires API key. Rename or re-describe. At least one field required. #### Path Parameters | Name | Type | Description | | --- | --- | --- | | `project_id` | string (required) | UUID of the project. | #### Request Body | Name | Type | Description | | --- | --- | --- | | `name` | string | New name. 1–255 characters. | | `description` | string | New description. Max 2000 characters. | --- ### Delete a project Requires API key. Permanently deletes the project. Submissions in the project are unlinked (`project_id` set to `null`); they are not deleted. #### Path Parameters | Name | Type | Description | | --- | --- | --- | | `project_id` | string (required) | UUID of the project. | ## Project Sources --- Each project can carry up to **256 KB of plain-text reference material** split across files (max **64 KB per file**). Allowed extensions: `.md`, `.markdown`, `.txt`, `.text`. When you submit a new generation inside the project, the parsed text of every source file is used as shared context for every generation in the project, so this is where you put your style guide, glossary of in-world names, dimensions you want respected ("chairs are always 45 cm tall"), or any other context the generation should treat as constant. Drop a `style.md` in a "Gargoyle variations" project that describes your preferred polycount, the materials you want, and the silhouette rules. Every generation prompt in that project will be evaluated against those constants without you re-typing them. ### List source files Requires API key. #### Path Parameters | Name | Type | Description | | --- | --- | --- | | `project_id` | string (required) | UUID of the project. | #### Example Response ```json { "project_id": "p1q2r3s4-...", "sources": [ { "id": "src-9a8b...", "project_id": "p1q2r3s4-...", "filename": "style.md", "mime_type": "text/markdown", "size_bytes": 1842, "created_at": "2026-04-20T14:00:00Z", "guard_verdict": "ok", "is_applied": true } ], "count": 1, "total_bytes": 1842 } ``` `total_bytes` is the sum across the whole project, useful for checking how much of the 256 KB budget is left before uploading more. --- ### Get a single source Requires API key. Returns the metadata plus the **parsed text** of the file (what actually gets used as context: line endings normalized, content decoded UTF-8). #### Path Parameters | Name | Type | Description | | --- | --- | --- | | `project_id` | string (required) | UUID of the project. | | `source_id` | string (required) | UUID of the source file. | --- ### Upload a source file Requires API key. `multipart/form-data`. The file is parsed at upload time and stored securely. The parsed content is provided as shared context for every new submission created in the project. #### Path Parameters | Name | Type | Description | | --- | --- | --- | | `project_id` | string (required) | UUID of the project. | #### Form Fields | Name | Type | Description | | --- | --- | --- | | `file` | file (required) | `.md`, `.markdown`, `.txt`, or `.text`. Max 64 KB per file. | #### Example Request #### Errors | Code | Meaning | | --- | --- | | `400` | Empty file, or invalid UTF-8 content. | | `413` | File exceeds 64 KB, or the project would exceed 256 KB total. | | `415` | Unsupported file extension. | | `422` | Rejected by the content screen - see below. | A source file's text is placed in the system prompt of **every** generation in the project, so each upload (and each in-place edit) is screened once for attempts to manipulate the model rather than describe your project. A rejection is a `422` whose `detail.message` says what to remove; nothing is stored. Forceful project rules are fine and are the point of the feature - "ALWAYS use flat colours", "NEVER add a ground plane", "every asset must stay under 5000 triangles" all pass. What does not pass is text aimed at the system rather than the model being built: overriding its instructions, asking it to reveal them, or asking it to read or emit anything outside the 3D asset. Every source carries the result: | Field | Meaning | | --- | --- | | `guard_verdict` | `ok` once screened. Anything else means it has not been cleared. | | `is_applied` | Whether this file's text actually reaches your generations. | **Check `is_applied`.** A file can be stored and still be inert - screening has not finished, or it was flagged. Its text is not in your prompts until this is `true`. Screening applies to what you send us. Do not put credentials or private notes in a source file: its contents are given to the model on every run. --- ### Update a source file Requires API key. Rename or rewrite the file's content. At least one of `filename` / `content` must be present. Per-file and per-project caps still apply. #### Request Body | Name | Type | Description | | --- | --- | --- | | `filename` | string | New filename including an allowed extension. | | `content` | string | Replacement UTF-8 text. | --- ### Delete a source file Requires API key. Removes the source file from the project. Submissions already created in the project are unaffected; only **new** submissions after this point will see the reduced source set as context. ## Submission Management --- ### Set or clear the nickname Requires API key. #### Path Parameters | Name | Type | Description | | --- | --- | --- | | `submission_id` | string (required) | UUID of the submission. | #### Request Body | Name | Type | Description | | --- | --- | --- | | `nickname` | string \| null | Display label. Pass `null` to clear. Max 255 characters. | --- ### Archive or unarchive Requires API key. #### Path Parameters | Name | Type | Description | | --- | --- | --- | | `submission_id` | string (required) | UUID of the submission. | #### Request Body | Name | Type | Description | | --- | --- | --- | | `is_archived` | boolean (required) | `true` to archive, `false` to restore. | --- ### Toggle visibility Requires API key. Hides or shows the submission in list endpoints without deleting it. #### Path Parameters | Name | Type | Description | | --- | --- | --- | | `submission_id` | string (required) | UUID of the submission. | #### Request Body | Name | Type | Description | | --- | --- | --- | | `is_visible` | boolean (required) | `true` to show, `false` to hide. | --- ### Pin or unpin Requires API key. Pinned submissions sort to the top of lists. #### Path Parameters | Name | Type | Description | | --- | --- | --- | | `submission_id` | string (required) | UUID of the submission. | #### Request Body | Name | Type | Description | | --- | --- | --- | | `is_pinned` | boolean (required) | `true` to pin, `false` to unpin. | --- ### Assign to a project Requires API key. Move the submission into a project, or pass `null` to remove. Both the submission and the target project must belong to your API key. #### Path Parameters | Name | Type | Description | | --- | --- | --- | | `submission_id` | string (required) | UUID of the submission. | #### Request Body | Name | Type | Description | | --- | --- | --- | | `project_id` | string \| null | UUID of the target project, or `null` to unassign. | --- ### Soft-delete a submission Requires API key. Hides the submission from listings but preserves its data. Include soft-deleted items in `/submissions` via `include_deleted=true`. #### Path Parameters | Name | Type | Description | | --- | --- | --- | | `submission_id` | string (required) | UUID of the submission. | --- # Job Status Source: https://docs.thrixel.com/progress import { Tab, Tabs } from 'fumadocs-ui/components/tabs'; import { Callout } from 'fumadocs-ui/components/callout'; Once a submission is created, monitor its lifecycle by polling `/info`, streaming `/stream` (Server-Sent Events), or listing all your submissions. --- ## Get submission metadata Public. Returns current status, input, and parent chain. Use this for simple polling. ### Path Parameters | Name | Type | Description | | --- | --- | --- | | `submission_id` | string (required) | UUID returned by any submit endpoint. | ### Example Request ### Example Response ```json { "submission_id": "a1b2c3d4-...", "status": "completed", "model": "architect", "input": { "type": "text", "task": "a small stone gargoyle holding a lantern", "image": null, "images": null, "modification_request": null }, "parent_submission_id": null, "nickname": null, "is_archived": false, "is_visible": true, "is_deleted": false, "is_pinned": false, "adaptive_thinking": false, "created_at": "2026-04-20T14:32:11Z", "completed_at": "2026-04-20T14:34:02Z" } ``` --- ## Stream progress (SSE) Public. Server-Sent Events stream that emits progress updates, log messages, and status transitions in real time. Closes automatically when the submission reaches a terminal state. ### Path Parameters | Name | Type | Description | | --- | --- | --- | | `submission_id` | string (required) | UUID returned by any submit endpoint. | ### Example Request ```bash curl -N https://api.thrixel.com/api/v1/a1b2c3d4-.../stream ``` ```javascript const es = new EventSource( "https://api.thrixel.com/api/v1/a1b2c3d4-.../stream" ); es.onmessage = (e) => { const event = JSON.parse(e.data); console.log(event.type, event); }; ``` Event frames arrive as `data:` lines containing JSON. Common shapes: ```json // Progress update {"type": "progress", "percent": 0.42, "message": "Generating geometry..."} // Status change {"type": "status", "status": "processing"} // Terminal event: the stream closes after this {"type": "status", "status": "completed"} ``` --- ## List your submissions Requires API key. Returns all submissions owned by the authenticated API key. Filter by status or project. ### Query Parameters | Name | Type | Description | | --- | --- | --- | | `status` | string | Filter by status. Values: `queued`, `processing`, `completed`, `failed`. | | `project_id` | string | Filter by project. | | `include_deleted` | boolean | Include soft-deleted submissions. Default: `false`. | | `limit` | integer | Max results. Default: `50`. | ### Example Request ### Example Response ```json { "submissions": [ { "submission_id": "a1b2c3d4-...", "status": "completed", "nickname": null, "is_pinned": false, "project_id": null, "created_at": "2026-04-20T14:32:11Z" } ], "count": 1 } ``` --- # Writing Prompts Source: https://docs.thrixel.com/prompts import { Callout } from 'fumadocs-ui/components/callout'; import PromptRefiner from '@/components/PromptRefiner'; You don't need any 3D background to write good prompts, but a few wording patterns swing the result dramatically. This page is a cookbook of recipes that work. The same rules apply whether you're describing something new or editing an existing model. Type a rough draft, optionally drop a screenshot of your current model (and a reference image of what you want), and a refined version comes back that you can copy straight into Thrixel. The widget applies the same rules described below. --- ## Five rules that apply everywhere **1. Numbers beat adjectives.** "Decimate to 50,000 triangles" works; "make it simpler" gambles. "Scale to 2 meters tall" works; "make it bigger" doesn't say how. If you can put a number on it, do. **2. Name the part, don't describe it.** Once a model is generated, open the **Scene Hierarchy** panel: every named group (Body, Wheels, Chimney) is a handle you can refer to. Prompts that reference part names ("scale up the Wheels group by 1.5x") behave dramatically better than spatial guesses ("make the round things at the bottom bigger"). In the web app, you can also **click a part in the 3D viewer** to scope an edit to that part. A chip appears above the Edit prompt ("Apply to: Wheels"), and when you Apply Edit, every other part is left bit-identical. Same applies to the Refine button: it'll rewrite your draft to stay within that scope rather than expanding to the whole model. Via the API, pass `focus_on_node_names: ["Wheels"]` on `POST /api/v1/edit/submit` or `POST /api/v1/prompts/refine` to get the same behaviour. **3. Anchor scale and direction to known things, not axes.** "The size of a coffee mug" is unambiguous. "Facing the camera" beats "rotated 90° on Y"; *forward / left / front / behind* gets re-projected correctly more reliably than raw axes. **4. One change per edit.** The single biggest mistake. If you say "make the wheels red AND raise the body AND add a roof", at least one of those three will silently revert or interact badly. Split into three sequential edits; editing is iterative by design. **5. "Keep X unchanged" is a real instruction.** Without it, a small change can pull other parts along with it. Adding "keep the body, smokestack, and tender exactly as they are" ties everything else down. --- ## Creating a model A new-from-scratch prompt has to invent everything (geometry, proportions, materials, parts), so specifics buy you a lot. A useful prompt usually has three components: - **Subject**: what the object actually is. - **Style**: visual aesthetic (low-poly, stylized, realistic, voxel, cartoon). - **Scale anchor**: a reference for size, if it matters. "A wooden barrel with iron rings, low-poly stylized, the size of a person." "A cool barrel." The bad version leaves style, materials, scale, and detail level entirely unconstrained. Each of those guesses will surprise you. ### When you have a reference image If you upload an image, the prompt's job changes: it describes *intent*, not appearance. Use the prompt to call out: - The **focus** in the image (e.g. "model the dragon, ignore the background rocks"). - Anything the image doesn't show (e.g. "the back side has a tail similar to the front legs"). - Style overrides ("the image shows a real dog, but model it low-poly"). "Model the central character only. Stylized hand-painted look. The arms not visible in the image should match the legs." "Make this." *(prompt is empty, image attached)* The empty-prompt case actually works *okay* most of the time, but framing is guessed and the entire scene sometimes gets modelled rather than the subject you cared about. ### Style vocabulary | What you want | What works in the prompt | | --- | --- | | Cartoon look | "stylized", "exaggerated proportions", "no surface detail", "flat colors" | | Realistic look | "PBR materials", "photoreal", "fine surface detail" | | Game-asset look | "low-poly", "stylized PBR", "clean topology" | | Toy / blocky look | "voxel-style", "blocky geometry" | | Specific colors | Hex codes (`#a83232`) or named colors ("dark forest green") | --- A text-only [Sculptor](/sculptor) prompt is first drawn as **one three-quarter-view picture**, and only that picture becomes the mesh. One or two sentences about what that picture shows work best. Back sides, several objects, or context such as "hanging from the ceiling" get lost or garble the result, so a long, detailed prompt usually does worse there, not better. Architect prompts can be as specific as you like. ## Editing a model Edit prompts are imperative: they describe *what to change* on top of the existing model. The structure is already known, so you can refer to specific named parts and they'll be matched. ### Geometry edits | Goal | Recipe | | --- | --- | | Reduce triangle count | `Decimate the mesh so that there are 50000 triangles.` | | Uniform resize | `Scale the entire model up by 1.5x.` | | Anisotropic resize | `Scale only the height by 2x. Keep width and depth the same.` | | Move a part | `Move the Chimney 0.3 units forward. Keep everything else unchanged.` | | Rotate a part | `Rotate the Wheels group by 30 degrees.` | | Add a part | `Add a small handle on the right side of the Body, near the top.` | | Remove a part | `Remove the Antenna group entirely.` | | Smooth surfaces | `Apply smooth shading to the Body and Roof groups.` | ### Color and material edits | Goal | Recipe | | --- | --- | | Change one part's color | `Make the Wheels red (#cc2222). Keep the Body color unchanged.` | | Change material type | `Change the Body material from matte plastic to polished chrome.` | | Add an accent color | `Add a thin gold trim around the rim of the Hat.` | "Reduce the polygon count of the Body group to 8000 triangles. Keep the Wheels and Chimney unchanged." "Make it less complex. Also bigger. And the wheels should be black." The bad version mashes three unrelated edits into one prompt. Run them as three separate edits and you'll keep all three. ### Common phrases that misfire - **"Make it look better"**: there's no signal for what's wrong. Tell it. - **"Add more detail"**: *where*? On which part? At what scale? - **"More realistic"**: name the material instead ("rough cast iron", "polished chrome", "weathered painted wood"). - **"A bit bigger"**: give a number or a multiplier. - **"Like a [thing] but cooler"**: describe the cool part directly. --- ## Texturing: material vocabulary When you're dialing in surface appearance (the Texture / Detailer steps), prompts are usually shorter (2 to 6 words), and a reference image does most of the work. For texture work, **paste or drop a screenshot of what you want before typing a prompt**. The reference image dominates appearance; the prompt is a tiebreaker on material vocabulary. Long prompts without a reference image rarely beat short prompts with one. A retexture never changes the shape: carved or relief detail, such as engraving or a decal sculpted into the surface, comes back in the new texture too. To remove it, regenerate the model. Glow does not survive a texture or Detailer pass either; only [Architect](/architect) results keep emissive materials. These short phrases work better than full sentences: | Surface | Phrase that works | | --- | --- | | Old / aged | `weathered`, `oxidized`, `peeling paint`, `green patina` | | Smooth metal | `polished chrome`, `brushed steel`, `gold leaf` | | Rough metal | `cast iron`, `rusted steel`, `corroded bronze` | | Wood | `rough wood grain`, `dark walnut`, `whitewashed pine` | | Stone | `polished marble`, `weathered sandstone`, `mossy granite` | | Plastic / paint | `matte plastic`, `glossy enamel paint`, `flat acrylic` | | Cloth / leather | `worn leather`, `faded denim`, `quilted cotton` | | Glass | `frosted glass`, `tinted glass`, `smoked glass` | "Weathered bronze with green patina, especially in the recessed details." "Make it look real and old." --- ## Putting it together A typical newbie-friendly flow: 1. **Create** with a subject + style + scale anchor. Don't sweat materials yet. 2. **Auto-refine** (no prompt) to clean up obvious geometry issues. 3. **Edit** in one or more passes, one focused change per prompt, referencing part names from the Scene Hierarchy panel, with explicit "keep X unchanged" for everything you're not touching. 4. **Texture** with a short material descriptor and (ideally) a reference image. If something misfires, the fastest fix is almost always to **shrink the prompt**, not lengthen it. --- # Remesh Source: https://docs.thrixel.com/remesh import { Tab, Tabs } from 'fumadocs-ui/components/tabs'; import { Callout } from 'fumadocs-ui/components/callout'; Produce a new triangle count from an existing mesh - a [Detailer](/detailer) **or a [Sculptor](/sculptor)** result - without regenerating the texture. Fast. A Detailer pass is **not** a prerequisite: you can remesh a Sculptor output directly. To lighten a model you already have, remesh it rather than re-running the [Detailer](/detailer) at a lower `decimation_target`. Remesh reuses the existing geometry and texture, so it's much faster and cheaper - and you keep the same model, just lighter, instead of generating a new one. ## Re-decimate a mesh Requires API key. Two modes: - **Whole-mesh** (default): leave `focus_on_node_names` empty and the new `decimation_target` is spread across every part of the model. - **Part-focused**: pass one or more part names in `focus_on_node_names` and the budget is applied only to those parts. Every other part keeps its current resolution, so you can lighten (or refine) a single component without touching the rest. **Whole-mesh remesh works directly on a Sculptor output** - you do not need to run the Detailer first. **Part-focused** remesh is the only exception: it needs per-part data, so it is only supported on **part-preserving** Detailer results (created with `preserve_parts: true`). A merged or Sculptor-based parent has no per-part data and returns `400` for part-focused mode. ### Request Body | Name | Type | Description | | --- | --- | --- | | `parent_submission_id` | string (required) | ID of a completed Detailer **or Sculptor** submission. Whole-mesh remesh accepts either; part-focused remesh requires a part-preserving Detailer. | | `decimation_target` | integer | Target triangle count. 1,000–200,000. Default: `200000`. Whole-mesh when `focus_on_node_names` is empty; otherwise the budget applied to each focused part. | | `remesh` | boolean | Run quad remeshing before decimation. Default: `true`. | | `focus_on_node_names` | string[] | Restrict the new budget to these named parts. Empty = whole mesh. Part-preserving detailer parents only. | | `project_id` | string | Assign to a project. | ### Example Request --- # Rig Source: https://docs.thrixel.com/rig import { Tab, Tabs } from 'fumadocs-ui/components/tabs'; import { Callout } from 'fumadocs-ui/components/callout'; Rig adds a skeleton and per-vertex skin weights to a finished model, so it can be posed and animated in Blender, Unity, Unreal or any engine that reads skinned glTF or FBX. It is built for anything with a body and limbs: **characters, creatures and robots**. It is automatic: no markers, no T-pose needed, about half a minute per model. Rig takes any completed model as its parent: a [Create](/architect), [Sculptor](/sculptor), [Detailer](/detailer), [Edit](/edit) or [Upload](/upload) result. Rig runs on any model, but the skeleton is only meaningful on something with a body and limbs. On a building, a vehicle or a prop it usually comes out poor, or the run fails with `rig_unsupported` and the Cubes are refunded. ## Rig a model Requires API key. Flat price per run (5 Cubes today; the live figure is `flat_op_cubes.rig` on `GET /api/v1/tiers`). Refunded if the run fails. The Free plan allows 4 Rig runs a month; past that the submit returns `402` (see [Rate Limits](/getting-started#rate-limits)). ### Request Body | Name | Type | Description | | --- | --- | --- | | `parent_submission_id` | string (required) | ID of the completed model to rig. | | `skeleton` | string | Bone naming for a humanoid result: `mixamo` (default), `unity` or `ue5`. See below. | | `seed` | integer | Sampling seed. Random when omitted; rigging the same model again with a new seed gives a different skeleton. | | `project_id` | string | Assign to a project. | ### Example Request The response is the usual `submission_id`, `status: "queued"` and `created_at`. Follow it with [progress](/progress) like any other job, then [download](/download) the result. The rigged model is a new version of the parent; it looks the same, it now has bones. ## Bone names When the model is a humanoid (a spine, a head, two arms, two legs), its joints are renamed to the standard skeleton you pick, so engines and animation libraries recognise them without a manual mapping: | `skeleton` | Example names | Use it for | | --- | --- | --- | | `mixamo` | `mixamorig:Hips`, `mixamorig:LeftUpLeg`, `mixamorig:RightForeArm` | Mixamo animations; most tools auto-map these names. | | `unity` | `Hips`, `LeftUpperLeg`, `RightLowerArm`, `UpperChest` | Unity's Humanoid avatar maps these automatically. | | `ue5` | `pelvis`, `thigh_l`, `lowerarm_r`, `spine_03` | The UE5 Mannequin skeleton and its retargeter. | Creatures with tails or wings, four-legged animals and other non-humanoids keep generic names (`bone_0`, `bone_1`, ...). The submission's `params` report what was applied: | Field | Description | | --- | --- | | `bones` | Number of bones in the skeleton. | | `humanoid` | Whether a humanoid was found and named. | | `skeleton` | The naming applied: `mixamo`, `unity`, `ue5`, or `generic` when no humanoid was found. | Fingers, when present, keep generic names. ## Exporting to an engine - **glTF / GLB** carries the skeleton and weights as-is (up to 4 bone influences per vertex). - **FBX** export ([download](/download) with `format=fbx`) writes no extra end bones, so the bone count in the engine matches `bones`. With `ue5` naming, a `root` bone is added above `pelvis` at the model's origin, as the Mannequin expects for root motion. - You do not need to rig again for another engine: `target=unity`, `target=ue5` or `target=roblox` on the FBX download renames a humanoid's bones for that engine (Roblox gets a full [R15 rig](/download#roblox-r15)). See [FBX for an engine](/download#fbx-for-an-engine). ## Errors | Status | When | | --- | --- | | `402` | Your Cube balance does not cover the run. | | `404` | The parent submission does not exist. | | `400` | The parent is not completed yet. | A run that starts and then cannot produce a usable skeleton ends `failed` with `error_code: "rig_unsupported"`, and the Cubes are refunded. Any other failure is `error_code: "rig_failed"`, also refunded. --- # Sculptor Source: https://docs.thrixel.com/sculptor import { Callout } from 'fumadocs-ui/components/callout'; **When to use Sculptor:** organic and photoreal forms - characters, creatures, props with soft or intricate surfaces. The output is a **single dense mesh**, not an editable part hierarchy, so it's not made for part-scoped [Edit](/edit). For structured, editable, multi-part objects use [Architect](/architect) instead. The Sculptor engine produces photorealistic, organic results from an image or text prompt. It takes a **single** input: either a text prompt or one reference image. ## Sculpt a model Requires API key. ### Request Body | Name | Type | Description | | --- | --- | --- | | `task` | string | Text description of the object to sculpt. | | `image` | string | Single reference image: base64 data URL or HTTPS URL. | | `images` | string[] | Reference image(s); the first is used. Mutually exclusive with `image`. | | `seed` | integer | RNG seed. 0-999,999. **Omit for a random seed each run** (the default). Pin it only to reproduce a previous result: the same seed with the same inputs reproduces it. | | `target_size_m` | number | Real-world size: the result's longest side, in metres (0.01-1000; glTF units are metres). The finished mesh is scaled uniformly to it, standing on the ground, and every download format carries that size. Detail, texture, remesh and rebake of the result keep it. Omit to keep the default (longest side about 1 m). | | `style_reference_submission_id` | string | UUID of a **completed** submission whose look (palette, materials, finish, level of wear) this one should match. Contributes appearance only - the subject still comes from `task` / `image`. Pass the same id across a set to keep it visually consistent. | | `project_id` | string | Group the submission under a project. | | `auto_route_id` | string | Route id from [Auto](/auto) (`POST /route`), when Auto chose this engine. Single-use, expires after 30 minutes; charged 1 Cube when used. | Provide either `task` or an image. Sculptor uses one input. With `task` alone, Sculptor first draws a reference image of the subject. A clearly tall subject ("a tall, slender lamp post") is drawn on a portrait canvas and a clearly long or flat one ("a long wooden bench") on a landscape canvas, so its proportions carry through to the mesh. Everything else is drawn square. Say the shape in the prompt when it matters. `style_reference_submission_id` contributes **appearance only** - materials, palette, finish, how worn the object looks. The subject always comes from your `task` or your `image`; pointing at a wooden boat and asking for a barrel gives you a barrel, finished like that boat. It must be a **completed** submission of yours that still has its artefacts. Otherwise: `404` unknown id, `400` not completed or no artefacts, `403` it belongs to another account. Sculptor has no mesh prior, so with `task` alone the reference steers the image the mesh is built from. Supply an `image` **as well** and that image is restyled into the reference's look first - what you get back is no longer the picture you sent. For rules rather than looks - polycount budgets, naming, real-world sizes - attach a [project source](/organize) instead. Text states constraints well, a finished model shows appearance well, and they combine. ### Example Request ### Example Response ```json { "submission_id": "e5f6a7b8-...", "status": "queued", "created_at": "2026-04-20T14:33:05Z" } ``` ## Next steps Once the Sculptor job completes, chain any refinement step onto its `submission_id`. A Detailer pass is optional, not a prerequisite - you can [remesh](/remesh) a Sculptor output directly to a new triangle count, [generate textures](/detailer#generate-textures-for-an-existing-mesh) on it, or [detail](/detailer) it. `sculptor → remesh` is a valid path on its own. --- # Upload a mesh Source: https://docs.thrixel.com/upload import { Callout } from 'fumadocs-ui/components/callout'; Bring your own GLB file into Thrixel so you can run [Edit](/edit) or [Detailer](/detailer) operations on it. Multipart form upload, max 100 MB. ## Upload an existing model Requires API key. ### Request Body | Name | Type | Description | | --- | --- | --- | | `glb_file` | file (required) | Binary `.glb` file (`Content-Type: multipart/form-data`). | | `task` | string | Optional label describing the uploaded model. Max 2000 chars. | | `project_id` | string | Assign to a project. | ### Example Request ### Example Response ```json { "submission_id": "d4e5f6a7-...", "status": "completed", "original_filename": "my_model.glb", "file_size_bytes": 4528192, "created_at": "2026-04-20T14:40:15Z" } ``` ---