Delta sinhronizacija

Večina partnerskih integracij vzdržuje lokalni odsev locco virov (zaposleni, vozila, stroškovna mesta, oddelki, potni nalogi, izplačila, webhook naročnine). Po začetnem polnjenju je vprašanje na vsakem endpointu enako: kako povprašati, kaj se je spremenilo od zadnje sinhronizacije, brez ponovnega branja vsake vrstice?

Odgovor je query parameter modifiedSince. Podprt je na vsakem paginiranem list endpointu in je primarni podprt način inkrementalne sinhronizacije s partnerskim API-jem v v1.

Na kratko

  • Vsak paginiran list endpoint sprejme ?modifiedSince=<ISO 8601 UTC časovna oznaka>.
  • Strani se vrnejo razvrščene (updatedAt, id) ASC, ko je modifiedSince nastavljen brez izrecnega sort parametra. To je vrstni red, ki ga partnerji potrebujejo za prehod skozi časovno premico sprememb brez ponovnega branja že obdelanih vrstic.
  • Velikost strani je omejena na 100 na strani strežnika. Večje vrednosti se tiho omejijo, pageSize v odgovoru pa odraža omejeno vrednost (ne surovi zahtevek), tako da Math.ceil(total / pageSize) aritmetika ostane pravilna.
  • Kazalec je updatedAt najnovejše vrstice, vrnjene s prejšnjim uspešnim povpraševanjem. Shranite ga. Posredujte ga ob naslednjem povpraševanju. Ponovite.

Endpointi

Ista pogodba modifiedSince je podprta na:

VirEndpoint
ZaposleniGET /api/v1/employees
VozilaGET /api/v1/vehicles
Stroškovna mestaGET /api/v1/cost-centers
OddelkiGET /api/v1/departments
Potni nalogiGET /api/v1/travel-entries
IzplačilaGET /api/v1/payouts
Webhook naročnineGET /api/v1/webhooks

Webhook dostave (GET /api/v1/webhooks/{id}/deliveries) namerno NE podpirajo modifiedSince. Dostave so samo dodajane in efektivno nespremenljive po shranjevanju, zato partnerji, ki želijo slediti stanju dostav, povprašujejo po id-u ali po filtru statusa.

Kaj modifiedSince vrača

Za povpraševanje, kot je:

http
GET /api/v1/employees?modifiedSince=2026-04-30T12:00:00Z
Authorization: Bearer locco_live_...
X-Company-Id: 9c4e0d70-9d59-4a4a-a7d9-1e5ff6f1e3ec

Strežnik vrne vsako vrstico, katere updatedAt je v trenutku ali po posredovani časovni oznaki. updatedAt se na strani strežnika računa kot vrednost editedAt vrstice, s povratkom na createdAt za vrstice, ki niso bile nikoli urejene, tako da je časovna oznaka vedno izpolnjena.

Vrstica, ki je bila ustvarjena ob 2026-04-30T11:55:00Z in nikoli urejena, je izključena (njen updatedAt ob 11:55:00Z je pred kazalcem 12:00:00Z). Vrstica, ki je bila ustvarjena lani, vendar urejena danes, je vključena.

Jamstvo razvrščanja

Ko je modifiedSince nastavljen brez izrecnega sort parametra, strežnik razvrsti stran po (updatedAt ASC, id ASC). To je ključno za pravilnost delta sinhronizacije:

  • Brez id razrešilca enakih vrednosti bi se dve vrstici z enakim updatedAt skozi zahtevke pojavili nedeterministično, partnerji, ki gredo skozi paginacijo, pa bi tiho preskakovali ali podvajali vrstice.
  • ASC (najstarejša sprememba prva) pomeni, da je partnerjev kazalec na koncu strani največji updatedAt, ki ga je videl, kar je naravna izhodiščna točka za naslednje povpraševanje.

Če posredujete izrecen sort=...&sortDir=desc, strežnik še vedno doda id razrešilec, da paginacija ostane stabilna, vendar vrstni red strani ne bo tisti, ki ga želite za delta sinhronizacijo. Za sinhronizacijske primere uporabe pustite sort nenastavljen.

Omejitev paginacije

Vsak list endpoint omeji pageSize na 100 na strani strežnika. Polje pageSize v odgovoru odraža EFEKTIVNO omejeno vrednost, ne tega, kar ste zahtevali, zato:

http
GET /api/v1/employees?pageSize=10000

vrne do 100 elementov IN telo odgovora, ki pravi "pageSize": 100. Odgovor nosi tudi polje totalPages, izračunano pri EFEKTIVNI omejeni velikosti strani, zato je najpreprostejši zaključilec zanke page >= totalPages. Izogibajte se ponovnemu računanju števila strani iz total / pageSize glede na vašo surovo vrednost zahtevka: izračunali boste premalo strani in tiho izgubili podatke.

page je podobno omejen na minimum 1; nepozitivne vrednosti strani se normalizirajo. Polje page v odgovoru odraža dejansko vrnjeno stran.

Zanka povpraševanja

Pogodba je preprosta: preberite shranjeni kazalec, povprašujte z njim, prehodite vsako stran, shranite največji updatedAt iz prehoda kot naslednji kazalec.

python
import time
import 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]:
"""
Prehodi vsako stran zaposlenih, spremenjenih od `cursor`.
Vrne vrstice plus nov kazalec (največji updatedAt med prehodom).
"""
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"])
# Premakni kazalec na največji updatedAt na tej strani.
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
# Uporabi totalPages iz odgovora, NE vrednosti, izračunane iz zahtevka,
# tako da se zanka pravilno konča, tudi če je strežnik omejil pageSize.
if page >= body["totalPages"]:
break
page += 1
return rows, new_cursor
# Prvo izvajanje: cursor=None potegne vse (popolno polnjenje).
rows, cursor = poll_employees(cursor=None)
save_to_local_db(rows)
save_cursor(cursor)
# Stalno stanje: povprašuj vsakih N minut s shranjenim kazalcem.
while True:
time.sleep(300) # 5 minut
cursor = load_cursor()
rows, new_cursor = poll_employees(cursor=cursor)
save_to_local_db(rows)
save_cursor(new_cursor)

Nekaj subtilnosti, ki jih je vredno omeniti:

  • Kazalec shranite šele po uspešnem celotnem povpraševanju, vključno s pisanjem vrstic v vašo lokalno shrambo. Sicer zrušitev sredi izvajanja izgubi vrstice, ki ste jih pridobili, a niste shranili, naslednje povpraševanje pa jih ne bo ponovno pridobilo.
  • Kazalec je ekskluziven na strani strežnika, inkluziven na vaši meji. Vrstica z updatedAt == cursor se bo pojavila v naslednjem povpraševanju. Idempotentni upserti na vaši strani absorbirajo ponovno branje. Če želite strogo ekskluzivnost, premaknite svoj shranjeni kazalec na cursor + 1ms po uspešnem prehodu.
  • Sočasne spremembe so eventualno konsistentne. Vrstica, urejena sredi povpraševanja, se lahko pojavi na poznejši strani (ker je njen updatedAt napredoval čez mejo strani). Vaša upsert logika bo prejšnji prikaz prepisala z novejšim. Ročno reševanje konfliktov ni potrebno.

Začetno polnjenje

Pri prvem izvajanju posredujte modifiedSince=null (ali izpustite). Strežnik vrne vsako vrstico v podjetju. Pri velikosti strani 100 vrstic ima podjetje s 5000 zaposlenih 50 strani. Prilagodite ritem povpraševanja glede na rezervo omejitve zahtevkov, ki jo imate (glej Omejitve zahtevkov), in shranite kazalec na koncu.

Za zelo velika začetna polnjenja partnerji včasih raje uporabijo začetno vrednost modifiedSince=2000-01-01T00:00:00Z, da uporabljajo isto pot kode kot za povpraševanje stalnega stanja. Funkcionalno enako kot izpustitev parametra.

Kombiniranje z webhooki

Webhooks (glej Webhooks) potiska obvestila o dogodkih, ko se sproži domenski dogodek. So priporočen način odzivanja na spremembe v realnem času. Vendar so “best-effort”: dostava lahko propade (naročnik nedostopen, omrežna težava) in po izčrpanju politike ponovnih poskusov je dogodek izgubljen.

Delta sinhronizacija je trajnostni partner. Običajen vzorec:

  1. Webhooks za zakasnitev. Reagirajte na dogodke v sekundah po sproženju.
  2. Urna delta sinhronizacija kot varnostna mreža. Ujemite, kar koli so webhooki spustili (neuspele dostave, naročnina kratko onemogočena, prekinitev na strani partnerja).

Obe površini delita isti podatkovni model. Webhook telesa nosijo id-je virov, partnerji pa potegnejo celoten vir prek GET /api/v1/{resource}/{id} za trenutno stanje. Pull endpoint je edini vir resnice v obeh tokovih.