Live prices and stock

Change a price, a sale or the stock between two catalogs, and search shows it within seconds, without an index run.

Prices and stock change all day, while the catalog may arrive once a night. PUT /api/v1/offers writes such changes into the live search in between. This page changes the price of the pendant lamp LEUCHTE-KUGEL (Kugel: globe) and puts it back. Its two variants first:

Terminal
curl -s 'http://127.0.0.1:7800/v1/product?id=LEUCHTE-KUGEL' | jq -c '.product.variants[] | {id, price: .offer.price}'
200 · 4.1 ms · release b25a6aaa
{"id":"LEUCHTE-KUGEL-KLAR","price":149.0}
{"id":"LEUCHTE-KUGEL-RAUCH","price":159.0}

The lamp comes in Klarglas (clear glass) and Rauchglas (smoked glass), at 149 € and 159 €.

Change a price

Send each change as its product, its variant, the channel and the fields that change. Each change is answered on its own:

Terminal
changed=$(curl -s -X PUT -H "Authorization: Bearer $key" -H 'Content-Type: application/json' $api/offers -d '{"offers": [
  {"product": "LEUCHTE-KUGEL", "variant": "LEUCHTE-KUGEL-KLAR", "channel": "de", "price": 129},
  {"product": "LEUCHTE-KUGEL", "channel": "de", "price": 129}
]}')
revision=$(jq .revision <<< "$changed")
jq -c '.rows[]' <<< "$changed"
202 · 23 ms
{"state":"accepted"}
{"state":"rejected","code":"variant_needed","message":"\"LEUCHTE-KUGEL\" has variants, so name the one whose offer changes in `variant`."}

The first change is kept. The second is left out, since a product with variants names the one that changes; a product the live catalog lacks is left out too, as new products come with the catalog. The kept changes make one revision, the answer's revision.

No index run is queued. The indexer waits a second for changes that follow, compiles the changed products again and replaces their documents in the live indexes. Every answer's trace names the revision it reads in live.offers, so wait until it names this one:

Terminal
for _ in {1..30}; do
  curl -s 'http://127.0.0.1:7800/v1/product?id=LEUCHTE-KUGEL&debug=1' \
    | jq -e ".trace.live.offers >= $revision" >/dev/null && break
  sleep 1
done
curl -s 'http://127.0.0.1:7800/v1/product?id=LEUCHTE-KUGEL' | jq -c '.product.variants[] | {id, price: .offer.price}'
200 · 13 ms · release b25a6aaa
{"id":"LEUCHTE-KUGEL-KLAR","price":129}
{"id":"LEUCHTE-KUGEL-RAUCH","price":159.0}

The clear lamp now costs 129 € on its tile and its product page alike. Release and snapshot stay as they were, and with the revision they name what answered. GET $api/live names the revision too, with when it arrived, and the panel shows that time as Prices and stock on Overview and Releases.

Reference: PUT /api/v1/offers · rows · GET /api/v1/live

Put it back

A change replaces only the fields it names, so the old price is one more change away:

Terminal
revision=$(curl -s -X PUT -H "Authorization: Bearer $key" -H 'Content-Type: application/json' $api/offers \
  -d '{"offers": [{"product": "LEUCHTE-KUGEL", "variant": "LEUCHTE-KUGEL-KLAR", "channel": "de", "price": 149}]}' \
  | jq .revision)
for _ in {1..30}; do
  curl -s 'http://127.0.0.1:7800/v1/product?id=LEUCHTE-KUGEL&debug=1' \
    | jq -e ".trace.live.offers >= $revision" >/dev/null && break
  sleep 1
done
curl -s 'http://127.0.0.1:7800/v1/product?id=LEUCHTE-KUGEL' | jq -c '.product.variants[] | {id, price: .offer.price}'
200 · 6.5 ms · release b25a6aaa
{"id":"LEUCHTE-KUGEL-KLAR","price":149}
{"id":"LEUCHTE-KUGEL-RAUCH","price":159.0}

Both lamps cost what the catalog says again.

Sales, stock and availability

A change may name any field of an offer, and the fields it leaves out stay as they are. This one puts the smoked lamp on sale for a weekend and sells out the clear one:

Terminal
curl -s -X PUT -H "Authorization: Bearer $key" -H 'Content-Type: application/json' $api/offers -d '{"offers": [
  {"product": "LEUCHTE-KUGEL", "variant": "LEUCHTE-KUGEL-RAUCH", "channel": "de", "sale_price": 139,
   "sale_window": {"from": "2026-11-27T00:00:00+01:00", "until": "2026-11-30T00:00:00+01:00"}},
  {"product": "LEUCHTE-KUGEL", "variant": "LEUCHTE-KUGEL-KLAR", "channel": "de", "availability": "out_of_stock", "stock": 0}
]}'

end_sale: true ends a sale, and delivery_days changes the delivery time. A sale window that opens or closes rebuilds the search at that moment, as it does for a sale the catalog sets. Prices are what search shows; the checkout decides what the shopper pays.

Reference: sale_price · end_sale · availability

When the next catalog arrives

A change applies to every catalog that first arrived before it. Another catalog supersedes it, since the shop says what holds now:

What comes nextThe change
The same catalog, sent againStays
Another catalogGoes, and the catalog's offer holds
A rebuild, such as after a publishStays, as does every change since its catalog

A change sent while a new catalog's index run builds applies to that catalog too.

Until the next index run, what the run derived from the whole catalog stays as it was: the dictionary's counts, derived price buckets and how many values each facet counts.

Limits

  • 1,000 changes per call. A larger batch is refused whole with a 422, so send a large sync in calls of 1,000, back to back.
  • One write a second. Changes that arrive close together become one write, so a burst batches itself.
  • A catalog first. Before any catalog is live, the call answers 409.

Reference: offers_per_call · 409

Next

On this page