API INTERNAL DOCUMENTATION

Common API Elements

This section documents API elements that may be shared by multiple APIs.

Dates Object

Specifies the date(s) for which the data needs to be queried.

Note: The previous date parameter for the request will be deprecated after this release. In the future, to query for single date, use the dates object.

Single Date
Property Data Type Description Required
type String Use single for single-day query. Yes
value String The date to query. Yes

Example:

"dates": {    
   "type": "single",    
    "value": "2018-01-20"
}

Range Dates
Property Data Type Description Mandatory
type String Use range for range query. Yes
beginDate String The start date of the period to query. Yes
endDate String The end date of the period to query. Yes

Example:

"dates": {    
     "type": "range",    
     "beginDate": "2018-01-20",    
     "endDate": "2018-01- 27"
}

Location Object

Specifies a single location for which data needs to be queried.

Property Data Type Description Required
locationType String Currently, only locationHierarchyLevel is supported. Yes
levelType String

Hierarchy level for the specified location.

The following are three location levels supported for Singapore:

  • subzone
  • planningarea
  • planningregion

levelType is a combination of "name of API (except odmatrix)", followed by a underscore and then "location levels", for example, discretevisit_subzone or staypoint_planningarea.

For the odmatrix API, since there is origin and destination, the levelType starts with either a origin or destination followed by a underscore and then location level, for example, origin_planningregion or destination_planningregion.

Yes
id String Code of a location. For example, DTSZ05 represents "Raffles Place". Use the location code instead of the actual name of place as the value for the ID property. Yes

Example:

"location": {
  "locationType": "locationHierarchyLevel",
  "levelType": "origin_subzone",
  "id": "OTSZ02"
}

Granularity Object

Specifies the time interval of the time series to return. 

Property Data Type Description Required
type String Currently, only period is supported. Yes
period String Period for each time series interval follows the PnDTnHnMnS format where P denotes Period, n denotes numeric value, D denotes Day, T denotes Time, H denotes hour, M denotes minute, and S denotes second. It is not necessary to specify the period using the entire format, you only need to indicate what is required for the period. For example, P1D indicates daily time series (1D means 1 day), PT1H indicates hourly time series (T1H means time in 1 hour), PT2H30M indicates the hour and minute time series (T2H30M means time in 2 hour and 30 minutes). The origin of the time series is always at midnight. Yes

Example:

"granularity": {    
    "type": "period",    
    "period": "PT1H"
}

Filter Object

Apply dimension filters to the data before computing aggregate metrics. The following section lists the filters supported. 

Selector Filter

Filters records with a specific dimension value and can also be used to construct more complex Boolean filters. 

Property Data Type Description Required
type String Use selector as the filter type. Yes
dimension String Dimension name. Yes
value String Dimension value. Yes

Example:

"filter": {    
   "type": "selector",    
   "dimension": "agent_gender",    
   "value": "M"
}

Logical AND Filter

Filters records where both sub-filters are true. 

Property Data Type Description Required
type String Use and as the filter type. Yes
fields List of Filter Objects List of sub-filters as inputs to this filter. Yes

Example:

"filter": {    
   "type": "and",    
   "fields":[{         
       "type": "selector",
       "dimension": "agent_gender",
       "value": "M"
    },
    {
       "type": "selector",
       "dimension": "agent_year_of_birth",
       "values": "1990"    
    }]
}

Logical OR Filter

Filters records where either sub-filter is true. 

Property Data Type Description Required
type String Use or as the filter type. Yes
fields List of Filter Objects List of sub-filters as inputs to this filter. Yes

Example:

"filter": {
    "type": "or",
    "fields": [{
        "type": "selector",
        "dimension": "agent_gender",
        "value": "M"
    },
    {
        "type": "selector",
        "dimension": "agent_year_of_birth",
        "values": "1990"
    }]
}

Logical NOT Filter

Filters records where the sub-filter is false.

Property Data Type Description Required
type String Use not as the filter type. Yes
field Filter Object Sub-filter as input to this filter. Yes

Example:

"filter": {
    "type": "not",
    "field": {
       "type": "selector",
       "dimension": "agent_gender",
       "value": "M"
     }
}

Bound Filter

Filters records based on a range of dimension values. The bounds can be either one-sided (greater than or less than) or two-sided (between). They can also be either inclusive or exclusive. 

Property Data Type Description Required Default
type String Use bound as the filter type. Yes
dimension String Dimension to filter. Yes
lower String Filter for lower bound. No, but at least either one of lower or upper bound must be set.
upper String Filter for upper bound. No, but at least either one of lower or upper bound must be set.
lowerStrict Boolean Perform strict comparison on the lower bound (< instead of <=). No False
upperStrict Boolean Perform strict comparison on the lower bound (> instead of >=). No False
ordering String Specifies the sorting order when comparing values to the bounds. See the following Sorting Orders for more information. No Lexicographic

Example:

"filter": {    
    "type": "bound",
    "dimension": "stay_duration",
    "lower": 100,
    "upper": 400,
    "ordering": "numeric"
}

Interval Filter

The Interval filter enables range filtering on columns that is formatted as timestamp. It is suitable for the _time column, long metric columns, and dimensions with values that can be parsed as long milliseconds.

Property Data Type Description Mandatory
type String This should always be interval. Yes
dimension String Dimension to filter. Yes
intervals Array A JSON array containing ISO-8601 interval strings. This defines the time ranges to filter. Yes

Example:

"filter": {
    "type": "interval",
    "dimension": "_time",
    "intervals":[
           "2017-10-01T00:00:00.000+8/2017-10-07T23:59:59.999+8",
           "2017-11-15T00:00:00.000+8/2017-11-16T23:59:59.999+8"
     ]
}


Dimension Facets

Dimension facets allows a GroupBy statement to be executed by a certain dimension.

String

The dimension can be expressed simply by a string of the dimension name.

Example:

{    
     "dimensionFacets": ["agent_gender", "agent_age"]
}

Sorting Orders

The following lists the sorting orders used by bound filter: 

  • lexicographic—Sorts values by converting strings to UTF-8 byte array representations and comparing lexicographically, byte-by-byte. 
  • alphanumeric—Sorts alphanumeric strings in a natural order (see java-alphanum). Not suitable for numbers with decimal points or negative numbers. For example, "1.3" precedes "1.15" in this ordering because "15" has more significant digits than "3". Negative numbers are sorted after positive numbers because numeric characters precede the "-" in the negative numbers. 
  • numeric—Sorts values by numbers, supporting integers, floating point values, and negative numbers. Unparseable strings are treated as nulls and precede numbers. Multiple unparseable strings are sorted lexicographically. 
  • strlen—Sorts values by string length. When there is a tie, the comparator falls back to use the string compareTo method. 

Aggregation Object

Specifies metrics to return, and how to aggregate them.

Property Data Type Description Required Default
metric String The metric to aggregate on. Yes
type String Type of aggregation to perform on the metric. See the following Aggregation Types for more information. Yes
describedAs String New field name for the aggregated output, if renaming is required. No Original name of metric.

Example:

"aggregations": [{    
        "metric": "unique_agents",
        "type": "hyperUnique",
        "describedAs": "Unique Count"
}]

Aggregation Types

The following lists the aggregation types used by aggregation object: 

  • hyperUnique—Unique count. Only applies to hyperlog metrics. 
  • longSum—Sum of values as a 64-bit signed integer. 
  • doubleSum—Sum of values as a 64-bit floating point number. 
  • longMin—Minimum values as a 64-bit signed integer. 
  • doubleMin—Minimum values as a 64-bit floating point number. 
  • longMax—Maximum values as a 64-bit signed integer. 
  • doubleMax—Maximum values as a 64-bit floating point number. 

Multi-Day Aggregation Object

Specifies how data should be aggregated across the multiple days.

Property Data Type Description Mandatory
dimensionFacets List The list of dimensions to aggregate the inner query with additional time dimension.
List of time dimension:
  • hourOfDay—Aggregates by the hour of the day.
  • day—Aggregates by the day.
  • month—Aggregates by the month.
  • year—Aggregates by the year.
Yes for the time dimension only.
aggregations Object Aggregation object determines how the data will be aggregated across the across the multiple days.
  • type—The type of aggregation for the metrics. Available aggregation type:
    • sum—Adds the data across the multiple days based on the groups specified by dimensionFacets.
Yes

Example:

"multiDayAggregations": {
    "dimensionFacets": [        
         "hourOfDay"    
     ],    
     "aggregations": [{
          "type": "sum"
     }]
}

Extraction Functions

Extraction functions define the transformation applied to each dimension value.

Transformations can be applied to both regular (string) dimensions, as well as the special _time dimension, which represent the current time bucket according to the query aggregation granularity.

Note:  __time dimension values will be formatted in ISO-8601 format before getting passed to the extraction function.

Substring Extraction

Returns a substring of the dimension value starting from the supplied index and the desired length. Both index and length are measured by the number of Unicode code units presented in the string as both were encoded in UTF-16. Note that some Unicode characters may be represented by two code units, it has the same behavior as the Java string class—substring method.

If the desired length exceeds the length of the dimension value, the remainder of the string starting at index will be returned. If index is greater than the length of the dimension value, null value will be returned.

Example:

"extractionFn": {
    "type": "substring",
    "index": 1,
    "length": 4
}

Time Format Extraction

Returns the dimension value formatted according to the given format string, time zone, and locale.

Example:

"extractionFn": {    
      "type": "timeFormat",    
      "format": "EEEE",    
      "timeZone": "Asia/Singapore",    
      "locale": "en"
}

By using the range query in the dates object, another step of aggregation is used to aggregate the multiple day using the multiDayAggregations object.

The following section explains the multi-day query concept.

Multi-Day Query

Inner and Outer Query

To enable multi-day query, there will be an additional step to aggregate the data over multiple day query. The query is separated into inner and outer query.

  • Inner Query—Takes care of the multiple day query, produces a long list of results by applying query (including dimension facets and filters) to every selected date. This query will be equivalent to the legacy single day query.
    The following shows an example of what the inner query will produce with two days query and hourly bucket:
Timestamp Location Count
2018-01-01:00:00:00 Area01 12
2018-01-01:01:00:00 Area01 24
2018-01-01:02:00:00 Area01 54
... Area01 ...
2018-01-02:00:00:00 Area01 14
2018-01-02:01:00:00 Area01 42
2018-01-02:02:00:00 Area01 63
... Area01 ...

  • Outer Query—Takes care of the aggregated list of results by grouping them based on the time dimension as well as additional dimensions listed. The outer query groups the data from the inner query by dimension facets property listed in the outer query. It also aggregates the preceding table (Inner query data organization) into the following:

    Aggregate by hourOfDay
    Before (48 rows):
Timestamp Location Count
2018-01-01:00:00:00 Area01 12
2018-01-01:01:00:00 Area01 24
2018-01-01:02:00:00 Area01 54
... Area01 ...
2018-01-02:00:00:00 Area01 14
2018-01-02:01:00:00 Area01 42
2018-01-02:02:00:00 Area01 63
... Area01 ...

                  After (24 rows):

hourOfDay Location Count
00 Area01 26
01 Area01 66
02 Area01 117
... Area01 ...

                  Aggregate by day
                   Before (48 rows):

Timestamp Location Count
2018-01-01:00:00:00 Area01 12
2018-01-01:01:00:00 Area01 24
2018-01-01:02:00:00 Area01 54
... Area01 ...
2018-01-02:00:00:00 Area01 14
2018-01-02:01:00:00 Area01 42
2018-01-02:02:00:00 Area01 63
... Area01 ...

                  After (2 rows):

Day Location Count
01 Area01 ...
02 Area01 ...

Indicator

To indicate whether multiple days queries are aggregated (and later how it is aggregated). It is an additional parameter included in the API request.

The metric aggregated will be every metric listed in the metric parameter in the request.

Multi-Day Aggregation Object

The multi-day aggregation object is an array of objects so that additional multi-day aggregator can be added into the array in the future. The following code shows an example of the multi-day aggregation object.

"multiDayAggregations": {
        "dimensionFacets": [
             "hourOfDay",
             "agent_age"    
        ],
        "aggregations": [{
             "type": "sum"
        }]
}

Note: If multi-day aggregation is used, the value of timestamp field is no longer valid.

Sample API Calls

The API call responses will be divided into several cases:

  • Range Query without Aggregation
  • Range Query with Aggregation
  • Recurring Query
  • ‍Abitrary Days Query
  • Unique of Uniques

Note: The following samples assume that the privacy threshold is 40.

Range Query without Aggregation

Sends query for a range of dates within the start and end date.

Time 2018-01-28 2018-01-29 2018-01-30
00:00 87(87) 51(51) 104(104)
01:00 120(120) 100(100) 120(120)
02:00 145(145) 123(123) 231(231)
03:00 10(-1) 10(-1) 10(-1)

and so on...

The numbers in the parentheses are the actual numbers in the response that are returned after applying privacy filter.

Request:

{
    "dates": {
       "type": "range",
       "beginDate": "2018-01-28",
       "endDate": "2018-01-30"
     },
    "timeSeriesReference": "origin",
    "location": {
       "locationType": "locationHierarchyLevel",
       "levelType": "origin_subzone",
       "id": "OTSZ02"
     },
     "queryGranularity": {
       "type": "period",
       "period": "PT1H"
     },
     "aggregations": [{
         "metric": "total_records",
         "type": "longSum"
     }]
}

Response:

[
 {
   "timestamp": "2018-01-28T00:00:00.000+08:00",
   "event": {
     "longSum_total_records": 80,
     "origin_subzone": "OTSZ02"
   }
 },
 {
   "timestamp": "2018-01-28T01:00:00.000+08:00",
   "event": {
     "longSum_total_records": 65,
     "origin_subzone": "OTSZ02"
   }
 },
 {
   "timestamp": "2018-01-28T02:00:00.000+08:00",
   "event": {
     "longSum_total_records": 42,
     "origin_subzone": "OTSZ02"
   }
 },
 {
   "timestamp": "2018-01-28T03:00:00.000+08:00",
   "event": {
     "longSum_total_records": -1,
     "origin_subzone": "OTSZ02"
   }
 },
 {
   "timestamp": "2018-01-28T04:00:00.000+08:00",
   "event": {
     "longSum_total_records": -1,
     "origin_subzone": "OTSZ02"
   }
 },
 {
   "timestamp": "2018-01-28T05:00:00.000+08:00",
   "event": {
     "longSum_total_records": -1,
     "origin_subzone": "OTSZ02"
   }
 },
 {
   "timestamp": "2018-01-28T06:00:00.000+08:00",
   "event": {
     "longSum_total_records": 50,
     "origin_subzone": "OTSZ02"
   }
 },
 {
   "timestamp": "2018-01-28T07:00:00.000+08:00",
   "event": {
     "longSum_total_records": 163,
     "origin_subzone": "OTSZ02"
   }
 },
 {
   "timestamp": "2018-01-28T08:00:00.000+08:00",
   "event": {
     "longSum_total_records": 203,
     "origin_subzone": "OTSZ02"
   }
 },
 {
   "timestamp": "2018-01-28T09:00:00.000+08:00",
   "event": {
     "longSum_total_records": 248,
     "origin_subzone": "OTSZ02"
   }
 },
 {
   "timestamp": "2018-01-28T10:00:00.000+08:00",
   "event": {
     "longSum_total_records": 419,
     "origin_subzone": "OTSZ02"
   }
 },

...
]

Range Query with Aggregation

In the event that multiple dates and aggregation are selected, the API returns the row sum of the matrix and applies the privacy filter only at the aggregated value.

Time 2018-01-28 2018-01-29 2018-01-30 Aggregated(sum)
00:00 14 4 18 36(-1)
01:00 12 32 12 56(56)
02:00 34 23 87 144(144)
... ... ... ... ...
23:00 43 76 41 160(160)

and so on...

The last column is the vector that returns in the API response and the numbers in parentheses are the actual numbers in the response after privacy filter.

Request:

{    
    "dates": {
       "type": "range",
       "beginDate": "2018-01-28",        
       "endDate": "2018-01-30"    
     },    
     "timeSeriesReference": "origin",    
      "location":{            
          "locationType": "locationHierarchyLevel",
          "levelType": "origin_subzone",        
          "id": "OTSZ02"    
      },
      "queryGranularity": {
          "type": "period",
          "period": "PT1H"    
      },    
     "aggregations": [{
          "metric": "total_records",
          "type": "longSum"
      }],    
      "multiDayAggregations": {        
          "dimensionFacets": [            
               "hourOfDay"        
           ],
      "aggregations": [{            
          "type": "sum"        
      }]
    }
}

Response:

[
 {
   "timestamp": "1970-01-01T07:30:00.000+07:30",
   "event": {
     "hourOfDay": "00",
     "longSum_total_records": 151,
     "origin_subzone": "OTSZ02"
   }
 },
 {
   "timestamp": "1970-01-01T07:30:00.000+07:30",
   "event": {
     "hourOfDay": "01",
     "longSum_total_records": 130,
     "origin_subzone": "OTSZ02"
   }
 },
...
 {
   "timestamp": "1970-01-01T07:30:00.000+07:30",
   "event": {
     "hourOfDay": "23",
     "longSum_total_records": 211,
     "origin_subzone": "OTSZ02"
   }
 }
]

Recurring Query

Sends a query for every period of time with recurring pattern such as specific days of the week or weekdays.

Every Monday—

Request:

{    
     "dates": {
        "type": "range",
        "beginDate": "2018-01-01",
        "endDate": "2018-01-31"    
     },    
     "timeSeriesReference": "origin",
     "location": {        
         "locationType": "locationHierarchyLevel",
         "levelType": "origin_subzone",
         "id": "OTSZ02"    
      },
      "queryGranularity": {
         "type": "period",
         "period": "PT1H"
      },    
      "aggregations": [{        
          "metric": "total_records",
          "type": "longSum"
      }],    
      "filter": {        
         "type": "selector",        
         "dimension": "__time",        
         "value": "Monday",
         "extractionFn":{   
             "type": "timeFormat",
             "format": "EEEE",
             "timeZone": "Asia/Singapore",
             "locale": "en"
         }    
     },    
     "multiDayAggregations": {
           "dimensionFacets": [
                "hourOfDay"
            ],        
            "aggregations": [{            
                "type": "sum"        
            }]    
}}

Response:

[
   {
       "timestamp": "1970-01-01T07:30:00.000+07:30",
       "event": {
           "hourOfDay": "00",
           "longSum_total_records": 266,
           "origin_subzone": "OTSZ02"
       }
   },
   {
       "timestamp": "1970-01-01T07:30:00.000+07:30",
       "event": {
           "hourOfDay": "01",
           "longSum_total_records": 351,
           "origin_subzone": "OTSZ02"
       }
   },
   {
       "timestamp": "1970-01-01T07:30:00.000+07:30",
       "event": {
           "hourOfDay": "02",
           "longSum_total_records": 219,
           "origin_subzone": "OTSZ02"
       }
   },
   {
       "timestamp": "1970-01-01T07:30:00.000+07:30",
       "event": {
           "hourOfDay": "03",
           "longSum_total_records": 181,
           "origin_subzone": "OTSZ02"
       }
   },
   ...
   {
       "timestamp": "1970-01-01T07:30:00.000+07:30",
       "event": {
           "hourOfDay": "23",
           "longSum_total_records": 362,
           "origin_subzone": "OTSZ02"
       }
   }
]

Every Weekday—

Request:

{
   "dates": {
       "type": "range",
       "beginDate": "2018-01-01",
       "endDate": "2018-01-31"
   },
   "timeSeriesReference": "origin",
   "location": {
       "locationType": "locationHierarchyLevel",
       "levelType": "origin_subzone",
       "id": "OTSZ02"
   },
   "queryGranularity": {
       "type": "period",
       "period": "PT1H"
   },
   "aggregations": [{
       "metric": "total_records",
       "type": "longSum"
   }],
   "filter": {
       "type": "or",
       "fields":[
       {
           "type": "selector",
           "dimension": "__time",
           "value": "Monday",
           "extractionFn": {
               "type": "timeFormat",
               "format": "EEEE",
               "timeZone": "Asia/Singapore",
               "locale": "en"
           }
       },
       {
           "type": "selector",
           "dimension": "__time",
           "value": "Tuesday",
           "extractionFn": {
               "type": "timeFormat",
               "format": "EEEE",
               "timeZone": "Asia/Singapore",
               "locale": "en"
           }
       },
       {
           "type": "selector",
           "dimension": "__time",
           "value": "Wednesday",
           "extractionFn": {
               "type": "timeFormat",
               "format": "EEEE",
               "timeZone": "Asia/Singapore",
               "locale": "en"
           }
       },
       {
           "type": "selector",
           "dimension": "__time",
           "value": "Thursday",
           "extractionFn": {
               "type": "timeFormat",
               "format": "EEEE",
               "timeZone": "Asia/Singapore",
               "locale": "en"
           }
       },
       {
           "type": "selector",
           "dimension": "__time",
           "value": "Friday",
           "extractionFn": {
               "type": "timeFormat",
               "format": "EEEE",
               "timeZone": "Asia/Singapore",
               "locale": "en"
           }
       }]
   },
   "multiDayAggregations": {
       "dimensionFacets": [
           "hourOfDay"
       ],
       "aggregations": [{
           "type": "sum"
       }]
   }
}

Response:

[
   {
       "timestamp": "1970-01-01T07:30:00.000+07:30",
       "event": {
           "hourOfDay": "00",
           "longSum_total_records": 1165,
           "origin_subzone": "OTSZ02"
       }
   },
   {
       "timestamp": "1970-01-01T07:30:00.000+07:30",
       "event": {
           "hourOfDay": "01",
           "longSum_total_records": 1103,
           "origin_subzone": "OTSZ02"
       }
   },
   {
       "timestamp": "1970-01-01T07:30:00.000+07:30",
       "event": {
           "hourOfDay": "02",
           "longSum_total_records": 921,
           "origin_subzone": "OTSZ02"
       }
   },
   ...
   {
       "timestamp": "1970-01-01T07:30:00.000+07:30",
       "event": {
           "hourOfDay": "23",
           "longSum_total_records": 1619,
           "origin_subzone": "OTSZ02"
       }
   }
]

Arbitrary Date Query

Sends a query for several arbitrary dates within the date range.

Request:

{    
     "dates": {        
        "type": "range",        
        "beginDate": "2018-01-01",        
        "endDate": "2018-01-31"    
        },    
      "timeSeriesReference": "origin",    
         "location" {        
             "locationType": "locationHierarchyLevel",        
             "levelType": "origin_subzone",        
             "id": "OTSZ02"    
       },    
       "queryGranularity": {        
           "type": "period",        
           "period": "PT1H"    
       },    
       "aggregations": [{        
           "metric": "total_records",        
           "type": "longSum"
       }],    
       "filter": {        
          "type": "or",        
          "fields":[        
          {            
             "type": "selector",            
             "dimension": "__time",            
             "value": "2018-01-01",            
             "extractionFn": {                
                 "type": "timeFormat",                
                 "format": "YYYY-MM-DD",                
                 "timeZone": "Asia/Singapore",
                 "locale": "en"            
                 }        
           },
           {
              "type": "selector",
              "dimension": "__time",
              "value": "2018-01-15",
              "extractionFn": {                
                 "type": "timeFormat",                
                 "format": "YYYY-MM-DD",                
                 "timeZone": "Asia/Singapore",
                 "locale": "en"            
                 }
            }]    
      }    
           "multiDayAggregations": {
               "dimensionFacets": [            
                    "hourOfDay"        
           ],        
           "aggregations": [{
                "type": "sum"        
            }]    
       }
}

Response:

[
   {
       "timestamp": "1970-01-01T07:30:00.000+07:30",
       "event": {
           "hourOfDay": "00",
           "longSum_total_records": 169,
           "origin_subzone": "OTSZ02"
       }
   },
   {
       "timestamp": "1970-01-01T07:30:00.000+07:30",
       "event": {
           "hourOfDay": "01",
           "longSum_total_records": 251,
           "origin_subzone": "OTSZ02"
       }
   },
   {
       "timestamp": "1970-01-01T07:30:00.000+07:30",
       "event": {
           "hourOfDay": "02",
           "longSum_total_records": 140,
           "origin_subzone": "OTSZ02"
       }
   },
    ...
   {
       "timestamp": "1970-01-01T07:30:00.000+07:30",
       "event": {
           "hourOfDay": "23",
           "longSum_total_records": 117,
           "origin_subzone": "OTSZ02"
       }
   }
]

Unique of Uniques

Sends query over a certain period, grouped by hours to obtain the total number of unique people for the entire period.

Request:

{    
      "dates": {
         "type": "range",
         "beginDate": "2018-01-01",
         "endDate": "2018-01-31"    
       },    
       "timeSeriesReference": "origin",
       "location": {        
           "locationType": "locationHierarchyLevel",
           "levelType": "origin_subzone",
           "id": "OTSZ02"    
       },    
       "queryGranularity": {        
           "type": "period",
           "period": "P31D"    
       },
       "aggregations": [{        
           "metric": "uniqueAgents",
           "type": "hyperUnique"    
       }],    
       "dimenstionFacets": [{
           "dimension": "__time",
           "extractionFn": {            
              "format": "HH",
              "type": "timeFormat",
              "timeZone": "Asia/Singapore"
            },
            "outputName": "hourOfDay",            
            "type": "extraction"    
        }]
}

Response:

[
   {
       "timestamp": "2018-01-01T00:00:00.000+08:00",
       "event": {
           "hourOfDay": "00",
           "hyperUnique_unique_agents": 1557,
           "origin_subzone": "OTSZ02"
       }
   },
   {
       "timestamp": "2018-01-01T00:00:00.000+08:00",
       "event": {
           "hourOfDay": "01",
           "hyperUnique_unique_agents": 1394,
           "origin_subzone": "OTSZ02"
       }
   },
   {
       "timestamp": "2018-01-01T00:00:00.000+08:00",
       "event": {
           "hourOfDay": "02",
           "hyperUnique_unique_agents": 1122,
           "origin_subzone": "OTSZ02"
       }
   },
    ...
   {
       "timestamp": "2018-01-01T00:00:00.000+08:00",
       "event": {
           "hourOfDay": "23",
           "hyperUnique_unique_agents": 1965,
           "origin_subzone": "OTSZ02"
       }
   }
]