Event Connectors

Categorization

TRCItemCategories — Types and Categories, the ontologies that back them, account overrides, and how translations are pulled in live

The trcItemCategories object on every TRCItem carries two distinct classification systems: Types and Categories. Both refer to entries in ontologies — dictionaries of permitted values. Items in the database store only the IDs; the human-readable labels and per-language translations are pulled in live from the ontology when the API serves a response.

This page explains both systems, where to fetch the dictionaries, how account-level overrides work, and the gotcha around cross-account reads.

Types vs Categories

TypesCategories
What it expressesWhat kind of thing this item isA specific attribute or property of the item
Examples (Event)"Concert", "Market", "Exhibition""Wheelchair accessible: yes", "Genre: classical"
Examples (Location/Venue)"Hotel", "Camping", "Congress centre""Number of rooms: 16", "Has Wi-Fi: true"
Storage shapetrcItemCategories.types[] with catidtrcItemCategories.categories[] with catid (and valueid for choice types)
Backing ontologyCategorizationOntology (one tree per entity type)PropertiesOntology (categories → properties → options)
Dictionary endpointGET /api/dictionary/categorizationOntologyGET /api/dictionary/propertiesOntology

Multiple types are allowed on a single item; one is usually marked isDefault: true to indicate the primary type.

{
  "trcItemCategories": {
    "types": [
      {
        "catid": "1.3.4",
        "isDefault": true,
        "categoryTranslations": [
          { "lang": "nl", "label": "Hotel" },
          { "lang": "en", "label": "Hotel" }
        ]
      }
    ],
    "categories": [
      {
        "catid": "3.14.1",
        "value": "true",
        "datatype": "yesno",
        "categoryTranslations": [
          { "lang": "nl", "label": "Rolstoeltoegankelijk" },
          { "lang": "en", "label": "Wheelchair accessible" }
        ]
      }
    ]
  }
}

Retrieving the dictionaries

Use the dictionary endpoints to discover which catids exist, what they mean, and which valueids are permitted for choice/multichoice categories.

PurposeEndpoint
Type ontology (for types[])GET /api/dictionary/categorizationOntology
Property/category ontology (for categories[])GET /api/dictionary/propertiesOntology
Update an account's category ontologyPUT /api/dictionary/propertiesOntology?accountId={id} (admin only)

Both GET endpoints accept an optional ?accountId={id} query parameter. If supplied, the account's overridden ontology is returned. If omitted, the response is the ontology for the current account (your own, derived from your auth token), falling back to the global base ontology when your account has not customised it.

# Type ontology for your own account
curl -H "Authorization: Bearer YOUR_API_TOKEN" \
  https://app.eventconnectors.nl/api/dictionary/categorizationOntology

# Type ontology for a specific account (used when reading items
# owned by another account/userorganisation)
curl -H "Authorization: Bearer YOUR_API_TOKEN" \
  "https://app.eventconnectors.nl/api/dictionary/categorizationOntology?accountId=ABC123"

# Property/category ontology
curl -H "Authorization: Bearer YOUR_API_TOKEN" \
  https://app.eventconnectors.nl/api/dictionary/propertiesOntology
const headers = { Authorization: "Bearer YOUR_API_TOKEN" };
const base = "https://app.eventconnectors.nl/api/dictionary";

const types = await fetch(`${base}/categorizationOntology`, { headers })
  .then((r) => r.json());

const props = await fetch(`${base}/propertiesOntology`, { headers })
  .then((r) => r.json());

// For an item owned by a different account
const foreignTypes = await fetch(
  `${base}/categorizationOntology?accountId=ABC123`,
  { headers },
).then((r) => r.json());

The category ontology response includes a lastModified timestamp at the root — useful for caching. Refresh your local copy when lastModified advances.

The base ontology

Event Connectors ships with a base ontology that is shared between accounts unless they explicitly override it. The base files live in the ff-api repo at src/main/resources/dictionary/:

  • CategorizationOntology.json — the type ontology, grouped by entity type (EVENEMENT, LOCATIE, ROUTE). Note that VENUE items use the LOCATIE types internally; both share the same type tree.
  • PropertiesOntologyTranslated.json — the property/category ontology, organised hierarchically as propertyCategories → properties → options/sub-properties.

Most accounts use the base ontology unmodified, which is why the categorizationOntology ID space is largely consistent across the platform.

Type ontology shape (excerpt)

{
  "lastModified": "2024-09-20T10:00:00.000Z",
  "categorizations": [
    {
      "categorizationId": "8",
      "cnetID": "1.3",
      "categorization": "Logies",
      "entityType": "LOCATIE",
      "child": [
        { "categorizationId": "37", "cnetID": "1.3.4", "categorization": "Hotel" },
        { "categorizationId": "36", "cnetID": "1.3.1", "categorization": "Bed and Breakfast" },
        { "categorizationId": "39", "cnetID": "1.3.3", "categorization": "Hostel" },
        { "categorizationId": "33", "cnetID": "1.2.3", "categorization": "Mini-camping", "deprecated": true }
      ]
    }
  ]
}

The cnetID is the canonical type identifier — that's what you reference from trcItemCategories.types[].catid. The deprecated: true flag marks values that are no longer recommended; they are filtered out of the response by default.

Property ontology shape (excerpt)

{
  "lastModified": "2024-11-27T20:00:00.000Z",
  "propertyCategories": [
    {
      "id": "10",
      "name": "Ligging",
      "datatype": "freetext",
      "propertytranslations": [
        { "lang": "nl", "text": "Ligging" },
        { "lang": "en", "text": "Location" },
        { "lang": "de", "text": "Standort" }
      ],
      "properties": [
        {
          "id": "10.1",
          "name": "Rustig gelegen",
          "datatype": "yes",
          "propertytranslations": [
            { "lang": "nl", "text": "Rustig gelegen" },
            { "lang": "en", "text": "Quiet area" },
            { "lang": "de", "text": "Ruhebereich" }
          ]
        }
      ]
    }
  ]
}

A Property's id is what trcItemCategories.categories[].catid references. For choice categories, valueid references an Option.id further nested inside the property.

Account overrides

Any account can override the default ontology. Overrides are full replacements, not patches: an account either uses the base ontology (no override) or supplies its own complete CategorizationOntology / PropertiesOntology — at which point the base is no longer consulted for that account.

What overrides allow:

  • Add new types/properties with new catid values that don't exist in the base.
  • Rename existing types/properties (change the categorization / name and/or propertytranslations).
  • Mark base entries as deprecated to hide them from your account's UI.
  • Delete entries by simply not including them in the override.

Account overrides live on the Account model in MongoDB (account.categorizationOntology and account.propertiesOntology). Updates to the property ontology go through PUT /api/dictionary/propertiesOntology?accountId={id} (admin-only); the type ontology is currently maintained directly on the Account document.

Storage: items reference IDs, not labels

A core design point: TRCItems persist only the catid (and valueid where applicable). The human-readable labels and per-language translations on categoryTranslations arrays are not stored on the item — they are populated live, on every read, by the API.

The fill-in happens server-side via two services in ff-api:

  • CategorizationOntology.fixTranslations(trcItem, account) — for types[]. Looks up each type.catid against the account's type ontology (or the base one) and populates type.categoryTranslations for the languages available on the item.
  • PropertiesOntology.fixTranslations(trcItem, account) — for categories[]. Looks up each category.catid against the account's property ontology, copies the property's datatype, and populates categoryTranslations (and, for choice/multichoice, valueCategoryTranslations).

Consequences for consumers:

  • You can ignore the categoryTranslations you receive on writes. The server overwrites them on the next read using whatever the ontology says now.
  • A renamed category in the ontology immediately appears in API responses for items already in the database — no migration required.
  • A category whose catid no longer exists in the ontology is returned without translations (and effectively becomes a "ghost" the UI can't render). Cleaning up unknown catids is a maintenance task on the account, not on items.

Cross-userorganisation reads: which ontology applies?

Each Account is bound to a userorganisation (a string in account.userOrganisation). TRCItems carry their owning organisation in trcItem.userorganisation. When the API serves an item, it uses the owning account's ontologynot yours — to populate translations.

What that means in practice:

  • If you fetch one of your own items, translations are filled in from your account's ontology. The simple case.
  • If you fetch an item owned by another userorganisation (e.g. you have a multi-tenant setup, or you're an aggregator), the translations on that item come from that other account's ontology.
  • If you cache or post-process those translations on your side, you need to know which account's ontology produced them — otherwise renaming a category in your own ontology won't change what you see for foreign items.

If you need to render a foreign-userorganisation item with the foreign labels (the usual case), the API has already done that for you — the categoryTranslations on the response are the right ones. If you need to look up additional information about a catid (e.g. which sub-properties exist under it), call the dictionary endpoint with ?accountId={id} set to the owning account's id, not yours:

# Lookup the type ontology that produced the labels on a foreign event
curl -H "Authorization: Bearer YOUR_API_TOKEN" \
  "https://app.eventconnectors.nl/api/dictionary/categorizationOntology?accountId=OWNING_ACCOUNT_ID"

The owning account's id is not directly exposed on the TRCItem; you can resolve it via userorganisation lookup against GET /api/dictionary/userorganisations, which returns each known userorganisation alongside its accountId and the ontology in use.

DataType enum

The datatype field on a category determines how to interpret the value field (and whether valueid / categoryvalues are used instead).

DataTypeMeaningHow value / related fields are used
yesPresence flagIf the category is present, it's true. value is normalised server-side to "True" or "False".
yesnoBooleanvalue is "true" or "false". Server normalises to "True"/"False".
nullableyesnoThree-statevalue is "true", "false", or null (unknown).
choiceSingle selectionvalueid references an Option.id in the property ontology. value is filled in with the option's Dutch label; valueCategoryTranslations holds the per-language labels.
multichoiceMultiple selectionscategoryvalues[] lists chosen Option.ids. value is filled with ;-joined Dutch labels.
freetextFree-form textvalue carries the user-entered string (per-language values may live on categoryTranslations[].value).
integerWhole numbervalue is the number as a string (e.g. "250").
decimalDecimal numbervalue is the number as a string (e.g. "12.50").
dateA datevalue is an ISO date string.
dataArbitrary structured datavalue carries the data; HTML is stripped server-side.
urlA URLPer-language URLs live on categoryTranslations[].value.
emailAn email addressvalue is the address.
phoneA phone numbervalue is the number.

Reading multichoice values

For multichoice categories, the selected values are in the categoryvalues array rather than the value field:

{
  "catid": "4.2.1",
  "datatype": "multichoice",
  "categoryvalues": [
    { "catid": "4.2.1.1" },
    { "catid": "4.2.1.3" }
  ]
}

Each entry in categoryvalues references an Option.id from the property ontology. The server fills in value (semicolon-joined Dutch labels) and categorytranslations for each entry.

Auto-mapping from text values on feed input

When submitting categories via a feed, you can supply either a catid or a plain-text value. If you provide a value instead of a catid, Event Connectors will attempt to auto-map it to the correct internal category ID:

{
  "trcItemCategories": {
    "categories": [
      { "value": "Klassieke muziek" },
      { "value": "Cultureel festival" }
    ]
  }
}

This is useful when your source system doesn't store Event Connectors category IDs. Auto-mapping works best with Dutch category names (the base ontology's primary language). For reliable results, prefer catid when you have the ID available.

  • Keywords and Markers — free-form tags and technical routing flags that complement the structured categorization.
  • TRCItem — the container that holds trcItemCategories.
  • Translations — how availableLanguages interacts with which translations get filled in.

See the API Reference for the full TRCItemCategories schema and the dictionary endpoints.

On this page