Platform

Assets

Metaloot gives coding agents a complete Blender asset workflow: discover Metaloot templates and starter assets, author bpy scripts locally, execute them on bounded headless Blender workers, and receive hosted GLB, preview, inspection, and .blend artifacts. This agent-driven Blender path is the recommended way to create new 3D assets.

Preferred: Agent-Driven Blender#

Use metaloot blender for new 3D asset workflows. The coding agent on your computer supplies the creative reasoning and authors Blender Python. Metaloot supplies versioned templates, a curated starter library, remote Blender execution, validation, and durable asset delivery. There is no remote prompt-to-3D agent.

Install the CLI (npm install -g @metaloot/cli or npx @metaloot/cli) and sign in once with metaloot login. Start by letting the local agent inspect the worker capabilities, available templates, and semantic starter library:

metaloot blender health
metaloot blender capabilities
metaloot blender templates --json
metaloot blender library --json

# Narrow the library by intent:
metaloot blender library material --json
metaloot blender library dungeon --category Environment --json

The agent then writes a normal Blender Python script. Metaloot injects the selected starter data blocks into starter_assets; the script can otherwise use the headless Blender bpy API directly:

build_crate.py
import bpy

paint = starter_assets["material.painted-metal"]

bpy.ops.mesh.primitive_cube_add(location=(0, 0, 0.75), scale=(1.2, 0.8, 0.75))
crate = bpy.context.object
crate.name = "Clockwork_Crate"
crate.data.materials.append(paint)

bevel = crate.modifiers.new("Soft game-ready edges", "BEVEL")
bevel.width = 0.08
bevel.segments = 3
metaloot blender create \
  --name "Clockwork Crate" \
  --template scene.blank.v1 \
  --starter material.painted-metal \
  --script ./build_crate.py \
  --face-limit 8000 \
  --visibility private \
  --wait --json
  • The local agent is the intelligence — it chooses the template and starters, writes the script, inspects the output, and decides the next edit. The worker does not translate a natural-language prompt into arbitrary geometry.
  • Templates are versioned .blend starting scenes. Use scene.blank.v1 when the script should own the scene, or select a character, prop, terrain, or modular environment template as a structured base.
  • Starter assets are referenced by stable semantic ids, not filesystem paths. They include materials, base meshes, models, kitbash parts, Geometry Nodes groups, lighting rigs, HDRIs, and curated community collections with provenance and license metadata.
  • Every successful job produces model.glb, preview.png, inspection.json, and scene.blend. High-level create and revise commands also register the result as a normal Studio asset with visibility, hosted URLs, and SDK support.
  • Jobs are asynchronous batch runs. Use --wait for an agent-friendly blocking call, or use jobs, status, cancel, artifacts, and download to control the queue explicitly.

Iterate by supplying another bpy script. revise creates a new Studio asset from an existing Blender-backed asset; action operates at the worker-job level:

metaloot blender revise <asset-id> \
  --name "Weathered Clockwork Crate" \
  --script ./weather_crate.py \
  --wait

metaloot blender action <job-id> --action inspect --wait
metaloot blender artifacts <job-id> --json
metaloot blender download <job-id> --artifact scene.blend --dir ./assets
Blender jobs are headless: scripts should not depend on interactive editors or UI clicks. Build the complete scene through bpy, leave it ready for validation/export, and use the preview plus inspection artifact as the feedback loop.

Legacy Tripo Generation#

The older metaloot assets generate text-to-3D and image-to-3D interface is backed by Tripo. It remains available for compatibility, but it is no longer the recommended path for new asset workflows and will be removed in a future release. Do not build new agent automation around it.

# Legacy compatibility only:
metaloot assets generate --prompt "low-poly treasure chest" --name "Treasure Chest" --wait
metaloot assets generate --image concept.png --name "Ember Mage" --wait

Existing Tripo-created assets remain ordinary Studio assets and continue to work with rigging, downloads, hosted URLs, and the SDK. For new work, have the local agent use metaloot blender with Metaloot templates and starters.

Rigging & Animations#

A finished model can be rigged (auto-skeleton + skinning) with preset animation clips retargeted onto it. Each finished preset is hosted as its own GLB containing the rigged model plus that clip, and all presets share one skeleton — so a game can load them into a single animation mixer and crossfade.

metaloot assets rig <asset-id> --presets idle,walk,run --wait

# The asset JSON gains rigStatus and animations:
#   { "animations": { "idle": { "status": "success", "url": "…" }, … } }
# Each preset is hosted at /api/assets/<id>/animation/<preset>

Re-running rig retries failed presets and can add new ones; the rig itself is built only once per asset. Loading the clips in a game is covered in the SDK section below.

Consuming Assets with @metaloot/sdk#

Public assets are hosted at stable URLs with CORS enabled, so your game streams them by id or slug — no download step, nothing bundled with the game. The zero-dependency @metaloot/sdk package picks the right URL automatically and works in the browser, Node 20+, and Workers with any engine:

npm install @metaloot/sdk

import { loadAssetObjectUrl } from "@metaloot/sdk";
// or: import { … } from "@metaloot/sdk/assets"

// Streams the hosted GLB — works with three.js, Babylon, any GLTF loader:
const url = await loadAssetObjectUrl("treasure-chest-1a2b3c4d");
new GLTFLoader().load(url, (gltf) => scene.add(gltf.scene));
  • URL resolution — on a <name>.metaloot.app game origin, assetUrl() returns the same-origin, edge-cached proxy /__metaloot/assets/<id-or-slug>.glb (zero CORS concerns). Everywhere else it returns https://studio.metaloot.app/api/assets/<id-or-slug>/file, served with Access-Control-Allow-Origin: * for public assets. Both URLs also work directly with GLTFLoader.load(...)if you'd rather skip the SDK.
  • Variants — every 3D asset can have a full-resolution source file and a game-ready lod (~15k faces). The default variant: "auto" serves the LOD once it's ready and falls back to the source until then. Force one with assetUrl(id, { variant: "source" }) — responses carry an X-Metaloot-Variant header telling you which file auto resolved to.
  • Loading helpersloadAsset() returns an ArrayBuffer for any engine's parse API; loadAssetObjectUrl() returns a loader-ready blob: URL (revoke it when done).
  • Private assets — only served to their owner. Pass a scoped API token as { token: "mtl_api_…" } using a token with the assets:read scope. Local browser games can fetch private assets this way; never commit the token or include it in a production bundle. For deployed games, make the asset public or download it and ship the file (below).

Want the SDK to handle the engine integration too? The optional adapters load, place, scale, ground, animate, and clean up the model while still leaving rendering and gameplay under your control:

// Three.js: GLTFLoader + mixer/actions + crossfades + bounds + disposal
import { loadThreeAsset } from "@metaloot/sdk/three";
const hero = await loadThreeAsset("ember-mage", {
  scene, animations: "available", targetHeight: 1.8,
  shadows: true, autoPlay: "idle",
});
hero.update(clock.getDelta());
hero.play("run");

// Babylon.js: AssetContainer + AnimationGroups + placement + bounds
import { loadBabylonAsset } from "@metaloot/sdk/babylon";

Rigged assets expose their animation presets through the same module — one GLB per preset, all sharing a skeleton:

import { getAsset, loadAnimationObjectUrl } from "@metaloot/sdk/assets";

const asset = await getAsset("frost-witch-c78f6ed7");
if (asset.animations?.idle?.status === "success") {
  const idle = await gltfLoader.loadAsync(await loadAnimationObjectUrl(asset.id, "idle"));
  const walk = await gltfLoader.loadAsync(await loadAnimationObjectUrl(asset.id, "walk"));
  const mixer = new THREE.AnimationMixer(idle.scene);
  mixer.clipAction(idle.animations[0]).play(); // crossfade to walk.animations[0] when moving
}

Before deploying a browser build with a private asset, download the file and deploy it like any other static asset so no token ships to players:

# Saved as <slug>.glb; for Vite, public/ is copied into dist/ by the build
metaloot assets download <asset-id> --dir public/assets
metaloot assets download <asset-id> --variant lod   # game-ready file (source is the default)

# Load it with your 3D stack, e.g. three.js:
#   new GLTFLoader().load("/assets/treasure-chest.glb", (gltf) => scene.add(gltf.scene));

metaloot deploy   # live at https://<name>.metaloot.app

Browsing the Catalog: Packs, Files & Metadata#

Beyond generated 3D models, the studio hosts curated packs of sprites, textures, audio, and animations. Every listed file is served through the same Metaloot API with a hashed manifest, so game code never depends on a creator site. Discover assets from the CLI or the SDK:

# CLI
metaloot assets list        # your assets            [--category] [--kind] [--json]
metaloot assets explore     # public gallery — kinds: model3d|image|video|audio|sprite|texture|animation
metaloot assets files <pack-id>   # every hosted file in a pack: path, MIME type, size, SHA-256
// SDK
import { listAssets, getAsset, getAssetManifest, loadAssetFile } from "@metaloot/sdk/assets";

const swords = await listAssets({ query: "sword" });              // public gallery
const chars  = await listAssets({ category: "Characters", kind: "model3d" });
const asset  = await getAsset("treasure-chest-1a2b3c4d");
// { id, name, slug, status, progress, visibility, category, tags,
//   modelUrl, previewUrl, sourceModelUrl, lodModelUrl, lodStatus,
//   rigStatus, animations, createdAt, … }

// Pull one file out of a pack (omit path for the whole hosted ZIP):
const manifest = await getAssetManifest("kenney-interface-sounds");
const sound = manifest.files.find((f) => f.contentType === "audio/ogg");
const bytes = await loadAssetFile("kenney-interface-sounds", { path: sound.path });

Agent Instructions

Paste this into your coding agent so it can discover Metaloot templates, author assets with Blender Python, inspect and iterate on the results, and ship them with a game deployed via metaloot deploy. The whole flow runs from the terminal; the agent only needs a scoped API token in METALOOT_TOKEN.

Author game-ready 3D assets for this game with Blender through the Metaloot CLI, then ship or stream them from the deployed game. Everything runs from the terminal.

Architecture:
- You, the local coding agent, are the asset author. Inspect the available Metaloot templates and starter library, write Blender Python (bpy), run it, inspect the artifacts, and iterate.
- Metaloot is the execution and delivery layer: it supplies versioned .blend templates, semantic starter assets, bounded headless Blender workers, validation, previews, inspection metadata, hosted GLBs, and downloadable .blend files.
- There is no remote prompt-to-3D agent. Do not expect a natural-language prompt to author arbitrary geometry.
- The older Tripo-backed "metaloot assets generate" text/image interface is legacy compatibility only and will be removed in a future release. Do not use it for new automation.

Prerequisites:
- The Metaloot CLI: npx @metaloot/cli or npm install -g @metaloot/cli
- A scoped API token with assets:read and assets:generate (ask the owner for one from https://metaloot.app/settings/api-tokens if it is not already available). Add assets:write when the workflow must publish, edit, rig, or animate assets. Export it as METALOOT_TOKEN:
  export METALOOT_TOKEN="mtl_api_…"
  metaloot whoami

Required discovery before authoring:
1. Check live worker capabilities:
   metaloot blender health
   metaloot blender capabilities
2. Read the versioned template registry:
   metaloot blender templates --json
3. Search the semantic starter library:
   metaloot blender library --json
   metaloot blender library material --json
   metaloot blender library dungeon --category Environment --json
4. Reuse an existing public asset when it already fits. Do not generate a near-duplicate unnecessarily.

Authoring:
1. Choose exactly one template. Prefer scene.blank.v1 when your script should own the complete scene. Use a specialized template only when its existing objects and structure are useful.
2. Choose only the starter assets the script will consume. Starters may be materials, models, base meshes, kitbash collections, Geometry Nodes groups, lighting rigs, HDRIs, or community packs. Preserve their provenance and license metadata.
3. Write a local Python file using bpy. The worker provides these globals:
   - bpy: the Blender Python API
   - request: the submitted job request
   - parameters: parsed --parameters and --param values
   - starter_assets: imported Blender data blocks keyed by semantic starter id
   - output_directory: the writable output directory
4. The script must work in Blender background mode without UI clicks or interactive editor state. Leave the scene ready for validation and export; Metaloot handles saving scene.blend, exporting model.glb, rendering preview.png, and writing inspection.json.
5. Submit the script:
   metaloot blender create \
     --name "Clockwork Gate" \
     --template scene.blank.v1 \
     --starter material.painted-metal \
     --script ./build_gate.py \
     --face-limit 8000 \
     --visibility private \
     --wait --json | sed -n '/^{/,$p' > asset.json
6. The JSON asset has id, slug, providerTaskId, status, modelUrl, and previewUrl. Keep both the Studio asset id and Blender job id.

Inspection and iteration:
- Inspect worker state and artifacts:
  metaloot blender status <job-id> --json
  metaloot blender artifacts <job-id> --json
  metaloot blender download <job-id> --artifact inspection.json --dir ./assets/inspection
  metaloot blender download <job-id> --artifact preview.png --dir ./assets/inspection
  metaloot blender download <job-id> --artifact scene.blend --dir ./assets/source
- Read inspection.json for object count, triangle count, armatures, bones, animations, starters, and warnings. Visually inspect preview.png. Do not treat a successful process exit as proof of asset quality.
- Revise an existing Blender-backed Studio asset with another bpy script:
  metaloot blender revise <asset-id> --name "Weathered Clockwork Gate" --script ./weather_gate.py --wait
- Worker-level actions are available for completed jobs:
  metaloot blender action <job-id> --action revise|render|inspect|export --script ./change.py --wait
- Jobs are asynchronous batch runs. Use --wait for a blocking agent loop, or jobs/status/cancel explicitly.

Animations (optional, for characters/creatures):
- Rig a finished model and retarget preset clips onto it:
  metaloot assets rig <asset-id> --presets idle,walk,run --wait
- Each finished preset is a hosted GLB (rigged model + that clip) at /api/assets/<id>/animation/<preset>; all presets share one skeleton, so load them into a single AnimationMixer and crossfade. In game code:
  import { getAsset, loadAnimationObjectUrl } from "@metaloot/sdk/assets";
  const asset = await getAsset("<asset-id>");        // has rigStatus + animations: { preset: { status, url } }
  const idleUrl = await loadAnimationObjectUrl(asset.id, "idle");

Managing and consuming Studio assets:
- metaloot assets list [--json]: your assets
- metaloot assets explore [--json]: public catalog
- metaloot assets files <pack-id> [--json]: hosted files in a curated pack
- metaloot assets publish <id> / unpublish <id>: change visibility
- metaloot assets update <id>: edit metadata

Getting the asset into the game:

A) Hosted asset (preferred when the asset is PUBLIC, i.e. generated with --visibility public): reference the hosted GLB by URL; no file ships with the game.
   - On a game deployed to <name>.metaloot.app, use the same-origin, edge-cached proxy (zero CORS concerns):
     /__metaloot/assets/<asset-id-or-slug>.glb
   - From any other origin, use the studio URL (public assets are served with Access-Control-Allow-Origin: *):
     https://studio.metaloot.app/api/assets/<asset-id-or-slug>/file
   - Or install @metaloot/sdk and use an engine adapter. Three.js integration handles the loader, scale, grounding, shadows, bounds, animation mixer, named actions, crossfades, and disposal:
     import { loadThreeAsset } from "@metaloot/sdk/three";
     const hero = await loadThreeAsset("<asset-id>", { scene, animations: "available", targetHeight: 1.8, shadows: true });
     hero.update(clock.getDelta());
     hero.play("run");
     Babylon.js games can use loadBabylonAsset from @metaloot/sdk/babylon.
   - Hosted URLs serve a game-ready LOD (~15k faces) automatically once the studio builds it, falling back to the full-res source until then. The SDK also exposes listAssets({ query, category, kind }) and getAsset(id) for discovering existing public assets before generating new ones.

B) Private/local development: pass the scoped token to the SDK only in local development. The assets:read scope can load public assets plus private assets owned by the token's user, with CORS from localhost:
   const hero = await loadThreeAsset("<private-asset-id>", { scene, token: import.meta.env.VITE_METALOOT_TOKEN });
   Never commit that environment file or include the token in a deployed browser bundle.

C) Download and ship (recommended for PRIVATE assets before production):
   1. Download the GLB into a folder that ships with the deployed site:
      metaloot assets download <asset-id> --dir public/assets
      The file is saved as <slug>.glb (slug comes from the asset JSON). For Vite games use public/ so the build copies it into dist/; for static games deployed with --dir ., any non-dot folder works.
   2. Load the GLB with the game's existing 3D stack, e.g. three.js:
      new GLTFLoader().load("/assets/<slug>.glb", (gltf) => scene.add(gltf.scene));

Deploying:
- metaloot deploy builds the game and publishes it at https://<name>.metaloot.app (auth and multiplayer are provisioned automatically). METALOOT_TOKEN authenticates this too.
- Every CLI command exits non-zero on failure (including --wait when a generation fails), so plain && chaining is safe.

Verify end-to-end: after deploy, fetch the asset URL the game uses — https://<name>.metaloot.app/__metaloot/assets/<asset-id>.glb for hosted assets, or https://<name>.metaloot.app/assets/<slug>.glb for downloaded ones — confirm it returns the model (Content-Type: model/gltf-binary), then confirm the game renders it.

Docs: https://metaloot.app/docs/assets and https://metaloot.app/docs/cli.

Fully Scriptable#

The whole pipeline runs non-interactively. Create a scoped API token at metaloot.app/settings/api-tokens with assets:read and assets:generate ( add assets:write for publishing, cancellation, and animation), then export it as METALOOT_TOKEN. The CLI login page still creates a full-access mtl_cli_… token for interactive terminal use. Commands exit non-zero on failure, so they chain safely:

export METALOOT_TOKEN="mtl_api_…"

metaloot blender templates --json
metaloot blender library material --json

metaloot blender create --name "Treasure Chest" \
  --template scene.blank.v1 \
  --starter material.painted-metal \
  --script ./build_treasure_chest.py \
  --visibility public --wait --json | sed -n '/^{/,$p' > /tmp/asset.json

# Hot-link it from game code via @metaloot/sdk (public assets), or ship the file:
ASSET_ID=$(node -p "JSON.parse(require('fs').readFileSync('/tmp/asset.json','utf8')).asset.id")
metaloot assets download "$ASSET_ID" --dir public/assets && metaloot deploy

Set METALOOT_STUDIO_ORIGIN=http://localhost:3001 to point the assets and blender commands at a local Studio control plane during development.