# Create a photoshoot

> Shoot your garments on AI models: each product on each model in its poses, on one backdrop, with optional product views. Delivered as one record whose items are the images.

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

Send photos of your garments and the models to wear them, and get a finished shoot back: every product on every model, in poses chosen for you or listed by you, on one backdrop for the whole shoot. Add product views of the same garments — on an invisible mannequin, laid flat or in close-up — in the same job.

## 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/photoshoot.create",
    headers=headers,
    json={
        "products": [
            {
                "image": "file:812",
            },
        ],
        "models": [
            "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/photoshoot.create", {
  method: "POST",
  headers,
  body: JSON.stringify({
    "products": [
      {
        "image": "file:812"
      }
    ],
    "models": [
      "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/photoshoot.create" \
  -H "Authorization: Key $REFABRIC_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Prefer: wait=60" \
  -d '{"products":[{"image":"file:812"}],"models":["model:5501"]}'
```

## Input schema

- `products` (array<object>, _required_, at least 1 items) — The garments to shoot, one entry each, at least one. Every product is shot on every model in each of its poses: `models × Σ poses` images, at most 100.
  Example: `[{"image":"file:812","items":[{"image":"https://files.example.com/photoshoot.create/1.jpg"}],"styling":"sleeves pushed up, shirt tucked in","poses":{"type":"custom","items":[{"type":"library","pose":"pose:3f7c1a52-0000-4000-8000-000000000001"},{"type":"prompt","prompt":"walking toward the camera"}]},"views":[{"type":"ghost","view":"front","reuse":true},{"type":"ghost","view":"back"},{"type":"close_up"}]},{"image":"art:c10d55","poses":{"type":"auto","count":2}}]`
  - `image` (string, _required_, pattern: ^(https://|art:|file:).+$) — The garment's photo, as one string (a url, `art:…` or `file:…`) — flat or worn; either works. A url or file of one of your products is that product: its back and detail photos, its category (`POST /v1/files`) and its saved views come with it.
    Example: `file:812`
  - `items` (array<object>, _optional_, at most 4 items) — Up to 4 other garments worn with it in every image — shoes, a bag, a jacket (the outfit). What each one is comes from its own product: the category it was uploaded with (`POST /v1/files`).
    Example: `[{"image":"https://files.example.com/photoshoot.create/1.jpg"}]`
    - `image` (string, _required_, pattern: ^(https://|art:|file:).+$) — The garment's photo.
      Example: `https://files.example.com/photoshoot.create/1.jpg`
  - `styling` (string, _optional_) — How the product is worn, in one sentence — 'sleeves pushed up, shirt tucked in, jacket open'. It steers the pose and the render.
    Example: `sleeves pushed up, shirt tucked in`
  - `poses` (object, _optional_) — This product's poses: an object whose `type` says how they are chosen.
    Example: `{"type":"custom","items":[{"type":"library","pose":"pose:3f7c1a52-0000-4000-8000-000000000001"},{"type":"prompt","prompt":"walking toward the camera"}]}`
    - type: auto — The poses are chosen for you, as many as `count` — the same `count` for every product of the shoot whose poses are chosen.
      - `type` (string, _required_) — Picks this alternative: `auto`.
        Values: `auto`
      - `count` (integer, _required_, 1 to 20) — How many, 1–20.
    - type: custom — You list each pose — from the library, a picture, or in words — in order.
      - `type` (string, _required_) — Picks this alternative: `custom`.
        Values: `custom`
      - `items` (array<object>, _required_, at least 1 items, at most 40 items) — What you list: 1–40 entries, each used once, in order.
        - type: library — One pose from the library, as `pose:<id>` (listed at `GET /v1/refs/pose`).
          - `type` (string, _required_) — Picks this alternative: `library`.
            Values: `library`
          - `pose` (string, _required_, pattern: ^pose:.+$) — A `pose:<id>` handle, listed at `GET /v1/refs/pose`.
        - type: image — A picture of the pose you want (a url, `art:…` or `file:…`).
          - `type` (string, _required_) — Picks this alternative: `image`.
            Values: `image`
          - `image` (string, _required_, pattern: ^(https://|art:|file:).+$) — A picture, as one string: a URL, a file from an earlier result (`art:…`, or its `url`) or an upload (`file:…`).
        - type: prompt — Describe it in words.
          - `type` (string, _required_) — Picks this alternative: `prompt`.
            Values: `prompt`
          - `prompt` (string, _required_, min length 1) — What you want, in words.
  - `views` (array<object>, _optional_) — This product's own views — they REPLACE the shoot's `views` for it. `reuse: true` copies the view the product already has saved instead of making it (not charged); a view it has not saved is made as usual, and the result says so (`summary.warnings[]` `view_generated`).
    Example: `[{"type":"ghost","view":"front","reuse":true},{"type":"ghost","view":"back"},{"type":"close_up"}]`
    - `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.); `close_up` (A close-up of the product's fabric and finish (one per product in a shoot). Not `detail`, which is a crop of one trim or component of a look. When you want a close-up of the product's fabric and finish.)
      Example: `ghost`
    - `view` (string, _optional_) — Which side it shows. Not sent for a `close_up`.
      Values: `front` (The front of the product. For the main product image.); `back` (The back of the product. To show what is on the back: closures, pockets, prints.); `side` (The side of the product. To show the product's profile and fit.)
      Example: `front`
    - `reuse` (boolean, _optional_) — Copy the view this product already has saved instead of making it again. Omitted, false.
      Example: `true`
- `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:5501"]`
- `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".
  Example: `SS27 lookbook`
- `background` (object, _optional_, default: `{"type":"auto"}`) — The one backdrop of the shoot, behind every image. The backdrop is chosen for you, so a shoot needs no scene from you. Send `keep` to leave each photo's own scene, or pick or describe one.
  Example: `{"type":"library","background":"background:77"}`
  - type: auto — The backdrop is chosen for you.
    - `type` (string, _required_) — Picks this alternative: `auto`.
      Values: `auto`
  - type: keep — Every photo keeps its own scene (a worn photo's 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 product besides the images on models, one `{type, view}` each — `ghost` (on an invisible mannequin) or `flat` (laid flat) with its side, `front`, `back` or `side`, or `{"type": "close_up"}` for one close-up of each product. A product's own `views` replace these for it. `reuse: true` copies the view a product already has saved (not charged); one it has not saved is made, and the result says so (`view_generated`). Each made view counts as an image. Omitted, none.
  Example: `[{"type":"ghost","view":"front"},{"type":"ghost","view":"back"},{"type":"close_up"}]`
  - `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.); `close_up` (A close-up of the product's fabric and finish (one per product in a shoot). Not `detail`, which is a crop of one trim or component of a look. When you want a close-up of the product's fabric and finish.)
    Example: `ghost`
  - `view` (string, _optional_) — Which side it shows. Not sent for a `close_up`.
    Values: `front` (The front of the product. For the main product image.); `back` (The back of the product. To show what is on the back: closures, pockets, prints.); `side` (The side of the product. To show the product's profile and fit.)
    Example: `front`
  - `reuse` (boolean, _optional_) — Copy the view this product already has saved instead of making it again. Omitted, false.
- `aspect_ratio` (string, _optional_, default: `9:16`) — The shape of every image. `9:16` is a tall portrait frame, the shape that fits a model standing in the picture. Send another ratio when your page or feed uses a different shape.
  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: `3:4`
- `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`

## Required-fields example

```json
{
  "products": [
    {
      "image": "file:812"
    }
  ],
  "models": [
    "model:5501"
  ]
}
```

## Full example

```json
{
  "name": "SS27 lookbook",
  "products": [
    {
      "image": "file:812",
      "items": [
        {
          "image": "https://files.example.com/photoshoot.create/1.jpg"
        }
      ],
      "styling": "sleeves pushed up, shirt tucked in",
      "poses": {
        "type": "custom",
        "items": [
          {
            "type": "library",
            "pose": "pose:3f7c1a52-0000-4000-8000-000000000001"
          },
          {
            "type": "prompt",
            "prompt": "walking toward the camera"
          }
        ]
      },
      "views": [
        {
          "type": "ghost",
          "view": "front",
          "reuse": true
        },
        {
          "type": "ghost",
          "view": "back"
        },
        {
          "type": "close_up"
        }
      ]
    },
    {
      "image": "art:c10d55",
      "poses": {
        "type": "auto",
        "count": 2
      }
    }
  ],
  "models": [
    "model:5501"
  ],
  "background": {
    "type": "library",
    "background": "background:77"
  },
  "views": [
    {
      "type": "ghost",
      "view": "front"
    },
    {
      "type": "ghost",
      "view": "back"
    },
    {
      "type": "close_up"
    }
  ],
  "aspect_ratio": "3:4",
  "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 its `view` (front · back · side) for a product view, `close_up` — 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), and one per view asked to be reused that the product had not saved, so it was made (`code: "view_generated"`).

## Response example

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

## Result example

```json
{
  "job_id": "0123456789abcdef0123456789abcdef",
  "files": [
    {
      "file": "photoshoot:0123456789abcdef0123456789abcdef",
      "url": "https://files.example.com/photoshoot.create/2.png",
      "media_type": "application/json",
      "task": "photoshoot.create",
      "job_id": "0123456789abcdef0123456789abcdef",
      "created_at": "2026-10-01T10:14:02Z",
      "data": {
        "name": "SS27 lookbook",
        "status": "ready",
        "items": [
          {
            "type": "look",
            "name": "Linen shirt",
            "external_id": "SKU-812",
            "url": "https://files.example.com/photoshoot.create/2.png"
          },
          {
            "type": "look",
            "name": "Linen shirt",
            "external_id": "SKU-812",
            "url": "https://files.example.com/photoshoot.create/3.png"
          },
          {
            "type": "ghost",
            "view": "front",
            "name": "Linen shirt",
            "external_id": "SKU-812",
            "url": "https://files.example.com/photoshoot.create/4.png"
          },
          {
            "type": "ghost",
            "view": "back",
            "name": "Linen shirt",
            "external_id": "SKU-812",
            "status": "failed"
          },
          {
            "type": "close_up",
            "name": "Linen shirt",
            "external_id": "SKU-812",
            "url": "https://files.example.com/photoshoot.create/5.png"
          },
          {
            "type": "look",
            "name": "Product 2",
            "url": "https://files.example.com/photoshoot.create/6.png"
          },
          {
            "type": "look",
            "name": "Product 2",
            "url": "https://files.example.com/photoshoot.create/7.png"
          },
          {
            "type": "ghost",
            "view": "front",
            "name": "Product 2",
            "url": "https://files.example.com/photoshoot.create/8.png"
          },
          {
            "type": "ghost",
            "view": "back",
            "name": "Product 2",
            "url": "https://files.example.com/photoshoot.create/9.png"
          },
          {
            "type": "close_up",
            "name": "Product 2",
            "url": "https://files.example.com/photoshoot.create/10.png"
          }
        ]
      }
    }
  ],
  "has_more": false,
  "summary": {
    "requested": 10,
    "delivered": 9,
    "warnings": [
      {
        "code": "output_not_produced",
        "field": "items",
        "message": "1 of the shoot's 10 images were not produced; they are in the record without a url."
      }
    ]
  }
}
```

One model × (2 listed poses + 2 poses chosen for you) = 4 looks, plus each product's ghost front / back and close-up. `model:5501` is a style of a base model (`GET /v1/refs/model?base=model:4412`), passed as a model; each more model multiplies the looks. A product's own `views` replace the shoot's `views` for it; `reuse: true` uses the view the product already has saved — had it none, the view would be made and the result would carry a `view_generated` warning naming `products[0].views[0]`. Items are named per product: the library product's own name and its SKU (`external_id`); the `art:` image has no product name, so it is `Product 2`, its place in `products`.

## Built for

- Store and catalogue images on models
- A lookbook with one cast and one backdrop
- On-model images and product views in one job

## 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 product view and its side) and the product it shows, by name and SKU.
- A `summary` that counts the images asked for and made, and warns about any image 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, per product, one image for each pose on each model, plus the views you ask for.

## Choosing a task

- Use this when you have product photos and want them on models in new poses, on a backdrop you choose. Use `mannequin_photoshoot.create` when your photos already show the garment worn, on a mannequin or a person, and each photo's pose and framing should stay.
- Use this when you want the garments worn by models. Use `ghost_photoshoot.create` when you want the products alone, with nobody in the images.

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

## Related

- `pose.create` — It runs before: its result is this input.
- `background.create` — It runs before: its result is this input.
- `image.polish` — It runs after: it takes this task's result.

## Good to know

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

## For agents and code generation

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