Delta sync
Väčšina partnerských integrácií udržiava lokálne zrkadlo zdrojov locco (zamestnanci, vozidlá, nákladové strediská, oddelenia, cestovné záznamy, výplaty, odbery pre webhook). Po počiatočnom backfill je otázka na každom endpoint rovnaká: ako môžem dopytovať (poll) to, čo sa zmenilo od mojej poslednej synchronizácie bez toho, aby som musel znova čítať každý riadok?
Odpoveďou je query parameter modifiedSince. Je podporovaný na každom stránkovanom list endpoint a je to primárny podporovaný spôsob, ako vykonávať inkrementálnu synchronizáciu voči partnerskému API vo v1.
V skratke
- Každý stránkovaný list endpoint akceptuje
?modifiedSince=<ISO 8601 UTC timestamp>. - Stránky sa vracajú zoradené podľa
(updatedAt, id)ASC, keď jemodifiedSincenastavené bez explicitného parametrasort. Toto je poradie, ktoré partneri potrebujú na to, aby mohli prechádzať časovou osou zmien vpred bez opätovného čítania už spracovaných riadkov. - Veľkosť stránky je na strane servera obmedzená na 100. Vyššie hodnoty sú potichu orezané a
pageSizev odpovedi odráža túto orezanú hodnotu (nie pôvodnú požiadavku), takže aritmetikaMath.ceil(total / pageSize)zostáva správna. - cursor je
updatedAtnajnovšieho riadka vráteného predchádzajúcim úspešným dopytom (poll). Uložte ho. Pošlite ho pri ďalšom poll. Opakujte.
Endpoints
Rovnaký kontrakt pre modifiedSince je podporovaný na:
| Resource | Endpoint |
|---|---|
| Zamestnanci | GET /api/v1/employees |
| Vozidlá | GET /api/v1/vehicles |
| Nákladové strediská | GET /api/v1/cost-centers |
| Oddelenia | GET /api/v1/departments |
| Cestovné záznamy | GET /api/v1/travel-entries |
| Výplaty | GET /api/v1/payouts |
| Odbery pre webhook | GET /api/v1/webhooks |
Doručenia pre webhook (GET /api/v1/webhooks/{id}/deliveries) zámerne NEPODPORUJÚ modifiedSince. Doručenia sú iba na pridávanie (append-only) a po uložení sú v podstate nemenné, takže partneri, ktorí chcú sledovať stav doručenia, namiesto toho dopytujú (poll) podľa id alebo podľa filtra stavu.
Čo vracia modifiedSince
Pri dopyte (poll) ako:
GET /api/v1/employees?modifiedSince=2026-04-30T12:00:00ZAuthorization: Bearer locco_live_...X-Company-Id: 9c4e0d70-9d59-4a4a-a7d9-1e5ff6f1e3ecServer vráti každý riadok, ktorého updatedAt je rovnaký alebo novší ako poskytnutá časová pečiatka. updatedAt sa počíta na strane servera ako hodnota editedAt daného riadka, pričom pre riadky, ktoré neboli nikdy upravené, sa použije createdAt, takže časová pečiatka je vždy vyplnená.
Riadok, ktorý bol vytvorený o 2026-04-30T11:55:00Z a nikdy nebol upravený, je vylúčený (jeho updatedAt s hodnotou 11:55:00Z je pred cursor s hodnotou 12:00:00Z). Riadok, ktorý bol vytvorený minulý rok, ale upravený dnes, je zahrnutý.
Záruka triedenia
Keď je modifiedSince nastavené bez explicitného parametra sort, server zoradí stránku podľa (updatedAt ASC, id ASC). Toto je kľúčové pre správnosť delta sync:
- Bez
idako rozhodovacieho kritéria (tiebreaker) by sa dva riadky s identickýmupdatedAtzobrazovali v nedeterministickom poradí naprieč požiadavkami a partneri prechádzajúci stránkovaním by potichu preskočili alebo duplikovali riadky. - ASC (najstaršia zmena ako prvá) znamená, že partnerov cursor na konci stránky je najvyššie
updatedAt, aké doteraz videli, čo je prirodzený počiatočný bod pre ďalší poll.
Ak pošlete explicitné sort=...&sortDir=desc, server stále pripojí id ako rozhodovacie kritérium, takže stránkovanie zostane stabilné, ale poradie na stránke nebude také, aké požadujete pre delta sync. Pre prípady použitia synchronizácie nechajte sort nenastavené.
Orezanie stránkovania
Každý list endpoint obmedzuje (clamps) pageSize na 100 na strane servera. Pole pageSize v odpovedi odráža EFEKTÍVNU orezanú hodnotu, nie to, o čo ste žiadali, takže:
GET /api/v1/employees?pageSize=10000vráti maximálne 100 položiek A telo odpovede, ktoré hovorí "pageSize": 100. Odpoveď tiež obsahuje pole totalPages vypočítané pri EFEKTÍVNEJ orezanej veľkosti stránky, takže najjednoduchší ukončovač slučky je page >= totalPages. Vyhnite sa prepočítavaniu stránok z total / pageSize voči vašej pôvodnej hodnote požiadavky: vypočítate príliš málo stránok a potichu prídete o dáta.
page je podobne orezané na minimum 1; nekladné hodnoty stránok sú normalizované. Pole page v odpovedi odráža skutočnú vrátenú stránku.
Slučka dopytovania
Kontrakt je priamočiary: prečítajte si váš uložený cursor, vykonajte s ním poll, prejdite každú stránku, uložte najvyššie updatedAt z tohto behu ako ďalší cursor.
import timeimport requests
API = "https://api.getlocco.com"HEADERS = { "Authorization": "Bearer locco_live_<your-key>", "X-Company-Id": "<your-company-id>",}
def poll_employees(cursor: str | None) -> tuple[list[dict], str]: """ Walk every page of employees changed since `cursor`. Returns the rows plus the new cursor (the highest updatedAt seen during this run). """ 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"])
# Advance the cursor to the highest updatedAt on this page. 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
# Use the response's totalPages, NOT a value computed from the request, # so the loop terminates correctly even if the server clamped pageSize. if page >= body["totalPages"]: break page += 1
return rows, new_cursor
# First run: cursor=None pulls everything (full backfill).rows, cursor = poll_employees(cursor=None)save_to_local_db(rows)save_cursor(cursor)
# Steady state: poll every N minutes with the saved cursor.while True: time.sleep(300) # 5 minutes cursor = load_cursor() rows, new_cursor = poll_employees(cursor=cursor) save_to_local_db(rows) save_cursor(new_cursor)Niekoľko detailov, na ktoré stojí za to upozorniť:
- Uložte cursor až po úspešnom dokončení celého poll, vrátane zápisu riadkov do vášho lokálneho úložiska. V opačnom prípade pri páde uprostred behu stratíte riadky, ktoré ste stiahli, ale neuložili, a ďalší poll ich už znova nestiahne.
- cursor je exkluzívny na strane servera, inkluzívny na vašej hranici. Riadok s
updatedAt == cursorsa objaví v ďalšom poll. Idempotentné upsert operácie na vašej strane absorbujú opätovné prečítanie. Ak chcete striktnú exkluzivitu, posuňte váš uložený cursor nacursor + 1mspo úspešnom behu. - Súbežné úpravy sú eventuálne konzistentné. Riadok upravený uprostred poll sa môže objaviť na neskoršej stránke (pretože jeho
updatedAtsa posunulo za hranicu stránky). Vaša upsert logika prepíše skoršie prečítanie tým novším. Nie je potrebné žiadne manuálne riešenie konfliktov.
Počiatočný backfill
Pri úplne prvom behu pošlite modifiedSince=null (alebo ho vynechajte). Server vráti každý riadok v spoločnosti. Pri veľkosti stránky 100 riadkov predstavuje spoločnosť s 5000 zamestnancami 50 stránok. Prispôsobte tempo dopytovania rezerve pre rate-limit, ktorú máte (pozrite si Rate limits), a na konci uložte cursor.
Pre veľmi veľké počiatočné backfill partneri niekedy uprednostňujú inicializáciu s modifiedSince=2000-01-01T00:00:00Z, aby použili rovnakú cestu v kóde ako pri bežnom poll v ustálenom stave. Funkčne je to identické s vynechaním parametra.
Kombinácia s webhooks
Webhooks (pozrite si Webhooks) odosielajú notifikácie o udalostiach, keď nastane doménová udalosť. Sú odporúčaným spôsobom, ako reagovať na zmeny v takmer reálnom čase. Sú však best-effort: doručenie môže zlyhať (odberateľ je nedostupný, výpadok siete) a po vyčerpaní politiky opakovaných pokusov je udalosť stratená.
Delta sync je odolným náprotivkom. Bežný vzor:
- Webhooks pre latenciu. Reagujte na udalosti v priebehu niekoľkých sekúnd od ich vzniku.
- Hodinový delta sync ako záchranná sieť. Zachyťte všetko, čo webhooks zahodili (zlyhané doručenia, krátkodobo zakázaný odber, výpadok partnera).
Tieto dve rozhrania zdieľajú rovnaký dátový model. Payload pre webhook nesie id zdrojov a partneri sťahujú celý zdroj cez GET /api/v1/{resource}/{id} pre získanie aktuálneho stavu. Tento pull endpoint je jediným zdrojom pravdy v oboch tokoch.