Naar de inhoud
← Blog

Techniek

Koppelen met Exact Online, NetSuite en Business Central

De verschillen tussen de API's en de authenticatie van drie ERP-systemen, voor ontwikkelaars die ertegen moeten bouwen.

9 november 2026 · 6 min lezen

In het engineeringteam van Eagl bouw en onderhoud ik koppelingen met Exact Online, NetSuite en Business Central. Dit artikel overloopt wat de publieke documentatie zegt en waar de drie systemen van elkaar verschillen. De code en de klanten van Eagl komen er niet in voor. Deze API’s veranderen, dus controleer de details in de officiële documentatie voor je erop bouwt.

Exact Online

De REST API werkt in OData-stijl, met queryparameters zoals $select, $filter en $top. Twee dingen bepalen het adres. Exact heeft per land een eigen domein, dus een Belgische klant zit op een ander domein dan een Nederlandse. Een administratie heet een division en staat in het pad. Via current/Me vraag je op welke division de ingelogde gebruiker actief is.

De authenticatie is OAuth 2.0 met de authorization code flow, dus een gebruiker bij de klant moet inloggen en toestemming geven. De access token is kort geldig, rond de tien minuten op het moment van schrijven. Een nieuwe vraag je pas kort voor het verstrijken aan, want te vroeg geeft een fout. De refresh token roteert: elke refresh geeft een nieuwe, en de oude is daarna ongeldig.

Dat laatste vraagt om aandacht zodra je meer dan één proces hebt. Als twee workers tegelijk dezelfde refresh token gebruiken, verliest er een, en in het slechtste geval moet de klant opnieuw inloggen. Serialiseer de refresh per koppeling, en bewaar de nieuwe tokens voor je ze gebruikt.

// Vereenvoudigde illustratie, niet de code van Eagl.
async function getAccessToken(connectionId: string) {
  return withLock(`token:${connectionId}`, async () => {
    const conn = await db.connections.get(connectionId);
    if (conn.accessTokenExpiresAt > Date.now() + 30_000) return conn.accessToken;

    const fresh = await exchangeRefreshToken(conn.refreshToken);
    await db.connections.update(connectionId, fresh);
    return fresh.accessToken;
  });
}

Er gelden limieten per minuut en per dag, en de responses vertellen je via X-RateLimit-headers hoeveel je nog over hebt. Lees die headers uit en codeer de cijfers niet hard in je client. Voor grote hoeveelheden data bestaan er voor een deel van de resources aparte bulk- en sync-endpoints, waarbij je bij sync vanaf een timestamp verder leest. Op een deel van de endpoints moet je de velden expliciet kiezen met $select. Naast de REST API bestaat er ook nog een oudere XML-interface.

NetSuite

NetSuite heeft meerdere ingangen. SuiteTalk REST werkt met records. SuiteQL is een SQL-achtige querytaal die je via de REST-diensten aanspreekt, en die vaak de handigste manier is om grote hoeveelheden te lezen. RESTlets zijn eigen endpoints in SuiteScript die je in het account van de klant zet, voor wat de standaard-API niet kan. Daarmee rol je wel code uit in dat account.

SOAP wordt uitgefaseerd. Volgens de FAQ van Oracle horen nieuwe integraties sinds release 2026.1 op REST en OAuth 2.0 te draaien. Vanaf 2027.1 kan je geen nieuwe integraties meer bouwen met token-based authentication, en SOAP vereist die. Met 2028.2 gaan alle SOAP-endpoints uit. TBA is de oudere vorm waarbij je elk verzoek ondertekent in OAuth 1.0-stijl. Bestaande REST-integraties met TBA hoeven nog niet over te stappen, dus je zal het nog tegenkomen.

Het adres hangt af van het account: elk account heeft een eigen domein onder suitetalk.api.netsuite.com, en een sandbox is een apart account met een eigen id.

Wat NetSuite het meest onderscheidt, is hoe ver accounts van elkaar kunnen verschillen. Custom fields, custom records en custom segments maken dat twee klanten hetzelfde concept anders kunnen opslaan. De rol van je integratie bepaalt wat je ziet, dus dezelfde query kan bij de ene klant werken en bij de andere een fout of minder rijen geven. Test met de rol die de klant je geeft, want een administrator ziet meer.

De limiet is concurrency op het niveau van het account, die je deelt met de andere integraties van de klant. Over de limiet krijg je HTTP 429, en dan probeer je het later opnieuw. Via de operatie governanceLimits kan je de limiet van het account opvragen.

Business Central

Dit gaat over de online versie. De on-premises versie kan op een aantal punten verschillen.

De standaard-API v2.0 is REST op basis van OData v4. Het adres bevat de tenant, de omgeving en het bedrijf:

https://api.businesscentral.dynamics.com/v2.0/{tenant}/{environment}/api/v2.0/companies({id})/generalLedgerEntries

Een tenant kan meerdere omgevingen hebben, zoals productie en sandboxes, en elke omgeving meerdere bedrijven. Sla al die niveaus op per koppeling.

De authenticatie loopt via Microsoft Entra ID. Voor een integratie zonder gebruiker gebruik je service-to-service-authenticatie met de client credentials flow. Dat vraagt twee stappen: een app-registratie in Entra met de juiste API-rechten, en daarna de client-ID toevoegen op de pagina met Microsoft Entra-toepassingen in Business Central, met eigen machtigingensets. De SUPER-machtiging kan je daar niet toekennen. Reken erop dat de klant dit in zijn eigen tenant moet doen.

Wat de standaard-API niet aanbiedt, kan je toevoegen met eigen API-pagina’s in een AL-extensie, en die extensie moet je dan bij de klant installeren. Bij een PATCH stuur je de ETag mee in If-Match.

Business Central heeft ook webhooks. Een abonnement verloopt na drie dagen tenzij je het vernieuwt, en zowel aanmaken als vernieuwen vraagt om een handshake met een validationToken. Meldingen komen met wat vertraging en samengevoegd, en bij veel wijzigingen krijg je één melding van het type collection en moet je zelf de wijzigingen ophalen. Let vooral op de foutcodes van je ontvanger: alleen 408, 429 en 5xx leiden tot nieuwe pogingen. Bij elke andere code wordt het abonnement verwijderd.

Hetzelfde concept, drie namen

De scheiding tussen bedrijven heet division in Exact Online, subsidiary in NetSuite OneWorld en company in Business Central. Ze zitten ook niet op dezelfde plek: bij Exact en Business Central staan ze in het adres, bij NetSuite zijn het gegevens binnen één account.

Hetzelfde geldt voor analytische indeling. Exact Online heeft kostenplaatsen en kostendragers, NetSuite heeft departments, classes, locations en custom segments, en Business Central heeft dimensions. Een intern model dat dit plat slaat, gooit informatie weg die een boekhouder later nodig heeft. Bewaar de bron-ID en bij voorkeur de ruwe data naast je eigen vorm.

Wat in alle drie terugkomt

Bij incrementeel lezen bewaar je een cursor per koppeling en per resource. Neem een overlap in de tijd en maak je schrijfacties idempotent op de bron-ID, zodat je dezelfde rij veilig twee keer kan verwerken. Een filter op een wijzigingsdatum toont geen verwijderde records, dus daar heb je een aparte aanpak voor nodig.

Tokens bewaar je versleuteld en per klant. Toon ook de status van de koppeling ergens in je product, want een koppeling die opnieuw toestemming nodig heeft, moet een mens bij de klant oplossen.

Limieten behandel je per koppeling met een eigen wachtrij. Bij een 429 wacht je, en je volgt Retry-After als die er is.

Begin met leesrechten. Schrijven in een boekhouding vraagt meer voorzichtigheid: een dubbele boeking kan je niet zomaar als dubbele rij weggooien, en afhankelijk van het systeem en de status van een boeking corrigeer je ze met een tegenboeking. Periodes kunnen afgesloten zijn, dus een schrijfactie kan terecht geweigerd worden. Test tegen een testomgeving: Business Central heeft sandbox-omgevingen, NetSuite sandbox-accounts die niet elke klant heeft, en bij Exact Online ga je na welke testadministratie je kan krijgen.

Lees ook

Wat is jouw idee?

Vertel me wat je wil bouwen. Ik probeer binnen één werkdag te antwoorden.