# Create a ghost photoshoot

> Shoot your products with nobody in them: on an invisible mannequin, laid flat or in close-up, one image per view. Delivered as one record whose items are the images.

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

Send product photos — the garment on its own, or worn by a person — and get catalogue images with nobody in them: on an invisible mannequin, laid flat, or in close-up. Every product gets every view you list, in one shape and on one backdrop.

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

## Input schema

- `products` (array<object>, _required_, at least 1 items) — The products to shoot, one entry each, at least one. Every product gets every view: `products × views` images, at most 500.
  Example: `[{"image":"file:77"},{"image":"https://files.example.com/ghost_photoshoot.create/1.jpg","photo_type":"on_model","prompt":"the striped shirt"}]`
  - `image` (string, _required_, pattern: ^(https://|art:|file:).+$) — The product's photo, as one string (a url, `art:…` or `file:…`). A url or file of one of your products is that product, with its category.
    Example: `file:77`
  - `photo_type` (string, _optional_, default: `garment_only`) — What a ghost product's photo shows — the garment on its own, or worn by somebody.
    Values: `garment_only` (The garment on its own — laid flat, on a hanger or a mannequin.); `on_model` (Somebody wears the garment (say which garment in `prompt` when the photo shows several). When the product photo shows the garment worn by a person.)
    Example: `on_model`
  - `prompt` (string, _optional_) — With `photo_type: on_model` only: which garment to take from the photo, in your words ("the striped shirt"). Omitted, the main garment.
    Example: `the striped shirt`
- `aspect_ratio` (string, _optional_, default: `1:1`) — The shape of every image. `1:1` is a square frame, the usual shape for a product shown on its own in a store or catalogue grid. Send another ratio to match your own layout.
  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`
- `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":"auto"}`) — The backdrop of every image. The backdrop is chosen for you, so a shoot needs no colour from you. Send `colour` when every image must share one exact colour, such as your store's.
  Example: `{"type":"colour","colour":{"hex":"#F5F2ED"}}`
  - type: auto — The backdrop is chosen for you.
    - `type` (string, _required_) — Picks this alternative: `auto`.
      Values: `auto`
  - type: colour — One flat colour, locked onto every image.
    - `type` (string, _required_) — Picks this alternative: `colour`.
      Values: `colour`
    - `colour` (object, _required_) — One colour — as a record's `data.colours[]` shows it.
      - `hex` (string, _required_, pattern: ^#[0-9A-Fa-f]{6}$) — The colour as #RRGGBB, like "#E8D9C4".
      - `name` (string, _optional_) — A name for the colour.
      - `pantone` (string, _optional_) — Its Pantone code, when known.
- `views` (array<object>, _optional_) — The views to make of every product, 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. Every listed side is made in every listed type, so list each pair (front and back, ghost and flat: four entries). Omitted, the front on an invisible mannequin.
  Example: `[{"type":"ghost","view":"front"},{"type":"flat","view":"front"},{"type":"ghost","view":"back"},{"type":"flat","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`
- `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: `4K`

## Required-fields example

```json
{
  "products": [
    {
      "image": "file:77"
    }
  ]
}
```

## Full example

```json
{
  "products": [
    {
      "image": "file:77"
    },
    {
      "image": "https://files.example.com/ghost_photoshoot.create/1.jpg",
      "photo_type": "on_model",
      "prompt": "the striped shirt"
    }
  ],
  "background": {
    "type": "colour",
    "colour": {
      "hex": "#F5F2ED"
    }
  },
  "views": [
    {
      "type": "ghost",
      "view": "front"
    },
    {
      "type": "flat",
      "view": "front"
    },
    {
      "type": "ghost",
      "view": "back"
    },
    {
      "type": "flat",
      "view": "back"
    },
    {
      "type": "close_up"
    }
  ],
  "aspect_ratio": "1:1",
  "resolution": "4K"
}
```

## Output schema

- `files` (array<object>, _optional_) — ONE file: the shoot, `photoshoot:<id>` (`application/json`). Its `data.items` are the shoot's images — `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).

## Response example

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

## Result example

```json
{
  "job_id": "1123456789abcdef0123456789abcdef",
  "files": [
    {
      "file": "photoshoot:1123456789abcdef0123456789abcdef",
      "url": "https://files.example.com/ghost_photoshoot.create/2.png",
      "media_type": "application/json",
      "task": "ghost_photoshoot.create",
      "job_id": "1123456789abcdef0123456789abcdef",
      "created_at": "2026-10-01T10:14:02Z",
      "data": {
        "name": "Ghost photoshoot 2026-10-01",
        "status": "ready",
        "items": [
          {
            "type": "ghost",
            "view": "front",
            "name": "Blazer",
            "external_id": "SKU-77",
            "url": "https://files.example.com/ghost_photoshoot.create/2.png"
          },
          {
            "type": "ghost",
            "view": "back",
            "name": "Blazer",
            "external_id": "SKU-77",
            "url": "https://files.example.com/ghost_photoshoot.create/3.png"
          },
          {
            "type": "flat",
            "view": "front",
            "name": "Blazer",
            "external_id": "SKU-77",
            "url": "https://files.example.com/ghost_photoshoot.create/4.png"
          },
          {
            "type": "flat",
            "view": "back",
            "name": "Blazer",
            "external_id": "SKU-77",
            "url": "https://files.example.com/ghost_photoshoot.create/5.png"
          },
          {
            "type": "close_up",
            "name": "Blazer",
            "external_id": "SKU-77",
            "url": "https://files.example.com/ghost_photoshoot.create/6.png"
          },
          {
            "type": "ghost",
            "view": "front",
            "name": "Product 2",
            "url": "https://files.example.com/ghost_photoshoot.create/7.png"
          },
          {
            "type": "ghost",
            "view": "back",
            "name": "Product 2",
            "url": "https://files.example.com/ghost_photoshoot.create/8.png"
          },
          {
            "type": "flat",
            "view": "front",
            "name": "Product 2",
            "url": "https://files.example.com/ghost_photoshoot.create/9.png"
          },
          {
            "type": "flat",
            "view": "back",
            "name": "Product 2",
            "status": "failed"
          },
          {
            "type": "close_up",
            "name": "Product 2",
            "url": "https://files.example.com/ghost_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."
      }
    ]
  }
}
```

Every product gets every listed view — each side in each type, plus one close-up: 2 × (2 × 2 + 1) = 10 images; a shoot over the schema's limit is refused (`too_many_outputs`). `prompt` is taken only with `photo_type: on_model`; a product without `photo_type` is `garment_only`. No `name` was sent, so the shoot is named after its task and the UTC day. The product sent by url has no name of its own, so it is `Product 2` on every one of its images.

## Built for

- Product pages of an online store
- Ghost-mannequin and flat-lay catalogue images
- Product images taken from photos of a person wearing them

## What you get

- One `photoshoot:` record — the shoot's name, its status and its images in `data.items`.
- Each image's `url`, its type (`ghost`, `flat` or `close_up`) and side, 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 product and view.

## Choosing a task

- Use this when you want the products alone, with nobody in the images. Use `photoshoot.create` when you want the garments worn by models.

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

- `image.polish` — It runs after: it takes this task's result.
- `photoshoot.create` — It does a neighbouring job.
- `mannequin_photoshoot.create` — It does a neighbouring job.

## Good to know

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

## For agents and code generation

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