This is the full developer documentation for Rigyd API # Rigyd API > Convert any 3D file, image, or text prompt into a physics-enabled SimReady USD asset over a simple REST API. ## What you can do [Section titled “What you can do”](#what-you-can-do) [3D → SimReady ](/conversions/3d-to-simready)Upload a .glb / .gltf / .fbx / .obj / .stl / .ply / .usd\[a|c|z] (or a .zip of those + textures) and get back physics-enabled USD. [2D → SimReady ](/conversions/2d-to-simready)Send 1 image (single-view) or 4 images (multiview) and Rigyd reconstructs a SimReady asset. [Text → SimReady ](/conversions/text-to-simready)Describe what you want (\`a red metal toolbox\`) and get a SimReady asset back. [Simulate ](/conversions/simulate)Run a demo or drop physics simulation against a completed conversion. Returns a video, GIF, and log. [SimReady Validation ](/simready-validation/overview)Every conversion is checked against the SimReady Foundation specification before it ships. See what we cover today. ## Ways to use it [Section titled “Ways to use it”](#ways-to-use-it) [REST API ](/quickstart)Submit, poll, and download over HTTP. Start with the Quickstart. [CLI & Python SDK ](/tools/cli)\`pip install rigyd\` — convert from your terminal or Python, with a MuJoCo loader. [Isaac Sim extension ](/tools/isaac-sim)Generate and load SimReady assets straight onto your stage inside NVIDIA Isaac Sim. ## How it fits together [Section titled “How it fits together”](#how-it-fits-together) Every mutating call returns `202 Accepted` immediately with a job `id`. You then poll [`GET /api/conversions/:id`](/jobs/retrieve) until `status` is `completed` or `failed`, and download the result with [`GET /api/conversions/:id/result`](/jobs/download). ```text POST /api/conversions ─┐ POST /api/conversions/generate ─┼──► 202 { id, status: "queued" | "preprocessing" } POST /api/conversions/:id/simulate ─┘ │ │ poll ▼ GET /api/conversions/:id │ status === "completed" ▼ GET /api/conversions/:id/result?format=usd → streaming ZIP (USD + textures) ``` ## Where to start [Section titled “Where to start”](#where-to-start) [Quickstart ](/quickstart)Paste your key, run one curl, get a USD. [Authentication ](/authentication)The one header you need to send. [Job lifecycle ](/reference/job-lifecycle)Status enum, preprocessing, response shapes. [Errors ](/reference/errors)What every status code means. ## Account & billing [Section titled “Account & billing”](#account--billing) Sign up, mint API keys, and manage billing at **[app.rigyd.com](https://app.rigyd.com)**. Those flows are not part of the public API. # Authentication > Send Authorization Bearer rgyd_live_... on every request. Mint keys at app.rigyd.com. The Rigyd API authenticates every request with a single header: ```http Authorization: Bearer rgyd_live_<43-char-token> ``` That’s it. No OAuth, no signing, no expiring access tokens to refresh. ## Get a key [Section titled “Get a key”](#get-a-key) Mint an API key in **[app.rigyd.com](https://app.rigyd.com)** under **Settings → API Keys**. Click **Create key**, give it a name (e.g. `production-backend`), and optionally set an `expires_at` date. The plaintext token is shown **once** at creation time. Store it in a secret manager — Rigyd only keeps a SHA-256 hash, so we cannot recover it for you. Note Keys are scoped to the user that created them. Calls made with the key consume that user’s credits and inherit that user’s plan limits. Up to 10 active keys per user. ## Token shapes [Section titled “Token shapes”](#token-shapes) | Environment | Prefix | Example | | ----------- | ------------ | -------------------------------- | | Production | `rgyd_live_` | `rgyd_live_a1b2c3...` (43 chars) | | Other | `rgyd_test_` | `rgyd_test_a1b2c3...` (43 chars) | ## Use the key [Section titled “Use the key”](#use-the-key) * curl ```bash curl https://api.rigyd.com/api/conversions \ -H "Authorization: Bearer rgyd_live_..." ``` * JavaScript ```js await fetch('https://api.rigyd.com/api/conversions', { headers: { Authorization: `Bearer ${process.env.RIGYD_API_KEY}` }, }); ``` * Python ```python import os, requests requests.get( "https://api.rigyd.com/api/conversions", headers={"Authorization": f"Bearer {os.environ['RIGYD_API_KEY']}"}, ) ``` ## What happens if the key is bad [Section titled “What happens if the key is bad”](#what-happens-if-the-key-is-bad) | Situation | Status | Body | | ------------------------------- | ------ | ---------------------------------------- | | Missing `Authorization` header | `401` | `{ "error": "Authentication required" }` | | Unknown / revoked / expired key | `401` | `{ "error": "Invalid API key" }` | | Key valid but no credits left | `402` | `{ "error": "Insufficient credits" }` | See [Errors](/reference/errors) for the full table. ## Rotation and revocation [Section titled “Rotation and revocation”](#rotation-and-revocation) Both happen in **[app.rigyd.com](https://app.rigyd.com) → Settings → API Keys**: * **Rotate**: create a new key, ship it, then revoke the old one. * **Revoke immediately** if a key leaks — every authenticated request after the revoke call returns `401`. The Rigyd API does not currently expose key management programmatically by design — a leaked key cannot mint or revoke other keys. # Changelog > API changes that affect public consumers. The Rigyd API is currently versionless — backwards-incompatible changes will be announced here with at least 30 days’ notice. ## 2026-08 [Section titled “2026-08”](#2026-08) * **Correction: a SimReady conformance failure never failed your job, and never refunded a credit.** These pages previously stated that a conversion completes only when the asset clears the profile we target, and that a failing asset refunds your credit. Neither was true. A non-conformant asset is delivered like any other, with its verdict in `report.validation`. What does fail a conversion is a correctness problem — USD/MJCF parity, an internal invariant, a missing export, a rejected manifest. See [SimReady Validation](/simready-validation/overview) and [Job lifecycle](/reference/job-lifecycle). * **Correction: three requirement codes we published do not exist in the SimReady specification.** `VG.034`, `VM.D.001` and `RC.002` were ours, and they sat indistinguishable among the real ones on [Full requirement coverage](/simready-validation/full-coverage). So did a family of `AR.*` codes, under a profile name — `Articulated-Robotics-Neutral` — that was never published. All are removed. The coverage tables are now generated from NVIDIA’s vendored metadata, so a code appears if and only if they define it: 136 codes, verified mechanically. * **`report.validation` gained per-profile scoring.** `profiles_passing` is the field to badge, not `overall`. `profiles` scores all nine published profiles with what each one fails; `spec_release` names the specification release scored against (currently `2026.04.1`); `checks[].measured_status` carries the raw finding where `status` is scoped to the profiles we target; `checks[].na_reason` explains every `N/A` from a fixed three-value vocabulary. A profile with no failures but unwritten rules now reports `INCOMPLETE` rather than `PASS`. * **`skip_validation` no longer skips conformance scoring.** Every conversion is scored and every completed job carries `report.validation`. The flag now only suppresses the internal correctness gate — leave it unset. * **The MJCF package in a result download lost a folder level.** `mjcf//.xml` is now `mjcf/.xml`, from a SimReady `NP.005` folder-structure fix. The USD side of the download is unchanged. Breaking for anyone addressing files by path — see [Download result](/jobs/download). ## 2026-06 [Section titled “2026-06”](#2026-06) * **CLI & Python SDK released.** `pip install rigyd` wraps the conversion API for terminal and Python use, including a MuJoCo model loader. See [CLI & Python SDK](/tools/cli). * **Isaac Sim extension released (v1.0.0).** The `rigyd.simready` Omniverse Kit extension generates and loads SimReady assets onto the stage from inside NVIDIA Isaac Sim. See [Isaac Sim extension](/tools/isaac-sim). ## 2026-05 [Section titled “2026-05”](#2026-05) * **`POST /api/conversions/generate` — `images[]` count is now exactly 1 or 4** (was 1-4). Multiview is positional (Front, Right, Back, Left); partial selections caused malformed multiview submissions. Requests with 2 or 3 images now return `422`. * **Initial public docs.** Documents the conversion surface (`POST /api/conversions`, `POST /api/conversions/generate`, `POST /api/conversions/:id/simulate`, plus the `/api/conversions[/...]` read endpoints). * **`llms.txt` / `llms-full.txt` published.** AI agents can ingest the full API surface in one fetch from `https://docs.rigyd.com/llms-full.txt`. ## Earlier [Section titled “Earlier”](#earlier) For pre-public-docs history, see internal release notes. # 2D → SimReady > Send 1 image (single-view) or 4 images (multiview) and Rigyd reconstructs a SimReady asset. 3 credits. Send one or more images of an object and Rigyd reconstructs it as a SimReady USD asset. | | | | ---------------- | ------------------------------------------------------------------ | | **Method** | `POST` | | **Path** | `/api/conversions/generate` | | **Content-type** | `multipart/form-data` | | **Job type** | `image_to_simready` (1 image) · `multiview_to_simready` (4 images) | | **Credits** | 3 | The same endpoint handles single-view and multiview based on the number of `images[]` you send. Send exactly **1** image (single view) or exactly **4** images (multiview). Multiview images are positional and must be ordered **Front, Right, Back, Left**. ## Request fields [Section titled “Request fields”](#request-fields) | Field | Type | Required | Notes | | ----------------- | ------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------- | | `images[]` | file (1 or 4) | yes | `.jpg`, `.jpeg`, `.png`, `.webp`. Max 10 MB each. Must be exactly 1 or 4. For 4, order is Front, Right, Back, Left. | | `face_limit` | int | no | Cap output mesh face count. | | `model_version` | string | no | Override generation model (advanced). | | `threshold` | float | no | CoACD concavity threshold (advanced). | | `llm_provider` | string | no | Override material-identification LLM (advanced). | | `skip_validation` | boolean | no | Suppresses the internal correctness gate. Does **not** skip [SimReady scoring](/simready-validation/overview). Leave unset. Default `false`. | Caution Do not send a `prompt` field on the same request — that triggers a `422`. Use [Text → SimReady](/conversions/text-to-simready) for prompt-only generation. ## Examples [Section titled “Examples”](#examples) ### Single image [Section titled “Single image”](#single-image) * curl ```bash curl -X POST https://api.rigyd.com/api/conversions/generate \ -H "Authorization: Bearer rgyd_live_..." \ -F "images[]=@./toolbox-front.jpg" ``` * JavaScript ```js import fs from 'node:fs'; const form = new FormData(); form.append('images[]', new Blob([fs.readFileSync('./toolbox-front.jpg')]), 'toolbox-front.jpg'); const res = await fetch('https://api.rigyd.com/api/conversions/generate', { method: 'POST', headers: { Authorization: `Bearer ${process.env.RIGYD_API_KEY}` }, body: form, }); const { data } = await res.json(); ``` * Python ```python import os, requests with open("toolbox-front.jpg", "rb") as f: res = requests.post( "https://api.rigyd.com/api/conversions/generate", headers={"Authorization": f"Bearer {os.environ['RIGYD_API_KEY']}"}, files={"images[]": ("toolbox-front.jpg", f, "image/jpeg")}, ) job = res.json()["data"] ``` ### Multiview (4 images) [Section titled “Multiview (4 images)”](#multiview-4-images) Order matters — Tripo interprets the images positionally as Front (0°), Right (90°), Back (180°), Left (270°). All four are required. * curl ```bash curl -X POST https://api.rigyd.com/api/conversions/generate \ -H "Authorization: Bearer rgyd_live_..." \ -F "images[]=@./front.jpg" \ -F "images[]=@./right.jpg" \ -F "images[]=@./back.jpg" \ -F "images[]=@./left.jpg" ``` * JavaScript ```js import fs from 'node:fs'; const form = new FormData(); for (const name of ['front.jpg', 'right.jpg', 'back.jpg', 'left.jpg']) { form.append('images[]', new Blob([fs.readFileSync(name)]), name); } const res = await fetch('https://api.rigyd.com/api/conversions/generate', { method: 'POST', headers: { Authorization: `Bearer ${process.env.RIGYD_API_KEY}` }, body: form, }); ``` * Python ```python import os, requests files = [ ("images[]", (name, open(name, "rb"), "image/jpeg")) for name in ["front.jpg", "right.jpg", "back.jpg", "left.jpg"] ] res = requests.post( "https://api.rigyd.com/api/conversions/generate", headers={"Authorization": f"Bearer {os.environ['RIGYD_API_KEY']}"}, files=files, ) ``` ## Response — `202 Accepted` [Section titled “Response — 202 Accepted”](#response--202-accepted) ```json { "data": { "id": "abc123...", "status": "queued", "filename": "toolbox-front.jpg", "progress": 0, "job_type": "multiview_to_simready", "credits_charged": 3, "createdAt": "2026-05-06T12:00:00.000Z" } } ``` `job_type` is `image_to_simready` for a single image and `multiview_to_simready` for 4 images. Both cost 3 credits. ## Tips [Section titled “Tips”](#tips) * **More views = sharper geometry.** Four images covering Front / Right / Back / Left yield noticeably better reconstructions than a single hero shot. * **Plain backgrounds help** — the generation model isolates the object before reconstruction. * **One object per call.** If your image contains multiple objects, the result is undefined. ## Next: [poll the job](/jobs/retrieve) → [download the USD](/jobs/download). [Section titled “Next: poll the job → download the USD.”](#next-poll-the-job--download-the-usd) # 3D → SimReady > Upload any 3D file and get back a physics-enabled USD asset. 1 credit per conversion. Upload a 3D model and Rigyd returns a SimReady USD asset — physics properties, collision meshes, and materials filled in automatically. | | | | ---------------- | --------------------- | | **Method** | `POST` | | **Path** | `/api/conversions` | | **Content-type** | `multipart/form-data` | | **Job type** | `glb_to_simready` | | **Credits** | 1 | ## Accepted formats [Section titled “Accepted formats”](#accepted-formats) `.glb`, `.gltf`, `.fbx`, `.obj`, `.stl`, `.ply`, `.usd`, `.usda`, `.usdc`, `.usdz`, and `.zip` (for multi-file inputs). `.glb` skips the preprocessing stage and is the fastest path unless you enable mesh optimization. Anything else goes through Blender-based cleanup first — see [Supported formats](/reference/supported-formats) for the rules around ZIP archives, OBJ + MTL, GLTF + bin, and USD with references. ## Request fields [Section titled “Request fields”](#request-fields) | Field | Type | Required | Notes | | ----------------------- | ------------- | -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | | `file` | file (upload) | yes | The 3D model. See accepted formats above. | | `optimize` | boolean | no | Run mesh optimization during preprocessing. Default `false`. | | `target_triangle_count` | int | when `optimize=true` | Target triangle count for optimization. `1000`–`1000000`. | | `threshold` | float | no | CoACD collision-decomposition concavity threshold (advanced). | | `llm_provider` | string | no | Override the LLM used for material identification (advanced). | | `skip_validation` | boolean | no | Suppresses the internal correctness gate. Does **not** skip [SimReady scoring](/simready-validation/overview). Leave unset. Default `false`. | ## Examples [Section titled “Examples”](#examples) * curl ```bash curl -X POST https://api.rigyd.com/api/conversions \ -H "Authorization: Bearer rgyd_live_..." \ -F "file=@./toolbox.glb" \ -F "optimize=true" \ -F "target_triangle_count=50000" ``` * JavaScript ```js import fs from 'node:fs'; const form = new FormData(); form.append('file', new Blob([fs.readFileSync('./toolbox.glb')]), 'toolbox.glb'); form.append('optimize', 'true'); form.append('target_triangle_count', '50000'); const res = await fetch('https://api.rigyd.com/api/conversions', { method: 'POST', headers: { Authorization: `Bearer ${process.env.RIGYD_API_KEY}` }, body: form, }); const { data } = await res.json(); ``` * Python ```python import os, requests with open("toolbox.glb", "rb") as f: res = requests.post( "https://api.rigyd.com/api/conversions", headers={"Authorization": f"Bearer {os.environ['RIGYD_API_KEY']}"}, files={"file": ("toolbox.glb", f, "model/gltf-binary")}, data={"optimize": "true", "target_triangle_count": 50000}, ) job = res.json()["data"] ``` ## Response — `202 Accepted` [Section titled “Response — 202 Accepted”](#response--202-accepted) ```json { "data": { "id": "abc123...", "physiq_job_id": "phy_...", "status": "queued", "filename": "toolbox.glb", "progress": 0, "job_type": "glb_to_simready", "credits_charged": 1, "createdAt": "2026-05-06T12:00:00.000Z" } } ``` For non-GLB inputs, `status` starts at `preprocessing` while the model is converted to a clean GLB, then transitions to `queued` → `running` → `completed`. Tip The credit is reserved atomically when the request is accepted and refunded automatically if the job ends in `failed`. You will not be double-charged on retries. ## Next: [poll the job](/jobs/retrieve) → [download the USD](/jobs/download). [Section titled “Next: poll the job → download the USD.”](#next-poll-the-job--download-the-usd) # Simulate > Run a physics simulation against a completed conversion. Returns a video, GIF, and log. Free. Run a physics simulation against a previously completed conversion. Useful for sanity-checking that mass, friction, and collision shapes look right before you import the asset into your simulator. | | | | ---------------- | ------------------------------- | | **Method** | `POST` | | **Path** | `/api/conversions/:id/simulate` | | **Content-type** | `application/json` | | **Job type** | `simulate_usd` | | **Credits** | 0 (free) | The `:id` is the `id` of a completed source job (any of [3D](/conversions/3d-to-simready), [2D](/conversions/2d-to-simready), [Text](/conversions/text-to-simready) → SimReady). You cannot simulate a simulation. ## Request fields [Section titled “Request fields”](#request-fields) | Field | Type | Required | Notes | | ------- | ------ | -------- | ----------------------------------------------------- | | `scene` | string | no | `"demo"` (default) or `"drop"`. Picks the test scene. | ## Examples [Section titled “Examples”](#examples) * curl ```bash curl -X POST https://api.rigyd.com/api/conversions/abc123.../simulate \ -H "Authorization: Bearer rgyd_live_..." \ -H "Content-Type: application/json" \ -d '{"scene":"drop"}' ``` * JavaScript ```js const res = await fetch( `https://api.rigyd.com/api/conversions/${jobId}/simulate`, { method: 'POST', headers: { Authorization: `Bearer ${process.env.RIGYD_API_KEY}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ scene: 'drop' }), }, ); const { data } = await res.json(); ``` * Python ```python import os, requests res = requests.post( f"https://api.rigyd.com/api/conversions/{job_id}/simulate", headers={"Authorization": f"Bearer {os.environ['RIGYD_API_KEY']}"}, json={"scene": "drop"}, ) sim = res.json()["data"] ``` ## Response — `202 Accepted` [Section titled “Response — 202 Accepted”](#response--202-accepted) ```json { "data": { "id": "sim_xyz...", "status": "queued", "filename": "toolbox.glb", "progress": 0, "job_type": "simulate_usd", "credits_charged": 0, "source_job_id": "abc123...", "createdAt": "2026-05-06T12:05:00.000Z" } } ``` The simulation runs as its own job — poll [`GET /api/conversions/:id`](/jobs/retrieve) on the returned `id`. When `status` is `completed`, the response includes `output.sim_video`, `output.sim_gif`, and `output.sim_log` URLs. Note Simulations also appear nested under the source job’s `simulations[]` array when you fetch the parent. ## Errors specific to this endpoint [Section titled “Errors specific to this endpoint”](#errors-specific-to-this-endpoint) | Status | Body | When | | ------ | --------------------------------------------------------------- | -------------------------------------- | | `404` | `{ "error": "Conversion job not found" }` | Bad `id` or not your job | | `409` | `{ "error": "Source job must be completed before simulation" }` | Parent is still running / failed | | `422` | `{ "error": "Cannot simulate a simulation job" }` | `:id` was already a `simulate_usd` job | | `502` | `{ "error": "Simulation service unavailable: ..." }` | Upstream physics service is down | # Text → SimReady > Describe what you want and Rigyd generates a SimReady USD asset. 2 credits. Send a short text description and get back a SimReady USD asset. | | | | ---------------- | -------------------------------------------------------------- | | **Method** | `POST` | | **Path** | `/api/conversions/generate` | | **Content-type** | `multipart/form-data` (or `application/x-www-form-urlencoded`) | | **Job type** | `text_to_simready` | | **Credits** | 2 | ## Request fields [Section titled “Request fields”](#request-fields) | Field | Type | Required | Notes | | ----------------- | ------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------- | | `prompt` | string | yes | Up to 500 characters. | | `face_limit` | int | no | Cap output mesh face count. | | `model_version` | string | no | Override generation model (advanced). | | `threshold` | float | no | CoACD concavity threshold (advanced). | | `llm_provider` | string | no | Override material-identification LLM. | | `skip_validation` | boolean | no | Suppresses the internal correctness gate. Does **not** skip [SimReady scoring](/simready-validation/overview). Leave unset. Default `false`. | Caution Send `prompt` *or* `images[]`, never both — mixing the two returns `422`. For images, see [2D → SimReady](/conversions/2d-to-simready). ## Examples [Section titled “Examples”](#examples) * curl ```bash curl -X POST https://api.rigyd.com/api/conversions/generate \ -H "Authorization: Bearer rgyd_live_..." \ -F "prompt=a red metal toolbox with a black handle" ``` * JavaScript ```js const form = new FormData(); form.append('prompt', 'a red metal toolbox with a black handle'); const res = await fetch('https://api.rigyd.com/api/conversions/generate', { method: 'POST', headers: { Authorization: `Bearer ${process.env.RIGYD_API_KEY}` }, body: form, }); const { data } = await res.json(); ``` * Python ```python import os, requests res = requests.post( "https://api.rigyd.com/api/conversions/generate", headers={"Authorization": f"Bearer {os.environ['RIGYD_API_KEY']}"}, data={"prompt": "a red metal toolbox with a black handle"}, ) job = res.json()["data"] ``` ## Response — `202 Accepted` [Section titled “Response — 202 Accepted”](#response--202-accepted) ```json { "data": { "id": "abc123...", "status": "queued", "filename": "a red metal toolbox with a black handle", "progress": 0, "job_type": "text_to_simready", "credits_charged": 2, "createdAt": "2026-05-06T12:00:00.000Z" } } ``` The prompt is stored verbatim as `filename` so you can recognise the job in lists. The downloaded USD uses a slugified version (e.g. `a_red_metal_toolbox.usd`). ## Tips [Section titled “Tips”](#tips) * **Short, concrete prompts work best.** `"a red metal toolbox"` outperforms `"some kind of container, perhaps for tools, maybe red"`. * **Include material cues** (`metal`, `wood`, `plastic`) — they feed material identification. * **One object per prompt.** Compound scenes are not supported. ## Next: [poll the job](/jobs/retrieve) → [download the USD](/jobs/download). [Section titled “Next: poll the job → download the USD.”](#next-poll-the-job--download-the-usd) # Download result > Stream the SimReady USD (or MJCF) for a completed conversion as a ZIP. Download the result of a completed conversion as a ZIP archive. | | | | ----------- | ----------------------------- | | **Method** | `GET` | | **Path** | `/api/conversions/:id/result` | | **Returns** | streaming `application/zip` | ## Query parameters [Section titled “Query parameters”](#query-parameters) | Param | Values | Default | Notes | | -------- | ---------------------- | ------- | ----------------------------------------------------------------------- | | `format` | `usd` · `mjcf` · `all` | `all` | `usd` = USD + textures only. `mjcf` = MuJoCo XML package. `all` = both. | ## Examples [Section titled “Examples”](#examples) * curl ```bash curl -L "https://api.rigyd.com/api/conversions/abc123.../result?format=usd" \ -H "Authorization: Bearer rgyd_live_..." \ -o simready.zip ``` * JavaScript ```js import fs from 'node:fs'; import { Readable } from 'node:stream'; const res = await fetch( `https://api.rigyd.com/api/conversions/${jobId}/result?format=usd`, { headers: { Authorization: `Bearer ${process.env.RIGYD_API_KEY}` } }, ); if (!res.ok) throw new Error(`HTTP ${res.status}`); await new Promise((resolve, reject) => { Readable.fromWeb(res.body) .pipe(fs.createWriteStream('simready.zip')) .on('finish', resolve).on('error', reject); }); ``` * Python ```python import os, requests with requests.get( f"https://api.rigyd.com/api/conversions/{job_id}/result", headers={"Authorization": f"Bearer {os.environ['RIGYD_API_KEY']}"}, params={"format": "usd"}, stream=True, ) as res: res.raise_for_status() with open("simready.zip", "wb") as out: for chunk in res.iter_content(chunk_size=1024 * 1024): out.write(chunk) ``` ## ZIP contents [Section titled “ZIP contents”](#zip-contents) `` below is the asset name chosen by the pipeline, which is not always your uploaded filename. `` is your uploaded filename without its extension. * format=usd ```text usd//.usd usd//textures/*.png ``` * format=mjcf ```text mjcf/.xml mjcf/meshes/*.obj # visual meshes, with UVs mjcf/meshes/*.stl # collision hulls mjcf/textures/*.png ``` * format=all ```text usd//.usd usd//textures/*.png mjcf/.xml mjcf/meshes/*.obj mjcf/meshes/*.stl mjcf/textures/*.png ``` The MJCF tree lost a folder level The MuJoCo package used to nest everything one level deeper — `mjcf//.xml`, `mjcf//meshes/…`. It is now flat under `mjcf/`, as shown above. This came from a SimReady conformance fix upstream: `NP.005` (`asset-folder-structure`) wants an asset root with **exactly one** intermediate folder, and the extra level made NVIDIA’s validator read the asset root as literally `"mjcf"` and then ask the filename to contain it. **If you address files by path inside the archive, check that code.** Globbing for `*.xml` or `*.stl` anywhere under the root is unaffected. Already-downloaded archives are not rewritten, so if you store them you may need to handle both shapes. ## Errors specific to this endpoint [Section titled “Errors specific to this endpoint”](#errors-specific-to-this-endpoint) | Status | Body | When | | ------ | ------------------------------------------------------------------ | -------------------------------------------------------------- | | `400` | `{ "error": "Invalid format. Expected one of: usd, mjcf, all" }` | Bad `format` value | | `404` | `{ "error": "Conversion job not found" }` | Bad `id` or not your job | | `404` | `{ "error": "MJCF package is not available for this conversion" }` | `format=mjcf` but the pipeline did not produce an MJCF package | | `409` | `{ "error": "Job not yet completed", "status": "running" }` | Job is still in flight | | `502` | upstream fetch failure | Underlying media store unreachable | Tip Result downloads are unauthenticated *after* the redirect — the URL is signed and short-lived. Don’t cache them long-term; re-call this endpoint when you need the result again. # List jobs > Paginated list of your conversion jobs, optionally filtered by job type. List your conversion jobs, newest first. | | | | ---------- | ------------------ | | **Method** | `GET` | | **Path** | `/api/conversions` | Simulation jobs are excluded by default — they are surfaced inline on each parent job’s `simulations[]` array. To include them explicitly, pass `job_type=simulate_usd`. ## Query parameters [Section titled “Query parameters”](#query-parameters) | Param | Type | Default | Notes | | ---------- | ------ | ------- | ------------------------------------------------------------------------------------------------------------------------------------ | | `page` | int | `1` | 1-indexed page number. | | `pageSize` | int | `25` | Max `100`. | | `job_type` | string | — | Comma-separated. One or more of `glb_to_simready`, `text_to_simready`, `image_to_simready`, `multiview_to_simready`, `simulate_usd`. | ## Examples [Section titled “Examples”](#examples) * curl ```bash curl "https://api.rigyd.com/api/conversions?page=1&pageSize=25" \ -H "Authorization: Bearer rgyd_live_..." # Only text and image jobs curl "https://api.rigyd.com/api/conversions?job_type=text_to_simready,image_to_simready" \ -H "Authorization: Bearer rgyd_live_..." ``` * JavaScript ```js const res = await fetch( 'https://api.rigyd.com/api/conversions?page=1&pageSize=25', { headers: { Authorization: `Bearer ${process.env.RIGYD_API_KEY}` } }, ); const { data, meta } = await res.json(); console.log(`${data.length} of ${meta.total} jobs`); ``` * Python ```python import os, requests res = requests.get( "https://api.rigyd.com/api/conversions", headers={"Authorization": f"Bearer {os.environ['RIGYD_API_KEY']}"}, params={"page": 1, "pageSize": 25}, ) body = res.json() jobs, meta = body["data"], body["meta"] ``` ## Response [Section titled “Response”](#response) ```json { "data": [ { "id": "abc123...", "physiq_job_id": "phy_...", "status": "completed", "filename": "toolbox.glb", "file_size_bytes": 1245678, "stage": "export", "progress": 100, "job_type": "glb_to_simready", "credits_charged": 1, "createdAt": "2026-05-06T12:00:00.000Z", "updatedAt": "2026-05-06T12:01:32.000Z" } ], "meta": { "page": 1, "pageSize": 25, "pageCount": 4, "total": 87 } } ``` This is the trimmed list shape — to get input/output URLs, the `preprocess` component, simulations, and the full report, fetch a single job with [`GET /api/conversions/:id`](/jobs/retrieve). # Pricing > Look up the current credit cost for each conversion type. Returns the credit cost for each conversion type. Costs are read from the API at request time so this endpoint is the source of truth — your client should call it instead of hard-coding numbers. | | | | ---------- | -------------------------- | | **Method** | `GET` | | **Path** | `/api/conversions/pricing` | ## Examples [Section titled “Examples”](#examples) * curl ```bash curl https://api.rigyd.com/api/conversions/pricing \ -H "Authorization: Bearer rgyd_live_..." ``` * JavaScript ```js const res = await fetch('https://api.rigyd.com/api/conversions/pricing', { headers: { Authorization: `Bearer ${process.env.RIGYD_API_KEY}` }, }); const { data } = await res.json(); // data.glb_to_simready === 1 ``` * Python ```python import os, requests pricing = requests.get( "https://api.rigyd.com/api/conversions/pricing", headers={"Authorization": f"Bearer {os.environ['RIGYD_API_KEY']}"}, ).json()["data"] ``` ## Response [Section titled “Response”](#response) ```json { "data": { "glb_to_simready": 1, "text_to_simready": 2, "image_to_simready": 3, "multiview_to_simready": 3, "simulate_usd": 0 } } ``` | Job type | Credits | Endpoint | | ----------------------- | ------- | -------------------------------------------------------------------------- | | `glb_to_simready` | 1 | [`POST /api/conversions`](/conversions/3d-to-simready) | | `text_to_simready` | 2 | [`POST /api/conversions/generate`](/conversions/text-to-simready) (prompt) | | `image_to_simready` | 3 | [`POST /api/conversions/generate`](/conversions/2d-to-simready) (1 image) | | `multiview_to_simready` | 3 | [`POST /api/conversions/generate`](/conversions/2d-to-simready) (4 images) | | `simulate_usd` | 0 | [`POST /api/conversions/:id/simulate`](/conversions/simulate) | Costs are deducted atomically when a job is accepted (`202`) and refunded automatically when a job ends in `failed`. # Retrieve a job > Get the current status and full payload of a single conversion job. Fetch the current state of a single conversion job. This is what you poll after submitting. | | | | ---------- | ---------------------- | | **Method** | `GET` | | **Path** | `/api/conversions/:id` | The `:id` is the value returned in `data.id` from any conversion submission ([3D](/conversions/3d-to-simready), [2D](/conversions/2d-to-simready), [Text](/conversions/text-to-simready), [Simulate](/conversions/simulate)). 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”](#examples) * curl ```bash curl https://api.rigyd.com/api/conversions/abc123... \ -H "Authorization: Bearer rgyd_live_..." ``` * JavaScript ```js // Simple polling loop async 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)); } } ``` * Python ```python 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”](#response) ```json { "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": 1, "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" } ], "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" } } ``` See [Job lifecycle](/reference/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”](#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: ```js const v = job.report.validation; v.profiles_passing; // ["Prop-Robotics-Neutral"] — badge this v.profiles['Prop-Robotics-Neutral'].status; // "PASS" | "FAIL" | "INCOMPLETE" v.spec_release; // "2026.04.1" — the release scored against v.counts; // { total, passed, errors, warnings, not_applicable, ... } ``` `report.pipeline.physics` alongside it carries the measured mass, inertia, friction and restitution. Full detail in [SimReady Validation](/simready-validation/overview). Tip **Polling cadence**: every 3-5 seconds is plenty. Most jobs finish under two minutes. We do not currently rate-limit polling but you should still back off when not actively waiting on a result. ## When the job is done [Section titled “When the job is done”](#when-the-job-is-done) Continue to [Download result](/jobs/download). # Quickstart > Mint an API key, submit your first conversion, poll for completion, and download a SimReady USD — in five minutes. This guide takes you from zero to a downloaded SimReady USD in five minutes. 1. **Create an account and mint an API key.** Sign up at **[app.rigyd.com](https://app.rigyd.com)**, then go to **Settings → API Keys** and click **Create key**. Copy the `rgyd_live_...` token — you only see it once. Caution Treat the key like a password. It can spend your credits. 2. **Submit a conversion.** Send any 3D file you have. `glb` is the fastest path because it skips preprocessing. * curl ```bash curl -X POST https://api.rigyd.com/api/conversions \ -H "Authorization: Bearer rgyd_live_..." \ -F "file=@./model.glb" ``` * JavaScript ```js const form = new FormData(); form.append('file', new Blob([await fs.readFile('model.glb')]), 'model.glb'); const res = await fetch('https://api.rigyd.com/api/conversions', { method: 'POST', headers: { Authorization: 'Bearer rgyd_live_...' }, body: form, }); const { data } = await res.json(); console.log(data.id); // → "abc123..." ``` * Python ```python import requests with open("model.glb", "rb") as f: res = requests.post( "https://api.rigyd.com/api/conversions", headers={"Authorization": "Bearer rgyd_live_..."}, files={"file": ("model.glb", f, "model/gltf-binary")}, ) job_id = res.json()["data"]["id"] ``` You get back `202 Accepted`: ```json { "data": { "id": "abc123...", "status": "queued", "filename": "model.glb", "progress": 0, "job_type": "glb_to_simready", "credits_charged": 1, "createdAt": "2026-05-06T12:00:00.000Z" } } ``` 3. **Poll until it’s done.** ```bash curl https://api.rigyd.com/api/conversions/abc123... \ -H "Authorization: Bearer rgyd_live_..." ``` Status moves through `submitting → preprocessing → queued → running → completed | failed`. Most jobs finish in under two minutes. See [Job lifecycle](/reference/job-lifecycle) for the full payload shape. 4. **Download the result.** ```bash curl -L "https://api.rigyd.com/api/conversions/abc123.../result?format=usd" \ -H "Authorization: Bearer rgyd_live_..." \ -o simready.zip ``` The ZIP contains a `.usd` and its textures. Drop it into Isaac Sim, MuJoCo, or any USD-aware tool. ## Next steps [Section titled “Next steps”](#next-steps) * **Try the other modes**: [2D → SimReady](/conversions/2d-to-simready), [Text → SimReady](/conversions/text-to-simready), [Simulate](/conversions/simulate) * **Build a long-running integration**: read [Job lifecycle](/reference/job-lifecycle) for the full status flow and `preprocess` component shape * **Hand this site to your AI agent**: point it at [`https://docs.rigyd.com/llms-full.txt`](https://docs.rigyd.com/llms-full.txt) # Errors > Every status code the Rigyd API returns, with example bodies and how to react. Errors come back as JSON with an `error` field. Some endpoints add extra context (`status`, `details`). ```json { "error": "Insufficient credits" } ``` ## Status codes [Section titled “Status codes”](#status-codes) | Status | Meaning | Example body | | ------ | --------------------------------- | --------------------------------------------------------------------------- | | `400` | Bad request | `{ "error": "Unsupported format: .xyz. Allowed: glb, gltf, ..." }` | | `400` | Bad ZIP layout | `{ "error": "Archive must contain exactly one .obj/.gltf/.usd file" }` | | `400` | Invalid filename | `{ "error": "Invalid filename" }` | | `400` | Invalid `format` on download | `{ "error": "Invalid format. Expected one of: usd, mjcf, all" }` | | `401` | Missing / invalid auth | `{ "error": "Authentication required" }` · `{ "error": "Invalid API key" }` | | `402` | Out of credits | `{ "error": "Insufficient credits" }` | | `404` | Not found / not your job | `{ "error": "Conversion job not found" }` | | `404` | MJCF unavailable | `{ "error": "MJCF package is not available for this conversion" }` | | `409` | Wrong job state | `{ "error": "Job not yet completed", "status": "running" }` | | `409` | Source not completed for simulate | `{ "error": "Source job must be completed before simulation" }` | | `413` | Image too big | `{ "error": "Image too large (max 10MB): front.jpg" }` | | `422` | Conflicting fields on `/generate` | `{ "error": "Provide either a text prompt or image(s), not both" }` | | `422` | Missing both prompt and images | `{ "error": "Provide a text prompt or at least one image" }` | | `422` | Prompt too long | `{ "error": "Prompt must be 500 characters or less" }` | | `422` | Too many images | `{ "error": "Maximum 4 images are supported" }` | | `422` | Bad image format | `{ "error": "Unsupported image format: foo.tiff" }` | | `422` | Cannot simulate a simulation | `{ "error": "Cannot simulate a simulation job" }` | | `502` | Upstream pipeline unavailable | `{ "error": "Conversion service unavailable: ..." }` | | `502` | Generation service down | `{ "error": "Generation service unavailable: ..." }` | | `502` | Simulation service down | `{ "error": "Simulation service unavailable: ..." }` | ## Idempotency and credits [Section titled “Idempotency and credits”](#idempotency-and-credits) * **Credit reservation is atomic** — a `202 Accepted` means the credit is held, not yet spent. * **Failed jobs auto-refund** — there’s an internal `refund_applied_at` guard so retries don’t double-refund. * **`401` and `400` never charge** — the credit is only reserved after auth + format validation pass. * **A SimReady conformance failure is not an error and is not refunded** — the job completes, the asset is delivered, and the verdict is in `report.validation`. See [SimReady Validation](/simready-validation/overview). ## How to react [Section titled “How to react”](#how-to-react) | You see… | Do this | | ------------- | --------------------------------------------------------------------------------------- | | `401` | Check the key. Was it revoked? Has it expired? Is the prefix correct (`rgyd_live_`)? | | `402` | Top up at [app.rigyd.com](https://app.rigyd.com). Show the user — don’t silently retry. | | `409` (poll) | Keep polling. The job isn’t done yet. | | `422` | Surface the message — it’s a request-shape issue and the fix is on the caller side. | | `502` / `5xx` | Retry with exponential backoff. The credit is auto-refunded if the job fails. | Note Rate limiting is not enforced today but may be added without bumping the API version. Plan for `429 Too Many Requests` with a `Retry-After` header — that’s the shape we’ll use when we ship it. # Job lifecycle > Status enum, preprocessing stages, and the full payload shape returned by GET /api/conversions/:id. Every conversion follows the same lifecycle, regardless of whether you submitted a 3D file, image, text prompt, or simulation request. ## Status flow [Section titled “Status flow”](#status-flow) ```text submitting ──► preprocessing ──► queued ──► running ──► completed └─► failed ``` | Status | Meaning | | --------------- | --------------------------------------------------------------------------------- | | `submitting` | Initial state — credit reserved, file being staged. Usually invisible to clients. | | `preprocessing` | Non-GLB input is being converted to a clean GLB on the makina-worker. | | `queued` | Submitted to the SimReady pipeline, waiting for a runner. | | `running` | Pipeline is actively processing (export → align → physics → collision → USD). | | `completed` | Done. `output.model.url` is ready. Credit was charged. | | `failed` | Something went wrong. `error` is populated. Credit is auto-refunded. | GLB inputs skip the `preprocessing` state and go straight to `queued`. ### What does and does not reach `failed` [Section titled “What does and does not reach failed”](#what-does-and-does-not-reach-failed) A **SimReady conformance failure does not fail the job.** A non-conformant asset is still delivered, `status` is `completed`, and the credit is charged — the verdict rides along in `report.validation`, which is where you check it. See [SimReady Validation](/simready-validation/overview). What reaches `failed` is a correctness problem — a statement about our own output rather than about the specification: * the USD and the MJCF disagree about the asset they describe * an internal invariant is violated * an export is missing or unwritable * an input manifest does not match the mesh it describes Plus the ordinary infrastructure cases: a malformed upload, an unreachable upstream, a pipeline crash. ## Progress and stage [Section titled “Progress and stage”](#progress-and-stage) `progress` is `0`-`100`. `stage` is a free-form string identifying the current pipeline step (e.g. `"export"`, `"physics"`, `"collision_decompose"`). Don’t pattern-match on `stage` — treat it as a hint for human-readable progress UI. ## Preprocessing component [Section titled “Preprocessing component”](#preprocessing-component) When the input is non-GLB, the response includes a `preprocess` block: ```json { "preprocess": { "status": "completed", "steps": ["export", "align", "transform", "compress", "process"], "started_at": "2026-05-06T12:00:05.000Z", "completed_at": "2026-05-06T12:00:42.000Z", "error": null, "input_stats": { "vertex_count": 12345, "face_count": 6789, "...": "..." }, "telemetry": { "duration_ms": 37123, "...": "..." }, "intermediate_glb": { "url": "https://assets.rigyd.com/.../intermediate.glb", "name": "intermediate.glb" } } } ``` `preprocess.status` enum: `skipped` · `pending` · `running` · `completed` · `failed`. For GLB inputs `status === "skipped"` and the rest of the block is `null`. ## Full payload shape [Section titled “Full payload shape”](#full-payload-shape) ```jsonc { "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": "...", "started_at": "...", "completed_at": "..." }, "parameters": { // Echo of fields you sent (target_triangle_count, prompt, etc.) }, "report": { // pipeline.* — mass, friction, inertia, collision, timing // validation.* — SimReady conformance. Badge `validation.profiles_passing`; // `validation.spec_release` names the spec release scored against // invariants.* — our own correctness checks; `ok` is always true on a delivered asset }, "job_type": "glb_to_simready", "credits_charged": 1, "input": { "model": { "url": "...", "name": "toolbox.glb" }, // 3D submissions "images": [{ "url": "...", "name": "front.jpg" }], // Generate (images) "metadata": null // Set for ZIP uploads }, "preprocess": { /* see above */ }, "output": { "model": { "url": "...", "name": "toolbox.usd", "size": 982341 }, "textures": [{ "url": "...", "name": "diffuse.png" }], "mjcf_package": { "url": "...", "name": "toolbox-mjcf.zip", "size": 1234 }, "sim_video": null, // populated on simulate_usd jobs "sim_gif": null, "sim_log": null }, "source_job": null, // populated on simulate_usd jobs (points back at the parent) "simulations": [], // simulations[] of this job (children) "createdAt": "2026-05-06T12:00:00.000Z", "updatedAt": "2026-05-06T12:01:32.000Z" } } ``` Tip **Polling cadence**: 3-5 seconds while a job is `preprocessing` / `queued` / `running` is plenty. Most jobs finish in under two minutes. ## Refund guarantees [Section titled “Refund guarantees”](#refund-guarantees) A failed job is refunded **once** — Rigyd uses an internal `refund_applied_at` guard so retries don’t double-refund. You don’t need to track this client-side. Just check `credits_charged` on the final job payload. # Supported formats > Which 3D file formats Rigyd accepts, what triggers preprocessing, and how to package multi-file inputs as a ZIP. ## Input formats — 3D [Section titled “Input formats — 3D”](#input-formats--3d) | Extension | Preprocessing? | Notes | | --------- | -------------- | ------------------------------------------------------------- | | `.glb` | No (fastest) | Direct path to the SimReady pipeline. | | `.gltf` | Yes | Send as `.zip` if it has external `.bin` / textures. | | `.fbx` | Yes | Single-file binary FBX. | | `.obj` | Yes | Send as `.zip` to include the `.mtl` and textures. | | `.stl` | Yes | Geometry only — no materials. | | `.ply` | Yes | Geometry only. | | `.usd` | Yes | Single-file USD. | | `.usda` | Yes | ASCII USD. | | `.usdc` | Yes | Crate-binary USD. | | `.usdz` | Yes | USD-zipped (Pixar standard). | | `.zip` | Yes | Multi-file bundle. See [ZIP packaging](#zip-packaging) below. | When preprocessing runs, Rigyd uses Blender to convert the file to a clean GLB before submitting it to the SimReady pipeline. You don’t have to do anything — the API picks the right path based on the extension. ## Input formats — images (Generate) [Section titled “Input formats — images (Generate)”](#input-formats--images-generate) `.jpg`, `.jpeg`, `.png`, `.webp`. Max 10 MB per image. Up to 4 images per request. ## ZIP packaging [Section titled “ZIP packaging”](#zip-packaging) Use `.zip` when your model has companion files that must travel together: | Primary file | What goes in the ZIP | | ------------ | ----------------------------------------------------- | | `*.obj` | The `.obj` + its `.mtl` + every texture it references | | `*.gltf` | The `.gltf` + its `.bin` + every texture | | `*.usd[a]` | The `.usd` + every file referenced via `@./...@` | Rules: * The archive must contain **exactly one** primary file (`.obj` / `.gltf` / `.usd[a]`). * Texture / mesh references must be **relative** (`./diffuse.png`, not absolute paths). * The API validates the archive on upload — invalid layouts fail at `400` before any credit is reserved. Note When you send a ZIP, the response payload includes `input.metadata` describing the detected primary file, the texture set, and the file tree. See [`GET /api/conversions/:id`](/jobs/retrieve). ## File size limits [Section titled “File size limits”](#file-size-limits) 500 MB per upload (server-side nginx limit). For larger assets, decimate locally first. # What's covered today > The SimReady Foundation profile Rigyd targets, what a delivered asset actually scores, and the two requirements we knowingly fail. Every Rigyd conversion targets the **`Prop-Robotics-Neutral`** profile, and passes it: rigid-body props built for robotics simulation, runtime-neutral so they drop cleanly into [Isaac Sim](https://developer.nvidia.com/isaac/sim), [Isaac Lab](https://isaac-sim.github.io/IsaacLab/), [MuJoCo](https://mujoco.org/), [Gazebo](https://gazebosim.org/), or any other [OpenUSD](https://openusd.org/)-aware simulator. ## What the profile covers [Section titled “What the profile covers”](#what-the-profile-covers) * **Stage-level conformance** — `defaultPrim`, `upAxis = Z`, `metersPerUnit = 1.0`, `kilogramsPerUnit`, and a single root [`UsdGeomXform`](https://openusd.org/release/api/class_usd_geom_xform.html). * **Naming and on-disk layout** — prim, file, and folder names that survive a round-trip through Linux, Windows, and the Omniverse Nucleus path resolver, with no absolute paths in references. * **Geometry** — valid topology, indexed primvars, consistent face winding, normals on non-subdivided meshes, points within 32-bit float precision, and sane RTX world-space bounds. * **Visual materials** — [`UsdPreviewSurface`](https://openusd.org/release/spec_usdpreviewsurface.html) bindings that resolve, texture colour spaces set correctly, dimensions within RTX limits. * **Rigid-body physics** — [`UsdPhysics`](https://openusd.org/release/api/usd_physics_page_front.html) `RigidBodyAPI` and `MassAPI` applied correctly, mass and inertia coherent with collision volume, no skewed transforms. * **Collision** — [`CollisionAPI`](https://openusd.org/release/api/class_usd_physics_collision_a_p_i.html) and `MeshCollisionAPI` on the right prims, invisible colliders marked `purpose = "guide"` so they don’t render but still simulate. * **Physics materials and grasp** — friction and restitution bound on every collider, and a grasp vector for manipulation planning. ## What a delivered asset scores [Section titled “What a delivered asset scores”](#what-a-delivered-asset-scores) All nine profiles are scored on every conversion, not just the one we target — the ones we do not target are the only thing that says what is left to do. This is a representative rigid prop: | Profile | Requirements | Status | Unmet | | ---------------------------------------- | ------------ | ------------ | ----------------------------------- | | **`Prop-Robotics-Neutral`** *(targeted)* | 50 | **`PASS`** | — | | `Prop-Robotics-Physx` | 51 | `FAIL` | `COL.001` | | `Prop-Robotics-Isaac` | 29 | `FAIL` | `COL.001` · `ISA.001` · `RB.MB.001` | | `Robot-Body-Neutral` | 30 | `FAIL` | `RB.MB.001` | | `Robot-Body-Runnable` | 42 | `FAIL` | `RB.MB.001` | | `Robot-Body-Isaac` | 52 | `FAIL` | `COL.001` · `ISA.001` · `RB.MB.001` | | `Package` | 6 | `INCOMPLETE` | — | | `Package-NoBOM` | 5 | `INCOMPLETE` | — | | `Package-Candidate` | 2 | `INCOMPLETE` | — | Each entry in `report.validation.profiles` carries a one-line `note` describing where a passing asset can be used, plus `targeted` and — when we do not aim at it — `not_targeted_because`. Status is per profile, always The same requirement is a different answer under different profiles. `COL.001` is not part of `Prop-Robotics-Neutral`, so it reports `N/A` there with `na_reason: "not-in-profile"` — while `Prop-Robotics-Physx` requires it and records it as a genuine `FAIL`. If you re-score anything yourself, read `checks[].measured_status` (the raw finding) rather than `checks[].status` (what to act on, given the profiles we target). ## `PASS` and `INCOMPLETE` are different claims [Section titled “PASS and INCOMPLETE are different claims”](#pass-and-incomplete-are-different-claims) | Status | Means | | ------------ | ----------------------------------------------------------------------------------------- | | `PASS` | Every requirement in the profile was checked, and none failed | | `FAIL` | At least one requirement failed — `failing[]` names them | | `INCOMPLETE` | Nothing failed, but some requirements have no rule written yet — `unchecked[]` names them | `INCOMPLETE` is not a soft pass. It means *we do not know*. `PASS` is a claim about what we checked, so we only make it when we checked all of it — the Package profiles above report `INCOMPLETE` because they describe a distribution package rather than a single asset file, and we have a rule for only one of their requirements. ## The two we knowingly fail [Section titled “The two we knowingly fail”](#the-two-we-knowingly-fail) `Prop-Robotics-Physx` and `Prop-Robotics-Isaac` are `Prop-Robotics-Neutral` plus these, and nothing else: | Code | What it asks for | Why we don’t meet it yet | | --------- | --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `COL.001` | SDF collision approximation | We author convex-hull colliders. Moving to SDF changes contact behaviour for every asset already delivered, so it is an acceptance run of its own, not a flag flip. | | `ISA.001` | Isaac composition layout | We ship one flattened USD. Isaac composition is a packaging architecture — `kind='component'`, a `payloads/` tree, the asset split into `_base.usd` + `_physics.usd` — rather than stricter physics. | Both are scheduled work. They appear in `failing[]` **and** in `failing_deferred[]`, which is a subset rather than a replacement: the profile still fails and the requirement is still unmet. `deferred_reasons` carries the reason for each. Rendering deferred failures in a quieter style is the intended use — do not hide them. ## Where we disagree with NVIDIA’s own validator [Section titled “Where we disagree with NVIDIA’s own validator”](#where-we-disagree-with-nvidias-own-validator) Exactly one place, deliberately. NVIDIA’s `profiles.toml` marks the multi-body feature *“(This is an optional feature for this profile)”* under `Prop-Robotics-Neutral` and `Prop-Robotics-Physx`, and marks nothing of the sort under `Prop-Robotics-Isaac`. That sentence is a **TOML comment**, and their own tool parses the file with a reader that discards comments — so their validator reports every single-body prop as failing `RB.MB.001` (“asset contains at least two rigid bodies”), which is simply what a prop is. The spec text is the authority, so we honour it. `RB.MB.001` lands in `optional_unmet` rather than `failing`, and `optional_features` quotes their sentence verbatim so you can check the reading yourself. Not satisfying an optional feature is less capability, not non-conformance. Under `Prop-Robotics-Isaac`, where nothing is marked optional, the same requirement is a real `FAIL` — which is exactly why status is per profile. Two instruments, one answer Ours is a fast, offline pre-flight instrument that runs on every conversion. NVIDIA’s is the authority. Where they disagree, theirs is right and ours is the bug — that is how four of our own checks were found to be wrong. Verified on both verification assets across all three Prop profiles, the two agree exactly, with `RB.MB.001` above the single known and intentional exception. ## Warnings are advisory [Section titled “Warnings are advisory”](#warnings-are-advisory) `counts.warnings` never affects a profile verdict — ours counts only errors against a profile, and NVIDIA’s filters to errors before computing a pass. Label warnings advisory if you surface them, and don’t let them colour a pass/fail badge. For the per-requirement status of every code in the registry, see [Full requirement coverage](/simready-validation/full-coverage). # Full requirement coverage > Every requirement code in the SimReady Foundation specification at release 2026.04.1, and Rigyd's status against each. Every requirement in the SimReady Foundation registry at release **`2026.04.1`** — 136 codes — with what Rigyd does about each. The list is generated from [NVIDIA’s own published metadata](https://github.com/NVIDIA/simready-foundation), vendored verbatim, so a code appears here if and only if it appears in their specification. Codes that used to be listed here Earlier versions of this page listed `VG.034`, `VM.D.001` and `RC.002`. **None of them exist in the SimReady specification** — they were ours, and they read like requirement IDs, which is exactly what was wrong with them. They have been removed, along with a family of `AR.*` codes that scored articulated assets under a profile name that was never published. If you reference any of them by code, they are gone. See the [changelog](/changelog). ## How to read this [Section titled “How to read this”](#how-to-read-this) **Profiles** — which published profiles pull the requirement in: | Tag | Profile | | ----- | -------------------------------------------------------------------- | | `N` | `Prop-Robotics-Neutral` — the profile every Rigyd conversion targets | | `Px` | `Prop-Robotics-Physx` | | `Is` | `Prop-Robotics-Isaac` | | `RB` | the `Robot-Body-*` family | | `Pkg` | the `Package*` family | | — | in the registry, but no published profile requires it | **Rigyd** — whether we have a rule for it: | Value | Meaning | | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Checked** | A rule runs on every conversion. Reports `PASS`, `WARNING`, `ERROR`, or `N/A` with `na_reason: "not-applicable-to-asset"` when the requirement is required but vacuous — an untextured asset and a texture colour-space rule, say. | | **Planned** | No rule written yet. Always reports `N/A` with `na_reason: "planned"`, and counts toward a profile’s `INCOMPLETE` status rather than passing it. | A requirement outside every profile we target additionally reports `N/A` with `na_reason: "not-in-profile"` in the top-level `checks[]`, with the raw finding preserved in `measured_status`. Those three values are the whole `na_reason` vocabulary — nothing else appears, and neither instrument may extend it. Of the 136 codes, **80 are checked and 56 are planned**. All 50 requirements of `Prop-Robotics-Neutral` are checked, which is what lets that profile report `PASS` rather than `INCOMPLETE`. ## Naming & paths (NP) [Section titled “Naming & paths (NP)”](#naming--paths-np) | Code | Requirement | Profiles | Rigyd | | -------- | ------------------------------------------------------------------------------------- | ---------- | ------- | | `NP.001` | Prims shall follow consistent naming conventions | — | Checked | | `NP.002` | USD files shall follow consistent naming conventions | `N` · `Px` | Checked | | `NP.003` | Assets shall follow consistent directory structure | `N` · `Px` | Checked | | `NP.004` | File and directory paths shall not exceed platform limits | `N` · `Px` | Checked | | `NP.005` | Assets must follow a specific folder structure with the asset name as the root folder | `N` · `Px` | Checked | | `NP.006` | Asset metadata must be stored in the USD file or a sidecar JSON file | `N` · `Px` | Checked | | `NP.007` | All references must use relative paths | `N` · `Px` | Checked | | `NP.008` | All asset, reference, and payload paths must resolve to files that exist | `N` · `Px` | Checked | ## Atomic asset (AA) [Section titled “Atomic asset (AA)”](#atomic-asset-aa) | Code | Requirement | Profiles | Rigyd | | ----------- | ----------------------------------------------------------------- | -------------------------------- | ------- | | `AA.001` | Asset references should use anchored paths | `N` · `Px` · `Is` · `RB` · `Pkg` | Checked | | `AA.002` | Asset must use only supported file types | `N` · `Px` · `Is` · `RB` | Checked | | `AA.OV.001` | Texture UDIMs are not supported in USDZ files in NVIDIA Omniverse | — | Checked | ## SimReady metadata (SR) [Section titled “SimReady metadata (SR)”](#simready-metadata-sr) | Code | Requirement | Profiles | Rigyd | | -------- | -------------------------------------------------------------------------------------- | ---------- | ----------- | | `SR.001` | The asset stage must contain all required metadata fields | `N` · `Px` | Checked | | `SR.002` | The SimReady asset file should contain a thumbnail that is representative of the asset | `Pkg` | **Planned** | ## Units (UN) [Section titled “Units (UN)”](#units-un) | Code | Requirement | Profiles | Rigyd | | -------- | -------------------------------------------------------------------------------------------------------------- | ------------------------ | ------- | | `UN.001` | Stage must specify upAxis to define the orientation of the stage | `Is` · `RB` | Checked | | `UN.002` | Stage must specify metersPerUnit to define the linear unit scale | `Is` · `RB` | Checked | | `UN.003` | Stage must specify kilogramsPerUnit to define the mass unit scale, if physics objects are present in the stage | — | Checked | | `UN.004` | Must apply corrective transforms for different units | — | Checked | | `UN.005` | Stage must specify timeCodesPerSecond, if timesamples are present in the stage | — | Checked | | `UN.006` | Stage must specify upAxis = “Z” to define the orientation of the stage | `N` · `Px` | Checked | | `UN.007` | Stage must specify metersPerUnit = 1.0 to define the linear unit scale | `N` · `Px` · `Is` · `RB` | Checked | ## Hierarchy (HI) [Section titled “Hierarchy (HI)”](#hierarchy-hi) | Code | Requirement | Profiles | Rigyd | | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------ | ----------- | | `HI.001` | All prims in the hierarchy must be direct or indirect descendents of a single root root prim, preventing scattered or disconnected Xform hierarchies | `N` · `Px` | Checked | | `HI.002` | Every UsdGeomGprim must have a parent Xform with specific transform operations and hierarchy constraints | — | Checked | | `HI.003` | The root prim of a hierarchy must be transformable, meaning its’ prim type must inherit from UsdGeomXformable and be capable of receiving transform operations | `N` · `Px` | Checked | | `HI.004` | Stage must specify a default prim to define the root entry point | `N` · `Px` · `Is` · `RB` | Checked | | `HI.005` | Transformations (translate, rotate, scale, pivot) on prims that are intended to be translated, rotated or scaled by users (e.g. the root prim of an asset) should conform to the UsdGeomXformCommonAPI | — | Checked | | `HI.006` | All prims representing distinct objects or groups that require placement, posing or animation shall inherit from UsdGeomXformable | — | Checked | | `HI.007` | This is a placeholder requirement to maintain sequential numbering in the hierarchy requirements | — | Checked | | `HI.008` | Geometry should be grouped under parent Xforms in a way that is logical for the object’s structure and intended use in layouts or simulations | — | Checked | | `HI.009` | For assets (e.g., a robot with multiple articulated joints), the hierarchy should reflect the kinematic chain, with appropriate Xforms for each transformable link | — | **Planned** | | `HI.010` | Assets must not contain undefined prims (overs) | `N` · `Px` | Checked | ## Visual geometry (VG) [Section titled “Visual geometry (VG)”](#visual-geometry-vg) | Code | Requirement | Profiles | Rigyd | | ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | ----------- | | `VG.001` | Assets must contain at least one Imageable Geometry | `Is` · `RB` | Checked | | `VG.002` | Boundable geometry primitives must have valid extent values | `N` · `Px` | Checked | | `VG.003` | Only include geometry that contributes to visualization or simulation | — | **Planned** | | `VG.004` | Use efficient mesh boundaries for performance | — | **Planned** | | `VG.005` | Meshes should maintain appropriate scale and boundary volumes | — | **Planned** | | `VG.006` | Meshes should not overlap unnecessarily | — | **Planned** | | `VG.007` | Mesh geometry must be manifold | — | **Planned** | | `VG.008` | Meshes should not share the exact same space | — | **Planned** | | `VG.009` | Use indexed primvars when values are repeated | — | Checked | | `VG.010` | Use subdivision only when needed for smooth surfaces or displacement | — | Checked | | `VG.011` | Only include primvars that are actively used | — | Checked | | `VG.012` | Combine small meshes into larger ones where appropriate | — | **Planned** | | `VG.013` | Use appropriate tessellation density for geometry | — | **Planned** | | `VG.014` | Mesh topology must be valid | `N` · `Px` | Checked | | `VG.015` | Use time samples only when attribute values change | — | Checked | | `VG.016` | Each vertex position should be unique | — | **Planned** | | `VG.017` | Avoid tessellating primitive shapes | — | Checked | | `VG.018` | Mesh topology should be without unused vertices, edges, or faces | — | **Planned** | | `VG.019` | Faces should have non-zero area | — | **Planned** | | `VG.020` | The values of `points` must not exceed the limit at which a given precision can be represented using 32-bit floats | — | Checked | | `VG.021` | Meshes must be triangulated for optimal rendering performance and compatibility when considering automatic collider creation (convex-hull, convex-decomposition, etc…). Triangulating mesh will provide predictable results versus n-gons | — | Checked | | `VG.022` | Transparent physical bodies should be watertight to allow for simulation of light transmission and refraction | — | **Planned** | | `VG.023` | Meshes should be positioned with xform ops rather than by “baking” transformations into point positions | — | Checked | | `VG.024` | Repeated occurrences of identically shaped objects should have identical mesh connectivity | — | **Planned** | | `VG.025` | Geometry shall be defined as such that the asset is correctly positioned and oriented at the origin (0,0,0) | `N` · `Px` | Checked | | `VG.026` | The pivot point of an asset should be positioned logically: at the center of the object’s base for ground plane objects, and at the center of rotation for objects that rotate around specific points | — | **Planned** | | `VG.027` | All non-subdivided meshes must have normals | `N` · `Px` | Checked | | `VG.028` | Mesh normals values must be valid to produce correct shading | `N` · `Px` | Checked | | `VG.029` | The winding order of faces in a mesh must correctly represent the orientation (front/back) of the face | `N` · `Px` | Checked | | `VG.MESH.001` | All geometry shall be represented as non-subdivided mesh primitives using the UsdGeomMesh schema | `N` · `Px` | Checked | | `VG.RTX.001` | World space bounds must not exceed RTX limit | — | Checked | ## Visual materials (VM) [Section titled “Visual materials (VM)”](#visual-materials-vm) | Code | Requirement | Profiles | Rigyd | | ------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | ---------- | ------- | | `VM.BIND.001` | Material bindings must use appropriate scope to ensure proper material assignment and inheritance | `N` · `Px` | Checked | | `VM.BIND.002` | Shader inputs must have correct types matching their shader specification to ensure proper material behavior and prevent runtime errors | `N` · `Px` | Checked | | `VM.MAT.001` | Each renderable GPrim must have a computed material bound to it | `N` · `Px` | Checked | | `VM.MDL.001` | MDL material source assets must be properly referenced and accessible to ensure material loading and rendering | `N` · `Px` | Checked | | `VM.MDL.002` | MDL Shaders must standard OpenUSD shader source attributes to ensure compatibility | `N` · `Px` | Checked | | `VM.PS.001` | Material attributes must comply with the UsdPreviewSurface specification to ensure consistent rendering and viewer compatibility | — | Checked | | `VM.TEX.001` | Texture dimensions must not exceed 16,384 pixels on either axis to ensure optimal performance and memory usage in simulation environments | `N` · `Px` | Checked | | `VM.TEX.002` | Each PBR texture channel must use the correct color space | `N` · `Px` | Checked | ## Non-visual materials (NVM) [Section titled “Non-visual materials (NVM)”](#non-visual-materials-nvm) | Code | Requirement | Profiles | Rigyd | | --------- | ------------------------------------------------------------------ | -------- | ----------- | | `NVM.001` | Materials must specify additional “non-visual” material attributes | — | **Planned** | | `NVM.002` | Materials must specify a base material type | — | **Planned** | | `NVM.003` | Materials must specify surface coating | — | **Planned** | | `NVM.004` | Attributes must be on bound materials | — | **Planned** | | `NVM.005` | Properties must be consistent with visual materials | — | **Planned** | | `NVM.006` | Properties must not be time-varying | — | **Planned** | ## Semantic labels (SL) [Section titled “Semantic labels (SL)”](#semantic-labels-sl) | Code | Requirement | Profiles | Rigyd | | -------------- | -------------------------------------------------------------------------------------------------------------- | -------- | ----------- | | `SL.001` | All geometry prims must be semantically labeled | — | **Planned** | | `SL.003` | Semantic labels must use the SemanticsLabelsAPI schema | — | **Planned** | | `SL.NV.002` | Semantic label attributes must not contain time samples | — | **Planned** | | `SL.QCODE.001` | If the Wikidata ontology is used, Q-Codes must be valid, properly formatted, and retrievable from wikidata.org | — | **Planned** | ## Physics — rigid bodies (RB) [Section titled “Physics — rigid bodies (RB)”](#physics--rigid-bodies-rb) | Code | Requirement | Profiles | Rigyd | | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ | ------- | | `RB.001` | Assets must contain at least one rigid body | `N` · `Px` · `Is` · `RB` | Checked | | `RB.003` | Rigid bodies have to be UsdGeomXformable prims | `N` · `Px` · `Is` · `RB` | Checked | | `RB.005` | Rigid bodies cannot be part of a scene graph instance | `N` · `Px` · `Is` · `RB` | Checked | | `RB.006` | Rigid bodies can not be nested unless xformOp reset xform stack is used | `N` · `Px` · `Is` · `RB` | Checked | | `RB.007` | Rigid bodies *or* their descendant collision shapes must have a mass specification | `N` · `Px` · `Is` · `RB` | Checked | | `RB.008` | Rigid bodies *or* their descendent collision shapes may have detailed mass properties including density, center of mass, and inertia tensor | — | Checked | | `RB.009` | Rigid bodies have to be UsdGeomXformable prims without skew matrix | `N` · `Px` · `Is` · `RB` | Checked | | `RB.010` | Invisible collision meshes must have their purpose attribute set to ‘guide’ to be properly excluded from rendering | `N` · `Px` · `Is` · `RB` | Checked | | `RB.011` | Rigid bodies must have an explicit mass specification, or their descendant collision shapes must have non-zero volume to allow mass auto-computation. Nested rigid body subtrees are excluded from the traversal | `RB` | Checked | | `RB.012` | Rigid bodies should not be nested unless they are connected by a joint | `RB` | Checked | | `RB.COL.001` | Colliding Gprims must apply the Collision API | `N` · `Px` · `Is` · `RB` | Checked | | `RB.COL.002` | **UsdPhysicsMeshCollisionAPI** may only be applied to **UsdGeom.Mesh** prims, and any prim with MeshCollisionAPI must also have **UsdPhysicsCollisionAPI** applied | `N` · `Px` · `Is` · `RB` | Checked | | `RB.COL.003` | The Mesh Collision API can only be assigned to Mesh Prims | `N` · `Px` · `Is` · `RB` | Checked | | `RB.COL.004` | The collision shape scale must be uniform for the following geometries: Sphere, Capsule, Cylinder, Cone & Points | `N` · `Px` · `Is` · `RB` | Checked | | `RB.MB.001` | Assets must contain at least two rigid bodies | `N` · `Px` · `Is` · `RB` | Checked | ## Physics — joints (JT) [Section titled “Physics — joints (JT)”](#physics--joints-jt) | Code | Requirement | Profiles | Rigyd | | ------------ | ------------------------------------------------------------------------------------------- | ------------------------ | ------- | | `JT.001` | Rigid bodies which are not free floating should be connected using joints | `N` · `Px` · `Is` · `RB` | Checked | | `JT.002` | Targets set to Body0 and Body1 relationships must exist | `N` · `Px` · `Is` · `RB` | Checked | | `JT.003` | Body0 and Body1 relationships must not have more than one target | `N` · `Px` · `Is` · `RB` | Checked | | `JT.ART.001` | For stable and fast simulations of kinematic chains, an asset should define an articulation | — | Checked | | `JT.ART.002` | Articulation roots cannot be nested | `N` · `Px` · `Is` · `RB` | Checked | | `JT.ART.003` | Articulations are not allowed on kinematic bodies | `N` · `Px` · `Is` · `RB` | Checked | | `JT.ART.004` | Articulations are not allowed on static bodies | `N` · `Px` · `Is` · `RB` | Checked | ## Physics — driven joints (DJ) [Section titled “Physics — driven joints (DJ)”](#physics--driven-joints-dj) | Code | Requirement | Profiles | Rigyd | | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ----------- | | `DJ.001` | Validate that each driven joint has correct drive configuration and joint state, including required limits and consistency checks against the current joint state values | `RB` | **Planned** | | `DJ.002` | Driven joints must implement proper joint state API for simulation state management | `RB` | **Planned** | | `DJ.003` | Driven joints must maintain correct transform relationships and state consistency | `RB` | **Planned** | | `DJ.004` | PhysX driven joints must implement drive API or mimic functionality for controlled motion | `RB` | **Planned** | | `DJ.005` | PhysX driven joints must have appropriate maximum velocity limits configured | `RB` | **Planned** | | `DJ.006` | Drive joint parameters must be within reasonable ranges for stable simulation. The joint parameters should not be excessively big and should be natural numbers | `RB` | **Planned** | | `DJ.007` | Mimic API configuration must be properly validated for coordinated joint motion | `RB` | **Planned** | | `DJ.008` | Robot schema joints must exist and be properly defined for Isaac Sim integration | `RB` | **Planned** | | `DJ.009` | Robot schema links must exist and be properly connected to joints for kinematic chain definition | `RB` | **Planned** | | `DJ.010` | Robot joint and link relationships must be validated for proper kinematic tree structure | `RB` | **Planned** | | `DJ.011` | The articulation must have no loops and at most one joint between any two bodies. Only joints that participate in the articulation are checked; joints with `physics:excludeFromArticulation = true` are ignored | `RB` | **Planned** | ## Physics — base articulation (BA) [Section titled “Physics — base articulation (BA)”](#physics--base-articulation-ba) | Code | Requirement | Profiles | Rigyd | | -------- | -------------------------------------------------------------------------------------------------- | -------- | ----------- | | `BA.001` | The USD stage must contain at least one prim with the UsdPhysicsArticulationRootAPI applied | `RB` | Checked | | `BA.002` | Collision meshes on non-adjacent links in the articulation hierarchy must not overlap or intersect | `RB` | **Planned** | ## Physics — colliders (COL) [Section titled “Physics — colliders (COL)”](#physics--colliders-col) | Code | Requirement | Profiles | Rigyd | | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ | ------- | | `COL.001` | Every mesh collider (Mesh prim with USDPhysics.CollisionAPI and USDPhysics.MeshCollisionAPI) must have an SDF (Signed Distance Field) approximation for efficient collision detection | `Px` · `Is` · `RB` | Checked | ## Physics — PhysX colliders (PHYSX) [Section titled “Physics — PhysX colliders (PHYSX)”](#physics--physx-colliders-physx) | Code | Requirement | Profiles | Rigyd | | --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ----------- | | `PHYSX.COL.001` | CollisionAPI may only be applied to a UsdGeom Gprim or to an Xform that has PhysxMeshMergeCollisionAPI and whose collisionmeshes collection includes at least one Gprim | `RB` | **Planned** | | `PHYSX.COL.002` | MeshCollisionAPI may only be applied to a UsdGeom Mesh or to a prim that has PhysxMeshMergeCollisionAPI. CollisionAPI is required whenever MeshCollisionAPI is applied | `RB` | **Planned** | ## Physics materials (PMT) [Section titled “Physics materials (PMT)”](#physics-materials-pmt) | Code | Requirement | Profiles | Rigyd | | --------- | ---------------------------------------------------------------------------------------------------------------------- | ----------------- | ------- | | `PMT.001` | Every collider (prim with PhysicsCollisionAPI) must have a material:binding:physics relationship to a physics material | `N` · `Px` · `Is` | Checked | ## Grasp (GSP) [Section titled “Grasp (GSP)”](#grasp-gsp) | Code | Requirement | Profiles | Rigyd | | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------- | ------- | | `GSP.001` | In order for an asset to be considered graspable, an asset must have at least 1 line object defined within the asset. A line is at least 2 points in space, and this line must intersect the asset that needs to be grasped by the robotic grippers | `N` · `Px` · `Is` | Checked | ## Runtime — Isaac Sim (ISA) [Section titled “Runtime — Isaac Sim (ISA)”](#runtime--isaac-sim-isa) | Code | Requirement | Profiles | Rigyd | | --------- | -------------------------------------------------------------------------------------------------------------------------------- | ----------- | ------- | | `ISA.001` | The asset must be composed correctly for Isaac Sim using a structured payload and reference system with proper file organization | `Is` · `RB` | Checked | ## Robot materials (RM) [Section titled “Robot materials (RM)”](#robot-materials-rm) | Code | Requirement | Profiles | Rigyd | | -------- | ----------------------------------------------------------------------------------------------------------------- | -------- | ----------- | | `RM.001` | Materials must not contain nested materials to avoid unexpected rendering behavior | — | **Planned** | | `RM.002` | Materials must only be defined in the top-level Looks prim following USD best practices for material organization | — | **Planned** | ## Robot core (RC) [Section titled “Robot core (RC)”](#robot-core-rc) | Code | Requirement | Profiles | Rigyd | | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ----------- | | `RC.001` | named by a feature manifest; ships no requirement document at this release | `RB` | **Planned** | | `RC.003` | named by a feature manifest; ships no requirement document at this release | `RB` | **Planned** | | `RC.004` | named by a feature manifest; ships no requirement document at this release | `RB` | **Planned** | | `RC.005` | named by a feature manifest; ships no requirement document at this release | `RB` | **Planned** | | `RC.006` | named by a feature manifest; ships no requirement document at this release | `RB` | **Planned** | | `RC.007` | named by a feature manifest; ships no requirement document at this release | `RB` | **Planned** | | `RC.008` | Robot assets must declare a valid robot type on the default prim. The `isaac:robotType` attribute must be present, must be one of the schema-defined allowed tokens, and must not be `"Default"`. Allowed values are : “End Effector”, “Manipulator”, “Humanoid”, “Wheeled”, “Holonomic”, “Quadruped”, “Mobile Manipulators”, “Aerial” | `RB` | **Planned** | | `RC.009` | The root joint (the first target of `isaac:physics:robotJoints`) must be pinned for robot types that require a fixed base (e.g. Manipulator, End Effector) and must not be pinned for other robot types | `RB` | **Planned** | ## Packaging (PKG) [Section titled “Packaging (PKG)”](#packaging-pkg) | Code | Requirement | Profiles | Rigyd | | -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ----------- | | `PKG.BOM.001` | The BOM is a metadata file identified by the name `com.nvidia.simready.packaging.bom.json`. It MUST list all content files in the package with per-item fields, using forward-slash relative paths with no duplicates | `Pkg` | **Planned** | | `PKG.CONF.001` | Packages MAY include conformance metadata files recording SimReady Foundations validation results, following defined naming and JSON schema conventions | `Pkg` | **Planned** | | `PKG.CONF.002` | Packages MAY include an OpenUSD root layers metadata file declaring the top-level USD entry points that should be validated as self-contained assets | — | **Planned** | | `PKG.DEF.001` | Each package MUST have exactly one valid package definition with a globally unique, case-insensitive, immutable identity, placed at the package root alongside a `.metadata/` folder | `Pkg` | **Planned** | | `PKG.HASH.001` | Hash fields MUST be objects containing at least a `sha256` key with lowercase hexadecimal value. Implementations SHOULD also include `blake3` and/or `blake2b` when available, for performance. When `content_hash` or `package_hash` are present, their `sha256` values MUST be computed using the deterministic algorithms defined in this requirement | `Pkg` | **Planned** | | `PKG.META.001` | Metadata files MUST be JSON with UTF-8 encoding, use reverse domain naming, and follow write-once semantics | `Pkg` | **Planned** | ## References [Section titled “References”](#references) * [SimReady Foundation — specification home](https://nvidia.github.io/simready-foundation/) * [SimReady Foundation — features](https://nvidia.github.io/simready-foundation/guides/features/features.html) * [SimReady Foundation — profiles](https://nvidia.github.io/simready-foundation/guides/profiles/profiles.html) * [The SimReady Specification — Omniverse docs](https://docs.omniverse.nvidia.com/simready/latest/overview/simready-spec.html) * [OpenUSD](https://openusd.org/) * [NVIDIA Isaac Sim](https://developer.nvidia.com/isaac/sim) · [Isaac Lab](https://isaac-sim.github.io/IsaacLab/) # Overview > Every Rigyd asset is scored against the SimReady Foundation specification and ships with the result attached. Here is how to read it. Every asset that comes out of a Rigyd conversion is scored against the [SimReady Foundation specification](https://nvidia.github.io/simready-foundation/), and the result ships with the asset in `report.validation`. If you have ever opened a USD in Isaac Sim and watched it fall through the floor, intersect itself, or refuse to articulate, you already know what this is for. Validation is the line between “loads in a viewer” and “drops into a simulator and behaves.” ## Conformance is reported, never enforced [Section titled “Conformance is reported, never enforced”](#conformance-is-reported-never-enforced) **A conformance failure does not fail your job and does not refund your credit.** The asset is delivered either way, with the report attached. That is deliberate. A non-conformant asset is still a usable asset, and you are better served by getting it with a red report than by getting nothing. Conformance describes our output against someone else’s specification; it is a description, not a gate. What *does* fail a conversion is a different class of problem entirely — a statement about our own correctness rather than about the spec: | Fails the job | Why | | ------------------------------- | ------------------------------------------------------------------------ | | USD/MJCF parity disagreement | The two files we hand you must describe one asset | | An internal invariant violation | e.g. the centre of mass does not resolve to the point we declared for it | | A missing or unwritable export | There is no asset to deliver | | A rejected input manifest | The description does not match the mesh | When one of those trips, the job goes to `failed`, `error` is populated, and the credit is auto-refunded in the [normal way](/reference/job-lifecycle). A `completed` job always has a downloadable asset, whatever its conformance verdict. ## What we conform to [Section titled “What we conform to”](#what-we-conform-to) The [SimReady Foundation](https://nvidia.github.io/simready-foundation/) is an open specification layer on top of [OpenUSD](https://openusd.org/) that defines what a simulation-ready asset must contain — naming and folder structure, units, hierarchy, geometry, visual and non-visual materials, semantic labels, rigid-body and collision physics, joints and articulation, and runtime-specific requirements for [PhysX](https://docs.omniverse.nvidia.com/extensions/latest/ext_physics.html) and [NVIDIA Isaac Sim](https://developer.nvidia.com/isaac/sim). The foundation groups requirements into [features](https://nvidia.github.io/simready-foundation/guides/features/features.html) and rolls features up into [profiles](https://nvidia.github.io/simready-foundation/guides/profiles/profiles.html) — concrete bundles you can target. Rigyd’s validator does not keep its own list of requirements: the profile → feature → requirement map is **joined at runtime from NVIDIA’s published metadata**, so a requirement that moves between features, or a profile that gains one, moves here without anybody transcribing it. We are not an authority on SimReady — NVIDIA is. Every check traces to a requirement they define. Pinned to spec release 2026.04.1 `report.validation.spec_release` names the release every verdict is scored against. We are pinned to **`2026.04.1`** while the specification itself has moved on to `2026.06.0`, because `2026.04.1` is the newest release with a working published validator wheel. If you compare our report against a newer copy of the spec, that gap is why. ## Reading the result [Section titled “Reading the result”](#reading-the-result) Scoring is **per profile**, and the same requirement can be a different answer under each one. `COL.001` (SDF collision approximation) is not part of `Prop-Robotics-Neutral` at all, but it is required by `Prop-Robotics-Physx` and `Prop-Robotics-Isaac` — so a single asset reports `N/A` for it under the first and `FAIL` under the other two. There is no single global verdict that means anything. The field to badge is **`profiles_passing`**: ```js const passing = job.report.validation.profiles_passing; // ["Prop-Robotics-Neutral"] badge = passing.length ? `SimReady: ${passing.join(', ')}` : 'SimReady: no profile'; ``` `report.validation.overall` still exists, but it is scoped to the profiles we target and is not a conformance claim about any particular one. Prefer `profiles_passing`, or `report.validation.profiles[''].status` for a single red/amber/green. ## Where a Rigyd asset lands today [Section titled “Where a Rigyd asset lands today”](#where-a-rigyd-asset-lands-today) Every conversion available on this API produces a rigid-body prop, targets **`Prop-Robotics-Neutral`**, and **passes it**. `Prop-Robotics-Physx` and `Prop-Robotics-Isaac` fail on exactly two requirements, `COL.001` and `ISA.001`, and both are scheduled work rather than defects — see [What’s covered today](/simready-validation/coverage). skip\_validation no longer does what its name suggests The `skip_validation` field is still accepted on the [3D](/conversions/3d-to-simready), [2D](/conversions/2d-to-simready), and [Text](/conversions/text-to-simready) → SimReady requests, but **it no longer skips conformance scoring**. Every conversion is scored and every `completed` job carries a `report.validation` block, whether you send it or not. What it now suppresses is the internal **correctness** gate — the parity, invariant and export checks in the table above, the ones that are supposed to fail a job. Leave it unset. There is no longer anything to opt out of on the conformance side, and opting out on the correctness side means accepting an asset we could not verify we built properly. ## Where to go next [Section titled “Where to go next”](#where-to-go-next) * [What’s covered today](/simready-validation/coverage) — the profile we target, what passes, and the two requirements we knowingly fail. * [What’s coming next](/simready-validation/roadmap) — how our coverage tracks the foundation’s published profile families. * [Full requirement coverage](/simready-validation/full-coverage) — all 136 requirement codes and our status against each. # What's coming next > How Rigyd's SimReady coverage tracks the foundation's published profile families, and what stands between us and each one. The SimReady Foundation specification is itself young — the team’s stated position is that it [“currently covers basic static props (ones that don’t move or articulate)”](https://docs.omniverse.nvidia.com/simready/latest/overview/simready-spec.html), with the broader profile set being filled in as the foundation grows. Rigyd’s coverage moves with it. ## Where we are [Section titled “Where we are”](#where-we-are) Every conversion available on this API produces a single rigid-body prop and targets **`Prop-Robotics-Neutral`**, which it passes. Everything below is what it would take to claim the next profile — named requirements, not aspirations. | Profile family | Foundation profiles | Rigyd | | ---------------- | --------------------------------------------- | ---------------------------------------------------------------------------------------------------- | | **Props** | `Prop-Robotics-Neutral` | **Live** — every conversion targets it and passes | | | `Prop-Robotics-Physx` | **Next** — one requirement short: `COL.001` | | | `Prop-Robotics-Isaac` | **Next** — two requirements short: `COL.001`, `ISA.001` | | **Robot bodies** | `Robot-Body-Neutral` · `-Runnable` · `-Isaac` | **Further out** — needs driven joints; see below | | **Packaging** | `Package` · `-NoBOM` · `-Candidate` | Scored, reported `INCOMPLETE`. These describe a distribution package rather than a single asset file | ## The two requirements between us and PhysX / Isaac [Section titled “The two requirements between us and PhysX / Isaac”](#the-two-requirements-between-us-and-physx--isaac) `Prop-Robotics-Physx` and `Prop-Robotics-Isaac` are `Prop-Robotics-Neutral` plus `COL.001` and `ISA.001`. Both are reported as genuine failures on every conversion today rather than deferred to “validated at runtime” — that phrasing was a way of not answering. * **`COL.001` — SDF collision approximation.** We author convex-hull colliders. SDF changes contact behaviour for every asset already delivered, so it needs an acceptance run of its own. * **`ISA.001` — Isaac composition layout.** We ship one flattened USD. Isaac composition is a packaging architecture — `kind='component'`, a `payloads/` tree, the asset split into `_base.usd` + `_physics.usd`. Both are exporter work with a known shape. Neither is blocked on the specification. ## Articulation [Section titled “Articulation”](#articulation) The pipeline already builds articulated assets — `ArticulationRootAPI`, `UsdPhysics` revolute and prismatic joints with real limits, per-link mass and inertia — and they pass `Prop-Robotics-Neutral` alongside the rigid props. What is not yet here is the **API surface to ask for one**: every endpoint on this site produces a rigid prop today. Articulated assets are scored with the **same requirement set as everything else**. There is no separate articulated profile in the specification and no separate family of codes on our side; joint and articulation requirements are simply part of the registry, and an asset either meets them or does not: | | Status | | ------------------------------------------------------------------------- | -------------------------------------------------------------------- | | `JT.001`–`JT.003`, `JT.ART.001`–`JT.ART.004` (joints, articulation roots) | Checked | | `BA.001` (exactly one `ArticulationRootAPI`) | Checked | | `BA.002` (non-adjacent colliders don’t clash at the default pose) | Planned — needs a simulation step a structural validator doesn’t run | | `HI.009` (hierarchy reflects the kinematic chain) | Planned | A correction, if you built against an earlier version of this page This page previously described articulation support in terms of an `AR.*` family of codes and a profile called `Articulated-Robotics-Neutral`. **Neither exists in the SimReady specification.** They were ours: sixteen invented codes under a profile name that was never published, and routing articulated assets to them meant those assets skipped every naming, packaging, material, texture and path check in the real registry. They are gone, and articulated assets are now scored against NVIDIA’s requirements like everything else. ## Robot bodies [Section titled “Robot bodies”](#robot-bodies) The `Robot-Body-*` profiles are a further step rather than a flag flip. They require driven joints — `FET022_DRIVEN_JOINTS_NEUTRAL` and `FET024_BASE_ARTICULATION` — meaning actuated joints carrying drive specifications and joint-state APIs. **Rigyd’s joints are passive**: damping and friction sourced from real mechanism profiles, with no target or motor. A manipulable prop and a commandable robot body are different products. The gap is concrete. Against `Robot-Body-Neutral`’s 30 requirements we have rules for 26; the eleven `DJ.*` driven-joint requirements, the eight `RC.*` robot-core ones and the two `RM.*` robot-material ones are all unwritten, which is why those profiles report `INCOMPLETE` or `FAIL` rather than being quietly rounded up. ## Tracking the specification itself [Section titled “Tracking the specification itself”](#tracking-the-specification-itself) We validate against spec release **`2026.04.1`** while the specification has moved on to `2026.06.0`. That gap is a decision, not neglect: `2026.04.1` is the newest release with a working published validator wheel, and validating against a release we cannot run their tool on would mean we could no longer check our answers against theirs. Moving the pin means re-vendoring their metadata, diffing the requirement set, and re-running both verification assets through our validator and theirs — a new requirement we fail is a report change, not an emergency, because conformance never fails a job. `report.validation.spec_release` always names the release a given verdict was scored against, so a report never has to be guessed at after the fact. When the foundation publishes additional profiles — vehicles, characters, environments, all signposted as future work in the [SimReady overview](https://docs.omniverse.nvidia.com/simready/latest/overview.html) — our coverage tracks them. The profile map is joined from their metadata rather than transcribed, so adding a profile means adding a target, not rewriting the engine. # CLI & Python SDK > Convert 3D models, text prompts, or images into SimReady assets from your terminal or your Python code with the rigyd package. `rigyd` is the official command-line tool and Python SDK for the Rigyd API. It wraps the same [conversion endpoints](/quickstart) documented here, so anything you can do over REST you can do from a terminal or a Python script. Zero dependencies — `pip install rigyd` and go. ```bash pip install rigyd ``` Note The CLI and SDK call `https://api.rigyd.com` and spend your account’s credits, exactly like a raw API call. Mint a key at **[app.rigyd.com](https://app.rigyd.com)** (Settings → API Keys). ## CLI [Section titled “CLI”](#cli) 1. **Authenticate.** Stores your `rgyd_live_...` key at `~/.config/rigyd/config.json` (mode 600). ```bash rigyd login ``` Key resolution order: `--api-key` flag → `RIGYD_API_KEY` env → config file. 2. **Convert something.** ```bash # Text → SimReady rigyd generate --text "wooden chair" --export isaac -o ./assets # Image(s) → SimReady (1 image, or all 4 views) rigyd generate --image front.png --image right.png --image back.png --image left.png # 3D file → SimReady, optionally retopologized rigyd convert chair.glb --tris 50000 --export all ``` 3. **Inspect, re-download, simulate.** ```bash rigyd jobs list rigyd jobs get rigyd download --export mujoco # re-download any job, 0 credits rigyd simulate --scene drop # physics demo video, 0 credits rigyd whoami # user + credit balance ``` * `--export` takes a format (`usd`, `mjcf`, `all`) or a simulator alias (`isaac` → USD, `mujoco` → MJCF). Default: `usd`. * Progress goes to **stderr**, the result path to **stdout**, so it composes with other tools: `blender $(rigyd convert scan.obj --export usd)`. Add `--json` for a machine-readable manifest (handy for agents and CI). | Input | Command | Cost | | ------------------------------------------------ | ----------------------------- | --------- | | Text prompt | `rigyd generate --text "..."` | 2 credits | | 1 or 4 images | `rigyd generate --image ...` | 3 credits | | 3D file (`.glb/.gltf/.fbx/.obj/.stl/.ply/.usd*`) | `rigyd convert FILE` | 1 credit | See [Supported formats](/reference/supported-formats) for the full input list and [Pricing](/jobs/pricing) for credit costs. ## Python SDK [Section titled “Python SDK”](#python-sdk) ```python import rigyd rigyd.configure() # key from login / RIGYD_API_KEY job = rigyd.convert(prompt="a wooden dining chair") job.wait(on_progress=lambda j: print(j.status, j.stage, j.progress)) usd_path = job.download(fmt="usd") # or "mjcf" / "all" print(rigyd.account()) # user + credit balance ``` `job.wait()` polls the same [job lifecycle](/reference/job-lifecycle) (`submitting → preprocessing → queued → running → completed | failed`) and raises on failure. ### MuJoCo extra [Section titled “MuJoCo extra”](#mujoco-extra) Install the optional dependency to load a result straight into MuJoCo: ```bash pip install "rigyd[mujoco]" ``` ```python model = rigyd.load_model(prompt="a wooden chair") # -> mujoco.MjModel, ready to mj_step ``` ## Loading into a live simulator [Section titled “Loading into a live simulator”](#loading-into-a-live-simulator) * **NVIDIA Isaac Sim** — use the [Isaac Sim extension](/tools/isaac-sim) to generate and load assets onto the stage without leaving the app. The CLI produces the same USD files for offline or scripted pipelines. * **MuJoCo** — `rigyd.load_model(...)` above, or `mujoco.MjModel.from_xml_path()`. ## Links [Section titled “Links”](#links) [Source & full reference ](https://github.com/ARTLabs-Engineering/rigyd-cli)github.com/ARTLabs-Engineering/rigyd-cli [Package on PyPI ](https://pypi.org/project/rigyd/)pip install rigyd # Isaac Sim extension > Generate and load physics-enabled SimReady USD assets directly onto your stage from inside NVIDIA Isaac Sim. The **Rigyd SimReady Importer** is an NVIDIA Isaac Sim / Omniverse Kit extension (`rigyd.simready`) that brings the Rigyd conversion pipeline into the simulator. Convert any 3D model, text prompt, or image into a physics-enabled SimReady USD asset and reference it straight onto your stage — no manual download or drag. Note Rigyd emits **native SimReady USD** (Z-up, `metersPerUnit=1.0`, with mass / center-of-mass / inertia / friction / restitution), so the extension is a thin API client and stage loader, not a format converter. ## Install [Section titled “Install”](#install) 1. Clone [the extension repo](https://github.com/ARTLabs-Engineering/rigyd-isaac-sim). 2. In Isaac Sim: **Window → Extensions → ☰ → Settings → +** (Extension Search Paths) and add the absolute path to the repo’s `exts/` folder. 3. Find **rigyd.simready** in the extension list and enable it (toggle **Autoload** to keep it on). 4. Open **Window → Rigyd**, paste your `rgyd_live_…` API key, **Save key**, then **Test connection**. Tip Mint the key at **[app.rigyd.com](https://app.rigyd.com)** (Settings → API Keys). It spends your account’s credits, just like the [API](/quickstart) and [CLI](/tools/cli). ## Use [Section titled “Use”](#use) | Flow | Input | Cost | | ------------------ | ------------------------------------------------------------------------- | --------- | | Text → SimReady | a prompt | 2 credits | | 3D file → SimReady | `.glb/.gltf/.fbx/.obj/.stl/.ply/.usd*` (+ optional target triangle count) | 1 credit | | Image → SimReady | 1–4 images | 3 credits | The panel submits the job to `api.rigyd.com`, polls to completion ([same lifecycle as the API](/reference/job-lifecycle)), downloads the self-contained `format=usd` result ZIP, unzips it, and references the `.usd` under `/World`. Press **Play** to simulate. ## Status [Section titled “Status”](#status) **v1.0.0** — validated end-to-end on NVIDIA Isaac Sim (textures + SimReady physics intact). All three input flows (Text / Images / 3D file) are implemented. ## Links [Section titled “Links”](#links) [Source & install guide ](https://github.com/ARTLabs-Engineering/rigyd-isaac-sim)github.com/ARTLabs-Engineering/rigyd-isaac-sim [Prefer scripting? Use the CLI & SDK ](/tools/cli)Same USD output, offline pipelines.