Meshy API — verified animated-character pipeline (FALLBACK ONLY)

STOP — check the native CLI path first. If image_to_3d and 3d_rigging appear in higgsfield model list, do NOT use this raw API and do NOT ask the user for any API key. The native tool covers this entire pipeline with workspace credits: stage [1] = model image_to_3d (should_texture:true), stage [2] = enable_rigging:true (same job), stage [3] = one clip per job via enable_animation:true + animation_action_id (extra clips: model 3d_rigging on the result GLB; ids via higgsfield preset list animation-action), stage [4] = the same local merge script. This document exists only for environments with no native 3D tool.

Verified end-to-end 2026-06-10 (image → 3D → rig → 4 clips → merged GLB). Endpoint shapes re-checked against docs.meshy.ai 2026-06-11.

Before building ANY request, read meshy-input-rules.md (single-figure rule, cropping, low-poly params). Skipping it wastes credits.

Basics

Pipeline overview (humanoid)

input image ──> [1] image-to-3d ──> [2] rigging ──> [3] animations (xN) ──> [4] merge clips
 (validated)      static GLB         rigged GLB        1 GLB per clip          1 multi-clip GLB
                                     + walk/run free                           (Blender CLI)

Cost observed: ~85 credits for a full character (image-to-3d + rig + 2 library animations). Each extra library animation ≈ 5 credits.

[1] Image → 3D

POST /openapi/v1/image-to-3d

{
  "image_url": "<public URL or data URI>",
  "pose_mode": "t-pose",
  "should_remesh": true,
  "topology": "triangle",
  "target_polycount": 20000,
  "should_texture": true,
  "enable_pbr": false,
  "target_formats": ["glb"],
  "multi_view_thumbnails": true
}

text-to-3d note (two-phase) + stuck-refine recovery

text-to-3d is two tasks: preview (geometry, gray) then refine (texture, created with preview_task_id). Verified failure mode: a refine stuck at IN_PROGRESS progress: 0 for 1.5 h — a queue jam on Meshy's side.

Recovery recipe: - refine with progress: 0 for longer than ~15 min ⇒ stuck forever. - DELETE /openapi/v1/text-to-3d/<id> the stuck task, re-create with the same preview_task_id — re-ran in ~70 s. Credits for stuck/FAILED tasks are refunded. - Never block the pipeline on texture: run rigging + animations on the preview (gray) mesh in parallel; swap in the textured mesh at the end with the same scripts.

[2] Rigging

POST /openapi/v1/rigging

{
  "input_task_id": "<image-to-3d task id>",   // OR "model_url": "<public GLB url>"
  "height_meters": 1.8
}

[3] Animations (library retarget)

POST /openapi/v1/animations

{
  "rig_task_id": "<rigging task id>",
  "action_id": 0
}

[4] Merge clips into one multi-clip GLB

Meshy gives N single-clip GLBs (walk/run from the rig + one per animation task). Preferred: stdlib merger, no Blender (verified on a live run 2026-06-11 — full pipeline cost 41 credits: text-to-3d + rig + 2 anims):

python3 scripts/glb_merge_anims.py \
  rigged.glb walk.glb:Walk run.glb:Run idle.glb:Idle attack.glb:Attack out.glb

Blender alternative (same semantics):

blender -b -P scripts/merge_anim_glbs.py -- \
  rigged.glb walk.glb:Walk run.glb:Run idle.glb:Idle attack.glb:Attack out.glb

Note: rigged.glb may carry a service clip named like Armature|clip0|baselayer (bind pose) — drop it from the merged file before delivery.

MANDATORY post-merge check — root-bone scale bug (verified): library animations can bake a scale factor into the root bone's scale channel. Observed: Hips scale 1.176 baked into idle while walk/run were 1.0 — the character visibly grew 18% when idle played. Fix (automated in the merge script): for every clip, if the root bone's scale min/max ≠ 1.0 — set scale keys to 1.0 AND divide that clip's root translation keys by the same factor (otherwise the character hovers above ground). Verify with scripts/glb_inspect.py — it prints per-clip root-scale ranges.

Finish with the standard checks: glb_inspect.py (skins ≥ 1, all clips present, alphaMode OPAQUE) and zip before higgsfield upload create (raw .glb is a blocked extension).

Polling skeleton (bash)

TASK=$(curl -s -X POST https://api.meshy.ai/openapi/v1/image-to-3d \
  -H "Authorization: Bearer $MESHY_API_KEY" -H 'Content-Type: application/json' \
  -d @payload.json | python3 -c 'import sys,json;print(json.load(sys.stdin)["result"])')
while :; do
  S=$(curl -s https://api.meshy.ai/openapi/v1/image-to-3d/$TASK \
    -H "Authorization: Bearer $MESHY_API_KEY")
  ST=$(echo "$S" | python3 -c 'import sys,json;d=json.load(sys.stdin);print(d["status"],d["progress"])')
  echo "$ST"; case "$ST" in SUCCEEDED*|FAILED*|CANCELED*) break;; esac
  sleep 12
done

Error cheat-sheet

Code Meaning Action
400 bad params / unreachable image URL / >300k faces on rig fix payload; remesh first
402 out of credits tell user, show balance
422 (rigging) pose estimation failed — non-humanoid or unclear limbs procedural branch / Tripo
429 rate limit back off, retry
refine stuck progress 0 >15 min Meshy queue jam DELETE + recreate with same preview_task_id