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.
API-koppelvlak — algemene introductie tot de OpenWoo-API, authenticatie en datum-driven zichtbaarheid.
_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
publicationsis de slug van de catalog, geen vaste routenaam. Op een deployment zonder een catalog met deze slug krijg jeHTTP 404 — Catalog not found. Opopenwoo.commonground.nuis deze catalog standaard aanwezig.
Scope wordt bepaald door de catalog-configuratie. Endpoint 1 doorzoekt de schemas die in de catalog zijn geconfigureerd (
registers+schemasop het catalog-object). Standaard bevat een verse catalog alleen hetpublication-schema, dus krijg je alleen publicaties terug — passend bij het "publicatie-scoped"-karakter van dit endpoint. Voegt een beheerder ookdocument(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.schemaverschilt 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:
| Parameter | Effect |
|---|---|
| geen parameter | Standaard: 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
publicationDatein het verleden ligt endepublicationDatein 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.
/api/searchHet 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 resultatenMeerdere 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.
Zoeken in bestandsinhoud (content-search)
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 (
publicationDatein het verleden, geendepublicationDateof éé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
tsvectorGIN-index (metts_rank-scoring). Op MariaDB werkt de wire ook maar zonder ranking — eenLIKE-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
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 intypt | Wat er gebeurt |
|---|---|
_search=verzoek | Matcht "verzoek", "verzoeken", "Woo-verzoek", "aanvraagverzoeken" — substring op title/summary/description en overige tekst-velden |
_search=verzoek vergunning | Wordt als één string behandeld, niet als "beide woorden" |
_search="verzoek besluit" | Quotes zijn onderdeel van de match — geen phrase-operator |
_search=verzoek OR klacht | OR 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 —
verzoekmatchtVerzoek,VERZOEK. - Substring-match —
_search=erzomatchtverzoek,Woo-verzoek. - Combineerbaar met filters —
?_search=verzoek&publicationDate[gte]=2026-01-01&_limit=20&_order[publicationDate]=descwerkt 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.
Fuzzy 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.nuheeft dit aan staan). - Vergelijkt alleen op het naamveld — typos in
titel,samenvattingofbeschrijvingprofiteren 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
| Symptoom | Oorzaak / oplossing |
|---|---|
_search=verzoek vergunning geeft minder hits dan verwacht | Wordt 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 terug | Standaard 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-resultaten | Documenten hebben een geldige publication-verwijzing met id én slug nodig om in de envelope te verschijnen. |
_search=café matcht niet cafe | Diacritics-normalisatie is deployment-afhankelijk. Strip diacritics client-side voor consistent gedrag. |
Meervouden — verzoek vs verzoeken | Geen stemming, maar substring helpt: _search=verzoek matcht ook verzoeken. |
_search="verzoek besluit" doet niets bijzonders | Quotes zijn geen phrase-delimiter. Strip ze client-side. |
| Volgorde lijkt willekeurig op pagina 2 | Zonder 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.