> ## Documentation Index
> Fetch the complete documentation index at: https://docs.makelocalads.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Quick Shoot

> Turn a product into a fixed number of finished photos with a look and an optional direction.

[← Photoshoots](/photoshoots/overview)

**Endpoint:** `POST https://makelocalads.com/api/v1/photoshoots`
**Permission:** `photoshoots:generate`

<Card title="Open in LocalAds" icon="arrow-up-right" href="https://makelocalads.com/app/a1516a75-9be5-461a-93af-c1896a0a3127?tab=studio&type=photoshoot&section=quick-shoot">
  The Quick Shoot controls in the product's Photoshoot studio.
</Card>

Quick Shoot takes a product, a look and a count, and returns that many
finished images, optionally steered by a direction in text, reference images,
or both. Do not include `template_id` or `moodboard_shots` when you send
`quick_shoot`.

## Usage notes

* The product must have `status: "ready"` with at least one image, or the
  request returns `422 missing_product_images`.
* `look` defaults to `standard` and `count` defaults to `1`.
* Each requested image costs 1 credit. `max_credits` refuses the request with
  `422` before anything is charged if the shoot would cost more.
* Send an `Idempotency-Key` so a retry after a network failure never starts
  and charges for a duplicate shoot.

## Quick start

<CodeGroup>
  ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}}
  curl --request POST \
    --url https://makelocalads.com/api/v1/photoshoots \
    --header "Authorization: Bearer $LOCALADS_API_KEY" \
    --header "Content-Type: application/json" \
    --header "Idempotency-Key: quick-shoot-1042" \
    --data '{
      "product_id": "a1516a75-9be5-461a-93af-c1896a0a3127",
      "quick_shoot": {
        "look": "retro",
        "count": 5
      }
    }'
  ```

  ```javascript JavaScript theme={"theme":{"light":"github-light","dark":"github-dark"}}
  const response = await fetch("https://makelocalads.com/api/v1/photoshoots", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.LOCALADS_API_KEY}`,
      "Content-Type": "application/json",
      "Idempotency-Key": "quick-shoot-1042",
    },
    body: JSON.stringify({
      product_id: "a1516a75-9be5-461a-93af-c1896a0a3127",
      quick_shoot: { look: "retro", count: 5 },
    }),
  });

  const photoshoot = await response.json();
  ```

  ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}}
  import os
  import requests

  response = requests.post(
      "https://makelocalads.com/api/v1/photoshoots",
      headers={
          "Authorization": f"Bearer {os.environ['LOCALADS_API_KEY']}",
          "Content-Type": "application/json",
          "Idempotency-Key": "quick-shoot-1042",
      },
      json={
          "product_id": "a1516a75-9be5-461a-93af-c1896a0a3127",
          "quick_shoot": {"look": "retro", "count": 5},
      },
  )

  photoshoot = response.json()
  ```
</CodeGroup>

## Input fields

<ParamField body="product_id" type="string" required>
  The product to shoot, with `status: "ready"` and at least one image.
</ParamField>

<ParamField body="quick_shoot" type="object" required>
  The Quick Shoot options: an optional `direction`, an optional `moodboard_id`
  for a completed moodboard to steer the style, a `look`, and a `count`.
</ParamField>

<Expandable title="quick_shoot fields" defaultOpen>
  <ParamField body="quick_shoot.direction" type="object">
    Optional direction for the shoot. When present it must carry `text` or at
    least one `image_urls` entry. Third-party URLs must load without cookies or
    authorization headers. Ignored for the `retro` look (see below), except
    that `text` is still used as guidance.
  </ParamField>

  <ParamField body="quick_shoot.direction.text" type="string">
    Direction in words, 1 to 2,000 characters.
  </ParamField>

  <ParamField body="quick_shoot.direction.image_urls" type="array">
    1 to 8 public HTTPS reference images.
  </ParamField>

  <ParamField body="quick_shoot.moodboard_id" type="string">
    A completed moodboard from `GET /moodboards` to steer the style. See
    [From a moodboard](/photoshoots/from-a-moodboard).
  </ParamField>

  <ParamField body="quick_shoot.look" type="string">
    The visual treatment: `standard`, `close_up`, `lifestyle`,
    `out_of_the_box`, or `retro`. Defaults to `standard`.
  </ParamField>

  <ParamField body="quick_shoot.count" type="integer">
    How many images: `1`, `3`, `5`, `8`, or `15`. Defaults to `1`.
  </ParamField>
</Expandable>

<ParamField body="max_credits" type="integer">
  Refuse the request with `422 credit_limit_exceeded`, before anything is
  charged, if it would cost more than this many credits.
</ParamField>

<Accordion title="Complete JSON schema">
  ```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
  {
    "type": "object",
    "additionalProperties": false,
    "required": ["product_id", "quick_shoot"],
    "properties": {
      "product_id": { "type": "string", "format": "uuid" },
      "quick_shoot": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "direction": {
            "type": "object",
            "additionalProperties": false,
            "minProperties": 1,
            "properties": {
              "text": { "type": "string", "minLength": 1, "maxLength": 2000 },
              "image_urls": {
                "type": "array",
                "minItems": 1,
                "maxItems": 8,
                "items": { "type": "string", "format": "uri", "pattern": "^https://" }
              }
            }
          },
          "moodboard_id": { "type": "string", "format": "uuid" },
          "look": {
            "type": "string",
            "enum": ["standard", "close_up", "lifestyle", "out_of_the_box", "retro"],
            "default": "standard"
          },
          "count": { "type": "integer", "enum": [1, 3, 5, 8, 15], "default": 1 }
        }
      },
      "max_credits": { "type": "integer", "minimum": 1 }
    }
  }
  ```
</Accordion>

## The looks

| `look` | Workspace label | Output size |
| - | - | - |
| `standard` | Standard | Square, 1024x1024 |
| `close_up` | Close-up | Square, 1024x1024 |
| `lifestyle` | Lifestyle | Square, 1024x1024 |
| `out_of_the_box` | Out of the box | Square, 1024x1024 |
| `retro` | Retro | Card, 1200x1800 at 300 DPI |

### Optional direction

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "product_id": "a1516a75-9be5-461a-93af-c1896a0a3127",
  "quick_shoot": {
    "direction": {
      "text": "Place the product on a stone pedestal in warm sunlight.",
      "image_urls": ["https://cdn.example.com/reference.jpg"]
    },
    "look": "lifestyle",
    "count": 3
  }
}
```

An image that is only on your computer can be uploaded first with
[Image uploads](/setup/image-uploads).

## The retro look

`retro` is a generative look. The other looks apply a fixed treatment to the
product. `retro` reads the product first, then invents a different printed
scene for every image in the shoot, in the register of mid-century Indian
print culture: souvenir and advertising cards, halftone screens, faded ink,
paper grain.

Three things behave differently:

* **Output is a print-ready 4 x 6 card.** Every image is 1200x1800 at 300
  DPI: the artwork mounted on aged paper, the card's line set in italics
  beneath it, and the localads mark in the corner. It prints as it arrives, no
  layout step on your side.
* **`direction.image_urls` are ignored.** Each scene is composed from the
  product itself. `direction.text` is still read and used as guidance.
* **The first image takes about a minute longer.** The look derives a brief
  from the product and renders one shared reference packshot before any
  output starts. Every image in the shoot uses that same packshot, which is
  what keeps the set reading as one product.

Ask for more than one image when you use it. Each card is a distinct printed
format, so a shoot of `5` or `8` shows the range. A shoot of `1` returns one
card and none of the variety the look exists for.

## Response

The response is `202 Accepted` with one `outputs` entry per requested image,
all `queued`, and a `Location` header pointing at the photoshoot:

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "id": "e8766ffd-66f7-47c9-86bc-0d93e6868d06",
  "brand_id": "1423f915-beca-4c53-a5e4-7c8c99537be9",
  "product_id": "a1516a75-9be5-461a-93af-c1896a0a3127",
  "template_id": null,
  "credits_charged": 5,
  "status": "queued",
  "outputs": [
    {
      "id": "19025a65-5395-41c9-af90-d7cca7fb6421",
      "position": 1,
      "status": "queued",
      "image_url": null,
      "width": null,
      "height": null,
      "caption": null
    }
  ],
  "created_at": "2026-07-24T15:00:00.000Z",
  "updated_at": "2026-07-24T15:00:00.000Z"
}
```

Poll the photoshoot until its status is `completed`, `failed`, or `canceled`;
see [Get the photos](/photoshoots/results) and [Polling](/concepts/polling).

## Errors

| Status | Code | Meaning |
| - | - | - |
| 400 | `invalid_request` | An unknown field, or a `count` outside the allowed values |
| 404 | `product_not_found` | The product does not exist or belongs to another organization |
| 422 | `missing_product_images` | The product has no images yet. Wait for `status: "ready"` |
| 422 | `insufficient_credits` | Fewer credits than requested images |
| 422 | `credit_limit_exceeded` | The shoot would cost more than `max_credits` |
| 422 | `moodboard_not_ready` | The `moodboard_id` is not a completed moodboard for this or a related product |
| 409 | `idempotency_conflict` | The `Idempotency-Key` was reused with a different body |

See [Errors and retries](/concepts/errors) for the error format.
