Ga naar hoofdinhoud

Full-text search

De OpenWoo-API biedt twee endpoints voor tekstueel zoeken. Welke je gebruikt hangt af van wat je wilt terugvinden:

  • Endpoint 1 doorzoekt alleen publicaties (op titel, samenvatting, thema en andere publicatie-velden).
  • Endpoint 2 doorzoekt publicaties én documenten in één resultatenset — geschikt voor een centrale zoekbalk.

Beide endpoints respecteren dezelfde toegangs- en zichtbaarheidsregels: gebruikers krijgen alleen resultaten waar ze op basis van hun rol en de status van de publicatie recht op hebben.

Eerst lezen

API-koppelvlak — algemene introductie tot de OpenWoo-API, authenticatie en datum-driven zichtbaarheid.

Wat je in gedachten moet houden

_search doet een letterlijke substring-match. Er zijn geen booleaanse operatoren (AND/OR), geen wildcards (*), geen phrase-quotes, geen fuzzy-tilde (~) en geen relevantie-boosts (^n). Voor typo-tolerantie is er een aparte parameter — zie Fuzzy search hieronder.

Endpoint 1 — Zoeken binnen publicaties​

GET https://openwoo.commonground.nu/apps/opencatalogi/api/publications?_search=<query>

Doorzoekt alle publicaties in de WOO-catalogus waar de gebruiker toegang toe heeft. Matcht op alle tekst-velden van het publicatie-schema (title, summary, description, themes, …) plus de algemene metadata-velden.

Een publicatie telt als hit zodra één van deze velden de zoekterm bevat. De sortering volgt _order[<veld>] als je die opgeeft; zonder expliciete sortering is de volgorde niet gegarandeerd.

Deze variant is ideaal wanneer je resultaten wilt binnen één catalogus-context, bijvoorbeeld voor de publicatie-overzichtspagina van een organisatie.

Let op: het pad-segment publications is de slug van de catalog, geen vaste routenaam. Op een deployment zonder een catalog met deze slug krijg je HTTP 404 — Catalog not found. Op openwoo.commonground.nu is deze catalog standaard aanwezig.

Scope wordt bepaald door de catalog-configuratie. Endpoint 1 doorzoekt de schemas die in de catalog zijn geconfigureerd (registers + schemas op het catalog-object). Standaard bevat een verse catalog alleen het publication-schema, dus krijg je alleen publicaties terug — passend bij het "publicatie-scoped"-karakter van dit endpoint. Voegt een beheerder ook document (of andere schemas) toe aan de catalog, dan verschijnen die object-types hier ook. Wil je bewust een mixed envelope met documenten? Gebruik Endpoint 2 — dat endpoint negeert de catalog-scope en zoekt altijd over publicaties én documenten in één antwoord.

Endpoint 2 — Brede zoekopdracht over publicaties én documenten​

GET https://openwoo.commonground.nu/apps/opencatalogi/api/search?_search=<query>

Doorzoekt publicaties en documenten die daaraan hangen. Het resultaat is een gemengde lijst waarin beide soorten objecten samen voorkomen; het veld @self.schema geeft per rij aan wat het is — een publicatie of een document.

Elk document dat als hit terugkomt draagt een verwijzing naar de bijbehorende publicatie mee:

{
"id": "…",
"title": "…",
"publication": { "id": "…", "slug": "…", "title": "…" },
"@self": { "schema": "document", "…": "…" }
}

Zo kan een zoekpagina één lijst tonen en per resultaat correct doorlinken naar de publicatie waar het document bijhoort. Documenten die geen geldige publication-verwijzing hebben (id + slug) verschijnen niet in de resultaten.

Wat wordt doorzocht: standaard de metadata van publicaties én documenten — dus titels, samenvattingen, bestandsnamen, MIME-types en overige tekst-velden op het schema. De inhoud van PDF- of DOCX-bestanden wordt optioneel meegenomen door _content=true aan de query toe te voegen — zie Zoeken in bestandsinhoud hieronder.

Vorm van @self.schema verschilt per endpoint: endpoint 2 geeft de slug ("publication" / "document"), endpoint 1 geeft het numerieke schema-ID als string ("15", "16"). Bouw je één card-renderer voor beide endpoints? Normaliseer dan aan de client-kant.

Catalogus-scope​

De scope van endpoint 2 wordt afgeleid uit het catalogus-model. Elke catalogus declareert welke registers en schemas hij ontsluit; endpoint 2 doorzoekt élk schema in élke catalogus die de caller mag zien — niet alleen publication en document, ook eventuele extensies zoals besluit, verzoek of dataset.

Twee query-parameters bepalen welk deel van dat scope wordt geraakt:

ParameterEffect
geen parameterStandaard: unie van álle catalogi met listed: true én published in het verleden
_catalog=<slug>Beperk tot één catalogus (single slug)
_catalogi[]=<slug>&_catalogi[]=<slug>Beperk tot een unie van meerdere catalogi (met dedup)
GET https://openwoo.commonground.nu/apps/opencatalogi/api/search
?_search=verzoek
&_catalog=gemeente-nijmegen
GET https://openwoo.commonground.nu/apps/opencatalogi/api/search
?_search=verzoek
&_catalogi[]=gemeente-nijmegen
&_catalogi[]=gemeente-arnhem

Onbekende slugs leveren HTTP 200 met "total": 0 op — geen 404, zodat clients die de UI-lijst dynamisch samenstellen niet hoeven te branchen op error-shape. Een _catalog die naar een ongepubliceerde catalogus wijst gedraagt zich hetzelfde als een onbekende slug — vanuit de caller niet te onderscheiden van een niet-bestaande slug.

Scope kan NIET worden verbreed door de client. Parameters die scope zouden oprekken — _schema, _registers, fq — worden aan de server-side gestript en genegeerd. Zichtbaarheid wordt in SQL afgedwongen door de RBAC-regels op elk schema.

Zichtbaarheid — anoniem versus ingelogd​

Zichtbaarheid wordt bepaald door de authorization-regels op elk schema in OpenRegister: de read-regels leggen per rol vast wat zichtbaar is, en authorization.inheritFromPublic bepaalt of ingelogde gebruikers de publieke regels erven. Een client kan dit niet via query-parameters beïnvloeden.

  • Anoniem — alleen publicaties en documenten waarvan publicationDate in het verleden ligt en depublicationDate in de toekomst ligt of ontbreekt.
  • Ingelogd — op endpoint 2 op dit moment óók eigen concepten en objecten waarop de rol via eigenaarschap of admin-rechten toegang heeft; dus mogelijk meer dan anoniem voor dezelfde query.
Ingelogde callers zien tijdelijk meer op /api/search

Het beoogde gedrag van endpoint 2 is uniforme zichtbaarheid: hetzelfde antwoord met of zonder sessie. De schema-regels bieden nu geen manier om één aanroep anoniem te laten evalueren, dus dat wordt nog niet afgedwongen. Er komt een query-parameter _forceAnonymous=true waarmee dat wél kan; endpoint 2 zal die intern altijd meesturen. Die parameter bestaat nog niet — gebruik hem niet in je integratie. Deze pagina wordt bijgewerkt zodra hij live is.

Praktisch advies: bouw je een publieke zoekpagina, test dan met een niet-ingelogde sessie — dat is de definitieve resultatenset.

total telt hoger dan het aantal resultaten

Meerdere treffers op hetzelfde object — bijvoorbeeld een titel-match plus meerdere tekstfragmenten uit een bijlage — worden los geteld in total, maar één keer getoond in results. Gebruik total voorlopig niet als exact aantal. Dit wordt verholpen; deze pagina wordt daarbij bijgewerkt.

Wil je expliciet concepten of gedepubliceerde items zien als beheerder? Gebruik daarvoor endpoint 1 (/api/publications — honoreert sessie-rechten expliciet en blijft dat gedrag houden) of de OpenRegister-object-API direct.

Endpoint 2 kan optioneel ook zoeken in de inhoud van bijgehangen documenten — de tekst uit PDF-, DOCX-, XLSX- en andere ondersteunde bestandsformaten. Dit is een opt-in via _content=true:

GET https://openwoo.commonground.nu/apps/opencatalogi/api/search?_search=<query>&_content=true

Zonder _content=true blijft het gedrag ongewijzigd (metadata-only). Met de flag worden documenten waarvan de body-tekst matcht toegevoegd aan het resultaat — dezelfde platte envelope, dezelfde @self.schema-discriminator, geen extra response-velden.

Hoe het werkt: OpenRegister extraheert de tekst uit elke document-upload via zijn eigen text-extractie-pipeline en indexeert de resulterende chunks. Endpoint 2 forward _content=true als _content_search=true naar OpenRegister; de matchende chunks worden terug-gemapt naar het bijbehorende document-object. OpenCatalogi doet zelf geen extractie of indexering.

Gedrag:

  • Dedup — een document dat zowel op metadata (titel, samenvatting) als op body-tekst matcht verschijnt éénmalig in de resultaten.
  • Zichtbaarheid — dezelfde zichtbaarheidsregel als de metadata-only variant: een document verschijnt alleen als de gelinkte publicatie op dit moment gepubliceerd is (publicationDate in het verleden, geen depublicationDate of één die nog in de toekomst ligt).
  • Extractie loopt standaard asynchroon — dat is ook de aanbevolen instelling. Vlak na upload kan een document daardoor nog niet doorzoekbaar zijn omdat de indexeer-job nog niet gedraaid heeft; retry na ~5 minuten. Draait een omgeving synchroon — verplicht op Nextcloud 33 en ouder, zie Beheer — extractie aanzetten — dan is die wachttijd er niet, maar duurt de upload zelf langer.
  • Ranking database-afhankelijk — content-search draait op OR's PostgreSQL tsvector GIN-index (met ts_rank-scoring). Op MariaDB werkt de wire ook maar zonder ranking — een LIKE-fallback levert dezelfde matches, alleen ongesorteerd.

Voorbeeld:

GET https://openwoo.commonground.nu/apps/opencatalogi/api/search
?_search=verzoek
&_content=true
&_limit=10

Retourneert publicaties én documenten waarvan óf metadata óf body-tekst "verzoek" bevat.

Beheer — extractie aanzetten en bestaande bestanden bijwerken​

Zoeken in bestandsinhoud werkt alleen als OpenRegister de tekst uit de bijlagen daadwerkelijk heeft geëxtraheerd.

De standaard en de aanbeveling is Background Job: de extractie draait dan asynchroon, buiten het upload-verzoek om. Op Nextcloud 34 en nieuwer kun je die instelling gewoon laten staan.

Uitzondering voor Nextcloud 33 en ouder. Daar draaien Background Job en Cron Job de extractie in een achtergrondtaak zonder ingelogde gebruiker, waardoor het bestand niet gevonden wordt en er stilzwijgend niets geëxtraheerd wordt. Zet op die omgevingen — waaronder op dit moment openwoo.commonground.nu — Instellingen → Beheer → Open Register → Text Extraction → Extraction Mode op Immediate. De extractie draait dan binnen het upload-verzoek zelf, wat bij grote bestanden een tragere upload geeft. Op Nextcloud 34 en nieuwer geldt de uitzondering niet en volstaat Background Job.

Bestaande bestanden bijwerken. De extractie wordt alleen aangeroepen bij het aanmaken of wijzigen van een bestand. Bijlagen die al bestonden voordat de instelling goed stond, worden dus niet met terugwerkende kracht opgepakt. Draai daarvoor eenmalig, als beheerder:

POST https://openwoo.commonground.nu/apps/openregister/api/files/extract?limit=5000

Het antwoord bevat processed, failed en total. Herhaal de aanroep tot processed op 0 staat — dan zijn alle bereikbare bestanden verwerkt. Een enkel bestand bijwerken kan ook:

POST https://openwoo.commonground.nu/apps/openregister/api/files/{fileId}/extract?forceReExtract=true
Versies op de WOO-omgevingen

De _content-parameter zelf bestaat al sinds OpenCatalogi 1.0.9. Wat de WOO-omgevingen daarnaast nodig hadden is de hotfix 1.0.9-woo-2: zonder de catalogus-scope-correctie daarin gaf /api/search op deze meervoudige register-inrichting stilzwijgend nul resultaten, ongeacht _content. Alleen OpenCatalogi heeft die hotfix nodig; OpenRegister draait de reguliere 1.1.5.

Query-vorm & gedrag​

De volgende regels gelden voor beide endpoints:

Wat je intyptWat er gebeurt
_search=verzoekMatcht "verzoek", "verzoeken", "Woo-verzoek", "aanvraagverzoeken" — substring op title/summary/description en overige tekst-velden
_search=verzoek vergunningWordt als één string behandeld, niet als "beide woorden"
_search="verzoek besluit"Quotes zijn onderdeel van de match — geen phrase-operator
_search=verzoek OR klachtOR is gewone tekst, geen operator
_search=verzo** is gewone tekst; zonder * matcht al "verzoek", "verzoeken", "Woo-verzoek"
_search=verzoek~~ is gewone tekst, geen fuzzy-operator

Wat wél klopt:

  • Case-insensitive — verzoek matcht Verzoek, VERZOEK.
  • Substring-match — _search=erzo matcht verzoek, Woo-verzoek.
  • Combineerbaar met filters — ?_search=verzoek&publicationDate[gte]=2026-01-01&_limit=20&_order[publicationDate]=desc werkt zoals verwacht.

Praktische tips voor consumenten:

  • Wil de gebruiker "beide woorden" matchen? Splits de query client-side of laat de UI meerdere zoektermen aanbieden — server-side ondersteunt dit niet.
  • Voor "lijkt op"-zoeken (typo-tolerantie): zie Fuzzy search.
  • Voor filtering op categorie of datum: gebruik echte query-parameters (@self[schema]=<id>, publicationDate[gte]=…) náást _search.

Voor typo-tolerantie is er een aparte parameter _fuzzy=true:

GET https://openwoo.commonground.nu/apps/opencatalogi/api/publications?_search=Demonstartie&_fuzzy=true

Voegt een trigram-similariteit toe op het naamveld van elk object. Een rij komt terug als óf de gewone substring-match slaagt óf de naam voldoende lijkt op de zoekterm. Elke hit krijgt een @self.relevance-veld (geheel getal 0–100) — de score is de trigram-similariteit tussen zoekterm en het naamveld, dus zelfs een exacte substring-match kan een lagere score krijgen wanneer de zoekterm maar een klein deel van de volledige naam beslaat. Bij een actieve _search wordt standaard al op relevance aflopend gesorteerd; wil je expliciet forceren of omdraaien: _order[_relevance]=desc of _order[_relevance]=asc.

Beperkingen:

  • Werkt alleen op deployments met PostgreSQL en de pg_trgm-extensie ingeschakeld (openwoo.commonground.nu heeft dit aan staan).
  • Vergelijkt alleen op het naamveld — typos in titel, samenvatting of beschrijving profiteren niet.

Concrete voorbeelden​

Zoekbalk met paginatie​

GET https://openwoo.commonground.nu/apps/opencatalogi/api/publications
?_search=verzoek
&_order[publicationDate]=desc
&_limit=10
&_page=1

Zoekbalk met datumfilter​

GET https://openwoo.commonground.nu/apps/opencatalogi/api/publications
?_search=verzoek
&publicationDate[gte]=2026-01-01
&publicationDate[lte]=2026-12-31
&_limit=20

Zoeken binnen één informatiecategorie​

GET https://openwoo.commonground.nu/apps/opencatalogi/api/publications
?_search=convenant
&@self[schema]=<schema-id>
&_limit=10

@self[schema] filtert op één schema en verwacht het numerieke schema-ID (geen slug). Het ID is omgevings-specifiek — vraag op via een facet-call op je eigen omgeving:

GET https://openwoo.commonground.nu/apps/opencatalogi/api/publications
?_facetable=true
&_facets[@self][schema][type]=terms
&_limit=0

De buckets in het facets-blok geven per voorkomend schema-ID de count.

Centrale zoekbalk (publicaties + documenten)​

GET https://openwoo.commonground.nu/apps/opencatalogi/api/search
?_search=verzoek
&_limit=10

Retourneert gemengde resultaten. Onderscheid maken tussen publicaties en documenten kan via het @self.schema-veld op elke rij.

Type-ahead met lichte payload​

GET https://openwoo.commonground.nu/apps/opencatalogi/api/publications
?_search=verzo
&_limit=5
&_unset=attachments,beschrijving,bevindingen,conclusies

_unset laat de opgesomde velden weg uit elke resultaat-rij — handig om response-grootte klein te houden voor real-time suggesties.

Typo-tolerant zoeken​

GET https://openwoo.commonground.nu/apps/opencatalogi/api/publications
?_search=Demonstartie
&_fuzzy=true
&_order[_relevance]=desc
&_limit=10

Faceted-search UI​

GET https://openwoo.commonground.nu/apps/opencatalogi/api/publications
?_search=verzoek
&_facetable=true
&_facets[@self][schema][type]=terms
&_facets[publicationDate][type]=date_histogram
&_facets[publicationDate][interval]=year
&_limit=10

Response bevat een facets-blok met buckets per veld, geschikt voor filter-checkboxes met counts.

Gotchas​

SymptoomOorzaak / oplossing
_search=verzoek vergunning geeft minder hits dan verwachtWordt als één substring behandeld, niet als twee termen. Splits client-side of laat de UI losse velden aanbieden.
_search=WOZ matcht ook losse 'w', 'o', 'z'Substring-match is letterlijk; korte termen produceren veel false positives. Eis minimaal 3 karakters in de UI.
Inhoud van een PDF-bijlage komt niet terugStandaard wordt alleen metadata (bestandsnaam, MIME) doorzocht. Voeg _content=true toe aan de query om ook body-tekst mee te nemen — zie Zoeken in bestandsinhoud. Werkt de flag maar krijg je nog steeds niks? De OR-extractie loopt asynchroon; retry na ~5 min. Blijft het leeg, dan is de tekst waarschijnlijk nooit geëxtraheerd — zie Beheer — extractie aanzetten.
Document verschijnt niet in /api/search-resultatenDocumenten hebben een geldige publication-verwijzing met id én slug nodig om in de envelope te verschijnen.
_search=café matcht niet cafeDiacritics-normalisatie is deployment-afhankelijk. Strip diacritics client-side voor consistent gedrag.
Meervouden — verzoek vs verzoekenGeen stemming, maar substring helpt: _search=verzoek matcht ook verzoeken.
_search="verzoek besluit" doet niets bijzondersQuotes zijn geen phrase-delimiter. Strip ze client-side.
Volgorde lijkt willekeurig op pagina 2Zonder expliciete sortering is de volgorde niet gegarandeerd. Voeg altijd &_order[<veld>]=… toe.

Schrijfacties (POST / PUT / DELETE)​

Anonieme toegang geldt alleen voor lezen. Voor schrijfacties (bijvoorbeeld publiceren namens een organisatie) is standaard Nextcloud-authenticatie nodig — Basic-auth, OAuth of een app-token. Neem contact op met info@conduction.nl voor productie-toegang.

OpenAPI​

De volledige API-specificatie leeft onder /api/publications/ en /api/. Zie API-overzicht voor de sync-details.

Referentie-implementaties​

  • woo-website-template-apiv2 — de publieke WOO-publicatiepagina; gebruikt beide endpoints met een faceted-search UI.
  • api-koppelvlak — generiek koppelvlak-overzicht inclusief metadata-schema's, datum-driven zichtbaarheid en de architectuur achter de API-lagen.