---
title: "Job lifecycle"
description: "Status enum, preprocessing stages, and the full payload shape returned by GET /api/conversions/:id."
---

import { Aside } from '@astrojs/starlight/components';

Every conversion follows the same lifecycle, regardless of whether you submitted a 3D file, image, text prompt, or simulation request.

## 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`.

## 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

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

```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 + validation report (mass, friction, validation results, ...)
    },
    "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"
  }
}
```

<Aside type="tip">
  **Polling cadence**: 3-5 seconds while a job is `preprocessing` / `queued` / `running` is plenty. Most jobs finish in under two minutes.
</Aside>

## 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.