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

# Syncing an inventory

> The shape of a nightly run, and the one field that makes the rest easy.

One seller's whole stock lives under the business it belongs to:

```
/v1/sellers/{sellerId}/watches
```

Drafts, sold listings, everything in review — which the public catalogue
deliberately does not give you. This is what you sync against.

## Give every listing an externalIdentifier

Your own id for it, unique within one seller's inventory.

```json theme={null}
{ "data": { "type": "watches", "attributes": {
  "externalIdentifier": "PIM-10294", "reference": "126610LN", "price": 1450000
}}}
```

It is how you find your listing again without keeping our ids, and how a sold item
is matched back to your product through the order's items:

```http theme={null}
GET /v1/sellers/42/watches?filter[externalIdentifier][]=PIM-10294
```

The filter takes a list, so you can look several up in one call:
`filter[externalIdentifier]=PIM-10294,PIM-10295`, or as
`filter[externalIdentifier][]=PIM-10294&filter[externalIdentifier][]=PIM-10295`. One
works without the `[]` too. An identifier with a comma in it has to go in the `[]`
form, since the other splits on commas.

## Finding one, and counting them

`filter[q]` is the dashboard's listing search, by the same rule: the words in the
`name`, the `reference`, your `dealerProductCode` or `externalIdentifier`, or brand and
model as one phrase.

```http theme={null}
GET /v1/sellers/42/watches?filter[q]=Omega%20Seamaster
```

Every page carries `meta.statusCounts`: how many listings the business has in each
`status`, all seven present, across the whole inventory whatever you filtered — the
numbers on the dashboard's tabs. It is there only for a token that acts for the business:
our own app reading another shop's listings, as a visitor, gets what is for sale and no counts.

## The price, and what a sale costs

The dashboard's quick edit is a `PATCH` of `name`, `price` and `priceOnRequest`. A
listing needs a `price` of at least `100000` (€1,000.00) unless it is priced on
request: `price: null` without `priceOnRequest: true` is a `422`.

`applicationFee` on a listing is what we charge the business when it sells at its
price, in cents — the figure the dashboard shows beside the price, worked out with
the business's own VAT. For a price that is not saved yet, while somebody types it:

```http theme={null}
GET /v1/sellers/42/application-fee?price=1295000
```

answers in `meta`: the `price` you asked about, its `applicationFee`, and the table it
comes from — `fixedFee` up to and including `threshold`, `percentage` of the price above
it. Show what comes back; do not work it out yourself. A `price` that is missing or not a
whole number of cents is a `400` with `invalid_price`.

## Creating one

**reference** `string` *required*

The manufacturer's reference.

**name** `string` *required*

What the listing is called.

**description** `string` *required*

The text on the listing.

**price** `integer` *required*

In cents. `1450000` is €14,500.00. At least `100000` (€1,000.00). Leave it out, or send
`null`, only with `priceOnRequest: true`.

**specifications** `object` *required*

A flat map of names to values: `{"CONDITION": "VERY_GOOD"}`. Four are required —
`AVAILABILITY`, `STATE`, `CONDITION`, `YEAR_OF_PRODUCTION` — and a value we do not
know is refused rather than stored, because the whole point of the list is that
`STEEL` means one thing.

**`GET /v1/specifications` is that list**: every name, every value it accepts, and
which four are required. Generate your mapping from it rather than hard-coding
one; we add values.

On an update, the names you send are replaced and the ones you leave out stay as
they are. Send one as `null` to clear it: `{"DIAL_COLOR": null}`. The four required
ones cannot be cleared — a listing cannot be reviewed without them.

**watchModel** `relationship` *required*

Which model it is, from our own list: `GET /v1/watch-models`, by brand or by the
words somebody types.

The business comes from the address, so there is no need to name it in the body —
and if you do, it has to be the same one.

> [!WARNING]
>
> **A new listing never starts live.** Every one goes into review, the same as one
> added in the dashboard. Leave `status` out or send `available` and it lands in
> `awaiting_review` — so it needs a photo (`imageHandles` or `imageSources`) and a
> price or `priceOnRequest`, or the create is refused with `422` on
> `/data/attributes/status`; send `draft` and it stays a draft only you can see, with
> or without them. Anything else on a create is refused.

## The status is a word

You may set `available`, `sold`, `unavailable` and `draft`. `awaiting_review`,
`awaiting_update` and `unsubscribed_trader` are states a listing reaches on its
own — a new listing lands in review by itself, and billing decides the third.

**Publishing is sending `available`**, and what happens depends on where the
listing is:

| The listing is | `available` puts it in |
| - | - |
| `draft` | `awaiting_review` — once it has a photo and a price (or `priceOnRequest`); without them `422` on `status` |
| `awaiting_update` | `awaiting_review` |
| `awaiting_review` | `awaiting_review` — it is already on its way |
| `unavailable` | `available`, straight away — it was reviewed before |
| `sold` | refused with `409 listing_sold`: a sold listing stays sold |
| `unsubscribed_trader` | refused with `422`: it comes back with the subscription |

A shop without a subscription has nothing for sale: a listing that would go live
there lands in `unsubscribed_trader` instead, and comes back when the subscription
does. `visible` on the shop (`GET /v1/sellers/{id}`) says whether it is on show.

The answer carries the status it actually landed in, so read it rather than
assuming `available`.

A listing that has not been through review (`draft`, `awaiting_review`,
`awaiting_update`) can go to review or back to `draft`, and nothing else:
`unavailable` or `sold` are refused with `422`, because either would be a way
round review.

A draft may be saved without photos or a price, but review needs them: publishing a
draft without at least one photo and a price of at least € 1,000 (or `priceOnRequest`)
is refused with `422` on `/data/attributes/status`, as the dashboard refuses it. Photos
or a price sent in the same request count. The same holds for a new listing created into
review, and for a `duplicateOf` copy sent with `available`, which is judged on the
gallery and price it takes from its source unless the body sends others.

**A sold listing stays sold, and is not deleted.** Any other status for it is
`409 listing_sold`, and so is `DELETE` — an order, an invoice and a buyer's thread
point at it, and the dashboard offers neither. To sell the same watch again, start a
new listing from it with `duplicateOf` (see the business guide): a draft, which goes
through review when you publish it. A listing a buyer is paying for right now cannot
be deleted either (`409 listing_reserved`).

A listing in `awaiting_update` was sent back by a reviewer. `reviewNote` says what
they want changed; it is `null` in every other state.

## A nightly run

**1. Walk what we have**

`GET /v1/sellers/{id}/watches?fields[watches]=externalIdentifier,price,status`
and follow `links.next`. Sparse fields keep it small.

**2. Send only what changed**

A `PATCH` says only what it changes. Sending a listing whose price has not
moved is a write we both pay for and nobody needed.

**3. Mark what is gone**

`PATCH` it to `sold` or `unavailable` rather than deleting it — an order that
already exists still points at the listing that was bought. `sold` is final, so
use `unavailable` for a watch that may come back.

> [!TIP]
>
> Put an `Idempotency-Key` on every write in that run. A timeout halfway through is
> the normal failure, and a retry that duplicates a listing is the expensive one.

## Listings you do not own

A listing from a Connect feed is maintained in the seller's own application. The
fields that feed supplies answer `409` here, and the error names them.
[Why →](https://docs.wristler.eu/concepts/feed-ownership.md)


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