Everything OpenGate stores about your fleet is queryable through one consistent mechanism: POST a JSON query, get rows back. There is no query string to assemble and no SQL to learn — the resource lives in the URL, and the conditions live in a JSON body.

How a query is built

flowchart LR
    URL["<b>The URL</b><br>what you are querying<br>/north/v80/search/devices"] --> REQ(["POST"])
    BODY["<b>The JSON body</b><br>which rows you want<br>filter, select, sort, group, limit"] --> REQ
    REQ --> RES["<b>Rows</b><br>JSON or CSV"]

Two things to learn, and this section is organized around exactly that:

  1. What you can query — the index of every search endpoint, so you know which URL to POST to.
  2. Data Lake — the query language: filter, select, sort, group and limit.

Then, because three kinds of storage answer slightly differently, Query dialects lays their differences side by side.

Your first query in 60 seconds

Ask for your devices. No filter, no options — just the resource:

curl --request POST \
     --header "X-ApiKey: <your-api-key>" \
     --header "Content-Type: application/json" \
     --data '{}' \
     https://api.opengate.es/north/v80/search/devices

You get an array named after the resource. Each row is a flat map of dotted field paths, and each value is wrapped in _value._current.value — the same shape the platform uses to hold a current value and its metadata:

{
  "devices": [
    {
      "provision.administration.identifier": {
        "_value": { "_current": { "value": "device_battery_id" } }
      },
      "provision.administration.channel": {
        "_value": { "_current": { "value": "battery_channel" } }
      },
      "provision.device.location": {
        "_value": { "_current": { "value": {
          "position": { "type": "Point", "coordinates": [-3.7028, 40.41675] },
          "postal": "28013"
        } } }
      }
    }
  ],
  "page": { "number": 1 }
}

Those dotted paths are also the field names you filter and sort on. Narrow the query with a filter tree and page it with limit:

{
  "filter": {
    "and": [
      { "like": { "provision.device.administrativeState": "NORMAL" } },
      { "like": { "provision.device.communicationModules[].mobile.imei": "351873000102290" } }
    ]
  },
  "limit": { "start": 1, "size": 50 }
}
Discovering field names

Run the query with an empty body first. The paths you see in the response are exactly the paths you can filter, sort and select on — which is the fastest way to learn any resource’s fields.

Swap devices in the URL for datapoints, entities/alarms or any resource from What you can query, and the same body shape applies — only the field names change.

Reading the answer

You want Use Where
Rows The search endpoint Data Lake
How many, not which The same endpoint with /summary Summary
Only some fields select Selecting
Rows in pages limit Pagination
Rows grouped and aggregated group Grouping
A CSV file instead of JSON An HTTP header option Query dialects

What each store is for

Store Holds Query it when
Data streams The current value of each measurement You want the latest reading, not the history
Alarms Alarms raised on entities You are monitoring what went wrong
Time series Values pre-aggregated into fixed time buckets per device You want history at scale: hourly or daily aggregates already computed
Data sets A tabular projection of chosen data streams You want a flat table, typically to export as CSV
Notebook scheduler Notebooks run unattended, once or on a schedule Your analysis is code, not a query
Data points Every raw value a device ever sent Deprecated. Superseded by time series