Guides
Guide: syncing with other software
A reliable sync has three parts: one full load, regular deltas, and webhooks that trigger the delta early. Writes back to STRYK use ETags so you never overwrite a change made at the front desk.
1. Initial full load
Walk all pages of each list with the maximum limit of 200. Lists are ordered by updatedAt, then id, so the cursor stays stable while data changes. Remember the newest updatedAt you have seen per resource, your high-water mark.
import { stryk } from './stryk.js';
// Initial load: walk every page, then remember the newest updatedAt you saw.
export async function fullLoad(resource, upsert) {
let cursor = null;
let highWaterMark = null;
do {
const params = new URLSearchParams({ limit: '200', ...(cursor ? { cursor } : {}) });
const { data: page } = await stryk(`/${resource}?${params}`);
for (const item of page.data) {
await upsert(item);
if (!highWaterMark || item.updatedAt > highWaterMark) highWaterMark = item.updatedAt;
}
cursor = page.hasMore ? page.nextCursor : null;
} while (cursor);
return highWaterMark; // store it per resource, e.g. in your database
}from stryk import stryk
def full_load(resource, upsert):
cursor, high_water_mark = None, None
while True:
params = {"limit": 200, **({"cursor": cursor} if cursor else {})}
page, _ = stryk(f"/{resource}", params=params)
for item in page["data"]:
upsert(item)
if high_water_mark is None or item["updatedAt"] > high_water_mark:
high_water_mark = item["updatedAt"]
if not page["hasMore"]:
return high_water_mark
cursor = page["nextCursor"]2. Deltas with updated_since
updated_since returns everything with updatedAt greater than or equal to the given timestamp. Using the inclusive comparison means nothing changed in the same millisecond is lost; the price is that items at the mark come back. Deduplicate by id and updatedAt.
// Delta run: updated_since is inclusive (>=). Items at the mark arrive again, that is intended.
export async function syncChanges(resource, store) {
const since = await store.highWaterMark(resource);
let cursor = null;
let newest = since;
do {
const params = new URLSearchParams({ limit: '200', updated_since: since, ...(cursor ? { cursor } : {}) });
const { data: page } = await stryk(`/${resource}?${params}`);
for (const item of page.data) {
const known = await store.get(resource, item.id);
// Deduplicate by id + updatedAt; never overwrite newer local data with an older copy.
if (!known || item.updatedAt > known.updatedAt) await store.put(resource, item);
if (item.updatedAt > newest) newest = item.updatedAt;
}
cursor = page.hasMore ? page.nextCursor : null;
} while (cursor);
await store.setHighWaterMark(resource, newest);
}def sync_changes(resource, store):
since = store.high_water_mark(resource)
cursor, newest = None, since
while True:
params = {"limit": 200, "updated_since": since, **({"cursor": cursor} if cursor else {})}
page, _ = stryk(f"/{resource}", params=params)
for item in page["data"]:
known = store.get(resource, item["id"])
if known is None or item["updatedAt"] > known["updatedAt"]:
store.put(resource, item)
newest = max(newest, item["updatedAt"])
if not page["hasMore"]:
break
cursor = page["nextCursor"]
store.set_high_water_mark(resource, newest)3. Webhooks as triggers
Webhooks make the delay short: on booking.updated, fetch that booking or run the delta for bookings. Do not build state from events alone, deliveries can be late, repeated or, after the retry schedule ends, missing.
| Event | Re-read |
|---|---|
booking.created, booking.updated, booking.cancelled | GET /bookings/{id} |
guest.updated | GET /guests/{id} |
availability.changed | GET /availability for roomTypeIds, from, to |
rate.changed | GET /rates for the same range |
payment.updated | GET /bookings/{bookingId}/payments with data.bookingId |
4. Periodic reconciliation
Run the delta on a schedule even when webhooks work, every 5 to 15 minutes for bookings is typical, hourly for room types and rate plans. A nightly full load of the resources you mirror catches anything else, for example deletions in your own system.
Stay within the limit of your key (120 requests per minute by default, GET /me → rateLimit). With 200 items per page, a delta over thousands of changes takes a handful of requests; a full load spreads well over a few minutes. See Rate limits.
5. Writing with ETag and If-Match
Single resources return an ETag header and the same value as version. PATCH requires If-Match with the ETag you read. If the resource has changed in STRYK in the meantime, the write is rejected with 412 precondition_failed; without If-Match you get 428 precondition_required. If-None-Match on a GET returns 304 when nothing changed.
GET /api/partner/v1/guests/5e4d3c2b-1a09-4f8e-9d7c-6b5a4f3e2d1c
→ 200 OK
ETag: "7e5d3c1b"
{ "id": "5e4d3c2b-…", "phone": "+43 660 1234567", "version": "7e5d3c1b", … }
PATCH /api/partner/v1/guests/5e4d3c2b-1a09-4f8e-9d7c-6b5a4f3e2d1c
If-Match: "7e5d3c1b"
{ "phone": "+43 660 7654321" }
→ 200 OK ETag: "9f8e7d6c"
→ 412 precondition_failed (changed in STRYK meanwhile: read again)
→ 428 precondition_required (If-Match missing)// Optimistic concurrency: read, change, write with If-Match.
export async function updateGuestPhone(guestId, phone) {
for (let attempt = 1; attempt <= 3; attempt += 1) {
const { data: guest, etag } = await stryk(`/guests/${guestId}`);
try {
const { data: updated } = await stryk(`/guests/${guestId}`, {
method: 'PATCH',
headers: { 'If-Match': etag },
body: { phone },
});
return updated;
} catch (error) {
// 412: someone changed the guest in STRYK since your read. Read again and re-apply.
if (error.code !== 'precondition_failed') throw error;
}
}
throw new Error('Guest keeps changing; resolve manually.');
}def update_guest_phone(guest_id, phone):
for _ in range(3):
guest, headers = stryk(f"/guests/{guest_id}")
try:
updated, _ = stryk(f"/guests/{guest_id}", method="PATCH",
headers={"If-Match": headers["ETag"]}, json={"phone": phone})
return updated
except StrykError as error:
if error.code != "precondition_failed":
raise # 412 means: read again and re-apply
raise RuntimeError("Guest keeps changing; resolve manually.")6. Resolving conflicts
- STRYK is the source of truth for bookings, prices, availability and payments. Mirror them; do not correct them from the outside.
- For guest contact data, decide per field which system wins. On
412, read again, apply only your fields, and write with the new ETag. - Stop after a few attempts and flag the record for a person instead of looping.
- Never retry a
409 booking_not_modifiable: the booking is checked in, checked out or cancelled.