Retrieve a job
Fetch the current state of a single SimReady conversion job. This is what you poll after uploading an existing 3D asset or after Asset Composer supplies a conversion_job_id.
| Method | GET |
| Path | /api/conversions/:id |
The :id is the value returned in data.id from a 3D conversion or simulation, or the conversion_job_id returned by SimReady Asset Composer. A composition ID is not interchangeable with a conversion-job ID.
The endpoint also synchronises the latest status from the upstream pipeline before returning, so you always get a fresh progress, stage, and status.
Examples
Section titled “Examples”curl https://api.rigyd.com/api/conversions/abc123... \ -H "Authorization: Bearer rgyd_live_..."// Simple polling loopasync function waitFor(jobId) { while (true) { const res = await fetch(`https://api.rigyd.com/api/conversions/${jobId}`, { headers: { Authorization: `Bearer ${process.env.RIGYD_API_KEY}` }, }); const { data } = await res.json(); if (data.status === 'completed') return data; if (data.status === 'failed') throw new Error(data.error || 'failed'); await new Promise((r) => setTimeout(r, 3000)); }}import os, time, requests
def wait_for(job_id): headers = {"Authorization": f"Bearer {os.environ['RIGYD_API_KEY']}"} while True: data = requests.get( f"https://api.rigyd.com/api/conversions/{job_id}", headers=headers, ).json()["data"] if data["status"] == "completed": return data if data["status"] == "failed": raise RuntimeError(data.get("error") or "failed") time.sleep(3)Response
Section titled “Response”{ "data": { "id": "abc123...", "physiq_job_id": "phy_...", "status": "completed", "filename": "toolbox.glb", "file_size_bytes": 1245678, "stage": "export", "progress": 100, "error": null, "timing": { "queued_at": "2026-05-06T12:00:01.000Z", "started_at": "2026-05-06T12:00:05.000Z", "completed_at": "2026-05-06T12:01:32.000Z" }, "parameters": { "optimize": true, "target_triangle_count": 50000 }, "report": { /* validation + pipeline metadata */ }, "job_type": "glb_to_simready", "credits_charged": 5, "input": { "model": { "url": "https://assets.rigyd.com/...", "name": "toolbox.glb" }, "images": [], "metadata": null }, "preprocess": { "status": "skipped", "steps": null, "started_at": null, "completed_at": null, "error": null, "input_stats": null, "telemetry": null, "intermediate_glb": null }, "output": { "model": { "url": "https://assets.rigyd.com/.../toolbox.usd", "name": "toolbox.usd", "size": 982341 }, "textures": [ { "url": "https://assets.rigyd.com/.../diffuse.png", "name": "diffuse.png" } ], "usd_package": { "url": "...", "name": "toolbox_usd.zip", "size": 1048576 }, "mjcf_package": { "url": "...", "name": "toolbox-mjcf.zip", "size": 1234 }, "sim_video": null, "sim_gif": null, "sim_log": null }, "source_job": null, "simulations": [], "createdAt": "2026-05-06T12:00:00.000Z", "updatedAt": "2026-05-06T12:01:32.000Z" }}output.usd_package is present for composed SimReady assets whose root USD
references layers under usd/payloads/. The ZIP preserves those relative paths,
textures, and any bundled MDL modules. Older flat-USD jobs return null and remain
available through output.model and output.textures.
See Job lifecycle for the full status enum, the meaning of each field, and how preprocess relates to non-GLB inputs.
Reading the SimReady verdict
Section titled “Reading the SimReady verdict”report.validation carries the conformance result. It never affects status — a completed job is downloadable whatever the verdict — so this is the only place to look:
const v = job.report.validation;
v.kind; // "simready-conformance"v.spec_release; // "2026.08.0" — the release scored againstv.targeted_profiles; // ["Robotics-Prop"] — headline thesev.profiles['Robotics-Prop'].status; // "PASS" | "FAIL" | "INCOMPLETE"v.profiles['Robotics-Prop'].optional_unmet; // ["MUJOCO.COL.001", "RV.001", ...]v.profiles['Robotics-Prop'].optional_features; // { "FET_100_ISAAC": "Isaac composition", ... }v.profiles_passing; // ["Package-Candidate", "Robot-Body", // "Robot-Gripper", "Robotics-Prop"] — do not badgev.counts; // { total, passed, errors, warnings, not_applicable, ... }
// "Passes for Isaac": the profile passes and the Isaac feature is metconst rp = v.profiles['Robotics-Prop'];const isaacReady = rp.status === 'PASS' && !rp.optional_unmet.includes('ISA.001');Each profile also includes version, targeted, requirements, failing[], unchecked[], optional_unmet[], per-profile counts, and — on profiles we do not aim at — not_targeted_because. Each check includes both status (the actionable result for Rigyd’s targets) and measured_status (the raw finding); an N/A may include na_reason as not-in-profile, not-applicable-to-asset, or planned.
Headline targeted_profiles and each one’s status. Do not badge profiles_passing: at 2026.08.0 a plain prop also passes Robot-Body, Robot-Gripper and Package-Candidate, because their required checks are a subset of Robotics-Prop’s — that is not a claim that the asset is a robot. Do not badge overall either: it can be WARN because of an advisory finding while every targeted profile is PASS. The optional features and their codes are listed on What’s covered today.
report.pipeline.physics alongside it carries the measured mass, inertia, friction and restitution. Full detail in SimReady Validation.
SimReady metadata, labels and thumbnail
Section titled “SimReady metadata, labels and thumbnail”Reports scored at 2026.08.0 add four top-level blocks describing what was written into the delivered USD, on both uploaded-model and composition conversions, plus material_slots on uploaded models. All are optional: they are absent on reports from before 2026.08.0. When the stage that produces a block ran and failed, the block is exactly { "available": false } with no other fields; an absent field means not recorded, never a pass.
| Field | Type | Contents |
|---|---|---|
simready_metadata | object | What was stamped into the USD’s SimReady metadata (SR.004): available, author (always "Rigyd"), asset_license (the source asset’s license you sent, "proprietary" by default), rigid_body_count, extents_m ([x, y, z], Z-up), mass_kg, category, wikidata_qcode |
classification | object | available, category (one of a closed list of 31 keys, e.g. home_appliance, furniture, tool, other), wikidata_qcode, wikidata_label, source (manifest, mass_recall, classifier, vision_llm or fallback), rejected[] ({ offered, by } for categories proposed outside the list) |
thumbnail | object | available, path (relative to the folder holding the USD file), size_px, background, blank (true if the render came out empty) |
semantic_labels | object | The SemanticsLabelsAPI labels authored into the USD: available, class[] and wikidata_qcode[] (on /Root), materials[] ({ prim, material, labels[], source }, one per Material), fallbacks (materials that received a default label) |
material_slots | object | Uploaded models only: source material slot name → physical material |
source: "fallback" means no classifier could decide; its category is other with Q-code Q223557 (“physical object”), which is true of anything and is not a classification.
const r = job.report;
r.simready_metadata;// { available: true, author: "Rigyd", asset_license: "proprietary",// rigid_body_count: 1, extents_m: [0.866059, 0.863068, 1.86],// mass_kg: 109, category: "home_appliance", wikidata_qcode: "Q212920" }
r.classification;// { available: true, category: "home_appliance", wikidata_qcode: "Q212920",// wikidata_label: "home appliance", source: "vision_llm" }
r.thumbnail;// { available: true, path: ".thumbs/256x256/refrigerator.usd.png",// size_px: 256, background: "#1A1A1A" }
r.semantic_labels;// { available: true, class: ["home_appliance"], wikidata_qcode: ["Q212920"],// materials: [{ prim: "/Root/Looks/Material_0", material: "stainless_steel",// labels: ["metal", "stainless_steel"], source: "identifier" }, ...],// fallbacks: 0 }
r.material_slots;// { "Material.030": "stainless_steel", ... }
if (r.thumbnail?.available === false) { /* the stage ran and failed */ }The Q-code resolves at https://www.wikidata.org/wiki/<qcode>. The thumbnail itself ships inside the USD package — see Download result.
Reading the simulation evidence
Section titled “Reading the simulation evidence”Before delivery, the MJCF is loaded into MuJoCo and stepped. Like the SimReady verdict, the result never fails a job. It is evidence that travels with the asset, and issues[] lists what to look at:
const sim = job.report.checks.simulation;
sim.fixed_base; // true: the asset is anchored, so it is held rather than droppedsim.settled; // a free asset came to rest after the dropsim.rest_penetration_m; // deepest resting contactsim.joint_drift; // fixed base: [{ joint, drift_deg | drift_mm }] under gravity alonesim.contacts; // [{ bodies, depth_m, moving_into_static }] at restsim.issues; // [] when nothing needs attentionComposition conversions (authored_to_simready) also push every joint toward each end of its declared range. The push starts at what the joint itself declares resisting, then tries 4× and 16×:
const sweep = job.report.checks.joint_sweep;
sweep.overall; // "PASS" | "WARN" | "FAIL" | "N/A" (no joints)sweep.joints[0].name; // same name as report.joints[].namesweep.joints[0].travel_frac; // fraction of the declared range actually reachedsweep.joints[0].status; // "PASS" at 1×, "WARNING" only at 4×/16× (stiff), // "ERROR" never (blocked)sweep.joints[0].ends; // per end: reached_frac, drive (N·m or N), drive_multipleA push button, key or latch tab is a short prismatic joint with a return spring. Its entry in report.joints[] says so:
const key = job.report.joints.find((j) => j.mechanism === "press");
key.stiffness; // N/m, at rest in the delivered posekey.return_force_n; // N, the spring's push at the far end of the strokeNo other joint carries stiffness or return_force_n.
For composition conversions, each entry of report.bodies[] also says how its collision was built:
const c = job.report.bodies[0].collision;
c.method; // "surface" | "surface_per_part" | "remesh" | "remesh_refit" | "boxes_only"c.overshoot_m; // how far the collision reaches past the body's own meshc.support_boxes; // flat support surfaces shipped as exact boxes: [{ part, top_z, center, half_extents }]method: "remesh" is the fallback, and it is the one method whose hulls can stand proud of the surface. overshoot_by_face_m shows by how much, per face, in the order [-x, -y, -z, +x, +y, +z]. method: "remesh_refit" is the same fallback with each hull re-fitted to the modelled surface, so no hull stands proud of it; a concavity the fallback bridged stays bridged. A body whose parts fit into or around another part (a battery slot’s walls, a connector’s nose) has each of those parts decomposed on its own; its method is the least exact among those decompositions and the rest of the body’s, from "remesh" (least exact) through "remesh_refit" and "surface_per_part" to "surface".
Reports written before these checks existed carry none of these fields. Treat an absent field as not measured, not as a pass.
When the job is done
Section titled “When the job is done”Continue to Download result.