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 $select die meerdere velden combineert.
  • Het veld Attachment is bij een GET altijd leeg, zowel in de collectie als in een enkelvoudige GET per bijlage. Alle geprobeerde documenten gaven EXACT_ATTACHMENTS_WITHOUT_CONTENT zolang getDocumentFiles nog op Attachment las. Dit is geen envelopprobleem en geen “groot veld breekt de collectie-query” zoals bij DocumentViewUrl (zie het experiment hieronder): het veld komt gewoon altijd leeg terug.
  • Exacts eigen documentatie voor documents/DocumentAttachments bevestigt waarom: Attachment is Edm.Binary en verplicht bij een POST. 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=1 to the url.” Er is ook FileSize (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:

  1. Collectie-query op documents/DocumentAttachments met $filter: Document eq guid'<documentId>' en $select: 'ID,FileName,FileSize,Url'.
  2. Voor elke gevonden rij met een Url: download de inhoud via Url plus &Download=1 (of ?Download=1 als de url nog geen querystring heeft), via exactHttpClient met responseType: 'arraybuffer', en zet de bytes om naar base64.
  3. Nul bijlagerijen blijft een lege lijst (document zonder bijlagen, geen fout). Een rij zonder Url wordt overgeslagen; als geen enkele rij een Url had, gooit dit pad EXACT_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-type text/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 van FileSize. FileSize is een Edm.Double; de marge vangt afrondingsverschillen in Exacts eigen serialisatie op, niet een leeg of half binnengekomen bestand (zie het commentaar bij ATTACHMENT_SIZE_TOLERANCE_BYTES in exactService.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.

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.