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

# Photographs

> Two roads to a gallery: URLs we fetch, or files you upload.

A listing's gallery holds up to **8** images, in the order you give them. There are
two ways to fill it, and each replaces the gallery whole — so a shorter list is how
an image is removed.

> [tab] URLs we fetch
>
> The road for anything that already hosts its images: a PIM, an ERP, a shop.
>
> ```json theme={null}
> { "data": { "type": "watches", "id": "91827", "attributes": {
>   "imageSources": [
>     "https://your-pim.example/images/10294-front.jpg",
>     "https://your-pim.example/images/10294-clasp.jpg"
>   ]
> }}}
> ```
>
> One call instead of nine, and **sending the same URLs again costs nothing**: we
> hash the set we already hold and do not re-fetch an unchanged one. That makes it
> safe to send your whole record on every sync, which is what a sync wants to do.
>
> The fetching is queued, so the listing is saved immediately and the images
> follow. A client reading it back straight away sees the old gallery, which is
> the honest answer: the new one does not exist yet.

> [tab] Files you upload
>
> The road for a client holding the files themselves — the Wristler app, adding a
> listing from somebody's phone. Upload each file, then name the handles.
>
> ```bash theme={null}
> curl -X POST https://api.wristler.eu/v1/uploads \
>   -H "Authorization: Bearer $TOKEN" \
>   -H "Accept: application/vnd.api+json" \
>   -F file=@front.jpg
> ```
>
> The same endpoint takes a URL instead of bytes, if that is what you have:
>
> ```json theme={null}
> { "data": { "type": "pending-uploads", "attributes": {
>   "sourceUrl": "https://your-shop.example/images/10294-front.jpg"
> }}}
> ```
>
> That is one call per file, fetched while the request is open — so for a whole
> gallery `imageSources` is still the better trade, and for one replacement photo
> this is. [Both ways in, in full →](https://docs.wristler.eu/guides/uploads.md)
>
> ```json theme={null}
> { "data": { "type": "watches", "attributes": {
>   "imageHandles": [
>     "/wristler-image-storage/front-20260923.jpeg",
>     "/wristler-image-storage/clasp-20260923.jpeg"
>   ]
> }}}
> ```
>
> Nothing is fetched and nothing is queued here — the files are already ours — so
> a client that reads the listing back straight away sees the gallery it just
> sent.

> [!WARNING]
>
> A body carrying both fields is refused with `422`. Each replaces the gallery
> whole, so sending both is asking for two different galleries.

## Reading a gallery back

`include=images` gives you the files themselves, each with the sizes we render:

```json theme={null}
{
  "type": "uploads",
  "id": "5512",
  "attributes": {
    "url": "https://cdn.wristler.eu/…",
    "handle": "/wristler-image-storage/front-20260923.jpeg",
    "variants": {
      "thumbnail": "…", "square": "…", "squareLarge": "…",
      "square800": "…", "squareXLarge": "…", "hero": "…"
    },
    "position": 1
  }
}
```

| Variant | Size | For |
| - | - | - |
| `thumbnail` | 266×192 | A row in a list |
| `square` | 312×312 | A small tile |
| `squareLarge` | 506×506 | A two-column grid |
| `square800` | 800×800, WebP | The size the website's gallery shows |
| `squareXLarge` | 1440×1440 | A product page, or a grid on a 3× screen |
| `hero` | 1920×384 | A page banner |
| `url` | As uploaded | The photograph itself, uncropped |

Picking a variant saves fetching a whole photograph to show a thumbnail, which on a
phone is the difference that matters.

> [!WARNING]
>
> **`hero` is a banner crop**, 1920 wide by 384 tall. It is for the top of a page and
> is not a product photo — a watch does not survive being cut to a fifth of its
> height.

The squares are squares because a grid of watches wants every cell the same shape.
`url` is the only one that keeps the original proportions, and it is whatever the
seller uploaded — sometimes 2,500 pixels of it, so it is the wrong thing to put in a
list.

## Adding one photograph

Upload the new file, then send `imageHandles` with the `handle` of every image the
listing already has plus the new one, in the order the gallery should be in:

```json theme={null}
{ "data": { "type": "watches", "id": "91827", "attributes": {
  "imageHandles": [
    "/wristler-image-storage/front-20260923.jpeg",
    "/wristler-image-storage/clasp-20260923.jpeg",
    "/wristler-image-storage/caseback-20260929.jpeg"
  ]
}}}
```

The images you name again are kept as they are, id and all; only the new one is
attached. Nothing is uploaded twice. A handle can be named again only when it is on one
of this business's own listings (the one you are editing, or one it was copied from); a
handle already attached anywhere else — another business's listing, a message — is refused
with `422`. Upload the file again instead.

## Reordering without re-uploading

```http theme={null}
PATCH /v1/sellers/42/watches/91827/relationships/images
```

```json theme={null}
{ "data": [
  { "type": "uploads", "id": "5514" },
  { "type": "uploads", "id": "5512" }
]}
```

The gallery becomes exactly these, in this order. An image left out is removed —
deleted, not detached: a file that belongs to nothing is one we keep paying to
store and nobody can reach.

Only images already on one of this business's own listings can be named here. An upload
id from another business's listing, or from a message, is refused with `422` (pointer
`/data/{index}`), and a listing a Connect feed maintains answers `409` `feed_managed`.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.