# Overview

### What is InnerSpace?

InnerSpace specializes in providing advanced data-driven insights into how indoor spaces are used. The platform utilizes existing Wi-Fi infrastructure to gather data on occupancy and space utilization without the need for additional sensors. This approach allows organizations to optimize their real estate and workspace environments by understanding how different spaces are used, helping in making informed decisions about space planning and resource allocation. InnerSpace’s technology provides near real-time data on space utilization, pathways, and behaviors, which can be crucial for hybrid workplace strategies and optimizing real estate investments.

For more information go to <https://innerspace.io>

#### Terms of Service

<https://www.innerspace.io/terms>

### Accessing Data

Data can be accessed via a GraphQL API, defined in this document.

#### API Endpoints

A graphQL api is available for use by customers with valid contracts.

```
# graphql api
https://api.innerspace.io/v2
```


# Metrics and Features

What insights are available in the API?

InnerSpace data is available via API and published nightly.

### Definitions

**Metrics** are numbers, single values.

**Features** are objects with multiple values.

The following metrics are available via API.

Scope refers to whether a metric applies to a building, a site (typically a floor), or a zone (a small area defined by a polygon).

<table><thead><tr><th width="221.86175537109375">Metric or Feature Name</th><th width="104.738037109375">Type</th><th width="96">Scope</th><th width="319.97271728515625">Description</th></tr></thead><tbody><tr><td>occupancy_mean_room</td><td>metric</td><td>building/site</td><td>For a given site, the mean of the per minute occupancy counts. Only considering zones that have a corresponding zone type listed in site_config.room_zone_types</td></tr><tr><td>occupancy_percent_capacity_max_room</td><td>feature</td><td>building/site</td><td>For a given site, which zone has the largest occ % capacity mean. Only considering zones that have a corresponding zone type listed in site_config.room_zone_types</td></tr><tr><td>occupancy_percent_capacity_min_room</td><td>feature</td><td>building/site</td><td>For a given site, which zone has the smallest occ % capacity mean. Only considering zones that have a corresponding zone type listed in site_config.room_zone_types</td></tr><tr><td>occupancy_pos_mean_room</td><td>metric</td><td>building/site</td><td><p>For a given site, the mean of the non-zero per minute occupancy counts. Only considering zones that have a corresponding zone type listed in site_config.room_zone_types.  Where </p><p><em>pos</em> indicates that the feature applies/is calculated during the times when there is a person in the space, and disregards all times the space is empty</p></td></tr><tr><td>occupancy_pos_percent_capacity_max_room</td><td>feature</td><td>building/site</td><td><p>For a given site, which zone has the largest occ pos % capacity mean. Only considering zones that have a corresponding zone type listed in site_config.room_zone_types.  Where </p><p><em>pos</em> indicates that the feature applies/is calculated during the times when there is a person in the space, and disregards all times the space is empty</p></td></tr><tr><td>occupancy_pos_percent_capacity_min_room</td><td>feature</td><td>building/site</td><td><p>For a given site, which zone has the smallest occ pos % capacity mean. Only considering zones that have a corresponding zone type listed in site_config.room_zone_types.  Where </p><p><em>pos</em> indicates that the feature applies/is calculated during the times when there is a person in the space, and disregards all times the space is empty</p></td></tr><tr><td>service_rate_max</td><td>metric</td><td>zone</td><td>The maximum service rate over a given period of time.   Where service rate represents the number of people served per minute.  This is typically measured for a line.</td></tr><tr><td>service_rate_mean</td><td>metric</td><td>zone</td><td>The average service rate over a given period of time.  Where service rate represents the number of people served per minute.  This is typically measured for a line.</td></tr><tr><td>time_available_percent_room</td><td>metric</td><td>building/site</td><td>Using the per minute occupancy counts, the percentage of time in which a zone/site is available, only considering zones that have a corresponding zone type listed in site_config.room_zone_types</td></tr><tr><td>time_available_sum_room</td><td>metric</td><td>building/site</td><td>Using the per minute occupancy counts, the amount of time (in seconds) in which a zone/site is available, only considering that have a corresponding zone type listed in site_config.room_zone_types</td></tr><tr><td>utilization_percent_room</td><td>metric</td><td>building/site</td><td>Using the per minute occupancy counts, the percentage of time in which a zone/site is in use, only considering zones that have a corresponding zone type listed in site_config.room_zone_types</td></tr><tr><td>wait_time_max</td><td>metric</td><td>zone</td><td>Maximum wait time</td></tr><tr><td>session_duration_mean</td><td>metric</td><td>building/site</td><td><p>For a given site, mean of the session durations during the timeframe.</p><ul><li>Session duration is defined as the time (in seconds) between the end and start of the session.</li><li>This is different from the visit duration which is the time between the end and start of a visit (a visit is zone specific, so a session is made up of multiple visits)</li></ul><p>However, there are sessions in which a hash starts at floor A, spends some time on other floor(s), and then later revisits floor A. Thus, the session_duration is calculated as the difference in time between the revisit to A and the original visit to A. This would seem to be an inaccurate representation of the session duration for floor A. See Figure A in the Appendix (or link below) for an example.</p><p>https://www.notion.so/innerspace/Appendix-41890def10c641ae85342ed760f2cf56</p></td></tr><tr><td>wait_time_mean</td><td>metric</td><td>zone</td><td>Mean wait time</td></tr><tr><td>time_on_site_dist</td><td>feature</td><td>building/site</td><td>Distribution representing number of hashes that spent 1 hour on site, 2 hours on site, … 24 hours on site, etc.</td></tr><tr><td>weekly_frequency_dist</td><td>feature</td><td>building/site</td><td>Distribution representing number of hashes that spent 1 day on site, 2 days, …. 7 days on site, etc.</td></tr><tr><td>occupancy_max</td><td>metric</td><td>zone, building/site</td><td>Using the occupancy counts at each minute, the maximum occupancy count during the timeframe.</td></tr><tr><td>occupancy_max_zone</td><td>feature</td><td>building/site</td><td>For a given site, which zone has the largest mean occupancy. Only considering zones that have a corresponding zone type listed in site_config.zone_occupancy_types</td></tr><tr><td>occupancy_mean</td><td>metric</td><td>zone, building/site</td><td>Using the occupancy counts at each minute (per minute occupancy), the mean occupancy of the timeframe</td></tr><tr><td>occupancy_min_zone</td><td>feature</td><td>building/site</td><td>For a given site, which zone has the smallest mean occupancy. Only considering zones that have a corresponding zone type listed in site_config.zone_occupancy_types</td></tr><tr><td>occupancy_percent_capacity_mean</td><td>metric</td><td>zone, building/site</td><td>Using the occupancy counts at each minute, mean of the occupancy in the timeframe as a percentage of the target upper occupancy. Calculated as the occupancy_mean, divided by the site_config.occupancy_target_upper for the site multiplied by 100.</td></tr><tr><td>occupancy_pos_max_zone</td><td>feature</td><td>building/site</td><td><p>For a given site, which zone has the largest occupancy_pos_mean. Only considering zones that have a corresponding zone type listed in site_config.zone_occupancy_types.  Where </p><p><em>pos</em> indicates that the feature applies/is calculated during the times when there is a person in the space, and disregards all times the space is empty</p></td></tr><tr><td>occupancy_pos_min_zone</td><td>feature</td><td>building/site</td><td><p>For a given site, which zone has the smallest occupancy_pos_mean. Only considering zones that have a corresponding zone type listed in site_config.zone_occupancy_types.  Where </p><p><em>pos</em> indicates that the feature applies/is calculated during the times when there is a person in the space, and disregards all times the space is empty</p></td></tr><tr><td>occupancy_pos_percent_capacity_mean</td><td>metric</td><td>zone, building/site</td><td><p>Using the occupancy counts at each minute, only including minutes where occupancy > 0, mean of the occupancy in the timeframe as a percentage of the target upper occupancy. Calculated as the occupancy_pos_mean, divided by the site_config.occupancy_target_upper for the site multiplied by 100.  Where </p><p><em>pos</em> indicates that the feature applies/is calculated during the times when there is a person in the space, and disregards all times the space is empty</p></td></tr><tr><td>occupancy_target</td><td>metric</td><td>zone, building/site</td><td>Target occupancy level</td></tr><tr><td>occupancy_target_lower</td><td>metric</td><td>zone, building/site</td><td>Lower bound of the target occupancy “range”</td></tr><tr><td>occupancy_target_upper</td><td>metric</td><td>zone, building/site</td><td>Upper bound of the target occupancy “range”</td></tr><tr><td>occupany_pos_mean</td><td>metric</td><td>zone, building/site</td><td><p>Using the occupancy counts at each minute, but only when occupancy > 0, the mean occupancy of the timeframe.  Where </p><p><em>pos</em> indicates that the feature applies/is calculated during the times when there is a person in the space, and disregards all times the space is empty</p></td></tr><tr><td><del>people_new_count</del><br>(deprecated)</td><td>metric</td><td>zone, building/site</td><td><p>From session summary, counts number of hashes who are in a site/zone for the first time during the timeframe. The caveat of this metric is that since we only consider session_summary up to 1 month back of the desired start date, each hash is considered new sometime during this period. This is not quite accurate as many hashes would have already been seen beyond a month ago. For example,</p><p>The start date is Jan 1, the end date is Jan 2nd. We consider session summary roughly between Dec 1st to Jan 2nd. Suppose we saw hash ABC on Jan 1st and we know the only previous time we saw it was on Nov 25. Hash ABC will be counted as ‘new’, since in the timeframe of Dec 1st to Jan 2nd, it was seen for the first time on Jan 1st.</p></td></tr><tr><td><del>people_returning_count</del><br>(deprecated)</td><td>metric</td><td>zone, building/site</td><td>people_unique_count - people_new_count</td></tr><tr><td>people_unique_count</td><td>metric</td><td>zone, building/site</td><td>From session summary, counts distinct hashes during the timeframe.</td></tr><tr><td><del>session_count</del><br>(deprecated)</td><td>metric</td><td>zone, building/site</td><td>From session summary, count of distinct session_ids during the timeframe.</td></tr><tr><td>time_available</td><td>metric</td><td>zone, building/site</td><td>Using the per minute occupancy counts, the amount of time (in seconds) in which a zone/site is available during a given timeframe.</td></tr><tr><td>time_available_percent</td><td>metric</td><td>zone, building/site</td><td>The percentage of time in which a zone/site is available. Calculated as 100 - utilization_percent</td></tr><tr><td>time_on_site_mean</td><td>metric</td><td>building/site</td><td><p>For a given site, mean of each hash’s time on site during the day</p><p>Q: What is the difference between this metric and session_duration_mean? A: hashes can have multiple sessions in the same day, so time on site is the total time, rather than session specific.</p></td></tr><tr><td>utilization_percent</td><td>metric</td><td>zone, building/site</td><td>Using the per minute occupancy counts, the percentage of time in which a zone/site is in use.</td></tr><tr><td><del>visit_count</del><br>(deprecated)</td><td>metric</td><td>zone</td><td>Number of visits (rows in session summary) during the timeframe.<br><br>Number of times any person spent more than a few minutes in a space between the start and end time.<br><br>Example:<br>PersonA stayed in zone1 for from 10:01am -> 10:14am.<br>PersonB stayed in zone1 from 10:07am -> 10:38am<br>PersonA returned to zone1 from 10:37am -> 10:51am.<br><br>This counts as 3 visits within the hour 10am->11am.</td></tr><tr><td>visit_duration_mean</td><td>metric</td><td>zone</td><td>For a given zone, average duration of each visit during the timeframe.<br><br>The average duration each person stayed during the timeframe.<br><br>Example:<br>PersonA stayed in zone1 for from 10:01am -> 10:14am (13mins)<br>PersonB stayed in zone1 from 10:07am -> 10:38am (21mins)<br>PersonA returned to zone1 from 10:37am -> 10:51am (14mins)<br><br>3 visits.<br>mean = (13+21+14)/3 = 16mins</td></tr><tr><td>weekly_frequency_mean</td><td>metric</td><td>building/site</td><td>Average number of distinct days each hash is on site</td></tr><tr><td></td><td></td><td></td><td></td></tr></tbody></table>


# Groups

All metrics are available in the context of a group, or cohort of people.  A group can represent a team of people, like the Engineering Team or Marketing Team.  A group can also be defined by a behaviour, like  Early Birds, or Night Owls.

Groups can be inferred, meaning InnerSpace will use algorithms to decide which people are included in a particular group.  For example, members of the ***Engineering Team*** group might be inferred by how frequently they visit the Engineering Team area.  Similarly, members of the ***Early Birds*** group may represent all people who consistently come to the office before 7am.

Groups can also be explicitly defined by providing a list of MAC addresses.  This ensures group membership exactly matches your internal team.


# Authentication (v2)

How to authenticate to the InnerSpace API

### Summary

The InnerSpace API authenticates M2M clients using a session token. Exchange your credentials once at /v2/oauth/token to receive a session\_id, then include it as a header on all subsequent API calls.

### Developer Access

Contact **<sales@innerspace.io>** for developer access.  They will provide you with a ClientID and ClientSecret.

They will need to know the usage of the credentials - whether it is needed for a backend service, a native application, a single-page web app or regular web app.

### Backend Services

Backend services authenticate using the Client Credentials flow. Call /v2/oauth/token with your client\_id and client\_secret to receive a session\_id, then include it as an X-Session-Id header on all subsequent API calls

### Examples

{% code title="Python Example" overflow="wrap" lineNumbers="true" %}

```python
import requests            

  URL = "https://api.innerspace.io"                                                                                                              
   
  def get_session_id():                                                                                                                                    
      response = requests.post(                          
          url=f"{URL}/v2/oauth/token",
          json={                                                                                                                                           
              "client_id": "Your Client ID",
              "client_secret": "Your Client Secret",                                                                                                       
              "audience": "https://global-config.innerspace.io",
          },
          headers={"Content-Type": "application/json"},
      )                                                                                                                                                    
      response.raise_for_status()
      return response.json()["session_id"]                                                                                                                 
                                                         

  sample_graphql = {
      "operationName": "sample_graphql",
      "variables": {},
      "query": """                                                                                                                                         
          query sample_graphql {
              BUILDING_INSIGHTS(                                                                                                                           
                  where: {                               
                      DATE_PARTITION: {_eq: 20250501},
                      BUILDING_ID: {_eq: 71},
                      GRANULARITY: {_eq: "PT1H"},                                                                                                          
                      GROUP_ID: {_eq: "all-e707b3a8-0732-49ae-9156-13ff1138540e"},
                      SITE_ID: {_eq: 900000222}                                                                                                            
                  },                                                                                                                                       
                  order_by: {DATE_TIME_START: desc}
              ) {                                                                                                                                          
                  BUILDING_ID                            
                  DATE_TIME_START
                  OCCUPANCY_MEAN
                  GRANULARITY
                  GROUP_ID
                  SITE_ID                                                                                                                                  
              }
          }                                                                                                                                                
      """,                                               
  }

  session_id = get_session_id()
  response = requests.post(
      url=f"{URL}/v2/api",
      json=sample_graphql,
      headers={"X-Session-Id": session_id},                                                                                                                
  )
                                                                                                                                                           
  # re-authenticate if session expired and retry once                                                                                                      
  if response.status_code == 401:
      session_id = get_session_id()                                                                                                                        
      response = requests.post(                          
          url=f"{URL}/v2/api",
          json=sample_graphql,                                                                                                                             
          headers={"X-Session-Id": session_id},
      )                                                                                                                                                    
                                                         
  response.raise_for_status()
  print(response.json())
```

{% endcode %}

{% code title="CURL Example" %}

```bash
# Retrieve session ID       
  curl --request POST \
       --url https://api.innerspace.io/v2/oauth/token \                                                                                               
       --header 'Content-Type: application/json' \                                                                                                         
       --data '{"client_id":"Your Client ID","client_secret":"Your Client Secret","audience":"https://global-config.innerspace.io"}'                       
                                                                                                                                                           
  # Call API using session ID                            
  curl --request POST \                                                                                                                                    
       --url https://api.innerspace.io/v2/api \     
       --header 'Content-Type: application/json' \
       --header 'X-Session-Id: <session_id>' \                                                                                                             
       --data '{"operationName":"sample_graphql","variables":{},"query":"query sample_graphql { BUILDING_INSIGHTS(where: {DATE_PARTITION: {_eq: 20250501}, 
  BUILDING_ID: {_eq: 71}, GRANULARITY: {_eq: \"PT1H\"}, GROUP_ID: {_eq: \"all-e707b3a8-0732-49ae-9156-13ff1138540e\"}, SITE_ID: {_eq: 900000222}},         
  order_by: {DATE_TIME_START: desc}) { BUILDING_ID\n DATE_TIME_START\n OCCUPANCY_MEAN\n GRANULARITY\n GROUP_ID\n SITE_ID\n }\n }"}'
```

{% endcode %}

{% code title="Python GraphQL" lineNumbers="true" %}

```python
import sys
import logging
import requests
from gql import gql, Client
from gql.transport.aiohttp import AIOHTTPTransport
from gql.transport.aiohttp import log as requests_logger

requests_logger.setLevel(logging.WARNING)

API_BASE_URL = "https://api.innerspace.io"
AUDIENCE = "https://global-config.innerspace.io"
CLIENT_ID = "Your Client ID"
CLIENT_SECRET = "Your Client Secret"


def get_session_id():
    response = requests.post(
        url=f"{API_BASE_URL}/v2/oauth/token",
        json={
            "client_id": CLIENT_ID,
            "client_secret": CLIENT_SECRET,
            "audience": AUDIENCE,
        },
    )
    response.raise_for_status()
    return response.json()["session_id"]


def main(argv):
    session_id = get_session_id()

    transport = AIOHTTPTransport(
        url=f"{API_BASE_URL}/v2/api",
        headers={"X-Session-Id": session_id},
    )

    client = Client(transport=transport, fetch_schema_from_transport=False)

    query = """
        query sample_graphql {
            BUILDING_INSIGHTS(
                where: {
                      DATE_PARTITION: {_eq: 20250501},                                                                                                     
                      BUILDING_ID: {_eq: 71},
                      GRANULARITY: {_eq: "PT1H"},                                                                                                          
                      GROUP_ID: {_eq: "00000000-0000-0000-0000-000000000000"},
                      SITE_ID: {_eq: 900000222} 
                },
                order_by: {DATE_TIME_START: desc}
            ) {
                  BUILDING_ID                                                                                                                              
                  DATE_TIME_START
                  OCCUPANCY_MEAN                                                                                                                           
                  GRANULARITY                            
                  GROUP_ID
                  SITE_ID
            }
        }
    """
    print(client.execute(gql(query)))


if __name__ == "__main__":
    main(sys.argv)  
```

{% endcode %}


# Granularity

### Summary

All metrics and features are published for a period of time, defined by [ISO 8601 Duration syntax](https://en.wikipedia.org/wiki/ISO_8601). &#x20;

Meaning, a metric published at the PT1H granularity is meant to represent the aggregate value of the metric for the entire hour.

### Granularities Available

* ~~PT15M - quarter hour aggregate data~~ (deprecating)
* PT1H - hourly aggregate data
* P1D - daily aggregate data
* P1W - weekly aggregate data
* P1M - monthly aggregate data


# DateTime

Everything is stored in UTC.

This means, if your building is in PST (UTC-8), then for a date\_partition = `20250501` the  `date_time_start` values that will be returned ranging from `2025-05-02 07:00:00.000` to `2025-05-03 06:00:00.000` (DST)


# GraphQL Playground

Many playgrounds or developer tools exist to explore and test a GraphQL API.

One tool is [Hasura's GraphiQL](https://cloud.hasura.io/public/graphiql?header=content-type:application/json\&header=Authorization:Bearer%20%3Center%20your%20token%20here%3E\&endpoint=https://api.innerspace.io) , which provides Schema Introspection, allowing you to see what entities are available and easily construct queries.  All that is required is a bearer token, as described on the [Authentication (v1.5 - deprecated)](/deprecated-api-2025-05-26/authentication-v1.5-deprecated) page.


# API Reference (v2)

Description of the core GraphQL API calls (2025-05)

This is a graphql API.

Insights are published for buildings, floors (site) and zones (polygon on a floorplan).

All GraphQL APIs are available at `https://api.innerspace.io`

### Example

The following example uses `building_id` `71`, which is the DEMO building.

{% code title="Python GraphQL" lineNumbers="true" %}

```python
import sys
import logging
import requests
from gql import gql, Client
from gql.transport.aiohttp import AIOHTTPTransport
from gql.transport.aiohttp import log as requests_logger

requests_logger.setLevel(logging.WARNING)

API_BASE_URL = "https://api.innerspace.io"
AUDIENCE = "https://global-config.innerspace.io"
CLIENT_ID = "Your Client ID"
CLIENT_SECRET = "Your Client Secret"


def get_session_id():
    response = requests.post(
        url=f"{API_BASE_URL}/v2/oauth/token",
        json={
            "client_id": CLIENT_ID,
            "client_secret": CLIENT_SECRET,
            "audience": AUDIENCE,
        },
    )
    response.raise_for_status()
    return response.json()["session_id"]


def main(argv):
    session_id = get_session_id()

    transport = AIOHTTPTransport(
        url=f"{API_BASE_URL}/v2/api",
        headers={"X-Session-Id": session_id},
    )

    client = Client(transport=transport, fetch_schema_from_transport=False)

    query = """
        query sample_graphql {
            BUILDING_INSIGHTS(
                where: {
                      DATE_PARTITION: {_eq: 20250501},                                                                                                     
                      BUILDING_ID: {_eq: 71},
                      GRANULARITY: {_eq: "PT1H"},                                                                                                          
                      GROUP_ID: {_eq: "00000000-0000-0000-0000-000000000000"},
                      SITE_ID: {_eq: 900000222} 
                },
                order_by: {DATE_TIME_START: desc}
            ) {
                  BUILDING_ID                                                                                                                              
                  DATE_TIME_START
                  OCCUPANCY_MEAN                                                                                                                           
                  GRANULARITY                            
                  GROUP_ID
                  SITE_ID
            }
        }
    """
    print(client.execute(gql(query)))


if __name__ == "__main__":
    main(sys.argv)
```

{% endcode %}


# building\_insights

Publish Interval: Nightly

Fetch metrics at a building or site (typically a floor) level, where a metric is a measurement or insight with a single result.  Some metrics are a complex measurement or insight, possibly with multiple results.

When site\_id is populated, this is a floor level insight.\
When site\_is is null, this is a building level insight.

***\<metric\_name>*** would be one or more of the metrics outlined in [Metrics and Features](/metrics-and-features)

<pre class="language-graphql"><code class="lang-graphql">// query
query building_insights(
  $limit: Int,
  $offset: Int,
  $order_by: [building_metrics_order_by!],
  $where: building_metrics_bool_exp
) {
  BUILDING_INSIGHTS(
    limit: $limit,
    offset: $offset,
    order_by: $order_by,
    where: $where
  ) {
<strong>    DATE_TIME_START
</strong>    BUILDING_ID
    SITE_ID
    GRANULARITY
    ZONE_LABEL
    GROUP_ID
    &#x3C;METRIC_NAME>
  }
}
</code></pre>


# zone\_insights

Publish Interval: Nightly

A zone, also known as a space, is a closed area on a map.

Fetch metrics at a zone level, where a metric is a measurement or insight with a single result.  Some metrics are a complex measurement or insight, possibly with multiple results.

***\<metric\_name>*** would be one or more of the metrics outlined in [Metrics and Features](/metrics-and-features)

```graphql
// query
query zone_insights(
  $limit: Int,
  $offset: Int,
  $order_by: [zone_metrics_order_by!],
  $where: zone_metrics_bool_exp
) {
  ZONE_INSIGHTS(
    limit: $limit,
    offset: $offset,
    order_by: $order_by,
    where: $where
  ) {
    DATE_TIME_START
    BUILDING_ID
    SITE_ID
    GRANULARITY
    ZONE_LABEL
    GROUP_ID
    <METRIC_NAME>
  }
}
```


# zone\_type\_insights

Refresh Interval: nightly.

Zones, also known as spaces, can be assigned a type - similar to a category.

Fetch metrics for a `zone_type`, where a metric is a measurement or insight with a single result.  Some metrics are a complex measurement or insight, possibly with multiple results.

A set of zone types are calculated together as a whole to represent a floor or building.  Zone types are split into different views of a floor by `zone_type_view`, which represents a set of types

***\<metric\_name>*** would be one or more of the metrics outlined in [Metrics and Features](/metrics-and-features)

```graphql
// query
query zone_type_insights(
  $limit: Int,
  $offset: Int,
  $order_by: [zone_metrics_order_by!],
  $where: zone_metrics_bool_exp
) {
  ZONE_TYPE_INSIGHTS(
    limit: $limit,
    offset: $offset,
    order_by: $order_by,
    where: $where
  ) {
    DATE_TIME_START
    BUILDING_ID
    SITE_ID
    GRANULARITY
    ZONE_TYPE
    ZONE_TYPE_VIEW
    GROUP_ID
    <METRIC_NAME>
  }
}
```


# buildings

Refresh interval: hourly.

Retrieve a list of buildings this user has access to.

```graphql
query MyQuery {
  BUILDING {
    ID
    NAME
    DESCRIPTION
    TIME_ZONE
  }
}
```


# floors

Refresh Interval: hourly.

A floor in a building.  Each floor may contain multiple [sites](/api-reference-v2/sites).

```graphql
// query
query MyQuery {
  FLOOR {
    BUILDING_ID
    FLOOR_CODE
    ID
    LEVEL
    NAME
    TIME_ZONE
  }
}

```


# sites

Refresh Interval: hourly.

A site represents a contiguous area on a floor that is covered by the InnerSpace solution.  Most often, an entire floor is covered, thus the area of a site is no different than a floor.  However, some floors are very large, thus only certain areas of the floor are covered - in this case, a floor may have multiple 'sites'.

Retrieve a list of sites (typically a floor) available in a building.

```graphql
// query
query MyQuery {
  SITE(where: {BUILDING_ID: {_eq: 71}}) {
    ALTITUDE
    BUILDING_ID
    FLOOR_ID
    ID
    LATITUDE
    LONGITUDE
    SITE_CODE
    TIME_ZONE
  }
}

```


# groups

Update interval: nightly.

Retrieve a list of [groups](/groups), or cohorts of people, to return metrics or features for.

```graphql
// query
query MyQuery {
  GROUPS {
    BUILDING_ID
    CUSTOMER_ID
    DATE_TIME_END
    DATE_TIME_LAST_UPDATED
    DATE_TIME_START
    DESCRIPTION
    ID
    NAME
    SITE_ID
    TYPE
  }
}

```


# zones

Update interval: hourly

A zone, also known as a space, is a closed area on a map.

Returns all metadata associated to a zone, including `external_id`, which represents the string used by the customer to identify this area.

```graphql
// query
query MyQuery {
  ZONE {
    BUILDING_ID
    CAPACITY
    DESCRIPTION
    EXTERNAL_ID
    FLOOR_ID
    ID
    NAME
    SITE_ID
    TYPES
    ZONE_SET_ID
  }
}

```


# zone\_set

Refresh Interval: hourly.

A zone set represents a collection of zones.

Each site (or floor) may require multiple views or layouts.  For example, one view of a floor would cover every individual seat, while another may cover the higher level team areas.  InnerSpace would generate metrics for each different zone set.

Retrieve a list of zone\_sets available in a building.

```graphql
// query
query MyQuery {
  ZONE_SET {
    BUILDING_ID
    DESCRIPTION
    FLOOR_ID
    ID
    SITE_ID
    TYPE
  }
}

```


# zone\_occupancy (realtime) \[TBD]

Realtime zone occupancy

InnerSpace publishes occupancy of a zone in near realtime, typically with a latency of 30s-5mins. Latency is most strongly influenced by the frequency by which the WIFI Infrastructure pushes data to InnerSpace for processing.

### Example

```graphql
// query
query zone_metrics(
  $limit: Int,
  $offset: Int,
  $order_by: [zone_occupancy_order_by!],
  $where: zone_occupancy_bool_exp
) {
  zone_occupancy(
    limit: $limit,
    offset: $offset,
    order_by: $order_by,
    where: $where
  ) {
    site_id
    zone_id
    date_time
    granularity
    occupancy
  }
}
```


# zone\_occupancy (nightly) \[TBD]

Return the occupancy of a zone for a particular period of time.

```graphql
// query
query Zone_occupancy(
  $limit: Int,
  $offset: Int,
  $order_by: [zone_occupancy_order_by!],
  $where: zone_occupancy_bool_exp
) {
  zone_occupancy(
    limit: $limit,
    offset: $offset,
    order_by: $order_by,
    where: $where
  ) {
    date_time
    granularity
    id
    occupancy
    site_id
    zone_id
    zone_set_id
  }
}
```


# Deprecated API (2025-05-26)

The narrow style API (metrics-api.innrspc.com) is now deprecated.


# Authentication (v1.5 - deprecated)

How to authenticate to the InnerSpace API

### Summary

API access is available to users via their JWT bearer token, or can be made available to a backend system via an M2M token issued by InnerSpace.

### Developer Access

Contact **<sales@innerspace.io>** for developer access.  They will provide you with a ClientID and ClientSecret.

They will need to know the usage of the credentials - whether it is needed for a backend service, a native application, a single-page web app or regular web app.

### Backend Services

Backend services should use the `/oauth/token` endpoint below, which behaves as documented in the [Auth0 machine-to-machine (m2m) Client Credentials Flow](https://auth0.com/docs/get-started/authentication-and-authorization-flow/client-credentials-flow).

### Examples

{% code title="Python Example" overflow="wrap" lineNumbers="true" %}

```python
# retrieve auth token (cache for reuse)
auth_data = {
    "client_id": 'Your Client ID',
    "client_secret": 'Your Client Secret' }
# auth credentials should be sent as form URL encoded
response = requests.post(url="https://api.innerspace.io/api/rest/oauth/token", json=auth_data, headers={'Content-Type': 'application/json'}
)
access_token = response.json()['oauth_token']['access_token']
# call API using auth token
url_path = 'https://api.innerspace.io'
authorization = f'Bearer {access_token}'
sample_graphql = {
    "operationName": "sample_graphql",
    "variables": {},
    "query": "query sample_graphql { BUILDING_INSIGHTS(where: {DATE_PARTITION: {_eq: 20250501}, BUILDING_ID: {_eq: 71}, GRANULARITY: {_eq: \"PT1H\"}, GROUP_ID: {_eq: \"all-e707b3a8-0732-49ae-9156-13ff1138540e\"}, SITE_ID: {_eq: 900000222}}, order_by: {DATE_TIME_START: desc}) { BUILDING_ID\n DATE_TIME_START\n OCCUPANCY_MEAN\n GRANULARITY\n GROUP_ID\n SITE_ID\n }\n }"
}
response = requests.post(url_path, json=sample_graphql, headers={'Accept': 'application/json', 'Authorization': authorization})
```

{% endcode %}

{% code title="CURL Example" %}

```bash
# Retrieving token
curl --request POST \
     --url https://api.innerspace.io/api/rest/oauth/token \
     --header 'Content-Type: application/x-www-form-urlencoded' \
     --data 'client_id=Your%20Client%20ID&client_secret=Your%20Client%20Secret'

# Calling API
curl --request POST \
  --url https://api.innerspace.io \
  --header 'Content-Type: application/json' \
  --header 'Authorization: Bearer <Access Token>' \
  --data '{"operationName":"sample_graphql","variables":{},"query":"query sample_graphql { BUILDING_INSIGHTS(where: {DATE_PARTITION: {_eq: 20250501}, BUILDING_ID: {_eq: 71}, GRANULARITY: {_eq: \"PT1H\"}, GROUP_ID: {_eq: \"all-e707b3a8-0732-49ae-9156-13ff1138540e\"}, SITE_ID: {_eq: 900000222}}, order_by: {DATE_TIME_START: desc}) { BUILDING_ID\n DATE_TIME_START\n OCCUPANCY_MEAN\n GRANULARITY\n GROUP_ID\n SITE_ID\n }\n }"'
```

{% endcode %}

{% code title="Python GraphQL" lineNumbers="true" %}

```python
# retrieve auth token (cache for reuse)
auth_data = {
    "client_id": 'Your Client ID',
    "client_secret": 'Your Client Secret' }
# auth credentials should be sent as form URL encoded
response = requests.post(url="https://api.innerspace.io/api/rest/oauth/token", json=auth_data, headers={'Content-Type': 'application/json'}
)
access_token = response.json()['oauth_token']['access_token']
# call API using auth token
url_path = 'https://api.innerspace.io'
authorization = f'Bearer {access_token}'
sample_graphql = {
    "operationName": "sample_graphql",
    "variables": {},
    "query": "query sample_graphql { BUILDING_INSIGHTS(where: {DATE_PARTITION: {_eq: 20250501}, BUILDING_ID: {_eq: 71}, GRANULARITY: {_eq: \"PT1H\"}, GROUP_ID: {_eq: \"all-e707b3a8-0732-49ae-9156-13ff1138540e\"}, SITE_ID: {_eq: 900000222}}, order_by: {DATE_TIME_START: desc}) { BUILDING_ID\n DATE_TIME_START\n OCCUPANCY_MEAN\n GRANULARITY\n GROUP_ID\n SITE_ID\n }\n }"
}
response = requests.post(url_path, json=sample_graphql, headers={'Accept': 'application/json', 'Authorization': authorization})
```

{% endcode %}


# Authentication (v1 - deprecated)

How to authenticate to the InnerSpace API

### Summary

API access is available to users via their JWT bearer token, or can be made available to a backend system via an M2M token issued by InnerSpace.

### Developer Access

Contact **<sales@innerspace.io>** for developer access.  They will provide you with a ClientID and ClientSecret.

They will need to know the usage of the credentials - whether it is needed for a backend service, a native application, a single-page web app or regular web app.

### Backend Services

Backend services should use the `/oauth/token` endpoint below, which behaves as documented in the [Auth0 machine-to-machine (m2m) Client Credentials Flow](https://auth0.com/docs/get-started/authentication-and-authorization-flow/client-credentials-flow).

### Examples

{% code title="Python Example" overflow="wrap" lineNumbers="true" %}

```python
# retrieve auth token (cache for reuse)
auth_data = {
    "client_id": 'Your Client ID',
    "client_secret": 'Your Client Secret' }

# auth credentials should be sent as form URL encoded
response = requests.post(url="https://metrics-api.innrspc.com/api/rest/oauth/token", data=auth_data, headers={'Content-Type': 'application/json'}
)
access_token = response.json()['access_token']

# call API using auth token
url_path = 'https://metrics-api.innrspc.com'
authorization = f'Bearer {access_token}'

sample_graphql = {
    "operationName": "sample_graphql",
    "variables": {},
    "query": "query sample_graphql {\n  data: building_features(\n    where: {_and: [{building_id: {_eq: 71}}, {feature_name: {_eq: \"weekly_frequency_dist\"}}, {granularity: {_eq: \"P1W\"}}, {group_id: {_eq: \"all-e707b3a8-0732-49ae-9156-13ff1138540e\"}}, {date_time_start: {_gte: \"2024-01-01T06:00:00.000Z\", _lt: \"2024-01-08T06:00:00.000Z\"}}]}\n  ) {\n    feature_value\n    date_time_start\n  }\n}"
}

response = requests.post(url_path, json=sample_graphql, headers={'Accept': 'application/json', 'Authorization': authorization})
```

{% endcode %}

{% code title="CURL Example" %}

```bash
# Retrieving token
curl --request POST \
     --url https://metrics-api.innrspc.com/api/rest/oauth/token \
     --header 'Content-Type: application/json' \
     --data 'client_id=Your%20Client%20ID&client_secret=Your%20Client%20Secret'

# Calling API
curl --request POST \
  --url https://metrics-api.innrspc.com \
  --header 'Content-Type: application/json' \
  --header 'Authorization: Bearer <Access Token>' \
  --data '{"operationName":"sample_graphql","variables":{},"query":"query sample_graphql {\n  data: building_features(\n    where: {_and: [{building_id: {_eq: 71}}, {feature_name: {_eq: \"weekly_frequency_dist\"}}, {granularity: {_eq: \"P1W\"}}, {group_id: {_eq: \"all-e707b3a8-0732-49ae-9156-13ff1138540e\"}}, {date_time_start: {_gte: \"2024-01-01T06:00:00.000Z\", _lt: \"2024-01-08T06:00:00.000Z\"}}]}\n  ) {\n    feature_value\n    date_time_start\n  }\n}"}'
```

{% endcode %}

{% code title="Python GraphQL" lineNumbers="true" %}

```python
import logging
import sys
from gql import gql, Client
from gql.transport.aiohttp import AIOHTTPTransport
from gql.transport.aiohttp import log as requests_logger
requests_logger.setLevel(logging.WARNING)


def main(argv):
    # Select your transport with a defined url endpoint, using the auth token from above
    transport = AIOHTTPTransport(url="https://metrics-api.innrspc.com/", headers={"Authorization": "Bearer {access_token}"})

    # Create a GraphQL client using the defined transport
    client = Client(transport=transport, fetch_schema_from_transport=True)

    # Execute the query on the transport
    query = """
        query sample_graphql {
              data: building_features(
                 where: {_and: [{building_id: {_eq: 71}}, {feature_name: {_eq: "weekly_frequency_dist"}}, {granularity: {_eq: "P1W"}}, {group_id: {_eq: "all-e707b3a8-0732-49ae-9156-13ff1138540e"}}, {date_time_start: {_gte: "2024-01-01T06:00:00.000Z", _lt: "2024-01-08T06:00:00.000Z"}}]}
                  ) {
                    feature_value
                    date_time_start
                  }
            }
        """
    print(client.execute(gql(query)))


if __name__ == "__main__":
    main(sys.argv)
```

{% endcode %}


# API Reference (v1 - deprecated)

Description of the core GraphQL API calls

This is a graphql API.

Insights are published for buildings, floors (site) and zones (polygon on a floorplan).

All GraphQL APIs are available at `https://metrics-api.innrspc.com`

### Example

The following example uses `building_id` `71`, which is the DEMO building.

{% code title="Python GraphQL" lineNumbers="true" %}

```python

import logging
import sys
from is_common.logging import LogManager
from gql import gql, Client
from gql.transport.aiohttp import AIOHTTPTransport
from gql.transport.aiohttp import log as requests_logger
requests_logger.setLevel(logging.WARNING)
logger = LogManager(config={"app-log-level": "INFO"})


def main(argv):
    # Select your transport with a defined url endpoint, using the auth token from above
    transport = AIOHTTPTransport(url="https://metrics-api.innrspc.com/", headers={"Authorization": "Bearer {access_token}"})

    # Create a GraphQL client using the defined transport
    client = Client(transport=transport, fetch_schema_from_transport=True)

    # Execute the query on the transport
    query = """
        query sample_graphql {
              data: building_features(
                 where: {_and: [{building_id: {_eq: 71}}, {feature_name: {_eq: "weekly_frequency_dist"}}, {granularity: {_eq: "P1W"}}, {group_id: {_eq: "all-e707b3a8-0732-49ae-9156-13ff1138540e"}}, {date_time_start: {_gte: "2024-01-01T06:00:00.000Z", _lt: "2024-01-08T06:00:00.000Z"}}]}
                  ) {
                    feature_value
                    date_time_start
                  }
            }
        """
    print(client.execute(gql(query)))


if __name__ == "__main__":
    main(sys.argv)
```

{% endcode %}


# zone\_occupancy (realtime)

Realtime zone occupancy

InnerSpace publishes occupancy of a zone in near realtime, typically with a latency of 30s-5mins. Latency is most strongly influenced by the frequency by which the WIFI Infrastructure pushes data to InnerSpace for processing.

### Example

```graphql
// query
query zone_metrics(
  $limit: Int,
  $offset: Int,
  $order_by: [zone_occupancy_order_by!],
  $where: zone_occupancy_bool_exp
) {
  zone_occupancy(
    limit: $limit,
    offset: $offset,
    order_by: $order_by,
    where: $where
  ) {
    site_id
    zone_id
    date_time
    granularity
    occupancy
  }
}
```


# zone\_metrics (nightly)

Fetch metrics at a zone level, where a metric is a measurement or insight with a single result. &#x20;

***metric\_name*** should be set to one of the metrics outlined in [Metrics and Features](/metrics-and-features)

***metric\_value*** is the result.

```graphql
// query
query zone_metrics(
  $limit: Int,
  $offset: Int,
  $order_by: [zone_metrics_order_by!],
  $where: zone_metrics_bool_exp
) {
  zone_metrics(
    limit: $limit,
    offset: $offset,
    order_by: $order_by,
    where: $where
  ) {
    id
    site_id
    zone_id
    group_id
    date_time_end
    date_time_start
    granularity
    metric_name
    metric_value
  }
}
```


# zone\_features (nightly)

Fetch a feature at a zone level, where a feature is a complex measurement or insight, possibly with multiple results.

***feature\_name*** should be set to one of the features outlined in [Metrics and Features](/metrics-and-features)

***feature\_value*** is the result.

```graphql
// query
query Zone_features(
  $limit: Int,
  $offset: Int,
  $order_by: [zone_features_order_by!],
  $where: zone_features_bool_exp
) {
  zone_features(
    limit: $limit,
    offset: $offset,
    order_by: $order_by,
    where: $where
  ) {
    date_time_end
    date_time_start
    feature_name
    feature_value
    granularity
    group_id
    id
    site_id
    zone_id
  }
}
```


# site\_metrics (nightly)

Fetch metrics at a site level (typically a floor), where a metric is a measurement or insight with a single result. &#x20;

***metric\_name*** should be set to one of the metrics outlined in [Metrics and Features](/metrics-and-features)

***metric\_value*** is the result.

```graphql
// query
query Site_metrics(
  $limit: Int,
  $offset: Int,
  $order_by: [site_metrics_order_by!],
  $where: site_metrics_bool_exp
) {
  site_metrics(
    limit: $limit,
    offset: $offset,
    order_by: $order_by,
    where: $where
  ) {
    date_time_end
    date_time_start
    granularity
    group_id
    id
    metric_name
    metric_value
    site_id
  }
}
```


# site\_features (nightly)

Fetch a feature at a site level (typically a floor), where a feature is a complex measurement or insight, possibly with multiple results.

***feature\_name*** should be set to one of the features outlined in [Metrics and Features](/metrics-and-features)

***feature\_value*** is the result.

```graphql
// query
query site_features(
  $limit: Int,
  $offset: Int,
  $order_by: [site_features_order_by!],
  $where: site_features_bool_exp
) {
  site_features(
    limit: $limit,
    offset: $offset,
    order_by: $order_by,
    where: $where
  ) {
    date_time_end
    date_time_start
    feature_name
    feature_value
    granularity
    group_id
    id
    site_id
    zone_id
  }
}
```


# building\_metrics (nightly)

Fetch metrics at a building level, where a metric is a measurement or insight with a single result. &#x20;

***metric\_name*** should be set to one of the metrics outlined in [Metrics and Features](/metrics-and-features)

***metric\_value*** is the result.

```graphql
// query
query Building_metrics(
  $limit: Int,
  $offset: Int,
  $order_by: [building_metrics_order_by!],
  $where: building_metrics_bool_exp
) {
  building_metrics(
    limit: $limit,
    offset: $offset,
    order_by: $order_by,
    where: $where
  ) {
    building_id
    date_time_end
    date_time_start
    granularity
    group_id
    id
    metric_name
    metric_value
    version
  }
}
```


# building\_features (nightly)

Fetch a feature at a building level, where a feature is a complex measurement or insight, possibly with multiple results.

***feature\_name*** should be set to one of the features outlined in [Metrics and Features](/metrics-and-features)

***feature\_value*** is the result.

```graphql
// query
query Building_features(
  $limit: Int,
  $offset: Int,
  $order_by: [building_features_order_by!],
  $where: building_features_bool_exp
) {
  building_features(
    limit: $limit,
    offset: $offset,
    order_by: $order_by,
    where: $where
  ) {
    building_id
    date_time_end
    date_time_start
    feature_name
    feature_value
    granularity
    group_id
    id
    version
  }
}
```


# zone\_occupancy (nightly)

Return the occupancy of a zone for a particular period of time.

```graphql
// query
query Zone_occupancy(
  $limit: Int,
  $offset: Int,
  $order_by: [zone_occupancy_order_by!],
  $where: zone_occupancy_bool_exp
) {
  zone_occupancy(
    limit: $limit,
    offset: $offset,
    order_by: $order_by,
    where: $where
  ) {
    date_time
    granularity
    id
    occupancy
    site_id
    zone_id
    zone_set_id
  }
}
```


# groups (nightly) (TBD)

Retrieve a list of groups, or cohorts of people, to return metrics or features for.


# buildings (nightly) (TBD)

Retrieve a list of buildings this user has access to.


# sites (nightly) (TBD)

Retrieve a list of sites (typically a floor) available in a building.


# zones (nightly) (TBD)

Retrieve a list of zones available for a site (typically a floor).


