Integrations
Exact, AFAS, Twinfield REST: debiteuren-agent cheatsheet
Dinsdagochtend in Nijmegen: de debiteuren-agent draaide negen dagen toen een partner wees op een factuur van €14.200 ICP en vroeg waar de BTW-code was gebleven.

Dinsdagochtend in Nijmegen, 09:14. De debiteuren-agent die we hadden opgeleverd voor een accountancypraktijk van 25 mensen draaide negen werkdagen live. Hij had 312 openstaande facturen aangeraakt, 184 zonder menselijke check afgehandeld, 47 doorgezet naar het team en 81 geparkeerd met een vraag voor de partner. Toen liep een partner naar ons toe met een geprinte Opgaaf ICP en vroeg waarom een intracommunautaire factuur van €14.200 aan een Brusselse leverancier van machineonderdelen aan de Exact Online-kant zonder BTW-code stond. De agent had hem geboekt. De API gaf 200 OK terug. Het veld was gewoon leeg.
In de zes weken daarna brachten we er zestien in kaart. Een aantal verziekte al maandenlang stilletjes boekingen voordat de agent live ging — alleen werden ze niet opgemerkt omdat de boekhouder een regel opnieuw inklopte of tegenboekte zodra de data raar oogde, en niemand bijhield hoe vaak. De agent bracht ze aan het licht omdat de agent niet opnieuw inklopt: hij logt en gaat door.
Hieronder de gerangschikte cheatsheet. We groeperen op ernst in plaats van op leverancier, want de echte vraag als je een agent loslaat op Nederlandse boekhoudsoftware is welke quirks stilletjes geld kosten — niet welke leverancier de mooiste OpenAPI-doc heeft.
Wat de agent doet
De debiteuren-agent heeft een smalle taak. Elke ochtend om 06:30 leest hij het openstaande debiteurenboek uit het boekhoudsysteem waar de klant op draait, segmenteert op ouderdom en bedrag, stelt een herinnering op voor facturen tussen 14 en 60 dagen achterstallig, escaleert alles boven 60 dagen naar een partner met de bijbehorende MT940-mutaties erbij, en matcht betaalde facturen tegen de bankimport. Hij schrijft alleen terug naar de boekhouding als hij een betaling aan een factuur koppelt en als hij een credit-nota boekt voor een betwiste regel die de partner in Slack heeft goedgekeurd.
Juist die smalle scope maakte de quirks zichtbaar. Een menselijke boekhouder die vijftig writes per dag doet spreidt fouten dun genoeg uit dat geen enkele rekening kapot oogt. Een agent die er driehonderd per dag doet, concentreert ze. Als twee procent van de writes een BTW-code laat vallen, heb je in twee weken honderd missende codes — en die zitten allemaal bij elkaar in het Opgaaf ICP-reviewscherm, waar de partner ze ziet.
De twee die stilletjes geld kosten
1. Exact Online SalesEntry — PATCH zonder Type kantelt credit-nota naar debet
Het endpoint /salesentry/SalesEntries van Exact Online is OData v4. Het veld Type op een SalesEntry is 20 voor een verkoopfactuur en 21 voor een credit-nota. De OData-spec zegt dat PATCH alleen de velden moet updaten die je meestuurt. In de praktijk: als je agent een bestaande entry oplost op documentreferentie in plaats van op de entry-GUID, en twee zusjes delen die referentie — één factuur, één credit-nota — pakt Exact het laagste entry-nummer, en dat is bijna altijd de factuur. De PATCH landt op de verkeerde regel. De credit-nota blijft ongemoeid, de factuur krijgt de statusupdate van de credit-nota, en het debiteurensaldo groeit met tweemaal het regelbedrag omdat de credit-nota nooit is afgesloten.
We zagen dit in de eerste week vier keer gebeuren bij partial-match-updates. De oplossing is saai: los altijd op via EntryID (de GUID), stuur Type en Journal mee bij elke PATCH zodat de regelidentiteit nooit dubbelzinnig is, en accepteer nooit een server-side match. De Exact REST-referentie documenteert de velden, maar zegt niets over de matchvolgorde.
PATCH /api/v1/{division}/salesentry/SalesEntries(guid'9c0e...') HTTP/1.1
Content-Type: application/json
{
"EntryID": "9c0e...",
"Type": 21,
"Journal": "70",
"Status": 50
}
2. AFAS Profit FiEntries — 200 OK met lege BTW-code op ICP
De FiEntries-UpdateConnector van AFAS Profit accepteert een verkoopboekingspayload als geneste elementen verpakt in JSON. Voor een intracommunautaire factuur met BTW verlegd binnen de EU moet de BTW-code expliciet staan (meestal code 5 in een standaard schema) en moet het BTW-percentage 0 zijn. Als de payload binnenkomt met percentage 0 en het BTW-codeveld ontbreekt of leeg is, geeft de connector 200 OK terug, schrijft de regel zonder code weg, en verschijnt de factuur nooit op de Opgaaf ICP.
De €10.000-drempel die in dit verhaal opduikt is geen leverancierslimiet; het is de drempel waarboven een Nederlandse praktijk een intracommunautaire factuur afzonderlijk beoordeelt in het ICP-scherm. Kleinere lopen mee in een batch die de partner doorbladert. Grotere worden opengeklikt. Vandaar dat de bug elf maanden lang onzichtbaar bleef op kleinere facturen en pas opdook op de dag dat er één van €14.200 binnenkwam.
We vonden later eenennegentig eerdere gevallen op dezelfde administratie. De praktijk heeft een Suppletie ingediend.
Als je agent ICP-facturen wegschrijft naar AFAS Profit, schrijf dan een post-write reconciler die de entry terugleest en asserteert dat de BTW-code niet leeg is voor elke regel waar het debiteurenland afwijkt van het administratieland en het BTW-percentage 0 is. De API weigert een verkeerde payload niet — die moet je vanaf de clientkant weigeren.
De volgende vier die stilletjes je data verzieken
3. Twinfield — BTW-codes zijn office-scoped, en de session office plakt
De BrowseTransactions- en SalesInvoice-endpoints van Twinfield lezen BTW-codes uit de office-context van de sessie. BTW-codes zijn per office gedefinieerd. Als je agent een sessie heeft geopend op office A en je boekt een factuur voor een klant wiens primary office B is, kan de BTW-code-string ontbreken in het codebook van B. Twinfield vervangt hem dan door de office-default (vaak 0% binnenland) of weigert hem — wat van de twee gebeurt, hangt af van het belastingland van de klant en of de string überhaupt bestaat in het schema van B.
Wissel expliciet van office vóór elke write: SessionSwitchCompany aan de SOAP-kant, of zet de office in de request body bij REST. Vertrouw er niet op dat de sessie die je twintig minuten geleden opende nog op de juiste plek staat. De Twinfield webservices-documentatie beschrijft het sessiemodel, maar zwijgt over wat er gebeurt als office en klant niet matchen.
4. Exact Online — refresh-token-rotatie sloopt cross-division agents
OAuth-refresh-tokens van Exact Online roteren bij elk gebruik. De grant geeft een nieuwe refresh token terug; de oude wordt direct ongeldig. Als twee processen een token delen — bijvoorbeeld een schrijf-agent en een lees-dashboard — verliest degene die het laatst refresht, krijgt een 401, en heb je je leespad en je schrijfpad om-en-om gesloopt.
We hadden drie dagen aan mysterieuze 401's voordat we doorhadden dat de 06:00-cron van het dashboard tokens aan het opeten was vijf minuten voordat de agent wakker werd. Gebruik een token broker. De simpelste versie is een Redis-key met de huidige refresh token en een lock rond de refresh-call.
5. AFAS Profit — GetConnector skip stopt stilletjes bij 10.000
De default take op een GetConnector is 100, het maximum is 10.000, en als je skip=10001 opvraagt krijg je een response die eruitziet als een normale lege pagina. Geen error, geen total-count-header, geen waarschuwing in de response-envelope. Als je een lang debiteurenboek doorloopt moet je op datumrange filteren en opnieuw window-en, niet voorbij de 10.000 pagineren. De dag dat een praktijk de tienduizend debiteuren passeert, begint de agent die gisteren werkte stilletjes de staart te missen.
6. Twinfield — een gematchte regel kan een matched amount van nul hebben
In de transaction-lines payload van Twinfield kan het veld matchstatus op matched staan terwijl matchedamount 0.00 is. Dit gebeurt als een oud voorstel werd bevestigd en daarna binnen dezelfde matching-sessie werd weggeboekt. Je agent moet matchen op het kasbedrag, niet op de status flag. We leerden dit op één middag waarop de agent besloot dat elke regel betaald was omdat elke regel, technisch gezien, gematcht was.
De tien die je overleeft maar wel een middag kosten
De overige tien zijn kleiner. Elk kostte ons tussen de twintig minuten en een hele middag. Hier geïnventariseerd zodat jij dat niet hoeft.
- Exact Online — de Journals-lookup pagineert op 60, niet 100. De doc zegt 100. Het is 60. Loop de cursor af voor de zekerheid.
- Exact Online — BankEntry vereist de Division in de URL, zelfs als die in de sessie staat. Weglaten geeft een 401, geen 400. Je zit veertig minuten te denken dat de token kapot is.
- AFAS Profit — UpdateConnector ziet een lege string als laat ongewijzigd. Stuur
nullom een veld echt te legen. - AFAS Profit — het debiteurensaldo-endpoint rondt de response af op hele euro's. Bereken het saldo uit de grootboekregels, niet uit het convenience-endpoint.
- Twinfield — factuur-PDF-URL's hebben een token en verlopen na 60 minuten. Cache de bytes, niet de URL.
- Twinfield — REST en SOAP kunnen het oneens zijn over de transactiestatus binnen een eventual-consistency-window van 30 seconden na een write. Lees je writes het eerste minuut via SOAP terug.
- Exact Online — Subscriptions gefilterd op
Status eq 1slaan abonnementen in de grace period over. Gebruik in plaats daarvan de StatusDescription-string. - AFAS Profit — datumvelden kappen de timezone stilletjes af. Stuur datums in Europe/Amsterdam-tijd. UTC landt op de verkeerde dag bij writes laat in de avond.
- Twinfield — klantaanmaak accepteert een bestaande code en geeft 200 OK terug, terwijl de bestaande klant wordt overschreven. Check vóór de write. Altijd.
- Exact Online — webhook-payloads vuren voordat de entity opvraagbaar is. De webhook komt binnen in milliseconden; het lees-endpoint ziet de regel twee tot vijf seconden later. Zet een retry-loop op de directe fetch.
De vijf-minuten pre-flight die we nu draaien vóór elke go-live
Voordat een debiteuren-agent productie raakt, draaien we nu een pre-flight tegen de doeladministratie. Hij duurt vijf minuten en had ons zes weken bespaard.
- POST een test-SalesEntry van elk type — verkoopfactuur, credit-nota, EU ICP, EU non-ICP, derde land — en lees elke terug. Asserteer dat Type, BTW-code en BTW-percentage byte-voor-byte heen-en-terug komen.
- PATCH elke test-entry met een partiële body, wijzig één veld en lees terug. Asserteer dat er verder niets is verschoven. Asserteer dat de regel die terugkomt dezelfde EntryID heeft als de regel die je hebt gepatched.
- Loop de journals-lijst met paginatie af en asserteer dat het aantal precies overeenkomt met het UI-aantal.
- Open twee parallelle OAuth-sessies in twee processen en asserteer dat beide na elkaar kunnen refreshen zonder 401.
- POST een dubbele klantcode en asserteer dat de API weigert in plaats van overschrijft.
Als ook maar één van die vijf faalt, ga je die dag niet live. Dan schrijf je eerst de reconciler.
Wat we rond de quirks hebben gebouwd
Toen we de debiteuren-agent opleverden voor de Nijmeegse praktijk, hadden we niet verwacht hoeveel van deze quirks hun boeken al stilletjes verziekten voordat de agent live ging — de agent had de discipline om te loggen in plaats van opnieuw in te kloppen, en alleen dáárom kwamen ze boven. De fix was geen slimmere AI-agents; het was een reconciliatielaag die elke write terugleest en asserteert dat de velden waar het ons om gaat de round trip overleefden. We leveren elke Nederlandse boekhoud-integratie nu standaard met die reconciler, en we hebben een mening over welke velden de assertie per systeem moet dekken.
Ga je deze week een agent op Exact, AFAS of Twinfield richten: besteed een ochtend aan de round-trip-assertie voor SalesEntry Type en BTW-code op een testset van vijf regels. Als die twee houden, ben je het ergste voorbij.
Kern
Twee van de zestien kosten stilletjes geld: PATCH-zonder-Type van Exact kan credit-nota's omkeren, en AFAS geeft 200 OK terug zonder BTW-code op ICP. Schrijf een reconciler.
FAQ
Gelden deze quirks ook voor Snelstart of Yuki?
Andere systemen, andere bugs. De foutklasse — stille 200 OK op een misvormde BTW-payload, server-side partial-match resolution — laat zich generaliseren. De specifieke endpoints niet. Audit ze elk apart.
Gebeurt de credit-nota-flip bij een verse POST of alleen bij PATCH?
We hebben hem alleen kunnen reproduceren bij PATCH waar de entry server-side op referentie wordt gematcht. Een verse POST schrijft een nieuwe regel en het Type-veld wordt gehonoreerd. Het risico zit in het update-pad.
Kan de agent writes weigeren als de BTW-code er fout uitziet?
Ja, en dat hoort hij ook te doen. Valideer ICP-facturen client-side vóór de write: landmismatch, percentage 0, code moet gezet zijn. Weiger de payload als dat niet zo is. De leverancier-API's weigeren hem niet voor jou.
Waarom niet gewoon de officiële Exact .NET SDK gebruiken?
Alleen Exact levert een onderhouden SDK en die dekt niet elke OData-edge-case. AFAS- en Twinfield-clients zijn meestal handgemaakt. Zelfs de Exact-SDK behoedt je niet voor het Type-op-PATCH-probleem — dat is een gebruikspatroon, geen wire-format-bug.