# SkinTokens for Agents: auto-rigging API on ZeroGPU (agent guide) > Give a 3D mesh (glb, gltf, obj or fbx); get back the same mesh rigged: a predicted skeleton plus per-vertex > skin weights, as a rigged GLB, a rig JSON, the weights as NPZ and a preview. One call, synchronous, typically > 20-60 s. Model: SkinTokens / TokenRig (Zhang, Pu, Guo, Cao, Hu; ACM TOG 2026), weights and code MIT. Base URL: https://townbox-skintokens-for-agents.hf.space Send `Authorization: Bearer $HF_TOKEN` (a Hugging Face account token) with every request: ZeroGPU time (tens of seconds per rig) is then billed to that account's daily quota, never to the Space. Without a token the small anonymous per-IP quota applies. GET https://townbox-skintokens-for-agents.hf.space/api/identity shows who pays for the headers you send. Interfaces (same functionality): - REST: OpenAPI at https://townbox-skintokens-for-agents.hf.space/openapi.json, Swagger UI at https://townbox-skintokens-for-agents.hf.space/docs, service description at https://townbox-skintokens-for-agents.hf.space/api. - MCP (streamable HTTP): https://townbox-skintokens-for-agents.hf.space/gradio_api/mcp/ (schema: https://townbox-skintokens-for-agents.hf.space/gradio_api/mcp/schema). Tools: rig (mesh_url or example), list_examples, get_result, view_rig (returns a PNG), service_info, guide. - Gradio clients (Python gradio_client, JS @gradio/client): the same functions as endpoints (/rig, ...); schema at https://townbox-skintokens-for-agents.hf.space/gradio_api/info. ## Workflow (REST) 1. Rig a mesh. Three ways to pass it: a) the file itself as the raw body (name it with ?filename=, the extension picks the importer): curl -s -X POST "https://townbox-skintokens-for-agents.hf.space/api/rig?filename=robot.glb&num_beams=10&seed=7" \ -H "Authorization: Bearer $HF_TOKEN" -H 'content-type: application/octet-stream' --data-binary @robot.glb b) a multipart form: curl -s -X POST https://townbox-skintokens-for-agents.hf.space/api/rig -H "Authorization: Bearer $HF_TOKEN" -F file=@robot.glb -F num_beams=10 c) JSON with a URL or a bundled example: curl -s -X POST https://townbox-skintokens-for-agents.hf.space/api/rig -H "Authorization: Bearer $HF_TOKEN" -H 'content-type: application/json' \ -d '{"mesh_url": "https://example.com/robot.glb", "num_beams": 10, "seed": 7}' curl -s -X POST https://townbox-skintokens-for-agents.hf.space/api/rig -H "Authorization: Bearer $HF_TOKEN" -H 'content-type: application/json' \ -d '{"example": "giraffe", "seed": 7}' -> 200 {"job_id": "r_...", "state": "done", "input": {"filename", "bytes", "n_vertices", "n_faces", "had_skeleton", ...}, "options": {"num_beams", "seed", "keep_skeleton", "preserve_original", ...}, "rig": {"n_joints", "root", "max_depth", "leaves", "bone_length_mean", "skin": {...}}, "urls": {"rigged_glb", "rig_json", "skin_npz", "preview_png", "preview_glb"}, "timing": {"input_s", "prepare_s", "gpu_s", "inference_s", "gpu_overhead_s", "export_s", "outputs_s"}, "provenance": {...}, "next": [...]} 2. Download the files you need (same Authorization header). Results stay 24 h: GET https://townbox-skintokens-for-agents.hf.space/api/jobs/. 3. Judge the rig: rig.json gives joint count, hierarchy depth, leaves and skin statistics; preview.png shows the skeleton over the mesh (front and side). If the skeleton looks wrong (too few joints, a chain where there should be limbs), rig again with another seed; num_beams 10-20 gives the most stable skeletons. ## Parameters (JSON body fields, multipart fields or query parameters) - num_beams (1-20, default 10): beam-search width of the autoregressive decoder. 10 is the authors' default; 1 is fastest but skeletons degrade; 20 is slower and rarely better than 10. - seed (integer, default random): the same mesh, options and seed reproduce the rig (sampling is seeded). - keep_skeleton (default false): the file already has an armature; keep it and predict only the skin weights. Fails with 400 when the file has no armature. - preserve_original (default false): transfer the predicted rig onto the untouched input mesh, so materials, textures, UVs, object names and world scale survive. Default false writes the mesh in the normalized frame (centered, scaled into [-1, 1]) without materials, which is what the model saw. - postprocess (default false): voxel-based masking of the skin weights before normalization; a few seconds of CPU, can help with weights bleeding across nearby but unconnected parts. - influences (1-8, default 4): bones kept per vertex in the GLB (the largest weights, renormalized). - top_k (1-200, default 5), top_p (0.1-1.0, default 0.95), temperature (0.1-2.0, default 1.0), repetition_penalty (0.5-3.0, default 2.0): sampling of the decoder; the defaults are the authors' settings. - preview (default true): also render preview.png and preview.glb (about 1-3 s of CPU). - filename (query parameter, raw-body uploads): the file name; .glb/.gltf/.obj/.fbx selects the importer (detected from the bytes when missing). mesh_url downloads http(s) URLs up to 60 MB. ## Outputs - rigged.glb: glTF 2.0 with the mesh, an armature (joint nodes named bone_0 ... bone_{J-1}, bone_0 is the root) and a skin (JOINTS_0 / WEIGHTS_0, at most `influences` bones per vertex). Opens in Blender (File > Import > glTF 2.0), three.js, Unity, Godot. Unless preserve_original is set, the mesh is in the normalized frame. - rig.json: {"n_joints", "joint_names", "parents" (-1 = root), "children", "depth", "root", "leaves", "max_depth", "joints_gltf" (rest positions in the GLB's frame, Y up), "joints_input_units_gltf" (the same positions in the input file's scale and origin), "joints_blender" (Z up, the model's frame), "bone_length_mean", "skin": {"vertices", "influences_per_vertex_mean", "influences_per_vertex_max", "dominant_weight_mean", "vertices_with_dominant_weight_below_0_5", "joints_with_no_skin"}, "frame": {"scale", "center_gltf", ...}}. joints_with_no_skin lists bones that influence no vertex (a sign of a spurious bone). - skin.npz: vertices (N, 3) float32 and faces (F, 3) int32 of the input mesh in the normalized Blender frame, skin (N, J) float16 weights before the per-vertex cut (rows sum to 1), joints (J, 3), parents (J), joint_names. Omitted when it would exceed 48 MB. - preview.png: the mesh (flat shaded) with the skeleton drawn over it, front view (x, z) and side view (y, z). MCP: view_rig(job_id) returns it. - preview.glb: rigged.glb plus visible bone geometry (an octahedron per bone, parented to its joint) and one animation "skin test (procedural wiggle)" that bends every bone with phase-shifted sines, so a glTF viewer shows whether the weights deform the mesh sensibly. A convenience for looking, not a deliverable. ## Examples GET https://townbox-skintokens-for-agents.hf.space/api/examples lists the bundled input meshes (giraffe: textured quadruped from the SkinTokens repository, MIT; fox, cesium_man, rigged_figure: Khronos glTF sample characters, CC BY 4.0, with their original rigs removed) and the real results this Space produced from them (job id, options, seed, GPU seconds, joint count, file URLs) with the exact POST body that reproduces each. Files are served at https://townbox-skintokens-for-agents.hf.space/examples/. They are a showcase only: every API call generates fresh results. ## Reading a result - state is always "done" (calls are synchronous); errors come back as HTTP 4xx/5xx {"error", "hint"}. - timing.gpu_s is the whole ZeroGPU call (queue, worker start, inference); timing.inference_s is the model forward alone (mesh encoder, token generation, skin decoding). timing.prepare_s is Blender reading the file and the point sampling; timing.export_s is Blender writing the rigged GLB. GET /api/gpu_check runs a 10 s probe. - n_tokens: length of the generated token sequence (skeleton tokens + 4 skin tokens per joint); the decoder stops at 2048 tokens, which bounds the skeleton at roughly 200 joints. - provenance: model repo and revision, checkpoint, code commit, the official Space revision the Blender worker and parser were vendored from, licenses. ## Errors and hints - 400 could not read the mesh / no triangle mesh / unsupported format / keep_skeleton without an armature / out-of-range parameter: the message says what to change. - 404 unknown job or example: results are kept 24 h (or until the Space restarts). - 413 upload or mesh_url larger than 60 MB. - 500 "retry with another seed": the decoder stopped before writing the skin tokens; rig again with another seed (or num_beams 10-20). 500 with "quota" or "GPU limit": your ZeroGPU quota is exhausted or no token reached the service; send `Authorization: Bearer $HF_TOKEN`, check GET /api/identity, or wait for the daily reset. 500 "no GPU was available": ZeroGPU had no free slice; retry in a moment. ## Limitations - One mesh per call; multi-object files are merged into one mesh. The model saw humanoids, animals and articulated objects (Articulation-XL and related data); very thin, disconnected or non-manifold parts can get odd bones or weights. Try a few seeds and keep the best rig.json statistics. - Joint names are generic (bone_i); there is no semantic labeling. The skeleton is a tree with a single root. - The rigged GLB is in the normalized frame unless preserve_original is set. ## Provenance and licenses - Paper: Skin Tokens: A Learned Compact Representation for Unified Autoregressive Rigging. Jia-peng Zhang, Cheng-Feng Pu, Meng-Hao Guo, Yan-Pei Cao, Shi-Min Hu. ACM Transactions on Graphics 2026. https://arxiv.org/abs/2602.04805 (successor of UniRig, SIGGRAPH 2025). - Code: https://github.com/VAST-AI-Research/SkinTokens (MIT), commit 273b691d. The inference path is the repository's own (src/, configs/), vendored from the authors' Space https://huggingface.co/spaces/VAST-AI/SkinTokens at revision 1f4445ba (which adds the file-based Blender worker used here); no model code was changed. - Weights: https://huggingface.co/VAST-AI/SkinTokens (MIT), revision 79736cad: experiments/articulation_xl_quantization_256_token_4/grpo_1400.ckpt (TokenRig, 595M parameters: Michelangelo mesh encoder + Qwen3-0.6B-architecture decoder) and experiments/skin_vae_2_10_32768/last.ckpt (skin-token FSQ-CVAE). Qwen/Qwen3-0.6B (Apache 2.0) supplies only the architecture and tokenizer configuration. - Blender: bpy 4.5.4 (GPL; the Python wheel committed in the authors' Space) reads inputs and writes the GLB. - Example meshes: see GET /api/examples (MIT repository example; Khronos glTF sample assets, CC BY 4.0).