Skip to content

Explore timeseries

Get time series dataset

All time series datasets are registered by their ID in the metadata service. In the time series service, the GET/api/ts/dataset/{id} endpoint provides time series dataset details.

Click to show example shell script
projectid="<replacewithprojectid>"
openapikey="<replacewithopenapikey>"
datasetid="<replacewithdatasetid>"

curl -L -X GET "https://api.mike-cloud-dev.com/api/ts/dataset/$datasetid" \
  -H 'Content-Type: application/json' \
  -H "dhi-open-api-key: $openapikey" \
  -H "dhi-service-id: timeseries" \
  -H "dhi-project-id: $projectid" \
  -H "dhi-dataset-id: $datasetid" 
Click to show example response
{
  "id": "e061ac0b-f44e-4d5f-8bd0-82dc1848e6a3",
  "items": [
    {
      "name": "TestItem",
      "unit": "eumUUnitUndefined",
      "item": "eumIItemUndefined",
      "dataType": "Single",
      "timeSeriesType": "Instantaneous"
    }
  ],
  "timeSeriesProperties": [
    { "name": "CX", "dataType": "Double" },
    { "name": "CY", "dataType": "Double" }
  ],
  "metadata": {}
}

Get time series details and data

Details about existing time series and actual data can be obtained using endpoints below.

To get time series details use:

GET/api/ts/dataset/{id}/timeseries/{timeSeriesId}

Click to show example shell script
projectid="<replacewithprojectid>"
openapikey="<replacewithopenapikey>"
datasetid="<replacewithdatasetid>"
timeseriesid="<replacewithtimeseriesid>"

curl -k -X GET \
  "https://api.mike-cloud-dev.com/api/ts/dataset/$datasetid/timeseries/$timeseriesid" \
  -H 'Content-Type: application/json' \
  -H "dhi-service-id: timeseries" \
  -H "dhi-project-id: $projectid" \
  -H "dhi-dataset-id: $datasetid" \
  -H "dhi-open-api-key: $openapikey"
Click to show example response
{
    "id": "f3a12147-5276-4ed7-a322-31554656f72c",
    "item": {
        "name": "TestItem",
        "unit": "eumUUnitUndefined",
        "item": "eumIItemUndefined",
        "dataType": "Single",
        "timeSeriesType": "Instantaneous"
    },
    "properties": {
        "CX": 1.1,
        "CY": 2.2
    },
    "dataFields": [
        {
            "name": "DfSingle",
            "dataType": "Single"
        },
        {
            "name": "Quality",
            "dataType": "Flag",
            "flags": [
                {
                    "id": 0,
                    "name": "Bad",
                    "level": 0
                },
                {
                    "id": 1,
                    "name": "Ok",
                    "level": 0
                },
                {
                    "id": 2,
                    "name": "Semi",
                    "level": 0
                }
            ]
        }
    ]
}

To get values and flags of time series use:

GET/api/ts/dataset/{id}/timeseries/{timeSeriesId}/values

Click to show example shell script
projectid="<replacewithprojectid>"
openapikey="<replacewithopenapikey>"
datasetid="<replacewithdatasetid>"
timeseriesid="<replacewithtimeseriesid>"

# get time steps from January 1, 2010, 00:00:00 to February 1, 2010, 00:00:00
dtfrom="2010-01-01T000000"
dtto="2010-02-01T000000"

# get values from one time series
curl -k -X GET \
  "https://api.mike-cloud-dev.com/api/ts/dataset/$datasetid/timeseries/$timeseriesid/values?from=$dtfrom&to=$dtto" \
  -H 'Content-Type: application/json' \
  -H "dhi-service-id: timeseries" \
  -H "dhi-project-id: $projectid" \
  -H "dhi-dataset-id: $datasetid" \
  -H "dhi-open-api-key: $openapikey"
Click to show example response
{
    "data": [
        [
            "2016-05-01T00:00:00",
            10000000.0,
            2
        ],
        [
            "2016-05-01T00:01:00",
            100.5,
            2
        ],
        [
            "2016-05-01T00:02:00",
            100.5,
            2
        ]
    ]
}

or

POST/api/ts/dataset/{id}/timeseries/values

Click to show example shell script
projectid="<replacewithprojectid>"
openapikey="<replacewithopenapikey>"
datasetid="<replacewithdatasetid>"
timeseriesid="<replacewithtimeseriesid>"

# get time steps from January 1, 2010, 00:00:00 to February 1, 2010, 00:00:00
dtfrom="2010-01-01T000000"
dtto="2010-02-01T000000"

# array with three time series ids
arrayids="[\"7096E557-FD61-46E5-BD63-B4BD61A3AD30\", \
 \"4051CE9E-86FD-411D-B7D9-1DDE15F00DDC\", \
 \"1AE92295-3FEE-485A-B40B-17D249880254\"]"

# get values from three time series
curl -k -X POST \
  "https://api.mike-cloud-dev.com/api/ts/dataset/$datasetid/timeseries/values?from=$dtfrom&to=$dtto" \
  -H 'Content-Type: application/json' \
  -H "dhi-service-id: timeseries" \
  -H "dhi-project-id: $projectid" \
  -H "dhi-dataset-id: $datasetid" \
  -H "dhi-open-api-key: $openapikey" \
  -d "$arraytsids"
Click to show example response
{
    "data": [
        [
            [
                "2016-05-01T00:00:00",
                10000000.0,
                2
            ],
            [
                "2016-05-01T00:01:00",
                100.5,
                2
            ],
            [
                "2016-05-01T00:02:00",
                100.5,
                2
            ]
        ]
    ]
}

Two endpoints above have optional filter parameters from and to where the expected time format is yyyy-MM-ddTHHmmss.

Reading values in pages

A single time series can hold hundreds of millions of values, and the endpoints above always return the whole requested range in one response. Send the header api-version: 3 to get the same two endpoints with cursor pagination instead: each request returns at most limit values plus a cursor, so a large series is read in bounded steps and a failed page can be retried on its own rather than restarting the whole range.

Pass the cursor from the previous response on the next request and stop when no cursor is returned. Treat the cursor as opaque: it is a continuation token, not a value you should parse or construct. Omit limit to use the service default (100 000 values); values outside the service's page-size range are clamped into it in both directions, so avoid small pages - see Page size bounds. Which of from, to and limit you repeat on a continuation request matters - see Filter parameters on continuation requests below.

Version 3 covers only these two values endpoints. Send api-version: 3 on those requests and keep using api-version: 2 for everything else - listing, querying, uploading and deleting are unchanged and are not part of v3.

GET/api/ts/dataset/{id}/timeseries/{timeSeriesId}/values?limit={limit}&cursor={cursor}

Click to show example shell script
projectid="<replacewithprojectid>"
openapikey="<replacewithopenapikey>"
datasetid="<replacewithdatasetid>"
timeseriesid="<replacewithtimeseriesid>"

# read the whole time series one page at a time
cursor=""
while :; do
  url="https://api.mike-cloud-dev.com/api/ts/dataset/$datasetid/timeseries/$timeseriesid/values?limit=100000"
  # the cursor contains a '#', so it must be URL encoded
  [ -n "$cursor" ] && url="$url&cursor=$(printf '%s' "$cursor" | sed 's/#/%23/g')"

  body=$(curl -sk -X GET "$url" \
    -H 'Content-Type: application/json' \
    -H "api-version: 3" \
    -H "dhi-service-id: timeseries" \
    -H "dhi-project-id: $projectid" \
    -H "dhi-dataset-id: $datasetid" \
    -H "dhi-open-api-key: $openapikey")

  echo "$body" | jq '.data | length'

  cursor=$(echo "$body" | jq -r '.cursor // empty')
  [ -z "$cursor" ] && break
done
Click to show example response
{
    "data": [
        [
            "2016-05-01T00:00:00",
            10000000.0,
            2
        ],
        [
            "2016-05-01T00:01:00",
            100.5,
            2
        ]
    ],
    // pass this back as 'cursor' to get the next page; absent on the last page
    "cursor": "2#636001920600000000"
}

The multi-series endpoint POST/api/ts/dataset/{id}/timeseries/values is paged the same way, with limit bounding the total number of values across all requested series. Because a page can end part-way through a series, each entry names its time series rather than relying on position:

Click to show example response
{
    "data": [
        {
            "timeSeriesId": "f3a12147-5276-4ed7-a322-31554656f72c",
            "values": [
                [
                    "2016-05-01T00:00:00",
                    10000000.0,
                    2
                ]
            ]
        }
    ],
    "cursor": "0#636001920600000000.8443e2c3"
}

The cursor resumes at a position in the list of ids it was issued for, so send that list unchanged - same ids, same order - on every page. A cursor presented with a different list is rejected with 400 instead of silently resuming in another series. Cursors are not interchangeable between the two endpoints.

Filter parameters on continuation requests

from, to and limit do not all behave the same way once a cursor is in play, because the cursor only carries some of the request state. Both from and to are inclusive: a value whose time stamp is exactly from or exactly to is returned.

Parameter First request (no cursor) Continuation request (with cursor)
from Start of the range. Optional; defaults to the start of the series. Single series: rejected with 400. The cursor already carries the time to resume from, so sending both is ambiguous.
Multiple series: still meaningful - repeat it unchanged. It is the start time of every series the page has not begun reading yet.
to End of the range. Optional; defaults to the end of the series. Not carried by the cursor - repeat it unchanged on every page. If you omit it, the range silently widens to the end of the series and you keep receiving values past your original to.
limit Maximum values in this page. Same. Applied per page, so it may legitimately differ from one page to the next.
Why from differs between the two endpoints

A single-series read has exactly one position to resume from, and the cursor describes it completely - so from has nothing left to say and is rejected rather than silently ignored.

A multi-series page can end part-way through one series. The cursor pins that series and the time to resume it at, but every series after it in the list has not started yet and still needs a start time. That start time is from, which is why it stays in effect on every page. Only the one series being resumed uses the cursor's time instead.

Requests rejected with 400
  • from sent together with cursor on the single-series endpoint.
  • A cursor issued by the other values endpoint - single-series and multi-series cursors are not interchangeable.
  • Multiple series: an id list that differs from the one the cursor was issued for, in content or in order.
  • Multiple series: a cursor whose series position falls outside the requested list.
  • A malformed or truncated cursor. Remember that the token contains a #, which must be URL encoded when it is sent as a query parameter.
Page size bounds

A page is bounded by whichever limit is reached first:

  • limit values, defaulting to 100 000. For the multi-series endpoint this counts values across all series in the page, not per series.
  • 1 000 time series entries per page, on the multi-series endpoint only.

Because a page can stop on either bound, do not infer that iteration has finished from a page being smaller than limit. The only end-of-data signal is the absence of cursor in the response.

limit is clamped into the service's page-size range - currently a minimum of 16 536 and a maximum of 5 000 000 - rather than rejected. Note that this clamps in both directions: asking for 100 values returns 16 536, so size your buffers from the length of data rather than from the limit you sent.

The minimum exists because of how values are stored, and is deliberately set to one storage block. Values live in bit-packed blocks that cannot be seeked into, so resuming a page part-way through a block means re-reading every value before the resume point and discarding it. That waste is proportional to block size รท page size: paging a series at 1 000 values a page costs roughly five times the decode work of an unpaged read, and at 100 values a page roughly fifty times. At one block per page it disappears, because each page ends on a block boundary and the next one starts cleanly - which is why the floor sits exactly there.

So prefer large pages; the default of 100 000 is a good starting point. To read less data, narrow the range with from and to rather than shrinking limit - a small limit does not reduce the work the service does, it multiplies it.

To list all time series in a dataset use GET/api/ts/dataset/{id}/timeseries/list.

Click to show example shell script
projectid="<replacewithprojectid>"
openapikey="<replacewithopenapikey>"
datasetid="<replacewithdatasetid>"

curl -L -X GET "https://api.mike-cloud-dev.com/api/ts/dataset/$datasetid/timeseries/list" \
  -H 'Content-Type: application/json' \
  -H "dhi-service-id: timeseries" \
  -H "dhi-project-id: $projectid" \
  -H "dhi-dataset-id: $datasetid" \
  -H "dhi-open-api-key: $openapikey" \
  --data-raw ""
Click to show example response
{
    "data": [
        {
            "id": "f3a12147-5276-4ed7-a322-31554656f72c",
            "item": {
                "name": "TestItem",
                "unit": "eumUUnitUndefined",
                "item": "eumIItemUndefined",
                "dataType": "Single",
                "timeSeriesType": "Instantaneous"
            },
            "properties": {},
            "dataFields": [
                {
                    "name": "DfSingle",
                    "dataType": "Single"
                },
                {
                    "name": "Quality",
                    "dataType": "Flag",
                    "flags": [
                        {
                            "id": 0,
                            "name": "Bad",
                            "level": 0
                        },
                        {
                            "id": 1,
                            "name": "Ok",
                            "level": 0
                        },
                        {
                            "id": 2,
                            "name": "Semi",
                            "level": 0
                        }
                    ]
                }
            ]
        }
    ]
}

Query time series

You can also list only time series that match certain criteria using

POST/api/ts/dataset/{id}/timeseries/query.

Click to show example shell script
projectid="<replacewithprojectid>"
openapikey="<replacewithopenapikey>"
datasetid="<replacewithdatasetid>"

curl -L -X POST "https://api.mike-cloud-dev.com/api/ts/dataset/$datasetid/timeseries/query" \
  -H 'Content-Type: application/json' \
  -H "dhi-service-id: timeseries" \
  -H "dhi-project-id: $projectid" \
  -H "dhi-dataset-id: $datasetid" \
  -H "dhi-open-api-key: $openapikey" \
  --data-raw "{
  \"conditions\": [
    {
      \"type\": \"AttributeQueryCondition\",
      \"name\": \"Item\",
      \"operator\": \"Equal\",
      \"value\": \"TestItem\"
    },
    {
      \"type\": \"AttributeQueryCondition\",
      \"name\": \"CX\",
      \"operator\": \"Equal\",
      \"value\": \"1\"
    }
  ]
}"
Click to show example response
{
  "data": [
    {
      "id": "05f8141e-5a6f-4b89-aee6-774431f90970",
      "item": {
        "name": "TestItem",
        "unit": "eumUUnitUndefined",
        "item": "eumIItemUndefined",
        "dataType": "Single",
        "timeSeriesType": "Instantaneous"
      },
      "properties": {
        "CX": 1.0
      },
      "dataFields": [
        {
          "name": "DfSingle", "dataType": "Single"
        },
        {
          "name": "Quality",
          "dataType": "Flag",
          "flags": [
            { "id": 0, "name": "Bad", "level": 0 }, { "id": 1, "name": "Ok", "level": 0 }, { "id": 2, "name": "Semi", "level": 0 }
          ]
        }
      ]
    }
  ]
}