Delta sync
Většina partnerských integrací udržuje lokální zrcadlo zdrojů locco (zaměstnanci, vozidla, nákladová střediska, oddělení, cestovní záznamy, výplaty, odběry webhook). Po počátečním backfill je otázka na každém endpoint stejná: jak mohu provádět poll na to, co se změnilo od mé poslední synchronizace, aniž bych musel znovu číst každý řádek?
Odpovědí je query parameter modifiedSince. Je podporován na každém stránkovaném list endpoint a je to primární podporovaný způsob, jak provádět inkrementální synchronizaci vůči partnerskému API ve v1.
Ve zkratce
- Každý stránkovaný list endpoint přijímá
?modifiedSince=<ISO 8601 UTC timestamp>. - Stránky jsou vraceny seřazené podle
(updatedAt, id)ASC, když jemodifiedSincenastaveno bez explicitního parametrusort. Toto je pořadí, které partneři potřebují k procházení časové osy změn vpřed bez opětovného čtení již zpracovaných řádků. - Velikost stránky je na straně serveru omezena na 100. Vyšší hodnoty jsou tiše oříznuty a
pageSizev odpovědi odráží tuto oříznutou hodnotu (nikoli původní požadavek), takže aritmetikaMath.ceil(total / pageSize)zůstává správná. - Cursor je
updatedAtnejnovějšího řádku vráceného předchozím úspěšným poll. Uložte jej. Předejte jej při dalším poll. Opakujte.
Endpoints
Stejný kontrakt modifiedSince je podporován na:
| Zdroj | Endpoint |
|---|---|
| Zaměstnanci | GET /api/v1/employees |
| Vozidla | GET /api/v1/vehicles |
| Nákladová střediska | GET /api/v1/cost-centers |
| Oddělení | GET /api/v1/departments |
| Cestovní záznamy | GET /api/v1/travel-entries |
| Výplaty | GET /api/v1/payouts |
| Odběry webhook | GET /api/v1/webhooks |
Doručování webhook (GET /api/v1/webhooks/{id}/deliveries) záměrně NEPODPORUJE modifiedSince. Doručení jsou pouze pro přidávání (append-only) a po uložení jsou efektivně neměnná, takže partneři, kteří chtějí sledovat stav doručení, provádějí poll podle id nebo filtru stavu.
Co vrací modifiedSince
Při poll jako:
GET /api/v1/employees?modifiedSince=2026-04-30T12:00:00ZAuthorization: Bearer locco_live_...X-Company-Id: 9c4e0d70-9d59-4a4a-a7d9-1e5ff6f1e3ecServer vrátí každý řádek, jehož updatedAt je rovno nebo větší než zadané časové razítko. Hodnota updatedAt je vypočítána na straně serveru jako hodnota editedAt daného řádku, přičemž u řádků, které nebyly nikdy upraveny, se použije createdAt, takže časové razítko je vždy vyplněno.
Řádek, který byl vytvořen v 2026-04-30T11:55:00Z a nikdy nebyl upraven, je vyloučen (jeho updatedAt s hodnotou 11:55:00Z je před cursor s hodnotou 12:00:00Z). Řádek, který byl vytvořen loni, ale upraven dnes, je zahrnut.
Záruka řazení
Když je modifiedSince nastaveno bez explicitního parametru sort, server seřadí stránku podle (updatedAt ASC, id ASC). To je klíčové pro správnost delta sync:
- Bez
idjako rozhodovacího kritéria (tiebreaker) by se dva řádky se stejnýmupdatedAtobjevily v nedeterministickém pořadí napříč požadavky a partneři procházející stránkování by tiše přeskočili nebo duplikovali řádky. - ASC (nejstarší změna jako první) znamená, že cursor partnera na konci stránky je nejvyšší
updatedAt, které viděl, což je přirozený výchozí bod pro další poll.
Pokud předáte explicitní sort=...&sortDir=desc, server stále připojí id jako rozhodovací kritérium, takže stránkování zůstane stabilní, ale pořadí na stránce nebude takové, jaké byste pro delta sync chtěli. Pro případy použití synchronizace nechte sort nenastavené.
Omezení stránkování
Každý list endpoint omezuje pageSize na 100 na straně serveru. Pole pageSize v odpovědi odráží EFEKTIVNÍ oříznutou hodnotu, nikoli to, o co jste žádali, takže:
GET /api/v1/employees?pageSize=10000vrátí až 100 položek A tělo odpovědi, které říká "pageSize": 100. Odpověď také obsahuje pole totalPages vypočítané při EFEKTIVNÍ oříznuté velikosti stránky, takže nejjednodušší ukončovací podmínka smyčky je page >= totalPages. Vyhněte se přepočítávání stránek z total / pageSize vůči vaší původní hodnotě požadavku: vypočítali byste příliš málo stránek a tiše byste přišli o data.
page je podobně omezeno na minimum 1; nekladné hodnoty stránek jsou normalizovány. Pole page v odpovědi odráží skutečně vrácenou stránku.
Polling smyčka
Kontrakt je přímočarý: přečtěte si svůj uložený cursor, proveďte s ním poll, projděte každou stránku, uložte nejvyšší updatedAt z daného běhu jako další 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)Několik detailů, které stojí za zmínku:
- Uložte cursor až poté, co celý poll úspěšně skončí, včetně zápisu řádků do vašeho lokálního úložiště. Jinak při pádu uprostřed běhu ztratíte řádky, které jste stáhli, ale neuložili, a další poll je znovu nestáhne.
- Cursor je exkluzivní na straně serveru, inkluzivní na vaší hranici. Řádek s
updatedAt == cursorse objeví v dalším poll. Idempotentní upsert na vaší straně toto opětovné přečtení absorbuje. Pokud chcete striktní exkluzivitu, posuňte svůj uložený cursor nacursor + 1mspo úspěšném běhu. - Souběžné úpravy jsou eventuálně konzistentní. Řádek upravený uprostřed poll se může objevit na pozdější stránce (protože jeho
updatedAtse posunulo za hranici stránky). Vaše logika upsert přepíše dřívější čtení tím novějším. Není potřeba žádné manuální řešení konfliktů.
Počáteční backfill
Při úplně prvním běhu předejte modifiedSince=null (nebo jej vynechte). Server vrátí každý řádek ve společnosti. S velikostí stránky 100 řádků představuje společnost s 5000 zaměstnanci 50 stránek. Přizpůsobte tempo polling podle rezervy rate-limit, kterou máte (viz Rate limits), a na konci uložte cursor.
Pro velmi rozsáhlé počáteční backfill partneři někdy preferují inicializaci pomocí modifiedSince=2000-01-01T00:00:00Z, aby využili stejnou cestu v kódu jako při ustáleném poll. Funkčně je to identické s vynecháním parametru.
Kombinace s webhook
Webhook (viz Webhooks) odesílají oznámení o událostech, když nastane doménová událost. Jsou doporučeným způsobem, jak reagovat na změny v téměř reálném čase. Jsou však best-effort: doručení může selhat (odběratel je nedostupný, výpadek sítě) a po vyčerpání politiky opakování je událost ztracena.
Delta sync je odolným protějškem. Běžný vzor:
- Webhook pro latenci. Reagujte na události během několika sekund od jejich spuštění.
- Hodinový delta sync jako záchranná síť. Zachyťte vše, co webhook zahodil (selhaná doručení, krátkodobě zakázaný odběr, výpadek partnera).
Obě rozhraní sdílejí stejný datový model. Payload webhook nese id zdrojů a partneři stahují celý zdroj přes GET /api/v1/{resource}/{id} pro získání aktuálního stavu. Pull endpoint je jediným zdrojem pravdy v obou tocích.