
Meshy 3d Printing
- 790 installs
- 65 repo stars
- Updated July 31, 2026
- meshy-dev/meshy-3d-agent
meshy-3d-printing is a Claude Code skill from meshy-dev/meshy-3d-agent that helps developers prepare AI-generated 3D models for 3D printing workflows including mesh cleanup, export formats, and print-ready asset handoff.
About
meshy-3d-printing is a skill in the meshy-3d-agent repository focused on turning Meshy AI-generated 3D assets into printable models. It guides developers through mesh validation, export format choices, scale and wall-thickness checks, and handoff to slicer tools so generated geometry survives real fabrication. Teams use meshy-3d-printing when prototyping physical products, game collectibles, or demo hardware from generative meshes rather than manual CAD. The skill sits at the intersection of generative media APIs and maker hardware pipelines, emphasizing printability constraints that raw AI meshes often violate.
- meshy-3d-printing
- AI & Agent Building
- AI-coding skill
Meshy 3d Printing by the numbers
- 790 all-time installs (skills.sh)
- +97 installs in the week ending Aug 4, 2026 (Skillselion tracking)
- Ranked #1,332 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/meshy-dev/meshy-3d-agent --skill meshy-3d-printingAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 790 |
|---|---|
| repo stars | ★ 65 |
| Last updated | July 31, 2026 |
| Repository | meshy-dev/meshy-3d-agent ↗ |
How do you prepare AI meshes for 3D printing?
Helps with ai & agent building tasks.
Who is it for?
Developers or technical artists piping Meshy AI 3D output into FDM or resin printing pipelines for prototypes or merch.
Skip if: Pure software projects with no physical output or teams needing full CAD engineering instead of generative-to-print prep.
When should I use this skill?
A developer needs to convert Meshy-generated 3D models into printable STL or 3MF assets with mesh cleanup and slicer-ready settings.
What you get
Print-ready mesh exports, slicer settings notes, scale and wall-thickness checklist, and fabrication handoff documentation.
- Print-ready mesh export
- Printability checklist
- Slicer settings notes
Files
Meshy 3D Printing
Prepare and send Meshy-generated 3D models to a slicer for 3D printing. Supports white model (single-color) and multicolor printing workflows with automatic slicer detection.
Prerequisite: This skill reuses the utility functions (create_task, poll_task, download, get_project_dir, etc.) and environment setup from meshy-3d-generation. However, when the user wants to 3D print, this skill controls the entire workflow — including generation, format selection, downloading, and slicer integration. Do NOT run meshy-3d-generation's workflow first and then hand off here — this skill must control parameters from the start (e.g. target_formats with "3mf" for multicolor).
---
Intent Detection
Proactively suggest 3D printing when these keywords appear in the user's request:
- Direct: print, 3d print, slicer, slice, bambu, orca, prusa, cura, multicolor, multi-color, 3mf
- Implied: figurine, miniature, statue, physical model, desk toy, phone stand
When detected, guide the user through the appropriate print pipeline below.
---
Decision Tree: White Model vs Multicolor
IMPORTANT: When the user wants to 3D print, follow this flow:
1. Detect installed slicers first (see Slicer Detection Script below) 2. Ask the user: "Do you want a single-color (white) print or multicolor?" 3. If white model → follow White Model Pipeline 4. If multicolor: a. Check if a multicolor-capable slicer is installed b. Supported multicolor slicers: OrcaSlicer, Bambu Studio, Creality Print, Elegoo Slicer, Anycubic Slicer Next c. If no multicolor slicer detected, warn the user and suggest installing one d. Ask: "How many colors? (default: 4, max: 16)" and "Segmentation depth? (3=coarse, 6=fine, default: 4)" e. Confirm cost: generation (20) + texture (10) + multicolor (10) = 40 credits total (+10 if repair is needed) f. Follow Multicolor Pipeline 5. (Recommended) Insert a printability analysis step (POST /openapi/v1/print/analyze, FREE) after generation in either pipeline. Run `POST /openapi/v1/print/repair` (10 credits) only if analyze flags errors.
---
Slicer Detection Script
Append this to the reusable script template from meshy-3d-generation:
import subprocess, shutil, platform, os, glob as glob_mod
SLICER_MAP = {
"OrcaSlicer": {"mac_app": "OrcaSlicer", "win_exe": "orca-slicer.exe", "win_dir": "OrcaSlicer", "linux_exe": "orca-slicer"},
"Bambu Studio": {"mac_app": "BambuStudio", "win_exe": "bambu-studio.exe", "win_dir": "BambuStudio", "linux_exe": "bambu-studio"},
"Creality Print": {"mac_app": "Creality Print", "win_exe": "CrealityPrint.exe", "win_dir": "Creality Print*", "linux_exe": None},
"Elegoo Slicer": {"mac_app": "ElegooSlicer", "win_exe": "elegoo-slicer.exe", "win_dir": "ElegooSlicer", "linux_exe": None},
"Anycubic Slicer Next": {"mac_app": "AnycubicSlicerNext", "win_exe": "AnycubicSlicerNext.exe", "win_dir": "AnycubicSlicerNext", "linux_exe": None},
"PrusaSlicer": {"mac_app": "PrusaSlicer", "win_exe": "prusa-slicer.exe", "win_dir": "PrusaSlicer", "linux_exe": "prusa-slicer"},
"UltiMaker Cura": {"mac_app": "UltiMaker Cura", "win_exe": "UltiMaker-Cura.exe", "win_dir": "UltiMaker Cura*", "linux_exe": None},
}
MULTICOLOR_SLICERS = {"OrcaSlicer", "Bambu Studio", "Creality Print", "Elegoo Slicer", "Anycubic Slicer Next"}
def detect_slicers():
"""Detect installed slicer software. Returns list of {name, path, multicolor}."""
found = []
system = platform.system()
for name, info in SLICER_MAP.items():
path = None
if system == "Darwin":
app = info.get("mac_app")
if app and os.path.exists(f"/Applications/{app}.app"):
path = f"/Applications/{app}.app"
elif system == "Windows":
win_dir = info.get("win_dir", "")
win_exe = info.get("win_exe", "")
for base in [os.environ.get("ProgramFiles", r"C:\Program Files"),
os.environ.get("ProgramFiles(x86)", r"C:\Program Files (x86)")]:
if "*" in win_dir:
matches = glob_mod.glob(os.path.join(base, win_dir, win_exe))
if matches:
path = matches[0]
break
else:
candidate = os.path.join(base, win_dir, win_exe)
if os.path.exists(candidate):
path = candidate
break
else: # Linux
exe = info.get("linux_exe")
if exe:
path = shutil.which(exe)
if path:
found.append({"name": name, "path": path, "multicolor": name in MULTICOLOR_SLICERS})
return found
def open_in_slicer(file_path, slicer_name):
"""Open a model file in the specified slicer."""
info = SLICER_MAP.get(slicer_name, {})
system = platform.system()
abs_path = os.path.abspath(file_path)
if system == "Darwin":
app = info.get("mac_app", slicer_name)
subprocess.run(["open", "-a", app, abs_path])
elif system == "Windows":
exe = info.get("win_exe")
exe_path = shutil.which(exe) if exe else None
if exe_path:
subprocess.Popen([exe_path, abs_path])
else:
os.startfile(abs_path)
else:
exe = info.get("linux_exe")
exe_path = shutil.which(exe) if exe else None
if exe_path:
subprocess.Popen([exe_path, abs_path])
else:
subprocess.run(["xdg-open", abs_path])
print(f"Opened {abs_path} in {slicer_name}")
# --- Detect slicers ---
slicers = detect_slicers()
if slicers:
print("Installed slicers:")
for s in slicers:
mc = " [multicolor]" if s["multicolor"] else ""
print(f" - {s['name']}{mc}: {s['path']}")
else:
print("No slicer software detected. Install one of: OrcaSlicer, Bambu Studio, PrusaSlicer, etc.")---
Printability Analysis & Repair (FREE → optional 10-credit fix)
Before downloading and printing, run the automated printability check to decide whether the mesh needs repair. The analyze step is FREE (0 credits), so there's no reason to skip it for production prints.
Analyze Script
# Run after the generation/refine/retexture step that produced your printable mesh
INPUT_TASK_ID = refine_id # or whatever produced the textured / final mesh
# IMPORTANT: input_task_id MUST refer to a task that used Meshy 6 or any Preview model.
# For Meshy 4/5 outputs, pass `model_url` (the GLB download URL) instead.
analyze_id = create_task("/openapi/v1/print/analyze", {
"input_task_id": INPUT_TASK_ID,
# OR: "model_url": "https://example.com/model.glb"
})
analyze_task = poll_task("/openapi/v1/print/analyze", analyze_id)
p = analyze_task.get("printability") or {}
metrics = p.get("metrics", {})
status = p.get("status", "unknown")
print(f"Printability: {status} (issues: {p.get('issue_count', 0)} = "
f"errors {p.get('error_count', 0)} + warnings {p.get('warning_count', 0)})")
print(f" watertight={metrics.get('is_watertight')}, "
f"volume={metrics.get('volume')} m³, "
f"non_manifold_edges={metrics.get('non_manifold_edges')}, "
f"degenerate_faces={metrics.get('degenerate_faces')}, "
f"holes={metrics.get('holes')}")
needs_repair = status == "error" # warning is optional; error means won't print wellStatus meanings:
healthy— print as-is.warning— degenerate faces or holes present. Repair is OPTIONAL but recommended for thin-feature prints.error— non-watertight, non-positive volume, or non-manifold edges. Recommend repair before printing.unknown— analyze couldn't process the model. Inspect manually or retry.
Repair Script (only if analyze flagged errors)
if needs_repair:
repair_id = create_task("/openapi/v1/print/repair", {
"input_task_id": INPUT_TASK_ID, # output is GLB
# OR: "model_url": "https://example.com/model.stl" # output is STL
})
repair_task = poll_task("/openapi/v1/print/repair", repair_id)
# Output format mirrors input. Find the populated field:
repaired_url = next(
(url for url in repair_task["model_urls"].values() if url),
None
)
# Use this repaired URL for downstream download / multicolor / slicer steps.Note: repair preserves geometry only, not textures. If you need a textured + repaired model for multicolor printing, run repair first, then re-texture (or feed repair's task_id to multi-color directly — the API handles re-texturing internally if applicable).
---
White Model Print Pipeline
| Step | Action | Credits | Notes |
|---|---|---|---|
| 1 | Detect installed slicers | 0 | Run slicer detection script |
| 2 | Generate untextured model | 5–20 | Text to 3D or Image to 3D (should_texture: False) |
| 3 | Download OBJ | 0 | OBJ format for slicer compatibility |
| 4 | Fix OBJ for printing | 0 | Coordinate conversion (see below) |
| 5 | Open in slicer | 0 | open_in_slicer(obj_path, slicer_name) |
White Model Generation + Print Script
Use the create_task/poll_task/download/get_project_dir helpers from meshy-3d-generation, then:
# --- Step 2: Generate untextured model for printing ---
# Text to 3D:
task_id = create_task("/openapi/v2/text-to-3d", {
"mode": "preview",
"prompt": "USER_PROMPT",
"ai_model": "latest",
"target_formats": ["obj"], # Only OBJ for white model printing
})
# OR Image to 3D:
# task_id = create_task("/openapi/v1/image-to-3d", {
# "image_url": "IMAGE_URL",
# "should_texture": False, # White model — no texture
# "target_formats": ["glb", "obj"], # OBJ needed for slicer
# })
task = poll_task("/openapi/v2/text-to-3d", task_id) # adjust endpoint for image-to-3d
project_dir = get_project_dir(task_id, task.get("prompt", "print"))
# --- Step 3-4: Download OBJ + fix for printing ---
obj_url = task["model_urls"].get("obj")
if not obj_url:
print("OBJ format not available. Available:", list(task["model_urls"].keys()))
print("Download GLB and import manually into your slicer.")
obj_url = task["model_urls"].get("glb")
obj_path = os.path.join(project_dir, "model.obj")
download(obj_url, obj_path)
# --- Post-process OBJ for slicer compatibility ---
def fix_obj_for_printing(input_path, output_path=None, target_height_mm=75.0):
"""
Fix OBJ coordinate system, scale, and position for 3D printing slicers.
- Rotates from glTF Y-up to slicer Z-up: (x, y, z) -> (x, -z, y)
- Scales model to target_height_mm (default 75mm)
- Centers model on XY plane
- Aligns model bottom to Z=0
"""
if output_path is None:
output_path = input_path
lines = open(input_path, "r").readlines()
rotated = []
min_x, max_x = float("inf"), float("-inf")
min_y, max_y = float("inf"), float("-inf")
min_z, max_z = float("inf"), float("-inf")
for line in lines:
if line.startswith("v "):
parts = line.split()
x, y, z = float(parts[1]), float(parts[2]), float(parts[3])
rx, ry, rz = x, -z, y
min_x, max_x = min(min_x, rx), max(max_x, rx)
min_y, max_y = min(min_y, ry), max(max_y, ry)
min_z, max_z = min(min_z, rz), max(max_z, rz)
rotated.append(("v", rx, ry, rz, parts[4:]))
elif line.startswith("vn "):
parts = line.split()
nx, ny, nz = float(parts[1]), float(parts[2]), float(parts[3])
rotated.append(("vn", nx, -nz, ny, []))
else:
rotated.append(("line", line))
model_height = max_z - min_z
scale = target_height_mm / model_height if model_height > 1e-6 else 1.0
x_offset = -(min_x + max_x) / 2.0 * scale
y_offset = -(min_y + max_y) / 2.0 * scale
z_offset = -(min_z * scale)
with open(output_path, "w") as f:
for item in rotated:
if item[0] == "v":
_, rx, ry, rz, extra = item
tx = rx * scale + x_offset
ty = ry * scale + y_offset
tz = rz * scale + z_offset
extra_str = " " + " ".join(extra) if extra else ""
f.write(f"v {tx:.6f} {ty:.6f} {tz:.6f}{extra_str}\n")
elif item[0] == "vn":
_, nx, ny, nz, _ = item
f.write(f"vn {nx:.6f} {ny:.6f} {nz:.6f}\n")
else:
f.write(item[1])
print(f"OBJ fixed: rotated Y-up→Z-up, scaled to {target_height_mm:.0f}mm, centered, bottom at Z=0")
print(f"Output: {os.path.abspath(output_path)}")
fix_obj_for_printing(obj_path, target_height_mm=75.0)
# --- Open in slicer ---
if slicers:
open_in_slicer(obj_path, slicers[0]["name"])
else:
print(f"\nModel ready: {os.path.abspath(obj_path)}")
print("Open this file in your preferred slicer: File → Import / Open")Parameters:
-target_height_mm: Default 75mm. Adjust based on user's request (e.g. "print at 15cm" →150.0).
---
Multicolor Print Pipeline
| Step | Action | Credits | Notes |
|---|---|---|---|
| 1 | Detect slicers + check multicolor | 0 | Warn if no multicolor slicer |
| 2 | Generate 3D model | 20 | Text to 3D or Image to 3D |
| 3 | Add textures | 10 | Refine or Retexture (REQUIRED) |
| 4 | Multi-color processing | 10 | POST /openapi/v1/print/multi-color |
| 5 | Poll until SUCCEEDED | 0 | GET /openapi/v1/print/multi-color/{id} |
| 6 | Download 3MF | 0 | From model_urls["3mf"] |
| 7 | Open in multicolor slicer | 0 | open_in_slicer(path, slicer) |
| Total | 40 |
Multi-Color Full Script
Use the create_task/poll_task/download/get_project_dir helpers from meshy-3d-generation:
# --- Step 1: Check for multicolor slicer (already done above) ---
mc_slicers = [s for s in slicers if s["multicolor"]]
if not mc_slicers:
print("WARNING: No multicolor-capable slicer detected.")
print("Supported: OrcaSlicer, Bambu Studio, Creality Print, Elegoo Slicer, Anycubic Slicer Next")
print("Install one before proceeding.")
else:
print(f"Multicolor slicer(s): {', '.join(s['name'] for s in mc_slicers)}")
# --- Step 2-3: Generate + texture (with 3mf in target_formats!) ---
# Text to 3D preview:
preview_id = create_task("/openapi/v2/text-to-3d", {
"mode": "preview",
"prompt": "USER_PROMPT",
"ai_model": "latest",
# No target_formats needed — 3MF comes from the multi-color API, not from generate/refine
})
poll_task("/openapi/v2/text-to-3d", preview_id)
# Refine (add textures — REQUIRED for multicolor):
refine_id = create_task("/openapi/v2/text-to-3d", {
"mode": "refine",
"preview_task_id": preview_id,
"enable_pbr": True,
})
refine_task = poll_task("/openapi/v2/text-to-3d", refine_id)
project_dir = get_project_dir(preview_id, "multicolor-print")
# OR for Image to 3D with texture:
# task_id = create_task("/openapi/v1/image-to-3d", {
# "image_url": "IMAGE_URL",
# "should_texture": True,
# # No target_formats needed — 3MF comes from multi-color API
# })
# refine_task = poll_task("/openapi/v1/image-to-3d", task_id)
INPUT_TASK_ID = refine_id # Use the textured task
MAX_COLORS = 4 # 1-16, ask user
MAX_DEPTH = 4 # 3-6, ask user
mc_task_id = create_task("/openapi/v1/print/multi-color", {
"input_task_id": INPUT_TASK_ID,
"max_colors": MAX_COLORS,
"max_depth": MAX_DEPTH,
})
print(f"Multi-color task created: {mc_task_id} (10 credits)")
task = poll_task("/openapi/v1/print/multi-color", mc_task_id)
# --- Download 3MF ---
threemf_url = task["model_urls"]["3mf"]
threemf_path = os.path.join(project_dir, "multicolor.3mf")
download(threemf_url, threemf_path)
print(f"3MF ready: {os.path.abspath(threemf_path)}")
# --- Open in multicolor slicer ---
if mc_slicers:
open_in_slicer(threemf_path, mc_slicers[0]["name"])
else:
print(f"Open {threemf_path} in a multicolor-capable slicer manually.")---
Manual Sanity Checks (in addition to the automated analyze API)
The analyze API covers geometric correctness (watertight, manifold edges, degenerate faces, holes). Some print-quality concerns still need a human eye in the slicer:
| Check | Recommendation | Where to verify |
|---|---|---|
| Wall thickness | Minimum 1.2mm for FDM, 0.8mm for resin | Slicer (after import) |
| Overhangs | Keep below 45° or add supports | Slicer support generation |
| Minimum detail | At least 0.4mm for FDM, 0.05mm for resin | Visual inspection in slicer |
| Base stability | Flat base or add brim/raft in slicer | Slicer plate adhesion |
| Hollowing | Consider hollowing for figurines/miniatures | Slicer hollow tool (resin) |
The automated analyze API now handles: watertightness, volume, non-manifold edges, degenerate faces, holes — these no longer require manual inspection.
---
Key Rules for Print Workflow
- Always detect slicer first and report results to the user before proceeding
- Always run analyze (FREE) for production / functional prints, miniatures with thin features, mechanical parts
- Repair is conditional: only when analyze status = error, or warning if the user cares about quality
- White model: Download OBJ format, apply
fix_obj_for_printing()for coordinate conversion - Multicolor: The multi-color API outputs 3MF directly — no coordinate conversion needed (3MF uses Z-up natively)
- 3MF for multicolor: The Multi-Color Print API outputs 3MF directly — no need to request 3MF from generate/refine via
target_formats. For non-print use cases that need 3MF, pass"3mf"intarget_formatsat generation time. - For multicolor, verify slicer supports it before proceeding with the (costly) pipeline
- After opening in slicer, remind user to check print settings (layer height, infill, supports)
- If OBJ is not available: Download GLB and guide user to import manually
- Repair caveat: textures are NOT preserved. For a multicolor print on a model that needs repair, run repair first, then re-texture, then multicolor.
---
Additional Resources
For the complete API endpoint reference, read reference.md.
Meshy API
Meshy is an AI-powered 3D model generation platform. The Meshy API is a RESTful API that allows you to programmatically generate 3D models, textures, images, rig characters, and animate them.
Base URL: https://api.meshy.ai
Docs: https://docs.meshy.ai
Instructions for Large Language Models
When generating code that integrates with the Meshy API, follow these guidelines:
1. Authentication
All requests require a Bearer token in the Authorization header:
Authorization: Bearer msy_YOUR_API_KEYAPI keys are created at https://www.meshy.ai/settings/api and have the format msy_<random-string>.
2. Asynchronous Task Model
Meshy uses an asynchronous execution model. All generation endpoints return a task ID, not the result directly. You must poll the task status or use SSE streaming to get results.
Correct pattern:
import requests, time, os
headers = {"Authorization": f"Bearer {os.environ['MESHY_API_KEY']}"}
# Step 1: Create a task
response = requests.post(
"https://api.meshy.ai/openapi/v2/text-to-3d",
headers=headers,
json={"mode": "preview", "prompt": "a monster mask"},
)
task_id = response.json()["result"]
# Step 2: Poll until completion
while True:
task = requests.get(
f"https://api.meshy.ai/openapi/v2/text-to-3d/{task_id}",
headers=headers,
).json()
if task["status"] == "SUCCEEDED":
break
if task["status"] == "FAILED":
raise Exception(task["task_error"]["message"])
time.sleep(5)
# Step 3: Download result
model_url = task["model_urls"]["glb"]WRONG pattern (expecting synchronous result):
# WRONG - the POST does not return a model
response = requests.post("https://api.meshy.ai/openapi/v2/text-to-3d", ...)
model = response.json()["model_urls"] # WRONG3. SSE Streaming (Alternative to Polling)
All task endpoints support Server-Sent Events streaming at /<endpoint>/:id/stream:
import requests, json
headers = {
"Authorization": f"Bearer {API_KEY}",
"Accept": "text/event-stream"
}
response = requests.get(
f"https://api.meshy.ai/openapi/v2/text-to-3d/{task_id}/stream",
headers=headers,
stream=True
)
for line in response.iter_lines():
if line and line.startswith(b"data:"):
data = json.loads(line.decode("utf-8")[5:])
print(data["status"], data.get("progress"))
if data["status"] in ["SUCCEEDED", "FAILED", "CANCELED"]:
break
response.close()4. Task Statuses
All tasks follow the same lifecycle: PENDING -> IN_PROGRESS -> SUCCEEDED | FAILED | CANCELED
5. Timestamps
All timestamps in the API are Unix epoch milliseconds (not seconds). For example, 1693569600000 represents September 1, 2023 12:00:00 PM UTC.
6. Model Output Formats
3D model outputs are available in multiple formats: GLB, FBX, OBJ, USDZ, STL (via Remesh), 3MF. Access them via model_urls.glb, model_urls.fbx, model_urls["3mf"], etc. Not all formats are always available; omitted properties mean that format was not generated. Ask the user which format they need before downloading — do not download all formats.
IMPORTANT: 3MF is NOT included by default. To receive a 3MF file, you MUST explicitly include "3mf" in the target_formats parameter when creating the task (e.g. "target_formats": ["glb", "3mf"]). Without this, the default output includes GLB, OBJ, FBX, STL, USDZ but not 3MF. The Multi-Color Print API always outputs 3MF.
All generation endpoints (Text to 3D, Image to 3D, Multi-Image to 3D, Remesh, Retexture) support target_formats. Specifying only the formats you need can reduce task completion time.
7. Asset Retention
Generated assets are retained for a maximum of 3 days for non-Enterprise customers. Download and store models locally if you need them longer.
8. Rate Limits
| Tier | Requests/Second | Queue Tasks | Priority |
|---|---|---|---|
| Pro | 20 | 10 | Default |
| Studio | 20 | 20 | Higher |
| Enterprise | 100 | 50+ | Highest |
Exceeding limits returns 429 Too Many Requests.
9. Choosing the Right AI Model
- Use
"latest"or"meshy-6"for the best quality (default). - Use
"meshy-5"for the previous generation model. "latest"always resolves to the newest model (currently Meshy 6).
10. Common Mistakes to Avoid
- Don't call CORS-restricted endpoints from browser JavaScript. The API blocks CORS requests. Use a server-side proxy.
- Don't forget `enable_pbr: true` if you need metallic/roughness/normal maps.
- Don't set both `texture_prompt` and `texture_image_url` — if both are provided,
texture_prompttakes precedence. - Don't assume model format availability. Check that the URL key exists in
model_urlsbefore downloading.
---
Complete Code Example: Text to 3D (Preview + Refine)
import requests, os, time
headers = {"Authorization": f"Bearer {os.environ['MESHY_API_KEY']}"}
# 1. Create preview task
preview_resp = requests.post(
"https://api.meshy.ai/openapi/v2/text-to-3d",
headers=headers,
json={
"mode": "preview",
"prompt": "a monster mask",
"should_remesh": True,
},
)
preview_resp.raise_for_status()
preview_task_id = preview_resp.json()["result"]
# 2. Poll preview task
while True:
task = requests.get(
f"https://api.meshy.ai/openapi/v2/text-to-3d/{preview_task_id}",
headers=headers,
).json()
if task["status"] == "SUCCEEDED":
break
if task["status"] == "FAILED":
raise Exception(task["task_error"]["message"])
time.sleep(5)
# 3. Download preview model
with open("preview_model.glb", "wb") as f:
f.write(requests.get(task["model_urls"]["glb"]).content)
# 4. Create refine task (texturing)
refine_resp = requests.post(
"https://api.meshy.ai/openapi/v2/text-to-3d",
headers=headers,
json={
"mode": "refine",
"preview_task_id": preview_task_id,
"enable_pbr": True,
},
)
refine_resp.raise_for_status()
refine_task_id = refine_resp.json()["result"]
# 5. Poll refine task
while True:
task = requests.get(
f"https://api.meshy.ai/openapi/v2/text-to-3d/{refine_task_id}",
headers=headers,
).json()
if task["status"] == "SUCCEEDED":
break
if task["status"] == "FAILED":
raise Exception(task["task_error"]["message"])
time.sleep(5)
# 6. Download refined (textured) model
with open("refined_model.glb", "wb") as f:
f.write(requests.get(task["model_urls"]["glb"]).content)Complete Code Example: Image to 3D
import requests, os, time
headers = {"Authorization": f"Bearer {os.environ['MESHY_API_KEY']}"}
response = requests.post(
"https://api.meshy.ai/openapi/v1/image-to-3d",
headers=headers,
json={
"image_url": "https://example.com/photo.jpg",
"should_texture": True,
"enable_pbr": True,
},
)
response.raise_for_status()
task_id = response.json()["result"]
while True:
task = requests.get(
f"https://api.meshy.ai/openapi/v1/image-to-3d/{task_id}",
headers=headers,
).json()
if task["status"] == "SUCCEEDED":
break
if task["status"] == "FAILED":
raise Exception(task["task_error"]["message"])
time.sleep(5)
with open("model.glb", "wb") as f:
f.write(requests.get(task["model_urls"]["glb"]).content)---
API Endpoints Reference
Text to 3D API
The Text to 3D workflow has two stages: preview (mesh generation) and refine (texture generation).
POST /openapi/v2/text-to-3d — Create Preview Task
Creates a preview (mesh-only) 3D model from a text prompt.
Required parameters:
mode(string): Must be"preview".prompt(string): Description of the 3D model. Max 600 characters.
Optional parameters:
model_type(string):"standard"(default) or"lowpoly". When"lowpoly",ai_model,topology,target_polycount,should_remeshare ignored.ai_model(string):"meshy-5","meshy-6", or"latest"(default, resolves to Meshy 6).topology(string):"quad"or"triangle"(default).target_polycount(integer): 100–300,000. Default 30,000.should_remesh(boolean): Defaultfalsefor Meshy 6,truefor others.symmetry_mode(string):"off","auto"(default), or"on".pose_mode(string):"a-pose","t-pose", or""(default).moderation(boolean): Screen input for harmful content. Defaultfalse.target_formats(string[]): Output formats:"glb","obj","fbx","stl","usdz","3mf". Default: all except 3mf. 3mf must be explicitly included.auto_size(boolean): Use AI to auto-estimate real-world height. Defaultfalse.origin_at(string):"bottom"or"center". Default"bottom"when auto_size is true.
Cost: 20 credits (Meshy-6/lowpoly), 5 credits (other models).
Response: {"result": "<task_id>"}
POST /openapi/v2/text-to-3d — Create Refine Task
Textures a previously generated preview model.
Required parameters:
mode(string): Must be"refine".preview_task_id(string): ID of a succeeded preview task.
Optional parameters:
enable_pbr(boolean): Generate metallic/roughness/normal maps. Defaultfalse.texture_prompt(string): Additional text to guide texturing. Max 600 characters.texture_image_url(string): Image URL or data URI to guide texturing.ai_model(string):"meshy-5","meshy-6", or"latest"(default, resolves to Meshy 6).remove_lighting(boolean): Removes highlights and shadows from the base color texture. Defaulttrue. Meshy-6/latest only.moderation(boolean): Defaultfalse.target_formats(string[]): Output formats:"glb","obj","fbx","stl","usdz","3mf". Default: all except 3mf. 3mf must be explicitly included.auto_size(boolean): Use AI to auto-estimate real-world height. Defaultfalse.origin_at(string):"bottom"or"center". Default"bottom"when auto_size is true.
Cost: 10 credits.
Response: {"result": "<task_id>"}
GET /openapi/v2/text-to-3d/:id — Retrieve Task
Returns the full task object including status, progress, model_urls, texture_urls.
DELETE /openapi/v2/text-to-3d/:id — Delete Task
Permanently deletes a task and all associated data.
GET /openapi/v2/text-to-3d — List Tasks
Query params: page_num (default 1), page_size (default 10, max 50), sort_by (+created_at or -created_at).
GET /openapi/v2/text-to-3d/:id/stream — Stream Task
Server-Sent Events stream for real-time task progress updates.
Text to 3D Task Object
{
"id": "018a210d-8ba4-705c-b111-1f1776f7f578",
"type": "text-to-3d-preview",
"model_urls": {
"glb": "https://assets.meshy.ai/.../model.glb?Expires=...",
"fbx": "https://assets.meshy.ai/.../model.fbx?Expires=...",
"obj": "https://assets.meshy.ai/.../model.obj?Expires=...",
"usdz": "https://assets.meshy.ai/.../model.usdz?Expires=..."
},
"prompt": "a monster mask",
"thumbnail_url": "https://assets.meshy.ai/.../preview.png?Expires=...",
"progress": 100,
"status": "SUCCEEDED",
"created_at": 1692771650657,
"started_at": 1692771667037,
"finished_at": 1692771669037,
"texture_urls": [
{
"base_color": "https://assets.meshy.ai/.../texture_0.png?Expires=...",
"metallic": "https://assets.meshy.ai/.../texture_0_metallic.png?Expires=...",
"normal": "https://assets.meshy.ai/.../texture_0_normal.png?Expires=...",
"roughness": "https://assets.meshy.ai/.../texture_0_roughness.png?Expires=..."
}
],
"preceding_tasks": 0,
"task_error": {"message": ""}
}---
Image to 3D API
POST /openapi/v1/image-to-3d — Create Task
Generates a 3D model from a single image.
Required parameters:
image_url(string): Publicly accessible URL or base64 data URI (.jpg, .jpeg, .png).
Optional parameters:
model_type(string):"standard"(default) or"lowpoly".ai_model(string):"meshy-5","meshy-6", or"latest"(default).topology(string):"quad"or"triangle"(default).target_polycount(integer): 100–300,000. Default 30,000.should_remesh(boolean): Defaultfalsefor Meshy 6,truefor others.save_pre_remeshed_model(boolean): Store GLB before remeshing. Defaultfalse.should_texture(boolean): Generate textures. Defaulttrue. Without texture: 20 credits (Meshy-6) / 5 credits (others). With texture: +10 credits.enable_pbr(boolean): PBR maps. Defaultfalse.symmetry_mode(string):"off","auto"(default),"on".pose_mode(string):"a-pose","t-pose", or""(default).texture_prompt(string): Text to guide texturing. Max 600 characters.texture_image_url(string): Image to guide texturing.image_enhancement(boolean): Optimize input image. Defaulttrue. Meshy-6/latest only.remove_lighting(boolean): Removes highlights and shadows from the base color texture for cleaner results under custom lighting. Defaulttrue. Meshy-6/latest only.moderation(boolean): Defaultfalse.target_formats(string[]): Output formats:"glb","obj","fbx","stl","usdz","3mf". Default: all except 3mf. 3mf must be explicitly included.auto_size(boolean): Use AI to auto-estimate real-world height. Defaultfalse.origin_at(string):"bottom"or"center". Default"bottom"when auto_size is true.
Response: {"result": "<task_id>"}
GET /openapi/v1/image-to-3d/:id — Retrieve Task
DELETE /openapi/v1/image-to-3d/:id — Delete Task
GET /openapi/v1/image-to-3d — List Tasks
GET /openapi/v1/image-to-3d/:id/stream — Stream Task
---
Multi-Image to 3D API
POST /openapi/v1/multi-image-to-3d — Create Task
Generates a 3D model from 1–4 images of the same object from different angles.
Required parameters:
image_urls(array): 1–4 images as URLs or data URIs.
Optional parameters: Same as Image to 3D (except image_url → image_urls).
Response: {"result": "<task_id>"}
GET /openapi/v1/multi-image-to-3d/:id — Retrieve Task
DELETE /openapi/v1/multi-image-to-3d/:id — Delete Task
GET /openapi/v1/multi-image-to-3d — List Tasks
GET /openapi/v1/multi-image-to-3d/:id/stream — Stream Task
---
Remesh API
Remesh and export existing 3D models into various formats.
POST /openapi/v1/remesh — Create Task
Required (one of):
input_task_id(string): ID of a succeeded Text to 3D, Image to 3D, or Retexture task.model_url(string): URL or data URI of a 3D model (.glb, .gltf, .obj, .fbx, .stl).
If both are provided, input_task_id takes precedence.
Optional parameters:
target_formats(array): Formats to export. Values:"glb","fbx","obj","usdz","blend","stl","3mf". Default["glb"]. 3mf must be explicitly included.topology(string):"quad"or"triangle"(default).target_polycount(integer): 100–300,000. Default 30,000.resize_height(number): Height in meters. Default 0 (no resize). Mutually exclusive withauto_size.auto_size(boolean): Use AI to auto-estimate real-world height. Mutually exclusive withresize_height. Defaultfalse.origin_at(string):"bottom"or"center". Default"bottom"when auto_size is true.convert_format_only(boolean): Only convert format, skip remeshing. Defaultfalse.
Cost: 5 credits.
Response: {"result": "<task_id>"}
GET /openapi/v1/remesh/:id — Retrieve Task
DELETE /openapi/v1/remesh/:id — Delete Task
GET /openapi/v1/remesh — List Tasks
GET /openapi/v1/remesh/:id/stream — Stream Task
---
Retexture API
Apply new AI-generated textures to existing 3D models.
POST /openapi/v1/retexture — Create Task
Required (one of each):
input_task_idormodel_url: The model to retexture.text_style_promptorimage_style_url: The style to apply.
Optional parameters:
ai_model(string):"meshy-5","meshy-6", or"latest"(default, resolves to Meshy 6).enable_original_uv(boolean): Preserve original UV mapping. Defaulttrue.enable_pbr(boolean): PBR maps. Defaultfalse.remove_lighting(boolean): Removes highlights and shadows from the base color texture. Defaulttrue. Meshy-6/latest only.target_formats(string[]): Output formats:"glb","obj","fbx","stl","usdz","3mf". Default: all except 3mf. 3mf must be explicitly included.
Note: image_style_url takes precedence if both text_style_prompt and image_style_url are provided.
Cost: 10 credits.
Response: {"result": "<task_id>"}
GET /openapi/v1/retexture/:id — Retrieve Task
DELETE /openapi/v1/retexture/:id — Delete Task
GET /openapi/v1/retexture — List Tasks
GET /openapi/v1/retexture/:id/stream — Stream Task
---
Analyze Printability API
FDM printability analysis. Reports watertightness, volume, holes, non-manifold edges, degenerate faces. Cost: FREE (0 credits).
POST /openapi/v1/print/analyze — Create Task
Provide exactly one of:
input_task_id(string): A SUCCEEDED task you own (image-to-3d, multi-image-to-3d, text-to-3d, remesh, retexture). MUST use Meshy 6 or any Preview model.model_url(string): Public URL of a 3D model. Supported formats:.glb,.gltf,.obj,.fbx,.stl. Max 100 MB.
Response: {"result": "<task_id>"}
GET /openapi/v1/print/analyze/:id — Retrieve Task
Once SUCCEEDED, the task object contains:
{
"id": "...",
"type": "print-analyze",
"status": "SUCCEEDED",
"progress": 100,
"printability": {
"_version": "v1",
"status": "warning",
"issue_count": 1,
"error_count": 0,
"warning_count": 1,
"metrics": {
"is_watertight": true,
"volume": 1.316,
"non_manifold_edges": 0,
"degenerate_faces": 43242,
"holes": 0
},
"evaluated_at": 1700000001000
},
"consumed_credits": 0
}`printability.status` semantics:
healthy: no errors, no warnings.warning: at least one warning, no errors. (Triggered by degenerate faces or holes.)error: at least one error. (Triggered by non-watertight, non-positive volume, or non-manifold edges.) Recommend running repair.unknown: model could not be analyzed.
DELETE /openapi/v1/print/analyze/:id — Delete Task
GET /openapi/v1/print/analyze — List Tasks
GET /openapi/v1/print/analyze/:id/stream — Stream Task (SSE)
---
Repair Printability API
Repair non-manifold edges, degenerate faces, holes, and ensure watertightness. Cost: 10 credits.
POST /openapi/v1/print/repair — Create Task
Provide exactly one of:
input_task_id(string): A SUCCEEDED task with a GLB asset. Output is GLB.model_url(string): Public URL of.glb/.stl/.obj. Max 100 MB. Output format matches input extension.
Response: {"result": "<task_id>"}
GET /openapi/v1/print/repair/:id — Retrieve Task
{
"id": "...",
"type": "print-repair",
"status": "SUCCEEDED",
"model_urls": {
"glb": "https://...glb?Expires=...",
"fbx": "",
"obj": "",
"stl": "",
"usdz": "",
"3mf": "",
"mtl": ""
},
"thumbnail_url": "https://...preview.png",
"texture_urls": [],
"consumed_credits": 10
}Only the field matching the input format is populated; other fields are empty strings. Textures are NOT preserved (geometry-only repair).
DELETE /openapi/v1/print/repair/:id — Delete Task
GET /openapi/v1/print/repair — List Tasks
GET /openapi/v1/print/repair/:id/stream — Stream Task (SSE)
---
Multi-Color Print API
Process a textured 3D model for multi-color 3D printing. Segments the model's texture into discrete color regions and outputs a 3MF file. Cost: 10 credits.
POST /openapi/v1/print/multi-color — Create Task
Provide exactly one of:
input_task_id(string): ID of a completed task with textures (Text to 3D refine, Image to 3D with texture, or Retexture).model_url(string): Public URL of a textured.glbor.fbxmodel.
Optional parameters:
max_colors(integer, 1-16, default 4): Maximum number of colors for segmentation.max_depth(integer, 3-6, default 4): Color segmentation depth. Higher values produce finer color boundaries.
Response: {"result": "<task_id>"}
Example:
task_id = create_task("/openapi/v1/print/multi-color", {
"input_task_id": "textured-task-uuid",
"max_colors": 4,
"max_depth": 4,
})
# OR with a model URL:
# task_id = create_task("/openapi/v1/print/multi-color", {
# "model_url": "https://example.com/textured.glb",
# "max_colors": 6,
# })GET /openapi/v1/print/multi-color/:id — Retrieve Task
Returns the task object including status, progress, model_urls. Note: response type field is "print-multi-color".
Completed task `model_urls`:
{
"3mf": "https://assets.meshy.ai/.../model.3mf?Expires=..."
}GET /openapi/v1/print/multi-color/:id/stream — Stream Task (SSE)
Server-Sent Events stream. Events include: status, progress, model_urls (contains {"3mf": "https://..."}), task_error.
---
Auto-Rigging API
Create an internal skeleton and bind mesh to it for animation.
Currently works best with standard humanoid (bipedal) characters with clearly defined limbs.
POST /openapi/v1/rigging — Create Task
Required (one of):
input_task_id(string): ID of a succeeded task.model_url(string): URL or data URI of a GLB file.
Optional parameters:
height_meters(number): Character height in meters. Default 1.7.texture_image_url(string): UV-unwrapped base color texture (.png).
Cost: 5 credits.
Response: {"result": "<task_id>"}
Succeeded result includes:
rigged_character_glb_url,rigged_character_fbx_urlbasic_animations.walking_glb_url,walking_fbx_url,walking_armature_glb_urlbasic_animations.running_glb_url,running_fbx_url,running_armature_glb_url
GET /openapi/v1/rigging/:id — Retrieve Task
DELETE /openapi/v1/rigging/:id — Delete Task
GET /openapi/v1/rigging/:id/stream — Stream Task
---
Animation API
Apply animations to rigged characters.
POST /openapi/v1/animations — Create Task
Required parameters:
rig_task_id(string): ID of a succeeded rigging task.action_id(integer): Animation action ID from the Animation Library.
Optional parameters:
post_process(object):operation_type(string):"change_fps","fbx2usdz", or"extract_armature".fps(integer): 24, 25, 30, or 60. Only forchange_fps.
Cost: 3 credits.
Response: {"result": "<task_id>"}
Succeeded result includes:
animation_glb_url,animation_fbx_urlprocessed_usdz_url,processed_armature_fbx_url,processed_animation_fps_fbx_url
GET /openapi/v1/animations/:id — Retrieve Task
DELETE /openapi/v1/animations/:id — Delete Task
GET /openapi/v1/animations/:id/stream — Stream Task
---
Text to Image API
POST /openapi/v1/text-to-image — Create Task
Required parameters:
ai_model(string):"nano-banana"or"nano-banana-pro".prompt(string): Text description of the image.
Optional parameters:
generate_multi_view(boolean): Multi-angle views. Defaultfalse. Cannot be used withaspect_ratio.pose_mode(string):"a-pose"or"t-pose".aspect_ratio(string):"1:1"(default),"16:9","9:16","4:3","3:4".
Cost: 3 credits (nano-banana), 9 credits (nano-banana-pro).
Response: {"result": "<task_id>"}
GET /openapi/v1/text-to-image/:id — Retrieve Task
DELETE /openapi/v1/text-to-image/:id — Delete Task
GET /openapi/v1/text-to-image — List Tasks
GET /openapi/v1/text-to-image/:id/stream — Stream Task
---
Image to Image API
POST /openapi/v1/image-to-image — Create Task
Required parameters:
ai_model(string):"nano-banana"or"nano-banana-pro".prompt(string): Text description of the transformation.reference_image_urls(array): 1–5 reference images as URLs or data URIs.
Optional parameters:
generate_multi_view(boolean): Defaultfalse.
Cost: 3 credits (nano-banana), 9 credits (nano-banana-pro).
Response: {"result": "<task_id>"}
GET /openapi/v1/image-to-image/:id — Retrieve Task
DELETE /openapi/v1/image-to-image/:id — Delete Task
GET /openapi/v1/image-to-image — List Tasks
GET /openapi/v1/image-to-image/:id/stream — Stream Task
---
Balance API
GET /openapi/v1/balance — Get Balance
Returns the current credit balance.
Response: {"balance": 1000}
---
Enterprise API
GET /openapi/v1/showcases — List Showcases
Search and download community showcase models. Enterprise tier only.
Optional parameters:
page_size(integer): 1–10. Default 3.sort_by(string):"+created_at","-created_at","+updated_at","-updated_at","+downloads","-downloads".search(string): Text search in model names.format(string):"glb"(default),"fbx","obj","usdz".showcase_type(string):"all"(default),"animated","static".
Cost: 1 credit per request.
---
Webhooks
Configure webhooks at https://www.meshy.ai/settings/api to receive task status updates via HTTP POST. Max 5 active webhooks per account. HTTPS only.
Your server must respond with HTTP status < 400. Consecutive failures may auto-disable the webhook.
Webhook payloads contain the full task object in JSON format matching the corresponding API's task object schema.
---
Pricing Summary
| API | Cost |
|---|---|
| Text to 3D Preview (Meshy-6/lowpoly) | 20 credits |
| Text to 3D Preview (other models) | 5 credits |
| Text to 3D Refine | 10 credits |
| Image to 3D (Meshy-6, no texture) | 20 credits |
| Image to 3D (Meshy-6, with texture) | 30 credits |
| Image to 3D (other, no texture) | 5 credits |
| Image to 3D (other, with texture) | 15 credits |
| Multi-Image to 3D (Meshy-6, no texture) | 20 credits |
| Multi-Image to 3D (Meshy-6, with texture) | 30 credits |
| Multi-Image to 3D (other, no texture) | 5 credits |
| Multi-Image to 3D (other, with texture) | 15 credits |
| Retexture | 10 credits |
| Remesh | 5 credits |
| Multi-Color Print | 10 credits |
| Analyze Printability | 0 (free) |
| Repair Printability | 10 credits |
| Auto-Rigging | 5 credits |
| Animation | 3 credits |
| Text to Image (nano-banana) | 3 credits |
| Text to Image (nano-banana-pro) | 9 credits |
| Image to Image (nano-banana) | 3 credits |
| Image to Image (nano-banana-pro) | 9 credits |
---
Error Handling
HTTP Status Codes
200 OK: Success.202 Accepted: Task created, processing not yet complete.400 Bad Request: Missing or invalid parameter.401 Unauthorized: Invalid API key.402 Payment Required: Insufficient credits.403 Forbidden: CORS or permission issue.404 Not Found: Resource not found.422 Unprocessable Entity: Valid request but cannot process (e.g., non-humanoid model for rigging).429 Too Many Requests: Rate limit exceeded.5xx: Server error.
Error Response Format
{"message": "Invalid model file extension: .3dm"}Task Failure
When status is "FAILED", check task_error.message:
"The server is busy. Please try again later."— Timeout or server overload. Retry with exponential backoff."Internal server error."— Processing failure. Verify inputs and retry.
Related skills
FAQ
What does meshy-3d-printing help developers produce?
meshy-3d-printing helps developers turn Meshy-generated models into print-ready STL or 3MF exports. The skill covers mesh validation, scale checks, and slicer handoff so AI geometry survives real fabrication.
When should meshy-3d-printing be invoked?
meshy-3d-printing fits when generative 3D output must become a physical prototype or product sample. Invoke it after Meshy asset generation and before sending files to a slicer or printer.