# Create a mannequin photoshoot

> Put the garments of mannequin or worn photos on AI models, keeping each photo's pose and framing. Delivered as one record whose items are the images.

**Endpoint:** `POST https://api.refabric.com/v1/tasks/mannequin_photoshoot.create` · **Task:** `mannequin_photoshoot.create` · **Scope:** `tasks:run` · **Category:** Photoshoots

Send photos of garments worn on a mannequin or a person, and the models to wear them, and get each photo on each model with its pose and framing kept. Keep every photo's own backdrop or set one for the whole shoot, and add front views on an invisible mannequin or laid flat.

## Quick start

```python
import os
import time
import requests

headers = {"Authorization": f"Key {os.environ['REFABRIC_API_KEY']}"}

job = requests.post(
    "https://api.refabric.com/v1/tasks/mannequin_photoshoot.create",
    headers=headers,
    json={
        "products": [
            {
                "image": "https://files.example.com/mannequin_photoshoot.create/1.jpg",
            },
        ],
        "models": [
            "model:4412",
            "model:5501",
        ],
    },
).json()
print(job["job_id"])

while True:
    status = requests.get(job["status_url"], headers=headers).json()
    if status["lifecycle"] == "terminal":
        break
    time.sleep(5)

print(requests.get(job["result_url"], headers=headers).json())
```

```javascript
const headers = {
  Authorization: `Key ${process.env.REFABRIC_API_KEY}`,
  "Content-Type": "application/json",
};

const job = await fetch("https://api.refabric.com/v1/tasks/mannequin_photoshoot.create", {
  method: "POST",
  headers,
  body: JSON.stringify({
    "products": [
      {
        "image": "https://files.example.com/mannequin_photoshoot.create/1.jpg"
      }
    ],
    "models": [
      "model:4412",
      "model:5501"
    ]
  }),
}).then((r) => r.json());
console.log(job.job_id);

let status = job;
while (status.lifecycle !== "terminal") {
  await new Promise((r) => setTimeout(r, 5000));
  status = await fetch(job.status_url, { headers }).then((r) => r.json());
}

console.log(await fetch(job.result_url, { headers }).then((r) => r.json()));
```

```bash
curl -X POST "https://api.refabric.com/v1/tasks/mannequin_photoshoot.create" \
  -H "Authorization: Key $REFABRIC_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Prefer: wait=60" \
  -d '{"products":[{"image":"https://files.example.com/mannequin_photoshoot.create/1.jpg"}],"models":["model:4412","model:5501"]}'
```

## Input schema

- `products` (array<object>, _required_, at least 1 items) — The worn photos to re-cast, one entry each, at least one. Every photo is shot on every model: `products × models` images, at most 100.
  Example: `[{"image":"https://files.example.com/mannequin_photoshoot.create/1.jpg"}]`
  - `image` (string, _required_, pattern: ^(https://|art:|file:).+$) — A photo of the garment worn — on a mannequin or a person — as one string (a url, `art:…` or `file:…`). Its pose and framing are kept.
    Example: `https://files.example.com/mannequin_photoshoot.create/1.jpg`
- `models` (array<string>, _required_, at least 1 items) — Who wears the products, as ["model:<id>", …], at least one — a base model or one of its STYLES (`GET /v1/refs/model` lists both; `?base=model:<id>` lists one model's styles). A style changes hair, make-up and face, not the outfit, and counts as one more model: every product is shown on every model you name, so each one multiplies the images.
  Example: `["model:4412","model:5501"]`
- `aspect_ratio` (string, _optional_, default: `1:1`) — The shape of the product views (ghost, flat). The images on models keep each photo's own shape. `1:1` is a square frame, the usual shape for a product shown on its own. It shapes only the product views; the images on models keep each photo's shape whatever you send.
  Values: `1:1` (Square. For square places: product grids and social posts.); `3:4` (Portrait, slightly taller than wide. For portrait product pages.); `4:3` (Landscape, slightly wider than tall. For landscape layouts and slides.); `9:16` (Tall portrait, as for phone screens and stories. For phone screens: stories and reels.); `16:9` (Wide landscape, as for banners and video. For banners, headers and video frames.); `2:3` (Portrait, as for a classic photo print. For a portrait print or a lookbook page.); `3:2` (Landscape, as for a classic photo print. For a landscape print.); `4:5` (Portrait, as for social feeds. For portrait posts in social feeds.)
  Example: `1:1`
- `resolution` (string, _optional_, default: `2K`) — The size of every image. `2K` is sharp enough for screens and review; ask for `4K` when the result will be printed or zoomed into.
  Values: `2K` (Standard size, for screens and review.); `4K` (Large size, for print and zoom. When the image will be printed or zoomed into.)
  Example: `2K`
- `name` (string, _optional_, max length 255) — The shoot's name, as it appears in your library and as the record's `data.name` (at most 255 characters). Omitted: the kind of shoot and the day it was started, in UTC — "Photoshoot 2026-10-01".
- `background` (object, _optional_, default: `{"type":"keep"}`) — The one backdrop of the shoot, behind every image on a model. Every photo keeps the backdrop it already has. Choose another one when the photos' own scene should change.
  Example: `{"type":"keep"}`
  - type: auto — The backdrop is chosen for you.
    - `type` (string, _required_) — Picks this alternative: `auto`.
      Values: `auto`
  - type: keep — Every photo keeps its own backdrop.
    - `type` (string, _required_) — Picks this alternative: `keep`.
      Values: `keep`
  - type: library — One backdrop of the library, behind every image.
    - `type` (string, _required_) — Picks this alternative: `library`.
      Values: `library`
    - `background` (string, _required_, pattern: ^background:.+$) — A `background:<id>` handle, listed at `GET /v1/refs/background`.
  - type: prompt — The scene in your words, behind every image.
    - `type` (string, _required_) — Picks this alternative: `prompt`.
      Values: `prompt`
    - `prompt` (string, _required_, min length 1) — What you want, in words.
- `views` (array<object>, _optional_) — Product views to make of every photo besides the images on models, one `{type, view}` each — the front (`view: front`), on an invisible mannequin (`type: ghost`) and / or laid flat (`type: flat`). Each counts as an image. Omitted, none.
  Example: `[{"type":"flat","view":"front"}]`
  - `type` (string, _required_) — What the view is.
    Values: `ghost` (The product on an invisible (ghost) mannequin (`view`: which side). When you want the product shown on an invisible mannequin.); `flat` (The product laid flat, as a flat-lay photo (`view`: which side). When you want the product laid flat.)
    Example: `flat`
  - `view` (string, _optional_) — Which side it shows.
    Values: `front` (The front of the product. For the main product image.)
    Example: `front`

## Required-fields example

```json
{
  "products": [
    {
      "image": "https://files.example.com/mannequin_photoshoot.create/1.jpg"
    }
  ],
  "models": [
    "model:4412",
    "model:5501"
  ]
}
```

## Full example

```json
{
  "products": [
    {
      "image": "https://files.example.com/mannequin_photoshoot.create/1.jpg"
    }
  ],
  "models": [
    "model:4412",
    "model:5501"
  ],
  "background": {
    "type": "keep"
  },
  "views": [
    {
      "type": "flat",
      "view": "front"
    }
  ],
  "aspect_ratio": "1:1",
  "resolution": "2K"
}
```

## Output schema

- `files` (array<object>, _optional_) — ONE file: the shoot, `photoshoot:<id>` (`application/json`). Its `data.items` are the shoot's images — `type: look` on a model, `ghost` or `flat` with `view: front` for a product view — each with its `url` and named by its product: the product's own name, else `Product <n>` by its place in `products`, and its `external_id` (SKU) when it has one. An image that failed carries `status: failed` and no url. Read it again with `GET /v1/files/photoshoot:<id>`; pass any `items[].url` into an edit (`image.polish`, …).
- `summary` (object, _optional_) — How many images the shoot holds, how many came out with an image, and a warning for any that did not.
  - `requested` (integer, _optional_) — The shoot's images.
  - `delivered` (integer, _optional_) — How many of them have an image.
  - `warnings` (array<object>, _optional_) — `{code, field, message}` rows: one when some images were not produced (`code: "output_not_produced"`, `field: "items"`; they are in the record without a url).

## Response example

```json
{
  "job_id": "2123456789abcdef0123456789abcdef",
  "lifecycle": "queued",
  "status_url": "http://v3-api.refabric.com/v1/jobs/2123456789abcdef0123456789abcdef",
  "result_url": "http://v3-api.refabric.com/v1/jobs/2123456789abcdef0123456789abcdef/result",
  "cancel_url": "http://v3-api.refabric.com/v1/jobs/2123456789abcdef0123456789abcdef/cancel"
}
```

## Result example

```json
{
  "job_id": "2123456789abcdef0123456789abcdef",
  "files": [
    {
      "file": "photoshoot:2123456789abcdef0123456789abcdef",
      "url": "https://files.example.com/mannequin_photoshoot.create/2.png",
      "media_type": "application/json",
      "task": "mannequin_photoshoot.create",
      "job_id": "2123456789abcdef0123456789abcdef",
      "created_at": "2026-10-01T10:14:02Z",
      "data": {
        "name": "Mannequin photoshoot 2026-10-01",
        "status": "ready",
        "items": [
          {
            "type": "look",
            "name": "Product 1",
            "url": "https://files.example.com/mannequin_photoshoot.create/2.png"
          },
          {
            "type": "flat",
            "view": "front",
            "name": "Product 1",
            "url": "https://files.example.com/mannequin_photoshoot.create/3.png"
          },
          {
            "type": "look",
            "name": "Product 1",
            "url": "https://files.example.com/mannequin_photoshoot.create/4.png"
          }
        ]
      }
    }
  ],
  "has_more": false,
  "summary": {
    "requested": 3,
    "delivered": 3
  }
}
```

Every photo on every model, its pose and framing kept: 1 × 2 looks, plus one flat front per photo. `background: {type: keep}` (the default) leaves the backdrop as it is. No `name` was sent, so the shoot is named after its task and the UTC day; a photo is `Product <n>` by its place in `products`, on every model.

## Built for

- Mannequin photos turned into images on models
- One set of photos shown on several models
- Product views made from mannequin photos

## What you get

- One `photoshoot:` record — the shoot's name, its status and its images in `data.items`.
- Each image's `url`, its type (`look` on a model, or a `ghost` or `flat` front view) and the product it shows, by name and SKU.
- A `summary` that counts the images asked for and made, and warns when some were not made.

## Specs

- **Input formats:** Images, each one string: a URL, an upload (`file:…`) or a file from an earlier result (`art:…`). Upload types and sizes: `GET /v1/meta` `limits.upload`.
- **Input count:** `products`: at least 1, each with its image
- **Output format:** One `photoshoot:` record: its `data` is JSON in the record vocabulary, its `url` a preview image.
- **Output resolution:** `2K`, `4K`
- **Aspect ratios:** `1:1`, `3:4`, `4:3`, `9:16`, `16:9`, `2:3`, `3:2`, `4:5`
- **Outputs per job:** One record file, however large the shoot. Its `data.items` hold one image per photo on each model, plus the views you ask for of each photo.

## Choosing a task

- Use this when your photos already show the garment worn, and each photo's pose and framing should stay. Use `photoshoot.create` when you have product photos and want them on models in new poses.

## Errors

- `field_not_accepted`
- `field_not_supported`
- `invalid_option`
- `invalid_request`
- `too_many_outputs`
- `not_found`
- `request_refused`
- `insufficient_credits`
- `permission_denied`
- `content_refused`
- `processing_failed`

## Related

- `background.create` — It runs before: its result is this input.
- `image.polish` — It runs after: it takes this task's result.
- `photoshoot.create` — It does a neighbouring job.

## Good to know

- `name` is at most 255 characters.
- `products` holds at least 1 items.
- `models` holds at least 1 items.

## For agents and code generation

- https://api.refabric.com/v1/tasks/mannequin_photoshoot.create/llms.txt
- https://api.refabric.com/v1/tasks/mannequin_photoshoot.create/openapi.json
- GET https://api.refabric.com/v1/tasks/mannequin_photoshoot.create
