OGC SensorThings API for IoT Integration: A Vendor-Neutral Telemetry Guide
At 14:20 on a Tuesday, an ultrasonic sensor in a sewer chamber lifted its reading past 70 percent and stayed there. The alarm reached the control room in ninety seconds. Rebuilding that same reading inside two other systems took the utility’s integration engineer three weeks, because one device had said it in a shape nothing else could read.
That is the problem an OGC SensorThings API IoT integration exists to solve. The standard is an open one from the Open Geospatial Consortium for exposing IoT devices, their sensors and their readings over the web in one agreed structure, and it is why the phrase keeps appearing in municipal tender specifications.
Almost everything written about it online falls into two categories. There’s the specification, which is written to be implemented rather than decided with. And there’s the documentation for one server, which assumes you’ve already chosen the standard. Neither answers the two questions a project team actually has: what does my deployment look like as a data model, and when is this the wrong answer?
This guide answers both. It’s written for the municipal or utility data architect who owns the model, the SCADA or OT engineer who will implement it, and the consultant who has to write the clause a supplier can be held to. If you are still choosing between integration paths rather than implementing one, the GIS integration for smart manhole monitoring pillar compares REST push, MQTT middleware and batch import side by side.
By the end of this article you will be able to map a sensor fleet onto the standard’s entities correctly, run a real query against that graph, evaluate the reference stack, name the gap it deliberately leaves open, and decide honestly between SensorThings, raw MQTT and a proprietary managed path.
What the OGC SensorThings API Actually Is
The OGC SensorThings API is an open, non-proprietary, platform-independent standard from the Open Geospatial Consortium for interconnecting IoT devices, data and applications over the web. It uses a REST interface with JSON encoding, an OData-derived query language, and an MQTT extension for push delivery.
The model underneath it isn’t new. It’s built on OGC and ISO Observations & Measurements (ISO 19156). Most people describe it as Sensor Web Enablement made light for IoT. It keeps the conceptual rigour of the older Sensor Observation Service (SOS), but drops the XML weight that kept SOS out of most municipal programmes.
The standard already reaches sanitation networks, not just environmental research. Aigües de Manresa, a municipal water utility advised by CREAF, runs it across both its drinking-water and sewerage networks, with GIS handling in QGIS.
Where does it sit in the standards landscape?
Four neighbours explain its position. ISO 19156 sits underneath as the conceptual model. OData supplies the query surface, which is why every URL in this article looks like a filtered database call rather than a REST path. ITU-T Recommendation Y.4473 carries the standardisation into telecom procurement language. And oneM2M is the other serious IoT standard, the one whose own website publishes an introduction to this one.
One naming note: the standard is sometimes written as the sensor things api with a space. It is the same specification.
Which part do you actually need?
The standard has three parts. Part 1: Sensing is what roughly 95 percent of deployments use, and it’s what this article covers. Part 2: Tasking Core adds command-and-control, so a server can not only read a sensor but instruct it. Part 3: Rule Engine is early and rarely deployed. The version history matters more than most vendors admit. Part 1: Sensing v1.0 was published in 2016, updated to v1.1 in 2021, and v2.0 is landing as a draft that aligns with OData 4.01.
The Eight Entities in Plain Language
The entity model is the entire standard. Set it out as a chain of ordinary questions and it stops being intimidating.
| Entity | The question it answers | On a manhole |
|---|---|---|
| Thing | What is it? | The cover itself, carrying an asset ID |
| Location | Where is it? | A fixed WGS84 point on the chamber |
| HistoricalLocation | Where has it been? | The trail if a cover is relocated or replaced |
| Sensor | What measures it? | The physical element doing the measuring |
| ObservedProperty | What is measured? | Tilt angle, water level, gas concentration, battery voltage |
| Datastream | How is the measurement grouped? | One per ObservedProperty |
| Observation | What is the value, and when? | A single reading with a timestamp |
| FeatureOfInterest | What is being described? | The sewer chamber or the drainage basin it serves |
A Datastream is a stream, not a reading
This is the distinction most readers get wrong, and it determines the entire schema. A Datastream is the grouping of observations that share one ObservedProperty and one Sensor. An Observation is one reading inside it.
That means a cover reporting tilt, level, gas and battery does not produce one stream of mixed data. It produces multiple Datastreams on one Thing, and how many you create is a deliberate design decision rather than a property of the hardware. What a device can physically sense sets the ceiling on that number before any software choice is made, which is why the sensors a cover carries matter to a data architect. Get it wrong early and every downstream query inherits the mistake.
What changed between SensorThings 1.1 and 2.0?
Four changes matter to a specification. FeatureOfInterest is renamed to Feature. The older encoding fields are replaced by resultType and resultEncoding. unitOfMeasurement moves off the Datastream. And the whole document is re-aligned to OData 4.01.
The practical warning is more important than the changes themselves. Most running deployments are still on 1.1, so a specification that names “SensorThings 2.0” may describe something the server your integrator already has can’t implement. Name the version, then verify it.
What a Manhole Fleet Looks Like as a SensorThings Entity Graph
Take the composite utility from the pillar, Pinewood: 4,200 covers, 800 of them instrumented. Build the graph from the device outward, and the abstract entities become a parts list.
| Element | Entity | Manhole deployment |
|---|---|---|
| The cover | Thing | One Thing per cover, with properties for asset ID, chamber depth and load class |
| Its position | Location | A fixed WGS84 point; a HistoricalLocation trail if the cover is ever moved |
| What it senses | Sensor | Four logical sensors on one physical device: tilt, ultrasonic level, gas, battery |
| What is measured | ObservedProperty | Tilt angle, water level, CH₄/H₂S concentration, battery voltage |
| The stream | Datastream | One per ObservedProperty, as decided above |
| A reading | Observation | phenomenonTime for when it happened, result for the value |
| What is described | FeatureOfInterest | The sewer chamber or the drainage basin the cover serves |
Two design rules keep this graph clean. The Thing carries the asset identity, so the graph keys on the same ID as the manhole asset data model your registry already uses. That single shared key is what connects the observation graph to the asset graph, and it is the reason the two should be designed together rather than bolted together later.
A smart manhole cover becomes a Thing in this graph the moment its sensor set is fixed, because the sensor set determines the Datastream count.
What does $expand do? A worked SensorThings query
One query shows the whole model working. This fetches a single cover’s latest value from every Datastream it owns:
GET /v1.0/Things?$filter=properties/assetId eq 'MH-0417'&$expand=Datastreams($expand=Observations($orderby=phenomenonTime desc;$top=1))
$filter narrows to one Thing by its asset property. $expand=Datastreams inlines its streams instead of returning links, and the nested $expand=Observations inlines the readings. $orderby=phenomenonTime desc puts the newest first; $top=1 keeps only that one. The response is everything a status dashboard needs for that cover, from one call.
How many Datastreams per manhole cover?
The defensible convention is one Datastream per ObservedProperty, which gives a typical instrumented cover four to eight. The number is not cosmetic. It multiplies directly into the row count in the observations table, and therefore into the storage plan two sections below.
Where the Standard Deliberately Stops, and the Trap That Creates
The single most important fact about the standard is what it does not define. The OGC specification documents are explicit: SensorThings defines no security capability. It is designed to leverage existing and future IoT security, and a server announces its own requirements through a serverSettings section.
What that means in practice
Authentication, authorisation, transport encryption and tenant isolation are the implementer’s problem. Nothing in the entity model constrains them, so two compliant endpoints can be interoperable at the data layer and completely incompatible at the security layer.
Riverbend, the composite European combined-sewer district from the pillar, is the concrete case. It runs two SensorThings endpoints over one municipal network. Both expose the identical entity model, both answer the same queries, and each requires a different authentication scheme because the two were procured five years apart by different departments. The data model guaranteed interoperability. Nothing guaranteed the connection.
Open standards are a procurement instrument, not a technical guarantee
The real reason utilities adopt SensorThings isn’t architectural elegance. It’s insurance against lock-in. A specification that names the standard means the next server can replace the last one without a migration project, which is a legitimate and often decisive commercial argument.
Its limit is worth stating plainly. The standard guarantees the shape of your data. It says nothing about performance, hosting, uptime, or who operates the thing. Those stay in the contract. If you would rather buy them as a package, streaming the same data into ArcGIS Velocity is the managed alternative that bundles security and hosting.
Two more things that are not in the standard
There is no defined retention or archiving model, and no required query performance. Both are the implementer’s problem, and both are where municipal programmes actually fail. A system that answers correctly at 800 devices and times out at 4,000 was never non-compliant. It was under-specified.
Running an OGC SensorThings API: FROST-Server and the Reference Stack
FROST-Server is the Fraunhofer Open Source SensorThings API Server, built by Fraunhofer IOSB. It’s the first complete open-source reference implementation, covering Part 1: Sensing and Part 2: Tasking Core, and it’s how most practitioners meet the standard in practice.
How to deploy FROST-Server: what the stack requires
FROST-Server requires PostgreSQL with PostGIS. That dependency isn’t incidental. The observations table can optionally be converted to a TimescaleDB hypertable once volume demands it.
It ships as three packages, frost-server, frost-server-http and frost-server-mqtt, runnable on Tomcat or Docker. The first settings a reader meets are serviceRootUrl, defaultTop and maxTop.
The bridge nobody documents
Because FROST stores in PostGIS, and because a municipal GIS very often reads PostGIS, the storage layer is frequently already shared. The server writes into the same spatial database your mapping stack queries, which makes the database the integration point rather than an API call. Build the storage layer once, and the standard’s data arrives in the GIS you already own.
Where an alert must fire even when the service endpoint is unreachable, the answer is local logic at the edge rather than a longer chain. Note the coordinate convention here and nowhere else: the standard’s GeoJSON encoding assumes EPSG:4326, and transforming to a municipal projection belongs to the GIS layer.
Query design determines performance
FROST’s own guidance carries a rule worth adopting before you write any client. Start traversals from /Things and you get a structure that maps easily. Start from /Observations and you mix data across Datastreams, which is harder to render and slower to filter. Choose the entry point deliberately.
SensorThings vs MQTT vs ArcGIS-Native Ingestion
| Dimension | SensorThings API | Raw MQTT | ArcGIS-native |
|---|---|---|---|
| What it is | Modelled, queryable IoT service | Transport: publish/subscribe | Managed proprietary ingestion |
| Best at | Discovery, metadata, history | Real-time push from constrained devices | Integrated analytics and maps |
| Metadata and discovery | Defined by the entity model | None beyond the topic | Platform-managed |
| Historical query | OData over full history | Not provided | Platform-managed |
| Geospatial context | Location entities, GeoJSON | None | Native |
| Interoperability | Open standard, portable | Broker-dependent | Vendor-bound |
| Who operates it | You, or your integrator | You | The platform vendor |
| Cost model | Open source plus infrastructure | Broker plus engineering | Subscription and licence |
SensorThings vs MQTT for sensor data: they are complementary
The comparison is hard to draw because the two are not competitors. MQTT is transport. It moves bytes from a constrained device to a broker with almost no overhead, and it’s ideal for real-time push. SensorThings is a modelled, queryable service with metadata and history.
The honest framing is this: use MQTT to move the data, and SensorThings to make it discoverable and queryable. MQTT is not a rival to the standard. It is an extension of it, and the specification defines its topic pattern, in the form v1.0/Datastreams(5)/Observations.
When the entity model is not worth it
For a small fleet with one reader and one use, an MQTT topic writing into a single table is simpler, cheaper, and entirely sufficient. Say so before you build.
The entity model earns its overhead when data must be discoverable by parties who did not build it. A district that started with raw MQTT into one table was fine until a second department wanted the same readings. It ended up maintaining a bespoke interface per consumer, and the turn to SensorThings happened when the number of readers grew, not when the sensor count did. That is the tender scenario exactly, and it is why the standard appears in specifications.
And the neighbouring standard
oneM2M is the other serious IoT standard, and ITU-T Recommendation Y.4473 is where SensorThings enters telecom procurement language. Two competing standards both mattering is normal; a specification should say which one it means.
Sizing, Storage and the Cost Question
Every reading is one row in one table, so the observations table is the whole cost model. Work the arithmetic the way an engineer would, with Pinewood’s 800 instrumented covers.
At five Datastreams each and a reading every fifteen minutes, that is 800 × 5 × 96, or roughly 384,000 observations per day. Scale to the full 4,200-cover estate and the daily write approaches two million rows, with the annual figure in the hundreds of millions.
That single number decides the storage design. A plain PostgreSQL table handles the smaller figure comfortably. Sustained writes at the larger one are where the TimescaleDB hypertable conversion stops being optional and starts being the plan.
Is it free?
Directly, because almost no page in this space says it: the standard is open and the reference implementation is open source. The software cost of a SensorThings path is infrastructure and engineering, not a licence. The managed and proprietary alternatives are licensed, which is a legitimate trade of money for someone else carrying the operational burden.
What actually consumes budget
Four things, and none of them is the licence: hosting, the integration engineering, the security layer the standard does not provide, and the retention decision. That last one has no default. The standard offers no guidance on how long to keep an observation, so the decision belongs in the specification before the first reading lands, not in an incident review two years later.
Writing SensorThings Into a Specification
If you write the specification, you are the reader this article exists for. A clause that names the standard without naming the entities is unenforceable.
The clause a utility can issue
Require the parts explicitly. State which ObservedProperties must be exposed, rather than accepting whatever the supplier offers. Specify Part 1: Sensing conformance, and name the version, remembering that naming 2.0 may exceed what the integrator’s server implements. Then add the requirement the standard can’t provide: the vendor must state how security is handled, because SensorThings won’t.
What to require of the hardware supplier
The gateway must either expose the entity model itself or sit behind an adapter that does, and the specification should force the supplier to say which one is being offered before signatures. Require the supplier to publish its own Datastream and ObservedProperty definitions rather than leaving naming to the integrator. The aggregation layer that turns a cover fleet into one service endpoint is an IoT Data Collection Terminal class of device, with RS485, M-Bus and 4–20 mA inputs and an NB-IoT or LoRaWAN uplink.
The certifications that travel with it
ISO 19115 governs the metadata catalogue around the data. The physical certifications are the buyer’s real underground risk, and the standard has nothing to say about any of them: IP68 for water ingress, EN 124 load class for traffic, and ATEX where gas is present. Specify those separately, because a data model won’t keep water out of a chamber.
What to Do This Week
Three actions, in order. First, sketch your own fleet as an entity graph before writing any code, because the Datastream decisions you make there are expensive to change later. Second, ask what version your integrator’s server actually implements before a specification names one. Third, settle retention before the first observation lands.
An OGC SensorThings API IoT integration succeeds on the model, not the tooling, and the model is the part you own.
Get the SensorThings Entity Model Worksheet. One page, pre-filled with the eight-entity mapping for the manhole fleet in Figure 1, a blank version for other assets, the worked query above, and the specification clause from the previous section.



