Naar de inhoud
Documentatie
Nederlands
De applicatie openen

Continu synchroniseren

Use case stap voor stap om een kopie van een dossier actueel te houden met sync/bootstrap en sync/pull, de cursor te beheren en wijzigingen in batches te versturen met sync/push.

De NovaFisko-applicaties werken offline dankzij een wijzigingsstroom. Uw integratie kan diezelfde stroom gebruiken om een kopie van een dossier actueel te houden zonder alle lijsten opnieuw te lezen: een datawarehouse, een rapporteringstool of een ander boekhoudpakket.

Vereisten: een token met de bevoegdheid sync. Voeg read toe voor het eerste laden van de resources via hun gewone routes.

Het principe bestaat uit drie fasen:

  1. Bootstrap: u krijgt de inventaris van de resources en een startcursor.
  2. Eerste laadbeurt: u leest elke resource één keer.
  3. Pull: u vraagt regelmatig "wat is er nieuw sinds mijn cursor?".

Stap 1: bootstrap

curl https://api.novafisko.com/v1/companies/k3Jd9fPq2LmX/sync/bootstrap \
  -H "Authorization: Bearer $NOVAFISKO_TOKEN" \
  -H "X-Device-Id: bi-warehouse-prod" \
  -H "X-Device-Name: Entrep%C3%B4t%20BI" \
  -H "X-Device-Platform: integration"
{
  "cursor": 48211,
  "server_time": "2026-10-05T08:40:00+00:00",
  "full_resync": false,
  "retention_days": 90,
  "resources": [
    {"name": "company", "count": 1, "endpoint": "companies/k3Jd9fPq2LmX", "paginated": false},
    {"name": "company_settings", "count": 1, "endpoint": "companies/k3Jd9fPq2LmX/settings", "paginated": false},
    {"name": "third_parties", "count": 184, "endpoint": "companies/k3Jd9fPq2LmX/third-parties", "paginated": false},
    {"name": "accounts", "count": 412, "endpoint": "companies/k3Jd9fPq2LmX/accounts", "paginated": false},
    {"name": "entries", "count": 873, "endpoint": "companies/k3Jd9fPq2LmX/entries", "paginated": true},
    {"name": "documents", "count": 640, "endpoint": "companies/k3Jd9fPq2LmX/documents", "paginated": true},
    {"name": "bank_transactions", "count": 1290, "endpoint": "companies/k3Jd9fPq2LmX/bank-transactions", "paginated": true}
  ],
  "company": {"id": 318, "public_token": "k3Jd9fPq2LmX", "name": "Le Comptoir Montois SRL", "version": 6},
  "company_settings": {"vat": {"office_number": null}},
  "user": {"id": 12, "name": "Claire Dumont", "role": "firm_admin", "role_rank": 90, "can_write": true, "can_force": true},
  "push": {
    "resources": {
      "third_parties": {"ops": ["create", "update", "delete"], "actions": []},
      "entries": {"ops": ["create", "action"], "actions": ["entry", "invoice", "reverse"]}
    },
    "max_mutations": 200,
    "temp_id_prefix": "tmp-"
  }
}

De lijsten zijn ingekort. Sla cursor op voordat u met de eerste laadbeurt begint: alles wat tijdens het laden verandert, wordt u bij de eerste pull opnieuw bezorgd.

Stap 2: eerste laadbeurt

Roep voor elke vermelding in resources het endpoint aan (relatief ten opzichte van /v1/). Wanneer paginated de waarde true heeft, doorloopt u alle pagina's zoals beschreven in Paginering. Wanneer de waarde false is, is het antwoord een volledige array of één enkel object.

def initial_load(session, base, bootstrap):
    store = {}
    for resource in bootstrap["resources"]:
        url = f"{base}/{resource['endpoint']}"
        if not resource["paginated"]:
            store[resource["name"]] = session.get(url, timeout=60).json()
            continue
        rows, page = [], 1
        while True:
            payload = session.get(url, params={"per_page": 200, "page": page}, timeout=60).json()
            rows.extend(payload["data"])
            if page >= payload["last_page"]:
                break
            page += 1
        store[resource["name"]] = rows
    return store

Stap 3: incrementele pull

curl "https://api.novafisko.com/v1/companies/k3Jd9fPq2LmX/sync/pull?since=48211&limit=500" \
  -H "Authorization: Bearer $NOVAFISKO_TOKEN" \
  -H "X-Device-Id: bi-warehouse-prod"
Parameter Standaard Beschrijving
since Laatst ontvangen cursor
resources alle Door komma's gescheiden lijst om alleen bepaalde resources te volgen: entries,third_parties
limit 500 Aantal wijzigingen per aanroep, maximaal 1000
{
  "cursor": 48236,
  "has_more": false,
  "full_resync": false,
  "changes": [
    {
      "resource": "third_parties",
      "op": "upsert",
      "id": 41,
      "version": 4,
      "data": {"id": 41, "type": "supplier", "code": "BRASSCOL", "name": "Brasserie des Collines SA", "email": "compta@brasserie-collines.example", "version": 4},
      "changed_at": "2026-10-05T08:41:02+00:00",
      "seq": 48230
    },
    {
      "resource": "journal_rules",
      "op": "delete",
      "id": 9,
      "version": 3,
      "data": null,
      "changed_at": "2026-10-05T08:41:30+00:00",
      "seq": 48236
    }
  ],
  "server_time": "2026-10-05T08:41:39+00:00"
}

De wijzigingen toepassen

op Wat te doen
upsert Het record id van de resource aanmaken of vervangen door data
delete Het record verwijderen. Het is verwijderd of in de prullenbak geplaatst

data heeft exact de vorm die de lijstroute van de resource teruggeeft. Elk record verschijnt maar één keer per antwoord, in zijn laatste toestand. Als u een version ontvangt die lager is dan of gelijk is aan de versie die u al hebt, negeer dan de wijziging.

Regels voor de cursor

  1. Pas alle wijzigingen van een antwoord toe en sla daarna de nieuwe cursor op, indien mogelijk in dezelfde transactie.
  2. Als has_more de waarde true heeft, roep dan onmiddellijk opnieuw aan met de nieuwe cursor.
  3. Als full_resync de waarde true heeft, is uw cursor onbruikbaar. changes is leeg: voer de eerste laadbeurt van stap 2 opnieuw uit en vertrek daarna van de teruggegeven cursor.

full_resync treedt op wanneer since ontbreekt, wanneer hij ouder is dan de bewaartermijn van het logboek (90 dagen) of wanneer de server hem niet kent. Dat is met name het geval in het demokantoor na de nachtelijke reset.

Opmerking

Een wijziging wordt 2 seconden na het wegschrijven zichtbaar. Die vertraging garandeert dat een cursor nooit over een rij springt die nog wordt opgeslagen. Wees dus niet verbaasd als u een wijziging die u net hebt gemaakt niet onmiddellijk ziet.

Volledige lus

const BASE = "https://api.novafisko.com/v1";
const headers = {
  Authorization: `Bearer ${process.env.NOVAFISKO_TOKEN}`,
  Accept: "application/json",
  "X-Device-Id": "bi-warehouse-prod",
};

async function syncOnce(company, store) {
  let cursor = await store.getCursor(company);
  for (;;) {
    const url = `${BASE}/companies/${company}/sync/pull?since=${cursor ?? ""}&limit=1000`;
    const page = await (await fetch(url, { headers })).json();

    if (page.full_resync) {
      await store.reloadEverything(company); // initial load again
      await store.setCursor(company, page.cursor);
      return;
    }
    await store.transaction(async (tx) => {
      for (const change of page.changes) {
        if (change.op === "delete") await tx.remove(change.resource, change.id);
        else await tx.upsert(change.resource, change.id, change.version, change.data);
      }
      await tx.setCursor(company, page.cursor); // cursor saved with the data
    });
    cursor = page.cursor;
    if (!page.has_more) return;
  }
}

// Poll every 30 seconds; a webhook can trigger an immediate run
setInterval(() => syncOnce("k3Jd9fPq2LmX", store).catch(console.error), 30_000);
Tip

Een interval van 30 tot 60 seconden volstaat in de meeste gevallen. Om sneller te reageren zonder vaker te bevragen, start u een pull bij ontvangst van een webhook.

Stap 4: de status opvolgen

curl https://api.novafisko.com/v1/companies/k3Jd9fPq2LmX/sync/status \
  -H "Authorization: Bearer $NOVAFISKO_TOKEN" \
  -H "X-Device-Id: bi-warehouse-prod"
{
  "cursor": 48236,
  "server_time": "2026-10-05T08:45:00+00:00",
  "pending_conflicts": 0,
  "last_push_at": "2026-10-05T07:58:12+00:00",
  "devices": [
    {"id": "bi-warehouse-prod", "name": "Entrepôt BI", "platform": "integration", "user": {"id": 12, "name": "Claire Dumont"}, "last_seen_at": "2026-10-05T08:45:00+00:00", "last_push_at": null, "last_cursor": 48236}
  ]
}

Vergelijk uw cursor met cursor om uw achterstand te meten. Uw connector verschijnt in devices dankzij de header X-Device-Id.

Wijzigingen in batches versturen

POST sync/push past tot 200 mutaties toe in één aanroep, in volgorde, elk in haar eigen transactie. Elke mutatie wordt opnieuw afgespeeld door de overeenkomstige REST-actie, met dezelfde validaties.

curl -X POST https://api.novafisko.com/v1/companies/k3Jd9fPq2LmX/sync/push \
  -H "Authorization: Bearer $NOVAFISKO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "device": {"id": "erp-atelier-prod", "name": "ERP Atelier", "platform": "integration"},
    "mutations": [
      {
        "client_mutation_id": "c1b9a8d2-51b5-4a61-8f52-3a2d1d8c5a01",
        "occurred_at": "2026-10-05T08:00:00Z",
        "resource": "third_parties",
        "op": "create",
        "client_temp_id": "tmp-7d1f",
        "data": {"type": "supplier", "name": "Maraîchers du Borinage SC", "vat_number": "BE0999900233"}
      },
      {
        "client_mutation_id": "c1b9a8d2-51b5-4a61-8f52-3a2d1d8c5a02",
        "occurred_at": "2026-10-05T08:00:05Z",
        "resource": "entries",
        "op": "action",
        "action": "invoice",
        "data": {
          "journal_id": 3,
          "third_party_id": "tmp-7d1f",
          "entry_date": "2026-10-03",
          "reference": "2026-441",
          "lines": [{"account": "604000", "amount": "318.40", "vat_code": "A06"}]
        }
      }
    ]
  }'

De tweede mutatie verwijst naar de derde die door de eerste is aangemaakt, dankzij de tijdelijke identificator tmp-7d1f. De server vervangt hem door de echte id, zowel in dezelfde batch als in de volgende.

{
  "results": [
    {
      "client_mutation_id": "c1b9a8d2-51b5-4a61-8f52-3a2d1d8c5a01",
      "status": "applied",
      "resource": "third_parties",
      "op": "create",
      "id": 215,
      "client_temp_id": "tmp-7d1f",
      "version": 1,
      "data": {"id": 215, "type": "supplier", "name": "Maraîchers du Borinage SC", "version": 1},
      "conflict": null,
      "retryable": false
    },
    {
      "client_mutation_id": "c1b9a8d2-51b5-4a61-8f52-3a2d1d8c5a02",
      "status": "applied",
      "resource": "entries",
      "op": "action",
      "action": "invoice",
      "id": 1851,
      "data": {"id": 1851, "number": 213, "status": "posted"},
      "conflict": null,
      "retryable": false
    }
  ],
  "cursor": 48251,
  "server_time": "2026-10-05T08:46:10+00:00"
}
status Betekenis Wat te doen
applied Mutatie toegepast Uw kopie vervangen door data
duplicate client_mutation_id al verwerkt, oorspronkelijk resultaat teruggegeven Niets, de mutatie is verworven
conflict Verwerkt volgens de prioriteitsregels Uw kopie vervangen door data, de mutatie uit uw wachtrij halen
rejected Geweigerd, zie code Definitief, tenzij retryable de waarde true heeft
Let op

Alleen bepaalde bewerkingen verlopen via sync/push. De exacte lijst staat in push.resources van de bootstrap. Al de rest komt terug als rejected met de code not_supported_offline: gebruik dan de klassieke REST-route.

Wanneer twee wijzigingen betrekking hebben op hetzelfde record, voegt NovaFisko ze veld per veld samen. Gevalideerde of vergrendelde boekhoudgegevens winnen altijd. De volledige regels staan in Conflicten en prioriteiten.

Beperkingen om te kennen

  • De massaverwerkingen (afpunting, vergrendeling van de periodes bij de afsluiting, terugzetten naar te boeken na een tegenboeking) leveren geen rij op in de stroom. Laad na een tegenboeking documents en bank_transactions opnieuw.
  • Het wijzigingslogboek wordt 90 dagen bewaard. Een connector die langer stilligt, moet een volledige laadbeurt opnieuw uitvoeren.
  • De stroom is eigen aan één dossier. Om een heel kantoor te volgen, start u één lus per dossier.

Zie ook