---
name: img2model
description: Generate a 3D model (.glb, or a voxel grid) from an image using the img2model.com API. Use when the user wants a mesh, 3D asset, game asset or printable model made from a picture, photo, render or sketch. Requires an img2model API key.
---

# Generate a 3D model from an image

img2model.com turns one image into one 3D model. You submit an image and some
settings, the job runs on a GPU marketplace, and you download a `.glb`. A default
job costs 52 credits and takes about 41
seconds.

**Full reference: `https://img2model.com/api.md`** - fetch it when you need a field this file
does not cover. Everything below is the part you need to get a model back without
wasting the user's credits.

## Before you start

You need an API key, and you cannot create one yourself - it needs a browser
session. If the user has not given you one, ask them to:

1. Register at `https://img2model.com/register` and verify the email (free credits arrive immediately).
2. Generate a key on `https://img2model.com/profile`.
3. Paste it to you, or better, put it in an environment variable such as `IMG2MODEL_API_KEY`.

Keys look like `I2M_…`. Treat one as a secret: body parameters only, never a
query string, never a commit, never echoed into a log.

## The three calls

Every call is a POST, every key goes in the **body**, and every job field is
prefixed `new_job_form[…]`.

```bash
# 1. submit  ->  {"success":true,"message":"Job created","job_id":"…","remaining_credits":N}
curl -s -X POST https://img2model.com/api/jobs/new \
  -F "api_key=$IMG2MODEL_API_KEY" \
  -F "new_job_form[image]=@input.png" \
  -F "new_job_form[mode]=simple_pbr" \
  -F "new_job_form[detail]=3" \
  -F "new_job_form[polycount]=25000" \
  -F "new_job_form[texture_size]=2"

# 2. poll every ~5s  ->  {"success":true,"state":"IN_PROGRESS","finished":false,…}
curl -s -X POST https://img2model.com/api/jobs/status/$JOB_ID -d "api_key=$IMG2MODEL_API_KEY"

# 3. download once finished and COMPLETED
curl -s -X POST https://img2model.com/api/jobs/result/$JOB_ID -d "api_key=$IMG2MODEL_API_KEY" -o result.glb
```

Python, if you would rather not shell out:

```python
import os, time, requests

BASE, KEY = "https://img2model.com", os.environ["IMG2MODEL_API_KEY"]
F = "new_job_form"

with open("input.png", "rb") as fh:
    created = requests.post(f"{BASE}/api/jobs/new",
        data={"api_key": KEY, f"{F}[mode]": "simple_pbr", f"{F}[detail]": 3,
              f"{F}[polycount]": 25000, f"{F}[texture_size]": 2},
        files={f"{F}[image]": fh}).json()

if not created["success"]:
    raise RuntimeError(created["message"])          # a refusal is still HTTP 200

job = created["job_id"]
while True:
    status = requests.post(f"{BASE}/api/jobs/status/{job}", data={"api_key": KEY}).json()
    if status.get("finished"):
        break
    time.sleep(5)

if status["state"] != "COMPLETED":
    raise RuntimeError(f"job {status['state']}: {status['state_hr']}")  # credits already refunded

result = requests.post(f"{BASE}/api/jobs/result/{job}", data={"api_key": KEY})
open(f"result.{status['format']}", "wb").write(result.content)
print(f"{status['faces']} faces, {status['bytes']} bytes")
```

## The six things that will bite you

1. **A refusal is an HTTP 200 with `"success": false`.** Bad key, unknown job,
   invalid combination, no credits - all of them. Check `success` on every
   response before reading anything else. `raise_for_status()` alone will happily
   sail past every error this API produces.
2. **`finished` is true for `FAILED` and `REJECTED` too.** Exit the poll loop on
   `finished`, then check `state == "COMPLETED"` separately. Waiting for
   COMPLETED alone waits forever on a failed job.
3. **One job at a time per account.** A second submission is refused while an
   earlier job is unfinished. Never fire a batch off in parallel - run them in
   sequence, each one polled to `finished`.
4. **`texture_size` is a step, not a pixel count**: `1`=1024, `2`=2048, `3`=4096.
   Posting `2048` is out of range and fails the whole submission.
5. **Unknown field names fail the submission** rather than being ignored, so a
   typo costs you a round trip, not a wrong model. Check the field against
   `/api.md` if you get "This form should not contain extra fields".
6. **The account's plan caps polycount, texture size, voxel size and Full PBR.** The free
   plan stops at 250,000 faces,
   2048 textures and `simple_pbr`; paid plans go to
   1,000,000 faces and 4096 with `full_pbr`. Over the ceiling is a
   refusal, not a reduced job, and nothing is charged. **Do not retry it** - the
   answer will not change. Report the message to the user, and offer to re-run
   inside their plan (lower `polycount`, `texture_size=2`, `mode=simple_pbr`).

## Choosing settings

Set `new_job_form[mode]` and leave the rest alone unless the user asked for
something specific:

| mode | when |
| --- | --- |
| `simple_pbr` | **The default. Use this.** Colour plus a metallic/roughness map, fastest, best mesh. |
| `albedo_only` | Photographs, where a guessed material map renders like dull chrome. |
| `textureless` | 3D printing, or anything that will be re-textured by hand. Cheapest and fastest. |
| `full_pbr` | The user explicitly wants the best materials and will accept roughly double the wait and cost. |

Then three dials, all optional - and each one has a plan ceiling, listed in
`/api.md`, over which the submission is refused rather than reduced:

- `detail` 1-5 (default 3) - the biggest lever on both price and wait.
- `polycount` 1000-1000000 (default 25,000) - a target for the delivered mesh. Denser is barely slower but much larger.
- `texture_size` 1-3 (default 2) - ignored when textures are off.

For a voxel result set `new_job_form[format]=vox` (and optionally
`new_job_form[voxel_size]`, 10-1024). Voxel output ignores the texture
settings, and its status payload has no `vertices` or `faces`. `voxel_size` has
its own plan ceiling - 256 free,
1024 on Pro - and it is the *only* ceiling that applies to a voxel
job, since polycount and texture size describe a mesh it does not have.

## After the download

- Compare `faces` in the status payload against the `polycount` you asked for. A
  few percent under is normal; a fraction of the request means a thin result that
  will still look plausible as a thumbnail.
- **The generator is not reproducible.** The same image and the same settings can
  return a meaningfully different mesh. If one result is bad, re-running it is a
  reasonable first move - do not conclude anything about the parameters from a
  single run, and tell the user it costs credits again.
- Tell the user what it cost: `remaining_credits` comes back with the submission.

## Reporting back

Say what you got and what it cost: format, face count, file size, credits spent
and credits left. If the job failed, say that the credits were refunded
automatically - they were.
