{ "exact" :
Syncing Dutch company data into Exact Online
Short answer
Exact Online stores the KVK number in the ChamberOfCommerce field on crm/Accounts, as free text with no validation. You fill it through the Exact REST API from your own middleware script that performs our lookup first. We do not ship an Exact connector: the integration is your script sitting between two APIs.
# does the account exist?
GET ".../crm/Accounts?$filter="
"ChamberOfCommerce eq '68750110'"
# if not: fetch the profile
curl ".../v1/lookup/68750110" \
-H "Authorization: Bearer KEY"
{
"statutoryName": "Acme B.V.",
"isActive": true
}
Where the data lands
An Exact Online relation is an Account under crm/Accounts, scoped per administration:
POST https://start.exactonline.nl/api/v1/{division}/crm/Accounts
The fields a lookup can fill:
| Exact field | From the API | Watch out |
|---|---|---|
Name | statutoryName, else name | Invoice name |
ChamberOfCommerce | kvkNumber | Free text, no validation |
VATNumber | vat.number | Only when vat.valid is true |
AddressLine1 | address.street plus house number | Exact has no separate house number field |
Postcode | address.postalCode | With a space, keep it consistent |
City | address.city | |
Country | address.country | |
Website | websites[0] | Only with enrich=true |
That house number is the first thing that goes wrong. We return street, houseNumber and houseNumberAddition separately, Exact wants a single line. Concatenate in that order and keep the parts in your own database too, otherwise you can never match reliably on house number later.
The integration point is your script
There is no connector between us and Exact. What you build is a small piece of middleware, in any language, with four steps:
- A KVK number arrives, from a form, your CRM or an import file.
GET /v1/lookup/{kvkNumber}?enrich=trueon our side, checkingisActive.- A duplicate check in Exact with an OData filter on the KVK number.
POSTwhen absent,PUTon the existing GUID when present.
Step 3 is the one people skip, and it is exactly why Exact administrations fill up with duplicate relations:
GET /api/v1/{division}/crm/Accounts?$filter=ChamberOfCommerce eq '68750110'&$select=ID,Name
Authorization: Bearer {exact_access_token}
Accept: application/json
Zero results, then create:
{
"Name": "Acme B.V.",
"ChamberOfCommerce": "68750110",
"VATNumber": "NL123456789B01",
"AddressLine1": "Keizersgracht 123",
"Postcode": "1015 CJ",
"City": "Amsterdam",
"Country": "NL"
}
For the batch side of this, when you are loading thousands of accounts at once, there is a longer piece in batch enrichment of Dutch company data.
Where the KVK number comes from
Exact is the destination, not the input. Somewhere earlier, a human picks a company. Three places that happens in practice, each with different consequences for your sync:
- A signup form on your own site. Attach the widget to the KVK field: the visitor searches by name and you receive a number that was chosen, not retyped.
- Your CRM. The number was validated at lead capture, so the Exact sync is only a copy step. This is the calmest variant, because the duplicate check already happened earlier in the chain.
- An import file from an accountant or an old system. Here normalisation is step zero, not a nicety: that column definitely contains text, spaces and empty cells.
If all you have is a company name, add a search step with GET /v1/search?... first. It returns candidates with their KVK numbers and a human picks one. Auto-selecting the first hit for a name like “Van der Berg Holding” reliably produces the wrong account, and a wrong account in your bookkeeping only surfaces at the first invoice.
What actually breaks in production
Rotating refresh tokens. Exact uses short-lived access tokens and a refresh token that is replaced on every use. If two processes refresh at the same time, one wins and the other is logged out permanently. Store the tokens centrally, refresh under a lock, and persist the new refresh token before you use it. This is the single most common reason an Exact integration is dead on Monday morning.
KVK numbers without their leading zero. Older KVK numbers start with a zero. Run the data through Excel or a numeric column and 01234567 becomes 1234567, and your OData filter matches nothing. Always left-pad to eight digits before searching or writing.
Historically polluted ChamberOfCommerce values. Because Exact does not validate the field, it contains everything: numbers with dots, with spaces, prefixed with “KVK”, and sometimes a 12-digit branch number. Your duplicate check with an exact filter misses those records and creates a second relation. Normalise first: strip to digits, left-pad to eight, and clean up the existing mess in a one-off run.
Rate limits on both sides during an import. Exact meters per minute and per day and reports it in the X-RateLimit headers on every response. Our side has its own per-minute limit. Importing thousands of relations, you hit whichever is slower. Read the Exact headers instead of retrying blindly, and use POST /v1/lookup/batch on our side so the lookups are not the bottleneck.
A company that no longer exists. On NOT_FOUND the registration has been struck off. Do not create a relation, and do not auto-delete an existing one either: invoices and journal entries hang off it. Flag it in your own system and let a human decide.
Frequently asked
Do you have an Exact Online connector?
Which field holds the KVK number?
Do I need a separate integration per administration?
Should the statutory name be the account name?
Updated:
One call gives you the whole company
KVK data, the derived VAT number and a live VIES check, in a single request. The free plan covers 50 lookups a month and needs no card.
- 50
- free lookups a month
- 1
- request instead of three
- 0
- cards, contracts or sales calls