---
title: 3D
description: Turn one to four product photos into a GLB model you can view in a browser and download.
---

# 3D

Submit product or object photos to `POST /generate/3d`, keep the returned job id, then follow the job to its GLB asset. The output uses `model/gltf-binary` and the `.glb` extension; a preview thumbnail is included when the engine returns one.

## Choose an engine

<!-- gen:models modality=3d price=true -->
| Model id | Modality | Plan | Credits |
| --- | --- | --- | --- |
| `hunyuan3d-v3` | 3d | starter | 21 per generation |
| `trellis` | 3d | starter | 2 per generation |
<!-- /gen -->

### hunyuan3d-v3

| Property | Value |
| --- | --- |
| Quality | `standard`; the default when both `model` and `quality` are omitted |
| Input | One to four image assets, in front, back, left, right order; or one hosted HTTPS front image |
| Textures | On by default; `texture: false` makes a white untextured model |
| PBR | Optional; requires textures |
| Base price | 21 credits textured; 13 untextured |
| Extra views | Add 9 credits once, whether there are one, two or three extra views |
| PBR surcharge | Add 9 credits |

Textured totals are 21 credits, 30 with PBR or extra views, and 39 with both. Untextured totals are 13 credits, or 22 with extra views. Use a [quote](./billing.html#checking-prices-programmatically) for the exact request before submitting.

### trellis

| Property | Value |
| --- | --- |
| Quality | `draft` |
| Input | Exactly one image |
| Textures | Always enabled |
| PBR | Not supported |
| Price | 2 credits per generation |

> [!WARNING]
> Send exactly one of `image_asset_ids` and `image_url`. `quality: draft` selects `trellis` and `quality: standard` selects `hunyuan3d-v3`; a quality that contradicts an explicit model is `400 validation`. Trellis also refuses extra views, `texture: false` and `pbr: true`.

## Submit photos

Use [Uploads and files](./uploads.html) to create the input assets first. The UUID below is illustrative: replace it with one of your image asset ids. No text prompt is part of the 3D request.

<!-- gen:fields schema=Generate3DRequest -->
| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `confirmation_token` | string | No | Optional proof that this exact request and price were shown to the customer, from `POST /jobs/cost`.… |
| `model` | `Generate3DModel` | No | Defaults to hunyuan3d-v3 when both model and quality are omitted. |
| `quality` | one of `draft`, `standard` | No | Draft selects trellis; standard selects hunyuan3d-v3. A value that contradicts an explicit model returns 400. |
| `image_asset_ids` | array of string | No | Your image assets in front, back, left, right order. Trellis takes exactly one. Supply exactly one of image_asset_ids and image_url. |
| `image_url` | string | No | Hosted HTTPS front image. Supply exactly one of image_asset_ids and image_url. |
| `texture` | boolean | No | Defaults to true. False creates an untextured white model and is available only with hunyuan3d-v3. |
| `pbr` | boolean | No | Defaults to false. PBR materials require hunyuan3d-v3 with textures enabled. |
| `tags` | array of string | No | Applied to the 3D asset; normalized to lowercase. |
| `project_id` | string | No | Files the 3D asset into this project when the job completes. The project must exist and belong to the caller (400 otherwise). |
| `preset_slug` | string, nullable | No | Slug of the preset this generation was launched from, if any. Same semantics as on `POST /generate/video`. |
<!-- /gen -->

```bash title="3D submit — example"
$ curl -sS https://api.nolgia.ai/v1/generate/3d \
  -H "Authorization: Bearer $NOLGIA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"model":"hunyuan3d-v3","image_asset_ids":["3be6408a-cbf2-4964-b314-d7cbbf9a8c75"],"texture":true}'
```

This is a schema-built example of the accepted job, not a production capture.

```json title="202 Accepted — example from the Job schema"
{
  "id": "2ab37473-80b3-4c6c-b070-7f5e1677e172",
  "user_id": "4961eaef-70a6-4e9b-b746-c86ad3e62077",
  "modality": "3d",
  "model": "hunyuan3d-v3",
  "status": "queued",
  "created_at": "2026-09-21T04:00:00Z",
  "updated_at": "2026-09-21T04:00:00Z"
}
```

<!-- gen:fields schema=Job only=id,user_id,modality,model,status,created_at,updated_at,asset,failure -->
| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string | Yes |  |
| `user_id` | string | Yes |  |
| `modality` | `Modality` | Yes |  |
| `model` | string | Yes |  |
| `status` | `JobStatus` | Yes |  |
| `asset` | `Asset` | No |  |
| `failure` | `JobFailure` | No |  |
| `created_at` | string | Yes |  |
| `updated_at` | string | Yes |  |
<!-- /gen -->

## Follow the job

Use `GET /jobs/{id}` to poll, or `GET /jobs/{id}/wait` to hold a request until completion. A wait timeout leaves the generation running; keep following the same id. [Asynchronous: submit and poll](./jobs.html) covers the full loop, failures and refund reporting.

```bash title="Read the job — example"
$ curl -sS "https://api.nolgia.ai/v1/jobs/$JOB_ID" \
  -H "Authorization: Bearer $NOLGIA_TOKEN"
```

The schema-built response below shows a job still running; on `succeeded`, read its `asset`.

```json title="200 — example from the Job schema"
{
  "id": "2ab37473-80b3-4c6c-b070-7f5e1677e172",
  "user_id": "4961eaef-70a6-4e9b-b746-c86ad3e62077",
  "modality": "3d",
  "model": "hunyuan3d-v3",
  "status": "running",
  "created_at": "2026-09-21T04:00:00Z",
  "updated_at": "2026-09-21T04:00:02Z"
}
```

| Response field | Meaning |
| --- | --- |
| `id`, `user_id` | The durable job id and its owner |
| `modality`, `model` | `3d` and the resolved engine |
| `status` | Continue polling while `queued` or `running`; `succeeded`, `failed` and `canceled` are terminal |
| `created_at`, `updated_at` | Job timestamps |
| `asset` | Present on success; read the GLB's `signed_url`, `expires_at` and optional `thumbnail_url` |
| `failure` | Machine-readable failure and refund outcome when generation fails |

<!-- gen:fields schema=Asset only=id,modality,mime_type,signed_url,expires_at,thumbnail_url,status -->
| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string | Yes |  |
| `modality` | `Modality` | Yes |  |
| `signed_url` | string | Yes | Time-limited GCS signed URL for download.… |
| `expires_at` | string | Yes | Expiry of `signed_url`. |
| `mime_type` | string | No |  |
| `thumbnail_url` | string, nullable | No | Time-limited signed URL for a server-generated thumbnail (image downscale or video poster frame).… |
| `status` | `AssetStatus` | No |  |
<!-- /gen -->

Store the asset id, then re-read the asset when a fresh download URL is needed. A signed URL is temporary. Duplicate submissions inside the five-minute window return `409` with the original job id and are not billed; change `Idempotency-Key` only for a deliberate new take.

## Next steps

:::cards
- [Common model arguments](./model-arguments.html): Understand reference ids, project filing and confirmation tokens.
- [Pricing and credits](./billing.html): Quote each engine and follow credit holds and refunds.
:::
