Delta sinkronizacija
Većina partnerskih integracija drži lokalni odraz locco resursa (zaposlenici, vozila, mjesta troška, odjeli, putni unosi, isplate, webhook pretplate). Nakon početnog punjenja, pitanje je isto na svakom endpointu: kako anketirati što se promijenilo od posljednje sinkronizacije bez ponovnog čitanja svakog reda?
Odgovor je modifiedSince query parametar. Podržan je na svakom paginiranom list endpointu i primarni je podržani način inkrementalne sinkronizacije s partner API-jem u v1.
U kratkim crtama
- Svaki paginirani list endpoint prihvaća
?modifiedSince=<ISO 8601 UTC vremenska oznaka>. - Stranice se vraćaju sortirane
(updatedAt, id)ASC kada jemodifiedSincepostavljen bez eksplicitnogsortparametra. To je redoslijed koji partneri trebaju kako bi prošli kroz vremensku liniju promjena bez ponovnog čitanja već obrađenih redova. - Veličina stranice ograničena je na 100 na strani servera. Veće vrijednosti se tiho ograničavaju, a
pageSizeu odgovoru odražava ograničenu vrijednost (a ne sirovi zahtjev) tako daMath.ceil(total / pageSize)aritmetika ostaje ispravna. - Kursor je
updatedAtnajnovijeg reda vraćenog prethodnom uspješnom anketiranju. Pohranite ga. Proslijedite ga u sljedećem anketiranju. Ponovite.
Endpointi
Isti modifiedSince ugovor podržan je na:
| Resurs | Endpoint |
|---|---|
| Zaposlenici | GET /api/v1/employees |
| Vozila | GET /api/v1/vehicles |
| Mjesta troška | GET /api/v1/cost-centers |
| Odjeli | GET /api/v1/departments |
| Putni unosi | GET /api/v1/travel-entries |
| Isplate | GET /api/v1/payouts |
| Webhook pretplate | GET /api/v1/webhooks |
Webhook isporuke (GET /api/v1/webhooks/{id}/deliveries) namjerno NE podržavaju modifiedSince. Isporuke se samo dodaju i efektivno su nepromjenjive nakon pohrane, pa partneri koji žele pratiti stanje isporuka ankete po id-u ili po filtru statusa.
Što modifiedSince vraća
Za anketu poput:
GET /api/v1/employees?modifiedSince=2026-04-30T12:00:00ZAuthorization: Bearer locco_live_...X-Company-Id: 9c4e0d70-9d59-4a4a-a7d9-1e5ff6f1e3ecServer vraća svaki red čiji updatedAt je u trenutku ili nakon proslijeđene vremenske oznake. updatedAt se računa na strani servera kao vrijednost editedAt reda, s povratkom na createdAt za redove koji nikada nisu uređeni, tako da je vremenska oznaka uvijek popunjena.
Red koji je kreiran u 2026-04-30T11:55:00Z i nikada nije uređen je isključen (njegov updatedAt od 11:55:00Z je prije kursora od 12:00:00Z). Red koji je kreiran prošle godine, ali uređen danas, je uključen.
Jamstvo sortiranja
Kada je modifiedSince postavljen bez eksplicitnog sort parametra, server sortira stranicu po (updatedAt ASC, id ASC). Ovo je ključno za ispravnost delta sinkronizacije:
- Bez
idrazrješivača izjednačenih vrijednosti, dva reda s identičnimupdatedAtpojavila bi se nedeterministički kroz zahtjeve, a partneri koji prolaze kroz paginaciju tiho bi preskakali ili duplicirali redove. - ASC (najstarija promjena prva) znači da je partnerov kursor na kraju stranice najveći
updatedAtkoji je vidio, što je prirodna polazna točka za sljedeću anketu.
Ako proslijedite eksplicitni sort=...&sortDir=desc, server i dalje dodaje id razrješivač tako da paginacija ostaje stabilna, ali redoslijed stranica neće biti onaj koji želite za delta sinkronizaciju. Za sinkronizacijske slučajeve upotrebe, ostavite sort nepostavljenim.
Ograničenje paginacije
Svaki list endpoint ograničava pageSize na 100 na strani servera. Polje pageSize u odgovoru odražava EFEKTIVNU ograničenu vrijednost, a ne ono što ste tražili, pa:
GET /api/v1/employees?pageSize=10000vraća do 100 stavki I tijelo odgovora koje kaže "pageSize": 100. Odgovor također nosi polje totalPages izračunato pri EFEKTIVNOJ ograničenoj veličini stranice, pa je najjednostavniji terminator petlje page >= totalPages. Izbjegavajte ponovno računanje broja stranica iz total / pageSize prema vašoj sirovoj vrijednosti zahtjeva: izračunat ćete premalo stranica i tiho propustiti podatke.
page je slično ograničen na minimum 1; nepozitivne vrijednosti stranice se normaliziraju. Polje page u odgovoru odražava stvarno vraćenu stranicu.
Petlja anketiranja
Ugovor je jednostavan: pročitajte pohranjeni kursor, anketirajte s njim, prođite svaku stranicu, spremite najveći updatedAt iz prolaza kao sljedeći kursor.
import timeimport requests
API = "https://api.locco.hr"HEADERS = { "Authorization": "Bearer locco_live_<your-key>", "X-Company-Id": "<your-company-id>",}
def poll_employees(cursor: str | None) -> tuple[list[dict], str]: """ Prođi kroz svaku stranicu zaposlenika promijenjenih od `cursor`. Vraća redove plus novi kursor (najveći updatedAt viđen tijekom prolaza). """ rows = [] page = 1 new_cursor = cursor while True: params = {"page": page, "pageSize": 100} if cursor is not None: params["modifiedSince"] = cursor r = requests.get(f"{API}/api/v1/employees", headers=HEADERS, params=params) r.raise_for_status() body = r.json() rows.extend(body["items"])
# Pomakni kursor na najveći updatedAt na ovoj stranici. for row in body["items"]: updated_at = row.get("updatedAt") or row.get("createdAt") if updated_at and (new_cursor is None or updated_at > new_cursor): new_cursor = updated_at
# Koristi totalPages iz odgovora, NE vrijednost izračunatu iz zahtjeva, # tako da petlja ispravno završava čak i ako je server ograničio pageSize. if page >= body["totalPages"]: break page += 1
return rows, new_cursor
# Prvo izvršavanje: cursor=None povlači sve (potpuno punjenje).rows, cursor = poll_employees(cursor=None)save_to_local_db(rows)save_cursor(cursor)
# Stalno stanje: anketiraj svakih N minuta s pohranjenim kursorom.while True: time.sleep(300) # 5 minuta cursor = load_cursor() rows, new_cursor = poll_employees(cursor=cursor) save_to_local_db(rows) save_cursor(new_cursor)Nekoliko suptilnosti vrijedno spomenuti:
- Spremite kursor tek nakon što cijela anketa uspije, uključujući zapisivanje redova u vašu lokalnu pohranu. U suprotnom pad usred izvršavanja gubi redove koje ste dohvatili, ali niste pohranili, a sljedeća anketa ih neće ponovno dohvatiti.
- Kursor je ekskluzivan na strani servera, inkluzivan na vašoj granici. Red s
updatedAt == cursorpojavit će se u sljedećem anketiranju. Idempotentni upserti na vašoj strani apsorbiraju ponovno čitanje. Ako želite strogu ekskluzivnost, pomaknite svoj pohranjeni kursor nacursor + 1msnakon uspješnog prolaza. - Istovremene izmjene su eventualno konzistentne. Red uređen usred ankete mogao bi se pojaviti na kasnijoj stranici (jer je njegov
updatedAtnapredovao izvan granice stranice). Vaša upsert logika će prebrisati raniji prikaz s novijim. Nije potrebno ručno rješavanje sukoba.
Početno punjenje
Pri prvom izvršavanju, proslijedite modifiedSince=null (ili izostavite). Server vraća svaki red u tvrtki. S veličinom stranice od 100 redova, tvrtka s 5000 zaposlenika ima 50 stranica. Prilagodite ritam anketiranja prema rezervi ograničenja zahtjeva koju imate (vidi Ograničenja zahtjeva) i pohranite kursor na kraju.
Za vrlo velika početna punjenja, partneri ponekad preferiraju početnu vrijednost modifiedSince=2000-01-01T00:00:00Z kako bi koristili istu putanju koda kao i anketa stalnog stanja. Funkcionalno identično izostavljanju parametra.
Kombiniranje s webhooksima
Webhooks (vidi Webhooks) gura obavijesti o događajima kada se okine domenski događaj. Preporučeni su način reagiranja na promjene u realnom vremenu. Ali su “best-effort”: isporuka može propasti (pretplatnik nedostupan, mrežni problem) i nakon iscrpljivanja politike ponovnih pokušaja, događaj je nestao.
Delta sinkronizacija je trajni pandan. Uobičajeni obrazac:
- Webhooks za latenciju. Reagirajte na događaje u sekundama nakon okidanja.
- Satna delta sinkronizacija kao sigurnosna mreža. Uhvatite što god su webhooks ispustili (neuspjele isporuke, pretplata kratko onemogućena, prekid rada na strani partnera).
Dvije površine dijele isti podatkovni model. Webhook tijela nose id-ove resursa, a partneri povlače cijeli resurs preko GET /api/v1/{resource}/{id} za trenutno stanje. Pull endpoint je jedinstveni izvor istine u oba toka.