SAREF is ETSI's reference ontology for the Internet of Things. This repository has SAREF core and four of its extensions written as LinkML schemas. We also publish SAREF SHACL shapes, RDFS, and JSON Schemas generated from LinkML.
SAREF is published as OWL, which works well for reasoning and Semantic Web tools, but does not help with validation (constraints) or with data formats other than RDF. LinkML solves that: it is a modelling language that works across formats. You write a schema in LinkML, and it generates SHACL, RDFS, JSON Schema, Frictionless table schemas, GraphQL, ER diagrams, and code for several languages.
We use SAREF-LinkML in our work to:
- Guide agents when aligning data to SAREF.
- Validate SAREF data in RDF with SHACL.
- Express SAREF data in JSON and validate it with JSON Schema.
- Draw ER diagrams of SAREF classes and their relationships.
- Reason about SAREF with "light" semantics in RDFS.
Important
This is an unofficial, community conversion. It is not endorsed by, affiliated with, or maintained by ETSI, and it is not a normative representation of SAREF. Please report problems with these schemas here, not to ETSI.
Every release contains the LinkML schemas plus SHACL shapes, RDFS, and JSON Schema already generated from them. If you only want the artifacts, you can download them from the releases page without needing any LinkML tooling.
Tags are v<SAREF core version>-<date>, for example v4.1.1-2026-08-25. The first half says which
upstream ontology the conversion targets, the second says which conversion it is, so a fix to the
conversion alone still produces a higher version. Dated releases go out automatically on Mondays,
and only when something has actually changed. There is also a dev prerelease, rebuilt on every
push to main.
They are in schema/:
- saref-core.yaml - SAREF core, v4.1.1
- saref4bldg.yaml - SAREF4BLDG (buildings)
- saref4ener.yaml - SAREF4ENER (energy flexibility)
- saref4grid.yaml - SAREF4GRID (smart grid)
- saref4watr.yaml - SAREF4WATR (water)
If an extension that you need is not here, please open an issue.
Classes in LinkML are written in a YAML format. For example, the Sensor class looks like this:
Sensor:
class_uri: saref:Sensor
title: Sensor
description: A device designed to observe and measure one or more properties
or states of one or more features of interest.
mixins:
- Device
slots:
- observes
slot_usage:
observes:
required: trueGrab the five files from schema/. Then, we recommend using LinkML-Scala, which is a single binary with no runtime dependencies (see the installation instructions).
Generate a JSON Schema for SAREF4ENER:
linkml-scala generate json-schema --to saref4ener.schema.json schema/saref4ener.yamlGenerate SHACL shapes, so you can validate RDF data with any SHACL engine:
linkml-scala generate shacl --open --to shapes.ttl schema/saref-core.yamlRun linkml-scala --help for more generators and options.
You can also play with the schemas in the in-browser playground, or use use them in Python, Java, or JavaScript.
To make a SAREF JSON document, you need to pick a class that will be the root of the document (tree_root). SAREF itself does not say what that is, so you have to add one. examples/ has two small hand-written schemas that do this, alongside JSON data that matches them:
- device-catalog.yaml – a catalogue of sensors in an installation, the properties they observe, and the units those come in.
- observation-log.yaml – a batch of readings that refer to a sensor catalogue by IRIs.
A reading then looks like this:
{
"has_timestamp": ["2026-08-25T09:00:00Z"],
"made_by": ["ex:thermostat-hallway"],
"observes": ["ex:indoor-air-temperature"],
"has_result": [{ "has_value": [21.4], "is_measured_in": ["qudt-unit:DEG_C"] }]
}Those single-element arrays are arrays on purpose. SAREF declares these slots multivalued, and
slot_usage may only narrow a slot, never change its shape: turning a list into a scalar would
make data that is valid against the parent class invalid against the child. So the example
schemas write maximum_cardinality: 1, which says "at most one entry" without breaking that
rule. See the note on cardinality below.
To check that the JSON really does match:
pip install jsonschema neverblink-linkml
python checks/check-json.py --schema examples/observation-log.yaml --data examples/observation-log.jsonThe conversion from SAREF's original in OWL to LinkML is fully automated using the bin/owl2linkml.py script. It uses the translation rules defined in mapping/mapping_table.yaml.
We use ETSI's SAREF examples to test the generated SHACL shapes.
- The generated RDFS must match the source ontology. checks/check-rdfs.py compares the class hierarchy and property coverage of the generated RDFS against the original Turtle, and reports anything lost that the mapping table does not account for.
- The generated SHACL must be able to validate ETSI's examples. checks/check-examples.py validates ETSI's own examples against the generated SHACL shapes, with some exceptions (some examples are not fully compliant or incomplete).
- The LinkML schema must be valid under
linkml-scala validate --strict. - The hand-written JSON examples must validate against the generated JSON Schema. checks/check-json.py generates a JSON Schema from each schema in
examples/and validates the JSON file next to it.
To run the checks locally:
pip install -r requirements.txt
linkml-scala generate shacl --open --to build/shapes.ttl schema/saref-core.yaml
linkml-scala generate rdfs --to build/rdfs.ttl schema/saref-core.yaml
python checks/check-rdfs.py --rdfs build/rdfs.ttl --schema schema/saref-core.yaml \
--source source/SAREFCore/saref.ttl
python checks/check-examples.py --shapes build/shapes.ttl --schema schema/saref-core.yaml \
--examples source/SAREFCore/examples --mode open
python checks/check-json.py --schema examples/device-catalog.yaml --data examples/device-catalog.jsonMost of ETSI's examples needed a small edit before they would validate, usually because they use a term without ever saying what type it is, or because they predate a cardinality the ontology now requires. Every edit is noted down: source/README.md lists what was changed.
owl:minCardinality 1becomesrequired: true, andowl:minCardinality 0is skipped. An upper bound (max 1, cardinality 1) becomesmaximum_cardinality: 1inslot_usage. Only a bound of exactly 1 is written today; larger ones are dropped and reported, though nothing in SAREF has one.- Two restrictions are read as closed, stricter than the axiom:
owl:someValuesFrom Xbecomesrequired: trueandrange: X, andowl:hasValuebecomes a closed enum.owl:allValuesFromalso becomes a range. - By default all slots are
multivalued: true, constraints restrict that per class basis. Exception:owl:FunctionalPropertysetsmultivalued: falseon the slot itself, schema-wide. - When there are multiple restrictions on the same slot, the WIDEST is taken (e.g.
saref:represents). The loser is dropped whole, not just the key that lost. - An
owl:DatatypePropertythat declaresrdfs:range rdfs:LiteralbecomesAny(linkml:Any).
Bug reports, questions, and pull requests are welcome. If a class or slot came out wrong, an issue with the offending term and what you expected is the most useful thing you can send.
The files in schema/ are generated, and CI overwrites them. To fix the translation, edit the mapping table or the converter. CONTRIBUTING.md explains how to set up the environment, how to regenerate the schemas, how to run the checks, and how to add an extension or move to a new SAREF version.
This project follows a Code of Conduct.
You can also join our Discord to ask questions.
The LinkML schemas here are a conversion of ETSI's SAREF ontologies from OWL/Turtle into LinkML. The source ontologies are Copyright 2019 ETSI and licensed under the BSD 3-Clause License. The same license applies to this repository, and its terms carry over to the converted schemas. Class and slot definitions, labels, and descriptions are derived from the ETSI sources. The LinkML modelling decisions, the converter, and the tooling are the work of this project's contributors. See LICENSE.
"SAREF" is used here descriptively, to identify the source ontology.
This project is developed and maintained by NeverBlink. For inquiries, write to us at contact@neverblink.eu.
This work has been supported by the HEDGE-IoT project grant number 101136216 funded by the European Commission as part of the Horizon Europe Framework Programme. However, views and opinions expressed are those of the authors only and do not necessarily reflect those of the European Union or the European Climate, Infrastructure and Environment Executive Agency. Neither the European Union nor the granting authority can be held responsible for them.