---
name: unbound-zero
description: Turn an image into a game-ready 3D asset with the Unbound Zero API - a GLB mesh with clean quad topology, UVs and PBR textures. Use when the user wants a 3D model, mesh or game asset made from a picture, photo, render or concept art.
---

# Unbound Zero: image to game-ready mesh

One image in, one GLB out: quad-remeshed geometry, UVs, PBR textures (base color, metallic, roughness) and a normal map.

No image yet, and no image generator of your own? See "Optional: make or edit the input image" below.

## Setup

- API base: `https://unbound-3dgen-server-618775028388.us-central1.run.app/api/v2`
- Read the key from the `UNBOUND_API_KEY` environment variable. If it is not set, ask the user for it; they create one at https://loop.unbound.io/account/api-keys. Keys start with `ubue_`.
- Never print or log the key, and never write it into a project file or version control.
- Send it on every request: `Authorization: Bearer $UNBOUND_API_KEY`.

## Generate one asset

1. Submit the image. Each created task is billed.

   ```bash
   BASE=https://unbound-3dgen-server-618775028388.us-central1.run.app/api/v2
   curl -sS -X POST "$BASE/tasks" \
     -H "Authorization: Bearer $UNBOUND_API_KEY" \
     -F image=@input.png -F material=pbr -F quads=5000
   # 201 {"task_id":"…","status":"queued"}
   ```

2. Download the mesh. The server holds the request open until the mesh is ready, for up to 60 s, and answers `409` if it is not ready by then. Repeat on `409`; stop if the task failed.

   ```bash
   TASK=<task_id>
   while :; do
     code=$(curl -sS -o out.glb -w '%{http_code}' \
       -H "Authorization: Bearer $UNBOUND_API_KEY" \
       "$BASE/tasks/$TASK/download?wait=60")
     [ "$code" = 200 ] && break
     rm -f out.glb
     [ "$code" = 409 ] || { echo "download failed: HTTP $code" >&2; break; }
     curl -sS -H "Authorization: Bearer $UNBOUND_API_KEY" "$BASE/tasks/$TASK" \
       | grep -q '"status": *"failed"' && { echo "generation failed" >&2; break; }
   done
   ```

3. Tell the user where the file is, and what it is: quad count target, material, file size.

## Endpoints

| Method | Path | Does |
|---|---|---|
| `POST` | `/tasks` | Create a task (multipart form). `201 {"task_id","status"}` |
| `GET` | `/tasks/{id}/download?wait=60` | The GLB once completed. `409` while not ready. |
| `GET` | `/tasks/{id}` | Task status. `status` is `completed` or `failed` when done; any other value means still working. |
| `GET` | `/tasks` | Your tasks, newest first. |
| `DELETE` | `/tasks/{id}` | Cancel a queued task, or delete a finished one. `409` while it is running. |

## Parameters for `POST /tasks`

| Field | Values | Default | Notes |
|---|---|---|---|
| `image` | PNG, JPEG or WebP file, max 20 MB each | required | One object, fully in frame. The background is removed automatically. Repeat the field for several views of the same object (see Multi-view). |
| `quads` | 1000–20000 | 5000 | Target quad count. The result lands near the target, not exactly on it. |
| `material` | `pbr` | - | Always send `material=pbr`: base color, metallic and roughness maps. |
| `normal_map` | `true`, `false` | `true` | Normal map of the fine surface detail. `false` makes the file about 1.4 MB smaller. |
| `seed` | integer | 42 | Same image and seed give the same mesh. Change it for a variation. |

## Multi-view

When the user has several pictures of the same object from different sides, send them all in one task: repeat the `image` field, up to 4 images. Any viewpoints work; no camera poses are needed.

- Put the front view first. The textures follow the first image.
- Every image is a view of the same object. Never mix different objects in one task.
- Several views take longer than one image: generation uses every view at every step, so expect roughly N times the wait for N views.
- A task with more than one image costs 3× a single image. Use several views only when the user provides them or asks for them; one good image is usually enough.

```bash
curl -sS -X POST "$BASE/tasks" \
  -H "Authorization: Bearer $UNBOUND_API_KEY" \
  -F image=@front.png -F image=@left.png -F image=@back.png \
  -F material=pbr -F quads=5000
```

## Optional: make or edit the input image

Only for when there is no image to start from. If the user gave you an image, use it as is. If you or the user already have an image generator, use that instead. This endpoint is a convenience on the same key: an image from a prompt, or an edit of the user's image (Nano Banana 2), billed in the same credits.

- URL: `https://loop.unbound.io/api/v1/images` (not the API base above). Same `Authorization` header.
- Synchronous: the response body is the image itself, usually within 10-30 s. The credits charged are in the `X-Unbound-Credits` header.
- Cost per image: about 0.67 credits at 1K, 1.01 at 2K, 1.51 at 4K, on top of the mesh. Tell the user before you use it.

```bash
code=$(curl -sS -o input.png -w '%{http_code}' -X POST "https://loop.unbound.io/api/v1/images" \
  -H "Authorization: Bearer $UNBOUND_API_KEY" \
  -F prompt="A weathered wooden treasure chest with iron bands, three-quarter view, the whole object in frame and centered, plain white background, soft even lighting, no text")
[ "$code" = 200 ] || { cat input.png >&2; rm -f input.png; }   # on error the body is JSON
```

To edit an image, send it too: `-F image=@photo.jpg -F prompt="Remove the table and the clutter, keep only the lamp, plain white background"`. With several images, the prompt can name them "image 1", "image 2" in upload order.

For a good mesh, ask for: one object, whole and centered in the frame, plain background, soft even light, no text or floor shadow. For multi-view, make the front first, then send it back as `image` with "the same object seen from the back" (and the sides), and pass all of them to `/tasks`.

| Field | Values | Default | Notes |
|---|---|---|---|
| `prompt` | text, up to 4000 characters | required | What to make, or how to change the images. |
| `image` | PNG, JPEG or WebP, max 10 MB each, 14 MB in all | none | Up to 4. Images to edit or follow. |
| `size` | `1K`, `2K`, `4K` | `1K` | 1K is plenty as Unbound Zero input. |
| `aspect_ratio` | 1:1, 2:3, 3:2, 3:4, 4:3, 4:5, 5:4, 9:16, 16:9, 21:9 | follows the input image, else square | Use `1:1` for an image meant for `/tasks`. |

Limits: at most 20 images a minute and 3 requests at once per key. A `422` with a `reason` means the content filter declined the request: change the prompt, don't resend it. Every image returned is billed, so don't retry a request that timed out on your side in a loop.

## Limits - follow these

- Keep at most 3 tasks in progress per key (created, not yet completed or failed). For a batch, keep a pool of 3 and submit the next image only when one finishes.
- Ask the user before submitting more than 10 images in one go. Every task costs credits.
- Use `download?wait=60` to wait. If you poll `GET /tasks/{id}` instead, wait at least 5 seconds between requests.
- Never retry `POST /tasks` blindly. After a timeout or network error, call `GET /tasks` and check whether the task was created before submitting again.
- Don't resubmit to get the same result again: the same image and seed give the same mesh. Download it again instead.
- Download results within an hour of completion. Finished tasks are not kept.

## Errors

Errors are JSON: `{"detail": "..."}`.

| Status | Do |
|---|---|
| `401` | Key missing, wrong or revoked. Stop and ask the user. |
| `402` | Out of credits. Stop and tell the user. Do not retry. |
| `404` | Unknown task ID. |
| `409` | Not ready yet (repeat the download), task failed, or the task is running (for `DELETE`). |
| `422` | Bad input: file type, size, a parameter out of range, or more than 4 images. With several images the message starts with "Image N:" for the one at fault. Fix it; don't resend unchanged. |
| `429`, `503` | Wait for `Retry-After` (or 30 s if absent), then retry with backoff. |

## Output

- Binary glTF (`.glb`), one mesh, one material, UVs, embedded PNG textures.
- Draco-compressed. Loaders need a Draco decoder: three.js `DRACOLoader`; Blender and Unreal import it as is.
- Y-up, roughly within a unit cube centered on the origin. Scale it to real-world size in the target engine.
- glTF stores triangles, so each quad is written as two triangles.
