Exact Documents-API: geverifieerd gedrag
Vastgelegd op 8 augustus 2026 tegen de Exact-sandbox (divisie 4354084, “Tapster BV”)
via apiv7.dev.tapster.nl, issue #3080. Alles hieronder is waargenomen gedrag, geen
documentatie-aanname.
Wat is geverifieerd
Schrijven werkt
POST documents/Documents gevolgd door POST documents/DocumentAttachments slaagt.
Een archiveeractie met twee bijlagen (een tekstbestand en een binair bestand van 3 MB)
leverde documentId plus attachmentCount: 2 op. De 3 MB-bijlage (4 MB als base64)
ging zonder problemen door exactHttpClient heen, ruim binnen de timeout van 30
seconden.
Enkelvoudige GET werkt, met deze veldwaarden
GET documents/Documents(guid'...')?$select=ID,Subject,Type,TypeDescription,Created,Modified,DocumentViewUrl
geeft alle zeven velden terug. Alle veldnamen in de selectie bestaan dus.
| Veld | Waargenomen waarde |
|---|---|
ID |
21ace651-074c-41e8-a11d-9f6513ef3e13 |
Subject |
de meegegeven titel |
Type |
51 |
TypeDescription |
E-Mail |
Created / Modified |
/Date(1786220486250)/, dus geen ISO-string |
DocumentViewUrl |
https://start.exactonline.nl/docs/DocViewContent.aspx?ID=<id>&_Division_=<divisie>&Stamp=NL001 |
De vastgelegde respons staat in
apps/api/src/common/services/exact-service/__tests__/fixtures/exact-document-single.response.json
en wordt afgedwongen door exactService.documentsFixture.test.ts.
Let op bij toekomstig gebruik: Created en Modified komen als /Date(ms)/
binnen. De backend geeft ze ongewijzigd door. Wie ze in de UI wil tonen moet ze eerst
parsen.
Wat niet werkt (openstaand)
De drie document-filters (Account, Contact, Opportunity) werken inmiddels wel,
zie “Experiment: DocumentViewUrl uit de collectie-$select” hieronder: de oorzaak was
niet het filter, maar een veld in de $select dat de collectie-query stil liet
mislukken.
| Query | Resultaat |
|---|---|
documents/DocumentAttachments?$filter=Document eq guid'...' met Attachment in de $select |
leeg, terwijl het document aantoonbaar bijlagen heeft. Verklaard: zie hieronder, Attachment is een schrijfveld |
Bijlagen-inhoud: Attachment is een schrijfveld, lezen gaat via Url (bevestigd)
Vastgelegd op 9 augustus 2026 tegen de Exact-sandbox, met de foutcodes uit
getDocumentFiles als meetinstrument.
Bewezen:
- De collectie-query op
documents/DocumentAttachments($filter: Document eq guid'<documentId>') werkt: er komen 1, 2 of 3 bijlagerijen terug per document, ook mét een$selectdie meerdere velden combineert. - Het veld
Attachmentis bij eenGETaltijd leeg, zowel in de collectie als in een enkelvoudige GET per bijlage. Alle geprobeerde documenten gavenEXACT_ATTACHMENTS_WITHOUT_CONTENTzolanggetDocumentFilesnog opAttachmentlas. Dit is geen envelopprobleem en geen “groot veld breekt de collectie-query” zoals bijDocumentViewUrl(zie het experiment hieronder): het veld komt gewoon altijd leeg terug. - Exacts eigen documentatie voor
documents/DocumentAttachmentsbevestigt waarom:AttachmentisEdm.Binaryen verplicht bij eenPOST. Het is dus een schrijfveld, geen leesveld. - Voor lezen bestaat een apart veld:
Url, gedocumenteerd als “Url of the attachment. To get the file in its original format (xml, jpg, pdf, etc.) append&Download=1to the url.” Er is ookFileSize(Edm.Double), gebruikt om de gedownloade omvang te verifiëren.
Gevolg: de eerdere tweetrapsaanpak (enkelvoudige GET per bijlage om
Attachment te lezen) was een doodlopend spoor en is verwijderd. getDocumentFiles
(apps/api/src/common/services/exact-service/exactService.ts) doet nu:
- Collectie-query op
documents/DocumentAttachmentsmet$filter: Document eq guid'<documentId>'en$select: 'ID,FileName,FileSize,Url'. - Voor elke gevonden rij met een
Url: download de inhoud viaUrlplus&Download=1(of?Download=1als de url nog geen querystring heeft), viaexactHttpClientmetresponseType: 'arraybuffer', en zet de bytes om naar base64. - Nul bijlagerijen blijft een lege lijst (document zonder bijlagen, geen fout).
Een rij zonder
Urlwordt overgeslagen; als geen enkele rij eenUrlhad, gooit dit padEXACT_ATTACHMENTS_WITHOUT_CONTENT(rijen aanwezig, niets bruikbaars) — dezelfde code en boodschap als voorheen (#3080-referentie).
De aanroepen in stap 2 gebeuren sequentieel, niet parallel: dit is N aanroepen per document, en Exacts gedrag onder parallelle belasting (rate limits) is nog niet gemeten. Dat meten is opgepakt in issue #3182; tot die tijd is sequentieel de simpelste, voorspelbare keuze.
Nog niet bewezen: werkt onze OAuth-bearer op de downloadUrl? Url wijst naar
start.exactonline.nl/docs/..., niet naar het API-pad (api/v1/...). Het is niet
zeker dat onze OAuth-bearer daar geldig is; het kan zijn dat dat pad een
sessie-cookie verwacht in plaats van (of naast) een Authorization-header. Als dat
zo is, komt er vermoedelijk een HTML-inlogpagina terug in plaats van een bestand.
Daarvoor zijn twee expliciete vangrails ingebouwd:
EXACT_ATTACHMENT_DOWNLOAD_NOT_A_FILE: de respons ziet eruit als HTML (content-typetext/html/application/xhtml+xml, of de body begint met<!doctype html/<html). Detail bevat de HTTP-status, de content-type, en (alleen als de body-signature zelf al HTML is, dus nooit puur op basis van de header) de eerste tekens van de respons.EXACT_ATTACHMENT_DOWNLOAD_SIZE_MISMATCH: de gedownloade bytecount wijkt meer dan een kleine marge af vanFileSize.FileSizeis eenEdm.Double; de marge vangt afrondingsverschillen in Exacts eigen serialisatie op, niet een leeg of half binnengekomen bestand (zie het commentaar bijATTACHMENT_SIZE_TOLERANCE_BYTESinexactService.ts).
Dit is pas te verifiëren na de volgende deploy naar development, tegen de echte
Exact-sandbox: of de bearer op de downloadUrl werkt, en of
EXACT_ATTACHMENT_DOWNLOAD_NOT_A_FILE daadwerkelijk nooit afgaat. Zolang dat niet
is bevestigd, is dit de bekende onzekerheid in deze aanpak.
Verificatie herhalen
De sessie komt van POST /exact/outlook/session/recover met een gekoppeld
Outlook-adres. Daarna zijn alle /outlook-plugin/*-endpoints met die JWT te bevragen.
Zie ook #3165 voor het beveiligingsvraagstuk rond dat endpoint.
Experiment: DocumentViewUrl uit de collectie-$select (bevestigd)
Op 9 augustus 2026 is DocumentViewUrl uit de $select van de collectie-query in
_queryDocuments (apps/api/src/common/services/exact-service/exactService.ts)
gehaald, als gericht experiment tegen de hypothese dat dat veld de collectie-query
stil liet mislukken. Diezelfde dag is dit tegen de echte Exact-sandbox bevestigd:
de documentenlijst vult zich. Op de anker-organisatie kwamen 25 documenten terug,
inclusief het document dat daarvoor onvindbaar was. DocumentViewUrl in de
$select was dus de oorzaak; dit is geen lopend experiment meer.
De enkelvoudige GET in _getDocumentById selecteert DocumentViewUrl nog gewoon en
blijft ongewijzigd. De collectie-query in _queryDocuments levert viewUrl: null
op zolang DocumentViewUrl niet per document apart wordt opgehaald via die
enkelvoudige GET; dat is een openstaande vervolgstap voor de deeplinks uit #3102.
DocumentViewUrl is geen bruikbare browserlink (bevestigd op productie)
Waargenomen op 19 augustus 2026 op een productie-tenant. De URL die Exact in
DocumentViewUrl teruggeeft,
https://start.exactonline.nl/docs/DocViewContent.aspx?ID=<document-id>&_Division_=<divisie>&Stamp=NL001
leidt in een ingelogde browser naar
https://start.exactonline.nl/docs/SysAccessDenied.aspx?Mode=0&_Division_=1&Stamp=NL001.
Dat ligt niet aan de divisie of aan de rechten van de gebruiker: de vorm is per
divisie identiek (vergelijk de sandbox-fixture hierboven, divisie 4354084, met
dezelfde Stamp=NL001), dus er valt niets aan de parameters te repareren. Ook
buiten Tapster is dit bekend gedrag; het advies daar is hetzelfde: ga via de
bijlagen, want een document in Exact Online is alleen metadata.
Twee URL’s die wél werken:
| Doel | URL |
|---|---|
| De documentkaart openen | https://start.exactonline.nl/docs/DocEdit.aspx?ID=<document-id>&_Division_=<divisie> |
| Het bestand zelf openen | het Url-veld van documents/DocumentAttachments (dezelfde DocViewContent.aspx, maar op het bijlage-id) |
De eerste is overgenomen uit de adresbalk van Exact zelf; de sessieparameter
_csx_ en Stamp die daar ook in stonden zijn weggelaten, net als bij de zeven
deeplinks in outlook-plugin-exact-links.js. Die link bouwt de plugin zelf uit
divisie + document-id, dus zonder aanroep op Exact. De tweede komt van
getDocumentFileUrl en zit achter GET /outlook-plugin/documents/:id/view-url.
Documenttype bij archiveren
Beide schrijfpaden kiezen nu hetzelfde type via getDefaultDocumentTypeId: het
documenttype van de tenant waarvan de omschrijving E-mail/Email is (51 in de
sandbox), met EXACT_DEFAULT_DOCUMENT_TYPE_ID als override. uploadDocument
(de mail of afspraak als pdf onder een nieuwe actie) zette daar tot 19 augustus
2026 een vast Type: 55 neer, dat Exact toont als “Miscellaneous” (“Diversen”).
Dezelfde mail kwam dus onder een ander type te staan, afhankelijk van de route
die de gebruiker koos.