SensorThings API Extractor
Pricing
from $0.35 / 1,000 records
SensorThings API Extractor
Point at ANY OGC SensorThings API v1.1 instance (FROST-Server, GOST, SensorUp and thousands of air-quality, smart-city and research deployments) and get flat sensor rows — observations, things with coordinates, datastreams with units — plus raw JSON. @iot.nextLink paging, OData filters. Pay per row.
Pricing
from $0.35 / 1,000 records
Rating
0.0
(0)
Developer
Datamule
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
3 days ago
Last modified
Categories
Share
Point at any OGC SensorThings API v1.1 instance and get clean, flat sensor rows — plus the lossless raw JSON of every entity. One actor, the entire open sensor-data world.
The OGC SensorThings API (OGC 15-078r6 / 18-088) is the open standard for Internet-of-Things and environmental-sensor data on the web. Every conforming deployment — no matter what server it runs (FROST-Server, GOST, SensorUp, or a custom stack) — answers the identical RESTful JSON grammar over the same entity graph, and anonymous read is the norm (publishing sensor data is the protocol's purpose). So this single actor works against every SensorThings instance on Earth — air-quality networks, smart-city platforms, government monitoring, university research. No per-instance scraper, no host enumeration — just the base URL.
What you can extract
Pick a mode and point sources at one or more SensorThings base URLs. A plain server address such as https://iot.hamburg.de also works: the actor finds its /v1.1 (or /v1.0) root and logs which one it used.
| Mode | What you get (one row per…) |
|---|---|
| observations (default) | sensor reading — @iot.id, phenomenon time, result time, result (any type), result quality, parameters, valid time, the Datastream it belongs to (datastreamName, datastreamId) and its unit (unitName, unitSymbol) + Datastream / FeatureOfInterest links |
| things | monitored asset — id, name, description, properties, and its Location's coordinates (Locations auto-expanded) |
| datastreams | measurement series — name, observation type, unit of measurement, observed area, phenomenon/result time + Sensor / ObservedProperty / Thing links |
| discovery | instance fingerprint — available entity sets, declared conformance classes, server settings |
The SensorThings entity graph is
Things → Locations → Datastreams → (Sensor, ObservedProperty) → ObservationsdatastreamId) to pull
the readings.
Sensor readings spreadsheet (CSV) without code
Fastest start: the Console form opens pre-filled with one live sensor: Air temperature at DE-Bnb-ECStation (datastream 723, unit °C, updated every few minutes) on the KIT TERENO environmental server (https://sensorthings.imk-ifu.kit.edu/v1.1), newest first. Click Start to get its newest readings. The pre-filled Max records of 5 is a trial cap, so raise it for a real harvest. This is a third-party server, so readings can stop updating; check phenomenonTime. To use your own server, change the URL and clear or replace Datastream id, because 723 only exists on the KIT TERENO server. The pre-fill only fills in the form. API calls that leave these fields out still get the defaults: all observations, in server order, up to 1000 records.
Tested in Apify Console on the public Hamburg server. You fill in the form and don't write any JSON.
1. Find the sensor you want (Input → Form):
- SensorThings API URL(s):
https://iot.hamburg.de(a plain server address works). - Mode: the option starting with
datastreams. - OData $filter:
substringof('Photovoltaik',name)(use a word from the name of the sensor you want). - Max records (global cap):
5. Then click Save & start.
On Hamburg this returned 676 Photovoltaik HAW Erzeugung elektrisch (unit W, Watt) and 15717 Photovoltaik HAW Gesamtertrag elektrisch (Wh). Its @iot.id column is the number you need next.
2. Get its newest readings:
- Mode: the option starting with
observations. - Datastream id (observations mode):
676. - OData $orderby:
phenomenonTime desc. Without it, many servers return their oldest readings first. Across all Hamburg sensors, the oldest readings date from 1990 (sensor 676's go back to 2020). - Max records (global cap):
5. Under Run options, set Maximum cost per run (for example0.01). Then click Save & start.
3. Download the CSV: on the run, click Export, set View to SensorThings records, choose CSV, then click Download. The file has 11 columns, including datastreamName, phenomenonTime (when the reading was taken, UTC), result, unitSymbol, unitName and _source. All fields adds about 30 more raw columns.
Our run returned five readings from 2026-09-07 (1396, 1371, 1566, 1343 and 1269 W). All five match the server's own /Datastreams(676)/Observations reply. How recent the newest reading is depends on when the server last updated that sensor, so check phenomenonTime. Each run is charged per record delivered (2 sensors in step 1, 5 readings in step 2).
Why one actor covers thousands of sources
The wire format is uniform, but installs populate different optional fields — and this runner handles all of them:
- Every field is read by key presence. A Thing without
properties, a Datastream without anobservedArea, an Observation withoutresultQualityorparameters→ that column is simplynull, never a crash. @iot.nextLinkcursor pagination. Large collections are returned page by page; the actor follows the server's cursor automatically up to your globalmaxRecordscap.- Full OData query surface. Pass
$filter,$expand,$select,$orderbystraight through — e.g.$filter=result gt 0,$orderby=phenomenonTime desc,$expand=Datastream,FeatureOfInterest. resultis any type. A reading may be a number, a string, a boolean, an array, or a complex object (OM_ComplexObservation) — it is passed through verbatim.- Coordinates promoted. A Thing's Point location and a Datastream's observed area are promoted
to a clean
[lon, lat]coordinatescolumn, with the full GeoJSON kept ingeometry/_raw.
Example input
The newest 5 readings of one sensor. This is the same as the form pre-fill:
{"sources": ["https://sensorthings.imk-ifu.kit.edu/v1.1"],"mode": "observations","datastreamId": "723","$orderby": "phenomenonTime desc","maxRecords": 5}
A whole-server harvest, newest first across every sensor (mixed units, up to 1000 billable rows):
{"sources": ["https://iot.hamburg.de/v1.1"],"mode": "observations","$orderby": "phenomenonTime desc","maxRecords": 1000}
Map an instance's assets instead:
{"sources": ["https://sensorthings.imk-ifu.kit.edu/v1.1"],"mode": "things"}
Output
Every row is flat and fully nullable, with the same column set across all modes (unused columns
are null), plus a lossless _raw copy of the source entity and _source / _mode / _entity
provenance. Results stream to the dataset and export as JSON, CSV, Excel, or via the API.
Resilience & honesty
- A reachable instance with no matching rows → 0 records, clean success. Nothing is ever fabricated.
- An unreachable / DNS-failed host, an auth gate (401/403), or a non-SensorThings body is skipped with a warning and the batch continues; a run where every source is skipped fails fast so you know your input was wrong rather than getting a silent empty result.
- No authentication is required for public instances. An optional bearer token / extra headers can be supplied for gated deployments — they are never logged.
Pricing
Pay-per-event: you are charged per record emitted to the dataset. Use maxRecords (and, for
observations, datastreamId or a $filter) to bound cost precisely.
Your max charge per run is respected: the run writes and bills only the records it covers, asks the server only for as many records as that budget pays for, and makes no request at all if the budget cannot cover one record. If a later page fails, the records already delivered are kept and the run finishes with an error instead of a silent partial success.