GraphQL API
The GraphQL API is intended for software developers, who may want to integrate it in Collection Management Systems, Service Platforms and other software.
If you simply want to search for terms, you can use the demonstrator.
Endpoint
The GraphQL API is available at:
https://termennetwerk-api.netwerkdigitaalerfgoed.nl/graphql
For a web interface to try out queries, you can use https://termennetwerk-api.netwerkdigitaalerfgoed.nl.
TODO: https://github.com/netwerk-digitaal-erfgoed/network-of-terms-tutorial/wiki/GraphQL-API
List terminology sources
For a list of terminology sources that can be consulted through the Network of Terms, use this query:
query Sources {
sources {
uri
name
alternateName
description
inLanguage
mainEntityOfPage
creators {
uri
name
alternateName
}
genres {
uri
name
}
features {
type
url
}
status {
isAvailable
lastChecked
}
}
}
Filter sources by subject
The sources query accepts an optional genres parameter to filter sources by subject.
Genre URIs can be discovered from the genres field on each source (shown in the query above).
query Sources {
sources(genres: ["https://data.cultureelerfgoed.nl/termennetwerk/onderwerpen/Periodes"]) {
uri
name
genres {
uri
name
}
}
}
Source language
Terminology source information (name, description, genre and creator names, and the
sort order of the result) is returned in the language requested via the Accept-Language
HTTP header. Currently honored values are nl and en; the default and fallback is nl.
Accept-Language: en
See Language selection for the full picture of how language is
negotiated across the API, including the languages parameter that controls term labels.
Search terms
Query a single source
To query a single terminology source, select a source from the list of sources
and use its URI in the sources query parameter. For query, enter any literal search string that you want to find terms for.
query {
terms(
sources: ["https://data.cultureelerfgoed.nl/term/id/cht"],
query: "fiets"
) {
result {
__typename
... on Terms {
terms {
uri
prefLabel
altLabel
hiddenLabel
definition
scopeNote
seeAlso
broader {
uri
prefLabel
}
narrower {
uri
prefLabel
}
related {
uri
prefLabel
}
exactMatch {
uri
prefLabel
}
}
}
... on Error {
message
}
}
responseTimeMs
}
}
The result element is either:
- a list of terms found (
... on Terms), which can be empty if no terms were found - or an error message (
... on Error). Errors include timeouts.
Search result
The list of terms is sorted by relevance, if supported by the terminology source, or simply alphabetically if not. Each result contains the terminology source and the terms found in it.
Source
A terminology source object has:
- a URI that identifies the source
namedescriptionthat explains in a single sentence the intended use of the sourceinLanguagethat shows in which languages the source is available, usuallynland/orenalternateName: an optional, well-known short name for display purposescreators: the publisher or the source, with its ownuri,nameandalternateName(short name)genres: subjects that the source contains information aboutfeatures: capabilities supported by the source (e.g.GENRE_FILTER)mainEntityOfPage: a website that provides more information about the sourcestatus: availability of the source’s endpoint, with sub-fieldsisAvailable(boolean) andlastChecked(ISO 8601 timestamp).
Terms
A term object has:
- a URI that identifies the term
prefLabelthat is the main name of the termaltLabels andhiddenLabels that are alternative namesdefinitionthat defines the termscopeNotethat provides additional informationseeAlsothat links to a web page for humans about the termbroader,narrower, andrelatedterms that are related to the termexactMatches that link to terms in other terminology sources.
In collection management systems, the term’s URI is used to link data to the term.
Filter by subject
Some terminology sources cover multiple subjects. You can narrow search results to specific subjects
using the genres parameter with a list of genre URIs.
Genre URIs can be discovered from the genres field when listing sources.
Genre filtering is only available for sources that declare the GENRE_FILTER feature,
which is visible via the features field on each source.
query {
terms(
sources: ["https://data.cultureelerfgoed.nl/term/id/cht"],
query: "steen",
genres: ["https://data.cultureelerfgoed.nl/termennetwerk/onderwerpen/Periodes"]
) {
source {
uri
name
}
result {
__typename
... on Terms {
terms {
uri
prefLabel
altLabel
definition
scopeNote
}
}
... on Error {
message
}
}
}
}
Sources that currently support genre filtering include the Art & Architecture Thesaurus (AAT), Cultuurhistorische Thesaurus (CHT), and Wikidata.
Query multiple sources
Querying multiple sources is very similar to querying a single source, but now providing multiple URIs in the sources query parameter:
query {
terms(
sources: [
"https://data.rkd.nl/rkdartists",
"http://data.bibliotheken.nl/id/dataset/persons"
],
query: "Gogh"
) {
source {
uri
name
creators {
uri
name
alternateName
}
status {
isAvailable
lastChecked
}
}
result {
__typename
... on Terms {
terms {
uri
prefLabel
altLabel
hiddenLabel
definition
scopeNote
seeAlso
}
}
... on Error {
message
}
}
responseTimeMs
}
}
The results are grouped by terminology source. Each source returns either a list of terms or an error.
Look up terms by URI
Use the lookup query to look up terms whose URIs you know (for example, because you have stored the URIs previously).
You can look up multiple URIs at once, by providing them in the uris query parameter.
query {
lookup(
uris: ["https://data.rkd.nl/artists/32439", "https://data.cultureelerfgoed.nl/term/id/cht/15e29ea3-1b4b-4fb2-b970-a0c485330384"],
) {
uri
source {
... on Source {
uri
name
creators {
uri
name
alternateName
}
}
... on Error {
__typename
message
}
}
result {
... on Term {
uri
prefLabel
altLabel
hiddenLabel
definition
scopeNote
seeAlso
broader {
uri
prefLabel
}
exactMatch {
uri
prefLabel
}
}
... on Error {
__typename
message
}
}
responseTimeMs
}
}
Each URI is routed to the source whose url prefix it starts with. Sources declare one or more URL prefixes in the catalog, and the Network of Terms picks the matching source for every URI in the request. If no source claims the prefix, the source field returns an Error for that URI.
Discover reconciliation endpoints
Sources that offer a Reconciliation API advertise it via the features field. Each feature has a type and a url; the entry with type: RECONCILIATION carries the endpoint URL to configure in OpenRefine.
query {
sources {
uri
name
features {
type
url
}
}
}
Filter client-side for entries where type equals RECONCILIATION. Sources that publish their own reconciliation service (such as Wikidata) are not re-exposed here – see Reconciliation API for details.
Language selection
The Network of Terms uses two language controls:
Accept-Language– for single-language fields: source information (names, descriptions, genre and creator names) in this API, and all responses from the Reconciliation API. Served from our local catalog, translated to a fixed set (nlanden).languagesGraphQL argument – for multilingual term labels (prefLabel,altLabel,definition, etc.), returned side by side as{ language, value }pairs. Fetched in real time from remote sources, so available languages vary per source – see Discovering supported languages.
Two axes of language control
Accept-Language HTTP header | languages GraphQL argument | |
|---|---|---|
| Used for | Catalog metadata (sources, genres, creators) | Term labels (prefLabel, altLabel, etc.) |
| Input shape | Ranked list with quality values, per RFC 9110 (e.g. en, nl;q=0.8) | Ordered list of preferred languages |
| Output shape | One String per field — the server selects a single best match | Each term-label field is a list of { language, value } pairs on TranslatedTerms |
| Unknown language | Silently falls back to nl | Raises a GraphQL error (closed enum) |
| Multilingual output? | No — one chosen language wins | Yes — every requested language with available data is returned |
Which fields are affected
| Field | Controlled by |
|---|---|
All localized fields on Source returned by the top-level sources query | Accept-Language |
prefLabel, altLabel, hiddenLabel, definition, scopeNote on terms | languages |
Embedded Source inside a terms or lookup query result | First entry of languages, else Accept-Language |
Inside a terms or lookup operation, the languages argument doubles as a hint for
which catalog translation to pick for the embedded Source. The top-level sources
query has no operation-level language preference and uses Accept-Language directly.
The Accept-Language header
Accept-Language is the standard HTTP content-negotiation header: clients send a ranked
list of preferences and the server picks the best match. The Network of Terms currently
matches against nl and en; the default and fallback when nothing matches is nl.
Tags that don’t intersect with the honored set fall through to nl.
Accept-Language: en, nl;q=0.8
Catalog metadata is only translated into nl and en, even when the catalog has
term-level data in other languages. For example, fy (Frisian) is a valid value for
the languages GraphQL argument and returns Frisian
term labels where sources provide them, but Accept-Language: fy still resolves source
names, descriptions, genre names and creator names to nl.
The languages GraphQL argument
languages is a GraphQL argument accepted by the terms and lookup queries. It
takes a list of values from the Language enum, in order of preference.
Passing languages changes the result type. Without it, term results come back as
Terms with each label field as a plain [String!]. With it, results come back as
TranslatedTerms and each label field is a
list of { language, value } pairs covering every requested language that the source
provides data for. This is the only way to get a multilingual response from the API.
Untagged literals are treated as nl. Passing a value that is not part of the enum
(for example fr when the catalog has no French data) raises a GraphQL parse error.
This is by design: the enum is the API’s declarative statement of which languages it
currently offers.
Discovering supported languages
The Language enum is the authoritative list of supported languages. It is derived
from the catalog at server startup, so the schema grows automatically as new languages
are added. Clients can discover the current set via GraphQL introspection:
query SupportedLanguages {
__type(name: "Language") {
enumValues { name }
}
}
The recommended pattern for forward-compatible clients:
- Fetch the supported set via introspection (once, at startup or at build time via codegen).
- Intersect it with the languages your application supports.
- Send only that intersection in the
languagesargument.
When the Network of Terms adds a new language to its catalog, a fresh introspection picks it up without any change to the API contract on the client side.
Multilingual results: TranslatedTerms
Passing languages is what turns the API multilingual. Both
search and lookup queries switch to
TranslatedTerms as soon as the argument is present, exposing each label as a
{ language, value } pair so translations can be rendered side by side. Without
languages, you get plain Terms with [String!] labels in a single language.
query {
terms(
sources: [
"https://data.rkd.nl/rkdartists",
"http://data.bibliotheken.nl/id/dataset/persons"
],
query: "Gogh",
languages: [en, nl]
) {
source {
uri
name
}
result {
__typename
... on TranslatedTerms {
terms {
uri
prefLabel { language value }
altLabel { language value }
hiddenLabel { language value }
definition { language value }
scopeNote { language value }
seeAlso
}
}
... on Error {
message
}
}
responseTimeMs
}
}
Not all terminology sources are available in all languages. Inspect the inLanguage
field on each source (see List terminology sources) to
see which languages it provides.
If you omit languages, the result type is Terms instead and label fields are
returned as plain [String!]. Their values come from whichever language the source
provides, with untagged literals treated as nl.
Per-term label fallback
The languages parameter filters labels per term – it does not exclude terms from the
results. If a term has no labels in any of the preferred languages, it is still
returned, but its label fields (prefLabel, altLabel, hiddenLabel, definition,
scopeNote) fall back as follows:
- If the source provides untagged literals for the field, they are returned and treated
as Dutch (
nl). - Otherwise, the field is returned as an empty list.
The languages you can actually get back depend on what each source provides: see the
inLanguage field when listing sources.
Response times
Response times from the Network of Terms will vary depending on how fast each terminology source returns results for the query.
Use the responseTimeMs response property to inspect the response time (in ms) for each source.
query {
terms(
sources: [...],
query: "..."
) {
source { ... }
result { ... }
responseTimeMs
}
}
Timeouts
The default timeout is 10 seconds. You can raise this up till 60 seconds using the
timeoutMs query parameter. For example, to wait a maximum of 15 seconds for each terminology source to respond:
query {
terms(
sources: [...],
query: "...",
timeoutMs: 15000,
) {
...
}
}