
Meshy 3d Generation
- 1k installs
- 65 repo stars
- Updated July 31, 2026
- meshy-dev/meshy-3d-agent
Meshy 3D Generation is an Agent Skill that generates 3D models, textures, rigged characters, and animations from text or image prompts via the Meshy AI API for developers who need programmatic 3D asset creation inside co
About
Meshy 3D Generation is an MIT-licensed Agent Skill (version 1.0.0) that lets Claude Code, Cursor, and other Agent Skills-compatible tools create 3D assets through direct Meshy AI API HTTP calls. The skill covers API key detection, setup, and full workflows for text-to-3D, image-to-3D, texturing, rigging, and animation generation using Python 3 with the requests package. Developers reach for Meshy 3D Generation when a game, AR/VR, ecommerce, or agent project needs custom 3D models without opening Blender or a separate modeling pipeline. Allowed tools include Bash, Read, Write, Glob, and Grep, so agents can scaffold scripts, store outputs, and chain generation steps in the repo.
- Complete Meshy AI API lifecycle: setup, task creation, polling, download and multi-step chaining
- Automatic API key detection and environment configuration
- Supports text-to-3D, image-to-3D, texturing, rigging and character animation
- Direct HTTP calls with full endpoint coverage and error handling
- Dedicated routing: redirects all 3D printing workflows to meshy-3d-printing skill
Meshy 3d Generation by the numbers
- 1,033 all-time installs (skills.sh)
- +98 installs in the week ending Aug 4, 2026 (Skillselion tracking)
- Ranked #218 of 1,335 Generative Media 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-generationAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 1k |
|---|---|
| repo stars | ★ 65 |
| Last updated | July 31, 2026 |
| Repository | meshy-dev/meshy-3d-agent ↗ |
How do you generate rigged 3D models from prompts via API?
Generate 3D models, textures, rigged characters and animations directly from text or image prompts via the Meshy AI API.
Who is it for?
Game, AR/VR, and 3D web developers who want agent-driven asset pipelines tied to the Meshy AI API instead of manual DCC tooling.
Skip if: Developers who only need 2D images, lack a Meshy API key, or require offline proprietary mesh editing without cloud generation.
When should I use this skill?
The user asks to create 3D models, convert text or images to 3D, texture models, rig or animate characters, or interact with the Meshy API.
What you get
Textured 3D models, rigged characters, animation files, and Python scripts calling the Meshy AI API with configured credentials.
- 3D model files
- rigged character assets
- Python API integration scripts
By the numbers
- Ships as MIT-licensed Agent Skill version 1.0.0
- Requires Python 3 with the requests package
Files
Meshy 3D Generation
Directly communicate with the Meshy AI API to generate 3D assets. This skill handles the complete lifecycle: environment setup, API key detection, task creation, polling, downloading, and chaining multi-step pipelines.
For full endpoint reference (all parameters, response schemas, error codes), read reference.md.
---
IMPORTANT: 3D Printing → Use meshy-3d-printing Skill
If the user's request involves 3D printing (keywords: print, 3d print, slicer, slice, bambu, orca, prusa, cura, multicolor, 3mf, figurine, miniature, statue, physical model), use the `meshy-3d-printing` skill instead of this one for the entire workflow. The printing skill handles generation with correct print-optimized parameters (e.g. target_formats with "3mf" for multicolor), slicer detection, coordinate conversion, and slicer launch — all in one pipeline.
This skill's create_task/poll_task/download template functions are reused by the printing skill, but the workflow orchestration (what to generate, which formats, what to do after) must come from the printing skill when printing is involved.
Do NOT generate a model with this skill and then hand off to the printing skill — the printing skill needs to control parameters from the start (e.g. target_formats, should_texture).
---
IMPORTANT: First-Use Session Notice
When this skill is first activated in a session, inform the user:
All generated files will be saved tomeshy_output/in the current working directory. Each project gets its own folder ({YYYYMMDD_HHmmss}_{prompt}_{id}/) with model files, textures, thumbnails, and metadata. History is tracked inmeshy_output/history.json.
This only needs to be said once per session, at the beginning.
IMPORTANT: File Organization
All downloaded files MUST go into a structured meshy_output/ directory in the current working directory. Do NOT scatter files randomly.
- Each project gets its own folder:
meshy_output/{YYYYMMDD_HHmmss}_{prompt_slug}_{task_id_prefix}/ - For chained tasks (preview → refine → rig), reuse the same
project_dir - Track tasks in
metadata.jsonper project, and globalhistory.json - Auto-download thumbnails alongside models
The Reusable Script Template below includes get_project_dir(), record_task(), and save_thumbnail() helpers.
---
IMPORTANT: Shell Command Rules
Use only standard POSIX tools in shell commands. Do NOT use rg (ripgrep), fd, or other non-standard CLI tools — they may not be installed. Use these standard alternatives instead:
| Do NOT use | Use instead |
|---|---|
rg | grep |
fd | find |
bat | cat |
exa / eza | ls |
---
IMPORTANT: Run Long Tasks Properly
Meshy generation tasks take 1–5 minutes. When running Python scripts that poll for completion:
- Write the entire create → poll → download flow as ONE Python script and execute it in a single Bash call. Do NOT split into multiple commands. This keeps the API key, task IDs, and session in one process context.
- Use
python3 -u script.py(unbuffered) so progress output is visible in real time. - Be patient with long-running scripts — do NOT interrupt or kill them prematurely. Tasks at 99% for 30–120s is normal finalization, not a failure.
---
Step 0: Environment Detection (ALWAYS RUN FIRST)
Before any API call, detect whether the environment is ready:
echo "=== Meshy API Key Detection ==="
# 1. Check current env var
if [ -n "$MESHY_API_KEY" ]; then
echo "ENV_VAR: FOUND (${MESHY_API_KEY:0:8}...)"
else
echo "ENV_VAR: NOT_FOUND"
fi
# 2. Check .env files in workspace
for f in .env .env.local; do
if [ -f "$f" ] && grep -q "MESHY_API_KEY" "$f" 2>/dev/null; then
echo "DOTENV($f): FOUND"
export $(grep "MESHY_API_KEY" "$f" | head -1)
fi
done
# 3. Check shell profiles
for f in ~/.zshrc ~/.bashrc ~/.bash_profile ~/.profile; do
if [ -f "$f" ] && grep -q "MESHY_API_KEY" "$f" 2>/dev/null; then
echo "SHELL_PROFILE: FOUND in $f"
fi
done
# 4. Final status
if [ -n "$MESHY_API_KEY" ]; then
echo "READY: key=${MESHY_API_KEY:0:12}..."
else
echo "READY: NO_KEY_FOUND"
fi
# 5. Python requests check
python3 -c "import requests; print('PYTHON_REQUESTS: OK')" 2>/dev/null || echo "PYTHON_REQUESTS: MISSING (run: pip install requests)"
echo "=== Detection Complete ==="Decision After Detection
- Key found → Proceed to Step 1.
- Key NOT found → Go to Step 0a.
- Python requests missing → Run
pip install requests.
Step 0a: API Key Setup (Only If No Key Found)
Tell the user:
To use the Meshy API, you need an API key. Here's how to get one:
>
1. Go to https://www.meshy.ai/settings/api
2. Click "Create API Key", give it a name, and copy the key (it starts with msy_)3. The key is only shown once — save it somewhere safe
>
Note: API access requires a Pro plan or above. Free-tier accounts cannot create API keys. If you see "Please upgrade to a premium plan to create API tasks", you'll need to upgrade at https://www.meshy.ai/pricing first.
Once the user provides their key, set it and verify:
macOS (zsh):
export MESHY_API_KEY="msy_PASTE_KEY_HERE"
# Verify
STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
-H "Authorization: Bearer $MESHY_API_KEY" \
https://api.meshy.ai/openapi/v1/balance)
if [ "$STATUS" = "200" ]; then
BALANCE=$(curl -s -H "Authorization: Bearer $MESHY_API_KEY" https://api.meshy.ai/openapi/v1/balance)
echo "Key valid. $BALANCE"
echo 'export MESHY_API_KEY="msy_PASTE_KEY_HERE"' >> ~/.zshrc
echo "Persisted to ~/.zshrc"
else
echo "Key invalid (HTTP $STATUS). Check the key and try again."
fiLinux (bash):
export MESHY_API_KEY="msy_PASTE_KEY_HERE"
# Verify (same as above), then persist to ~/.bashrc
STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
-H "Authorization: Bearer $MESHY_API_KEY" \
https://api.meshy.ai/openapi/v1/balance)
if [ "$STATUS" = "200" ]; then
BALANCE=$(curl -s -H "Authorization: Bearer $MESHY_API_KEY" https://api.meshy.ai/openapi/v1/balance)
echo "Key valid. $BALANCE"
echo 'export MESHY_API_KEY="msy_PASTE_KEY_HERE"' >> ~/.bashrc
echo "Persisted to ~/.bashrc"
else
echo "Key invalid (HTTP $STATUS). Check the key and try again."
fiWindows (PowerShell):
$env:MESHY_API_KEY = "msy_PASTE_KEY_HERE"
# Verify
$status = (Invoke-WebRequest -Uri "https://api.meshy.ai/openapi/v1/balance" -Headers @{Authorization="Bearer $env:MESHY_API_KEY"} -UseBasicParsing).StatusCode
if ($status -eq 200) {
Write-Host "Key valid."
# Persist permanently
[System.Environment]::SetEnvironmentVariable("MESHY_API_KEY", $env:MESHY_API_KEY, "User")
Write-Host "Persisted to user environment variables. Restart terminal to take effect."
} else {
Write-Host "Key invalid (HTTP $status). Check the key and try again."
}Alternative (all platforms): Create a .env file in your project root:
MESHY_API_KEY=msy_PASTE_KEY_HERE---
Step 1: Confirm Plan With User Before Spending Credits
CRITICAL: Before creating any task, present the user with a summary and get confirmation:
I'll generate a 3D model of "<prompt>" using the following plan:
1. Preview (mesh generation) — 5-20 credits (meshy-6/lowpoly: 20, others: 5)
2. Refine (texturing with PBR) — 10 credits
3. Download as .glb
Total cost: 30 credits
Current balance: <N> credits
Shall I proceed?For multi-step pipelines (e.g., text-to-3d → rig → animate), present the FULL pipeline cost upfront:
| Step | API | Credits |
|---|---|---|
| Preview | Text to 3D | 20 |
| Refine | Text to 3D | 10 |
| Rig | Auto-Rigging | 5 |
| Total | 35 |
Note: Rigging automatically includes basic walking + running animations for free (inresult.basic_animations). Only addAnimate(3 credits) if the user needs a custom animation beyond walking/running.
Wait for user confirmation before executing.
Intent → API Mapping
| User wants to... | API | Endpoint | Credits |
|---|---|---|---|
| 3D model from text | Text to 3D | POST /openapi/v2/text-to-3d | 5–20 (preview) + 10 (refine) |
| 3D model from one image | Image to 3D | POST /openapi/v1/image-to-3d | 5–30 |
| 3D model from multiple images | Multi-Image to 3D | POST /openapi/v1/multi-image-to-3d | 5–30 |
| New textures on existing model | Retexture | POST /openapi/v1/retexture | 10 |
| Change mesh format/topology | Remesh | POST /openapi/v1/remesh | 5 |
| Add skeleton to character | Auto-Rigging | POST /openapi/v1/rigging | 5 (includes walking + running) |
| Animate a rigged character (custom) | Animation | POST /openapi/v1/animations | 3 |
| 2D image from text (recommended pre-step before image-to-3d) | Text to Image | POST /openapi/v1/text-to-image | 3–9 |
| Optimize/edit a 2D image (recommended pre-step before image-to-3d) | Image to Image | POST /openapi/v1/image-to-image | 3–9 |
| Check FDM printability (watertight / non-manifold edges / holes) | Analyze Printability | POST /openapi/v1/print/analyze | 0 (free) |
| Repair non-manifold/degenerate-face/hole topology | Repair Printability | POST /openapi/v1/print/repair | 10 |
| Multi-color 3D print | Multi-Color Print | POST /openapi/v1/print/multi-color | 10 |
| Check credit balance | Balance | GET /openapi/v1/balance | 0 |
---
Step 2: Execute the Workflow
CRITICAL: Async Task Model
All generation endpoints return {"result": "<task_id>"}, NOT the model. You MUST poll.
NEVER read model_urls from the POST response.
Reusable Script Template
Use this as the base for ALL generation workflows:
#!/usr/bin/env python3
"""Meshy API task runner. Handles create → poll → download."""
import requests, time, os, sys
API_KEY = os.environ.get("MESHY_API_KEY", "")
if not API_KEY:
sys.exit("ERROR: MESHY_API_KEY not set")
BASE = "https://api.meshy.ai"
HEADERS = {"Authorization": f"Bearer {API_KEY}"}
SESSION = requests.Session()
SESSION.trust_env = False # bypass any system proxy settings
def create_task(endpoint, payload):
resp = SESSION.post(f"{BASE}{endpoint}", headers=HEADERS, json=payload, timeout=30)
if resp.status_code == 401:
sys.exit("ERROR: Invalid API key (401)")
if resp.status_code == 402:
try:
bal = SESSION.get(f"{BASE}/openapi/v1/balance", headers=HEADERS, timeout=10)
balance = bal.json().get("balance", "unknown")
sys.exit(f"ERROR: Insufficient credits (402). Current balance: {balance}. Top up at https://www.meshy.ai/pricing")
except Exception:
sys.exit("ERROR: Insufficient credits (402). Check balance at https://www.meshy.ai/pricing")
if resp.status_code == 429:
sys.exit("ERROR: Rate limited (429). Wait and retry.")
resp.raise_for_status()
task_id = resp.json()["result"]
print(f"TASK_CREATED: {task_id}")
return task_id
def poll_task(endpoint, task_id, timeout=300):
"""Poll task with exponential backoff (5s→30s, fixed 15s at 95%+)."""
elapsed = 0
delay = 5 # Initial delay: 5s
max_delay = 30 # Cap: 30s
backoff = 1.5 # Backoff multiplier
finalize_delay = 15 # Fixed delay during finalization (95%+)
poll_count = 0
while elapsed < timeout:
poll_count += 1
resp = SESSION.get(f"{BASE}{endpoint}/{task_id}", headers=HEADERS, timeout=30)
resp.raise_for_status()
task = resp.json()
status = task["status"]
progress = task.get("progress", 0)
filled = int(progress / 5)
bar = f"[{'█' * filled}{'░' * (20 - filled)}] {progress}%"
print(f" {bar} — {status} ({elapsed}s, poll #{poll_count})", flush=True)
if status == "SUCCEEDED":
return task
if status in ("FAILED", "CANCELED"):
msg = task.get("task_error", {}).get("message", "Unknown")
sys.exit(f"TASK_{status}: {msg}")
current_delay = finalize_delay if progress >= 95 else delay
time.sleep(current_delay)
elapsed += current_delay
if progress < 95:
delay = min(delay * backoff, max_delay)
sys.exit(f"TIMEOUT after {timeout}s ({poll_count} polls)")
def download(url, filepath):
"""Download a file to the given path (within a project directory)."""
os.makedirs(os.path.dirname(filepath), exist_ok=True)
print(f"Downloading {filepath}...", flush=True)
resp = SESSION.get(url, timeout=300, stream=True)
resp.raise_for_status()
with open(filepath, "wb") as f:
for chunk in resp.iter_content(chunk_size=8192):
f.write(chunk)
size_mb = os.path.getsize(filepath) / (1024 * 1024)
print(f"DOWNLOADED: {filepath} ({size_mb:.1f} MB)")
# --- File organization helpers (see File Organization section above) ---
import re, json
from datetime import datetime
OUTPUT_ROOT = os.path.join(os.getcwd(), "meshy_output")
os.makedirs(OUTPUT_ROOT, exist_ok=True)
HISTORY_FILE = os.path.join(OUTPUT_ROOT, "history.json")
def get_project_dir(task_id, prompt="", task_type="model"):
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
slug = re.sub(r'[^a-z0-9]+', '-', (prompt or task_type).lower())[:30].strip('-')
folder = f"{timestamp}_{slug}_{task_id[:8]}"
project_dir = os.path.join(OUTPUT_ROOT, folder)
os.makedirs(project_dir, exist_ok=True)
return project_dir
def record_task(project_dir, task_id, task_type, stage, prompt="", files=None):
meta_path = os.path.join(project_dir, "metadata.json")
if os.path.exists(meta_path):
meta = json.load(open(meta_path))
else:
meta = {"project_name": prompt or task_type, "folder": os.path.basename(project_dir),
"root_task_id": task_id, "created_at": datetime.now().isoformat(),
"updated_at": datetime.now().isoformat(), "tasks": []}
meta["tasks"].append({"task_id": task_id, "task_type": task_type, "stage": stage,
"files": files or [], "created_at": datetime.now().isoformat()})
meta["updated_at"] = datetime.now().isoformat()
json.dump(meta, open(meta_path, "w"), indent=2)
# Update global history
if os.path.exists(HISTORY_FILE):
history = json.load(open(HISTORY_FILE))
else:
history = {"version": 1, "projects": []}
folder = os.path.basename(project_dir)
entry = next((p for p in history["projects"] if p["folder"] == folder), None)
if entry:
entry["task_count"] = len(meta["tasks"])
entry["updated_at"] = meta["updated_at"]
else:
history["projects"].append({"folder": folder, "prompt": prompt, "task_type": task_type,
"root_task_id": task_id, "created_at": meta["created_at"],
"updated_at": meta["updated_at"], "task_count": len(meta["tasks"])})
json.dump(history, open(HISTORY_FILE, "w"), indent=2)
def save_thumbnail(project_dir, url):
path = os.path.join(project_dir, "thumbnail.png")
if os.path.exists(path): return
try:
r = SESSION.get(url, timeout=15); r.raise_for_status()
open(path, "wb").write(r.content)
except Exception: passText to 3D (Preview + Refine)
Append this to the template above and run as one script:
PROMPT = "USER_PROMPT" # max 600 chars
# --- Preview ---
preview_id = create_task("/openapi/v2/text-to-3d", {
"mode": "preview",
"prompt": PROMPT,
"ai_model": "latest",
# "model_type": "standard", # "standard" | "lowpoly"
# "topology": "triangle", # "triangle" | "quad"
# "target_polycount": 30000, # 100–300000
# "should_remesh": False,
# "symmetry_mode": "auto", # "auto" | "on" | "off"
# "pose_mode": "t-pose", # "" | "a-pose" | "t-pose" (use "t-pose" if rigging/animating later)
})
task = poll_task("/openapi/v2/text-to-3d", preview_id)
project_dir = get_project_dir(preview_id, prompt=PROMPT)
download(task["model_urls"]["glb"], os.path.join(project_dir, "preview.glb"))
record_task(project_dir, preview_id, "text-to-3d", "preview", prompt=PROMPT, files=["preview.glb"])
if task.get("thumbnail_url"):
save_thumbnail(project_dir, task["thumbnail_url"])
print(f"\nPREVIEW COMPLETE")
print(f" Task ID: {preview_id}")
print(f" Project: {project_dir}")
print(f" Formats: {', '.join(task['model_urls'].keys())}")
# --- Refine ---
refine_id = create_task("/openapi/v2/text-to-3d", {
"mode": "refine",
"preview_task_id": preview_id,
"enable_pbr": True,
"ai_model": "latest",
# "texture_prompt": "",
# "remove_lighting": True, # Remove baked lighting (meshy-6/latest only, default True)
})
task = poll_task("/openapi/v2/text-to-3d", refine_id)
download(task["model_urls"]["glb"], os.path.join(project_dir, "refined.glb"))
record_task(project_dir, refine_id, "text-to-3d", "refined", prompt=PROMPT, files=["refined.glb"])
print(f"\nREFINE COMPLETE")
print(f" Task ID: {refine_id}")
print(f" Project: {project_dir}")
print(f" Formats: {', '.join(task['model_urls'].keys())}")Refine compatibility: All models (meshy-5, meshy-6, latest) support both preview and refine. The preview and refine ai_model should match — mismatched models may return 400 (model mismatch).(Optional but strongly recommended) 2D Optimization Pre-Step
Image quality directly determines 3D model quality. Before calling /openapi/v1/image-to-3d or /openapi/v1/multi-image-to-3d, evaluate the user's input and proactively suggest a 2D pass:
| User input | Recommended pre-step |
|---|---|
| Only a text description, no reference image | /openapi/v1/text-to-image with nano-banana-pro. For characters add generate_multi_view: True and pose_mode: "a-pose" or "t-pose" for rig-friendly output. |
| Reference image is low-resolution / cluttered background / unclear subject / bad lighting | /openapi/v1/image-to-image with nano-banana-pro to clean up (remove background, raise resolution, normalize lighting, fill occlusions). |
| User wants to adjust style / colors / details | /openapi/v1/image-to-image for style transfer, then 3D-ify. |
The optimized image URL feeds directly into /openapi/v1/image-to-3d's image_url. 3-9 extra credits typically buy a noticeable quality bump, and downstream refine / texture-on-mesh stages benefit too.
Skip when: the user already provided a clean front-facing studio shot — go straight to image-to-3d.
# Example: text-only request → text-to-image → image-to-3d
img_id = create_task("/openapi/v1/text-to-image", {
"ai_model": "nano-banana-pro",
"prompt": "studio render of a sci-fi helmet, neutral background, even lighting",
"aspect_ratio": "1:1",
# "generate_multi_view": True, # for character meshes use multi-view + pose_mode
})
img_task = poll_task("/openapi/v1/text-to-image", img_id)
generated_image_url = img_task["image_urls"][0] # use as input for image-to-3d belowImage to 3D
import base64
# For local files, convert to data URI:
# with open("photo.jpg", "rb") as f:
# image_url = "data:image/jpeg;base64," + base64.b64encode(f.read()).decode()
task_id = create_task("/openapi/v1/image-to-3d", {
"image_url": "IMAGE_URL_OR_DATA_URI",
"should_texture": True,
"enable_pbr": True, # Default is False; set True for metallic/roughness/normal maps
"ai_model": "latest",
# "image_enhancement": True, # Optimize input image (meshy-6/latest only, default True)
# "remove_lighting": True, # Remove baked lighting from texture (meshy-6/latest only, default True)
})
task = poll_task("/openapi/v1/image-to-3d", task_id)
download(task["model_urls"]["glb"], "model.glb")Multi-Image to 3D
task_id = create_task("/openapi/v1/multi-image-to-3d", {
"image_urls": ["URL_1", "URL_2", "URL_3"], # 1–4 images
"should_texture": True,
"enable_pbr": True, # Default is False; set True for metallic/roughness/normal maps
"ai_model": "latest",
# "image_enhancement": True, # Optimize input images (meshy-6/latest only, default True)
# "remove_lighting": True, # Remove baked lighting from texture (meshy-6/latest only, default True)
})
task = poll_task("/openapi/v1/multi-image-to-3d", task_id)
download(task["model_urls"]["glb"], "model.glb")Retexture
IMPORTANT: Before calling, ask the user to provide a texture style:
- Text prompt: e.g. "rusty metal", "cartoon style" →
text_style_prompt - Reference image: URL of style image →
image_style_url
One of these is required. If both provided, image_style_url takes precedence.
# REQUIRED: ask user for text_style_prompt OR image_style_url before calling
task_id = create_task("/openapi/v1/retexture", {
"input_task_id": "PREVIOUS_TASK_ID", # or "model_url": "URL"
"text_style_prompt": "wooden texture", # REQUIRED if no image_style_url
# "image_style_url": "URL", # REQUIRED if no text_style_prompt (takes precedence)
"enable_pbr": True,
# "remove_lighting": True, # Remove baked lighting (meshy-6/latest only, default True)
# "target_formats": ["glb", "3mf"], # 3mf must be explicitly requested
# "auto_size": True, # AI auto-estimate real-world height
})
task = poll_task("/openapi/v1/retexture", task_id)
download(task["model_urls"]["glb"], "retextured.glb")Remesh / Format Conversion
task_id = create_task("/openapi/v1/remesh", {
"input_task_id": "TASK_ID",
"target_formats": ["glb", "fbx", "obj"],
"topology": "quad",
"target_polycount": 10000,
})
task = poll_task("/openapi/v1/remesh", task_id)
for fmt, url in task["model_urls"].items():
download(url, f"remeshed.{fmt}")Auto-Rigging + Animation
IMPORTANT: When the user explicitly asks to rig or animate, the generation step (text-to-3d / image-to-3d) MUST use `pose_mode: "t-pose"` for best rigging results. If the model was already generated without t-pose, recommend regenerating with pose_mode: "t-pose" first.
Before rigging, verify the model's polygon count is under 300,000. The script should auto-check and block if exceeded:
# Pre-rig check: verify face count (MUST be ≤ 300,000)
source_endpoint = "/openapi/v2/text-to-3d" # adjust to match the source task's endpoint
source_task_id = "TASK_ID"
check_resp = SESSION.get(f"{BASE}{source_endpoint}/{source_task_id}", headers=HEADERS, timeout=30)
check_resp.raise_for_status()
source = check_resp.json()
face_count = source.get("face_count", 0)
if face_count > 300000:
print(f"ERROR: Model has {face_count:,} faces (limit: 300,000). Remesh first:")
print(f" create_task('/openapi/v1/remesh', {{'input_task_id': '{source_task_id}', 'target_polycount': 100000}})")
sys.exit("Rigging blocked: face count too high")# Rig (humanoid bipedal characters only, polycount must be ≤ 300,000)
rig_id = create_task("/openapi/v1/rigging", {
"input_task_id": "TASK_ID",
"height_meters": 1.7,
})
rig_task = poll_task("/openapi/v1/rigging", rig_id)
download(rig_task["result"]["rigged_character_glb_url"], "rigged.glb")
# Rigging automatically includes basic walking + running animations — download them:
download(rig_task["result"]["basic_animations"]["walking_glb_url"], "walking.glb")
download(rig_task["result"]["basic_animations"]["running_glb_url"], "running.glb")
# Only call meshy_animate if you need a CUSTOM animation beyond walking/running:
# anim_id = create_task("/openapi/v1/animations", {
# "rig_task_id": rig_id,
# "action_id": 1, # from Animation Library
# })
# anim_task = poll_task("/openapi/v1/animations", anim_id)
# download(anim_task["result"]["animation_glb_url"], "animated.glb")Text to Image / Image to Image
# Text to Image
task_id = create_task("/openapi/v1/text-to-image", {
"ai_model": "nano-banana-pro",
"prompt": "a futuristic spaceship",
})
task = poll_task("/openapi/v1/text-to-image", task_id)
# Result: task["image_url"]
# Image to Image
task_id = create_task("/openapi/v1/image-to-image", {
"ai_model": "nano-banana-pro",
"prompt": "make it look cyberpunk",
"reference_image_urls": ["URL"],
})
task = poll_task("/openapi/v1/image-to-image", task_id)---
Step 3: Report Results
After task succeeds, report:
1. Downloaded file paths and sizes 2. Task IDs (for follow-up operations like refine, rig, retexture) 3. Available formats (list model_urls keys — may include glb, fbx, obj, usdz, 3mf) 4. Thumbnail URL if present 5. Credits consumed and remaining balance (run balance check) 6. Suggested next steps:
- Preview done → "Want to refine (add textures)?"
- Model done → "Want to rig this character for animation?"
- Rigged → "Want to apply an animation?"
- Any model → "Want to remesh / export to another format?"
- Any textured model → "Want to 3D print this? Multicolor printing is available!" (requires
meshy-3d-printingskill) - Any model → "Want to 3D print this model?" (requires
meshy-3d-printingskill)
---
Error Recovery
| HTTP Status | Meaning | Action |
|---|---|---|
| 401 | Invalid API key | Re-run Step 0; ask user to check key |
| 402 | Insufficient credits | Auto-query balance (GET /openapi/v1/balance), show current balance, link https://www.meshy.ai/pricing |
| 422 | Cannot process | Explain limitation (e.g., non-humanoid for rigging) |
| 429 | Rate limited | Auto-retry after 5s (max 3 times) |
| 5xx | Server error | Auto-retry after 10s (once) |
Task FAILED messages:
"The server is busy..."→ retry with backoff (5s, 10s, 20s)"Internal server error."→ simplify prompt, retry once
---
Known Behaviors & Constraints
- 99% progress stall: Tasks commonly sit at 99% for 30–120s during finalization. This is normal. Do NOT kill or restart.
- CORS: API blocks browser requests. Always server-side.
- Asset retention: Files deleted after 3 days (non-Enterprise). Download immediately.
- PBR maps: Must set
enable_pbr: trueexplicitly. - Format availability: Check keys in
model_urlsbefore downloading — not all formats are always present. 3MF is available from the Multi-Color Print API. - Download format: ALWAYS ask the user which format they need before downloading. Recommend: GLB (viewing), OBJ (white model printing), 3MF (multicolor printing), FBX (game engines), USDZ (AR). Do NOT download all formats.
- 3MF format: 3MF is NOT included in default output of generation endpoints. To get 3MF from generate/remesh/retexture, pass
"3mf"intarget_formats. For multicolor 3D printing, the Multi-Color Print API outputs 3MF directly — no need to request it from generate/refine. - Timestamps: All API timestamps are Unix epoch milliseconds.
- Large files: Refined models can be 50–200 MB. Use streaming downloads with timeouts.
---
Execution Checklist
- [ ] Ran environment detection (Step 0)
- [ ] API key present and verified
- [ ] Presented cost summary and got user confirmation
- [ ] Wrote complete workflow as single Python script
- [ ] Ran script with
python3 -ufor unbuffered output - [ ] Reported file paths, formats, task IDs, and balance
- [ ] Suggested next steps
Additional Resources
For the complete API endpoint reference including all parameters, response schemas, deprecated fields, and detailed error codes, 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.
Alias note: The historical "text-to-texture" feature has been renamed to retexture. The public docs URL/api/text-to-textureredirects to/api/retexture, and there is no separate/openapi/v1/text-to-textureendpoint — use/openapi/v1/retexturefor both text-style-prompt and image-style-url retexturing.
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
---
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
Required parameters:
input_task_id(string): ID of a completed task with textures (Text to 3D refine, Image to 3D with texture, or Retexture).
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,
})GET /openapi/v1/print/multi-color/:id — Retrieve Task
Returns the task object including status, progress, model_urls, texture_urls.
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://..."}), texture_urls, 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 |
| 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 |
| Analyze Printability | 0 (free) |
| Repair Printability | 10 credits |
---
Print Automation APIs
POST /openapi/v1/print/analyze — Create Analyze Printability Task
Cost: FREE (0 credits).
Provide exactly one of:
input_task_id(string): A SUCCEEDED task you own. Supported task types: 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. Must usehttp,https, ordata:URL.
Returns: {"result": "<task_id>"}.
GET /openapi/v1/print/analyze/:id — Retrieve Analyze Task
Once SUCCEEDED, the task object contains a printability block with:
status:"healthy"/"warning"/"error"/"unknown"issue_count,error_count,warning_countmetrics:{ is_watertight, volume, non_manifold_edges, degenerate_faces, holes }- Errors triggered by: non-watertight, non-positive volume, or non-manifold edges. Recommend repair before printing.
- Warnings triggered by: degenerate faces or holes. Repair optional.
DELETE /openapi/v1/print/analyze/:id — Delete Analyze Task
GET /openapi/v1/print/analyze — List Analyze Tasks
GET /openapi/v1/print/analyze/:id/stream — Stream Analyze Task
---
POST /openapi/v1/print/repair — Create Repair Printability Task
Cost: 10 credits.
Repairs non-manifold edges, degenerate faces, holes, and ensures watertightness. Output format mirrors input format.
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.
Returns: {"result": "<task_id>"}.
GET /openapi/v1/print/repair/:id — Retrieve Repair Task
Once SUCCEEDED, model_urls contains the repaired model in the same format as the input. Other format fields are empty strings. Textures are NOT preserved (geometry-only repair). consumed_credits: 10.
DELETE /openapi/v1/print/repair/:id — Delete Repair Task
GET /openapi/v1/print/repair — List Repair Tasks
GET /openapi/v1/print/repair/:id/stream — Stream Repair Task
---
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
How it compares
Choose Meshy 3D Generation when you need API-driven 3D asset pipelines inside a coding agent rather than general image generation or manual mesh editing skills.
FAQ
What does Meshy 3D Generation require to run?
Meshy 3D Generation requires Python 3 with the requests package and a valid Meshy AI API key. The skill detects and configures the key, then issues direct HTTP calls for model, texture, rig, and animation workflows.
Which coding agents support Meshy 3D Generation?
Meshy 3D Generation is compatible with Claude Code, Cursor, and any Agent Skills-compatible tool per its v1.0.0 manifest. Allowed tools include Bash, Read, Write, Glob, and Grep for scripting and file output.