> ## Documentation Index
> Fetch the complete documentation index at: https://docs.cognite.com/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> Cognite Data Fusion (CDF) is an industrial DataOps platform. Prefer English docs unless the user asks for another locale.
> Follow /_llms/ index links recursively until you reach page URLs ending in .md. Fetch those Markdown twins instead of HTML.
> For REST APIs, default to calendar version 20230101 (stable). Use 20230101-beta or 20230101-alpha only if the user is on a preview API. Cite paths under /api-reference/concepts/<version>/ and the Cdf-Version header. Prefer the Python SDK (cognite-sdk) unless the user is in JavaScript/TypeScript.
> Start here by task: platform overview /cdf/index.md; data modeling /cdf/dm/index.md; data integration /cdf/integration/index.md; access /cdf/access/index.md; CDF Toolkit /cdf/deploy/cdf_toolkit/index.md; REST quickstart /dev/quickstart.md; Python SDK https://cognite-sdk-python.readthedocs-hosted.com/en/stable/; Atlas AI /cdf/atlas_ai/concepts/index.md; Flows apps /cdf/flows/index.md; MCP and IDE setup /dev/guides/ide_ai_integration.md.
> For implementation work, also load /skill.md.

# About units in CDF

> Learn how to use Units in Cognite Data Fusion (CDF) to standardize measurement data and enable seamless unit conversion across time series, data models, and records.

CDF uses the **Units service** to standardize measurement data across your industrial data resources. Whether you're working with time series or data models, the Units service helps ensure consistent measurement representation and seamless unit conversion.

## Understanding units

The Units service in CDF consists of three key components:

1. **Quantity**: A specific, measurable property or characteristic of a physical system or substance. For example, temperature, pressure, mass, and length.
2. **Unit**: A standard amount or value of a particular quantity by which other amounts of the same quantity can be measured or expressed. For example, `°C` and `°F` are used for temperature, and `bar` and `psi` are used for pressure.
3. **Unit system**: A collection of default units for given quantities. Examples include the `SI` unit system (the default value is `K` for temperature and `Pascal` for pressure) and the `Imperial` unit system (the default value is `°F` for temperature and `psi` for pressure).

### Unit structure

Every CDF project comes with a list of standard industry-relevant units. Each unit contains:

* `externalId`: A unique identifier structured as `{quantity}:{unit}` (for example, `temperature:deg_c`).
* `name`: The primary name of the unit, such as `DEG_C`.
* `longName`: A descriptive name for the unit, like `degree Celsius`.
* `symbol`: The symbol used for the unit, like `°C`.
* `aliasNames`: An array of alternative names or aliases for the unit.
* `quantity`: The physical quantity the unit measures like `Temperature`.
* `conversion`: An object containing conversion factors (`multiplier` and `offset`) for unit conversion.
* `source`: The primary source of the unit's definition, typically a standard organization like `qudt.org`.
* `sourceReference`: A URL reference to the external source for the unit's definition.

> [!TIP]
>
> For more information on how to work with units, see the [Units API documentation](https://docs.cognite.com/api-reference/concepts/20230101/units.md) or [Time series and datapoints](https://docs.cognite.com/api-reference/concepts/20230101/time-series-data.md).

## Accessing units

You can access units through two primary APIs:

1. The **Units API**: The primary source of truth for all unit definitions in your CDF project. This API provides direct access to the unit catalog, where units are defined and managed.

   ```python wrap theme={"languages":{"custom":["/_languages/kuiper.json"]}}
   from cognite.client import CogniteClient
   client = CogniteClient()

   units = client.units.list()
   ```

2. The **Data modeling API**: This API stores a copy of each unit in the `cdf_cdm_units` space. These nodes are used to assign direct relations from `CogniteTimeSeries` to a valid unit in the catalog.

   ```python wrap theme={"languages":{"custom":["/_languages/kuiper.json"]}}
   from cognite.client import CogniteClient
   import cognite.client.data_classes.data_modeling as dmc
   import cognite.client.data_classes.filters as dmf
   client = CogniteClient()

   unit_instances = client.data_modeling.instances.list(
      sources=[
         dmc.ViewId(
            space="cdf_cdm",
            external_id="CogniteUnit",
            version="v1"
         )
      ],
      filter=dmf.Equals(
         property=["node", "space"],
         value="cdf_cdm_units"
      ),
      limit=1000
   )
   ```

## Integration with time series

### Creating time series with units

CDF supports two methods for creating time series with units — by using the **Data modeling API** or the **Time series API**.

The choice between these methods depends on whether you want your time series to be part of the knowledge graph. For both methods, you can:

* Assign units during time series creation.
* Add or update units after the time series has been created.
* Change units at any time during the time series lifecycle.

> [!WARNING]
>
> **Always ensure you ingest data in the correct unit regardless of when the unit is assigned.**
>
> For both APIs, updating the unit (`unit` in the Data modeling API or `unitExternalId` in the Time series API) **will not automatically convert** previously ingested data points. The unit assignment provides typing information and enables unit conversion when querying data points, but the underlying data remains in its original unit.

**Using the Data modeling API**

Use this method when you want your time series to be part of the CDF knowledge graph. Note that the `unit` field needs to be a direct relation reference to a unit from Cognite's unit catalog stored in the `cdf_cdm_units` space.

```python wrap theme={"languages":{"custom":["/_languages/kuiper.json"]}}
from cognite.client import CogniteClient
import cognite.client.data_classes.data_modeling as dmc
from cognite.client.data_classes.data_modeling.cdm.v1 import CogniteTimeSeriesApply
client = CogniteClient()

ts_instances = client.data_modeling.instances.apply(
    nodes=[
        CogniteTimeSeriesApply(
            space="cdm-test",
            external_id="test_unit_ts_1",
            name="Temperature Sensor 1",
            description="Temperature measured in degrees Celsius",
            is_step=False,
            time_series_type="numeric",
            unit=dmc.DirectRelationReference("cdf_cdm_units", "temperature:deg_c"),
            source_unit="C",
        ),
        CogniteTimeSeriesApply(
            space="cdm-test",
            external_id="test_unit_ts_2",
            name="Temperature Sensor 2",
            description="Temperature measured in degrees Fahrenheit",
            is_step=False,
            time_series_type="numeric",
            unit=dmc.DirectRelationReference("cdf_cdm_units", "temperature:deg_f"),
            source_unit="F",
        )
    ]
)
```

**Using the Time series API**

Use this method when you need standalone time series that don't require integration with the knowledge graph. The `unitExternalId` field is used to assign a unit from the unit catalog to the time series.

```python wrap theme={"languages":{"custom":["/_languages/kuiper.json"]}}
from cognite.client import CogniteClient
from cognite.client.data_classes import TimeSeriesWrite
client = CogniteClient()

ts_list = client.time_series.create(
    time_series = [
        TimeSeriesWrite(
            external_id="test_unit_ts_1",
            name="Temperature Sensor 1",
            unit="deg C",
            unit_external_id="temperature:deg_c",
            description="Temperature measured in degrees Celsius"
        ),
        TimeSeriesWrite(
            external_id="test_unit_ts_2",
            name="Temperature Sensor 2",
            unit="F",
            unit_external_id="temperature:deg_f",
            description="Temperature measured in degrees Fahrenheit"
        )
    ]
)
```

> [!NOTE]
>
> Different field names in the two APIs exist for historical reasons. Naming in the Time series API is maintained for backwards compatibility. Equivalent fields pair like this:
>
> Time series API (legacy naming):
>
> * `unit`: Free-text unit label from the source system (for example, `"deg C"`)
> * `unitExternalId`: Reference to a unit from the unit catalog (for example, `"temperature:deg_c"`)
>
> Data modeling API:
>
> * `sourceUnit`: Free-text unit label from the source system (for example, `"deg C"`)
> * `unit`: Direct relation reference to a unit from the catalog (for example, `("cdf_cdm_units", "temperature:deg_c")`)

### Querying time series with units

#### Filtering time series

Once a time series is created (or updated) with units from the catalog, it's possible to filter both on `unitExternalId` and `unitQuantity` when using the Time series API.

The example below shows valid time series queries in the Python SDK:

```python wrap theme={"languages":{"custom":["/_languages/kuiper.json"]}}
from cognite.client import CogniteClient
client = CogniteClient()

# List time series assigned to degree Fahrenheit
client.time_series.list(
   unit_external_id="temperature:deg_f"
)

# List time series assigned to units of quantity Temperature
client.time_series.list(
   unit_quantity="Temperature"
)
```

When using the Data modeling API, you can filter on the `unit` field, which is a direct relation reference to a unit from Cognite's unit catalog stored in the `cdf_cdm_units` space.

The example below shows how to filter `CogniteTimeSeries` instances in the data modeling service (DMS) based on the direct relation reference to a unit.

```python wrap theme={"languages":{"custom":["/_languages/kuiper.json"]}}
from cognite.client import CogniteClient
import cognite.client.data_classes.data_modeling as dmc
import cognite.client.data_classes.filters as dmf
client = CogniteClient()

client.data_modeling.instances.list(
    sources=[
        dmc.ViewId(
            space="cdf_cdm",
            external_id="CogniteTimeSeries",
            version="v1"
        )
    ],
    filter=dmf.Equals(
        property=["cdf_cdm", "CogniteTimeSeries/v1", "unit"],
        value=dmc.DirectRelationReference("cdf_cdm_units", "temperature:deg_c"),
    )
)
```

The aggregate endpoints in the Time series API also support `unitExternalId` and `unitQuantity`, so you can get counts of the time series according to units and quantities.

The example below shows valid time series aggregations in the Python SDK.

```python wrap theme={"languages":{"custom":["/_languages/kuiper.json"]}}
# Get the number of unique unitExternalIds associated with time series
client.time_series.aggregate_cardinality_values(
   property="unitExternalId"
)

# Get the number of unique unitQuantities associated with time series
client.time_series.aggregate_cardinality_values(
   property="unitQuantity"
)

# Get the count per unitExternalIds associated with time series
client.time_series.aggregate_unique_values(
   property="unitExternalId"
)

# Get the count per unitQuantities associated with time series
client.time_series.aggregate_unique_values(
   property="unitQuantity"
)
```

#### Retrieving data with unit conversion

For time series with associated units from the catalog, you can query data points in a compatible unit (units from the same quantity).

The example below displays the data point queries with unit conversion in the Python SDK.

```python wrap theme={"languages":{"custom":["/_languages/kuiper.json"]}}
from cognite.client import CogniteClient
from cognite.client.data_classes import DatapointsQuery
import cognite.client.data_classes.data_modeling as dmc
client = CogniteClient()

# Retrieve data points using external_id
dps1 = client.time_series.data.retrieve(
   external_id=[
     {
       "external_id": "test_unit_ts_1",
       "target_unit": "temperature:deg_c"
     },
     {
       "external_id": "test_unit_ts_3",
       "target_unit": "length:m"
     }
   ],
   start="2w-ago",
   end="now"
)

# retrieve data points using instance_id
dps2 = client.time_series.data.retrieve(
    instance_id=[
        DatapointsQuery(
            instance_id=dmc.NodeId(
                space="cdm-test",
                external_id="test_unit_ts_1"
            ),
            target_unit="temperature:k"
        ),
        DatapointsQuery(
            instance_id=dmc.NodeId(
                space="cdm-test",
                external_id="test_unit_ts_3"
            ),
            target_unit="length:m"
        )
    ],
    start="2w-ago",
    end="now"
)
dps2
```

You can specify a target **unit system** instead of specific units. The API will target the default unit for the allocated unit system.

The example below shows the data points queries with unit conversion to a unit system in the Python SDK:

```python wrap theme={"languages":{"custom":["/_languages/kuiper.json"]}}
from cognite.client import CogniteClient
import cognite.client.data_classes.data_modeling as dmc
client = CogniteClient()

client.time_series.data.retrieve(
   external_id=["test_unit_ts_1", "test_unit_ts_3"],
   instance_id=[dmc.NodeId(space="cdm-test", external_id="test_unit_ts_2")],
   target_unit_system="SI",
   start="2w-ago",
   end="now"
)
```

## Integration with data models

### Creating data models with units

**1. Define containers with units**

You can associate float properties with specific units from the unit catalog within data models. This relationship occurs when you create or update a container.
The example below demonstrates creating a container with float properties assigned to a specific unit.

```python wrap theme={"languages":{"custom":["/_languages/kuiper.json"]}}
from cognite.client import CogniteClient
import cognite.client.data_classes.data_modeling as dmc
client = CogniteClient()

client.data_modeling.containers.apply(
    dmc.ContainerApply(
        space="pumps_space",
        external_id="PumpSpecificationsContainer",
        name="PumpSpecifications",
        description="Container storing pump specifications",
        used_for="node",
        properties={
            "maxPressure": dmc.ContainerProperty(
                nullable=True,
                description="Maximum Pump Pressure",
                name="maxPressure",
                type=dmc.Float64(
                    unit=dmc.data_types.UnitReference(
                        external_id="pressure:bar",
                        source_unit="BAR"
                    )
                )
            ),
            "maxTemperature": dmc.ContainerProperty(
                nullable=True,
                description="Maximum Pump Temperature",
                name="maxTemperature",
                type=dmc.Float64(
                    unit=dmc.data_types.UnitReference(
                        external_id="temperature:deg_c"
                    )
                )
            ),
            "rotationConfigurations": dmc.ContainerProperty(
                nullable=True,
                description="Rotation Configurations",
                name="rotationConfigurations",
                type=dmc.Float64(
                    is_list=True,
                    unit=dmc.data_types.UnitReference(
                        external_id="angular_velocity:rev-per-min"
                    )
                )
            )
        }
    )
)
```

Assigning `unit.externalId` and `unit.sourceUnit` to a float-type container property is optional. While `unit.externalId` must correspond to an entry in the unit catalog, `unit.sourceUnit` is a free text string where users can store the original name or alias used for the unit in the source system.

> [!WARNING]
>
> **Make sure you ingest data in the correct unit**
>
> Updating the `unit.externalId` on a container property will not automatically convert the values ingested to the container. The `unit.externalId` provides typing information and enables unit conversion when querying the data.

**2. Create views**

With the container in place, create or update a view that maps the container properties you want to expose. The Python example below builds `PumpSpecificationsView` from `PumpSpecificationsContainer`:

```python wrap theme={"languages":{"custom":["/_languages/kuiper.json"]}}
from cognite.client import CogniteClient
import cognite.client.data_classes.data_modeling as dmc
client = CogniteClient()

client.data_modeling.views.apply(
    dmc.ViewApply(
        space="pumps_space",
        external_id="PumpSpecificationsView",
        name="PumpSpecifications",
        description="View pointing to pump specifications",
        version="1",
        properties={
            "maxPressure": dmc.MappedPropertyApply(
                name="maxPressure",
                description="Maximum Pump Pressure",
                container=dmc.ContainerId("pumps_space", "PumpSpecificationsContainer"),
                container_property_identifier="maxPressure"
            ),
            "maxTemperature": dmc.MappedPropertyApply(
                name="maxTemperature",
                description="Maximum Pump Temperature",
                container=dmc.ContainerId("pumps_space", "PumpSpecificationsContainer"),
                container_property_identifier="maxTemperature"
            ),
            "rotationConfigurations": dmc.MappedPropertyApply(
                name="rotationConfigurations",
                description="Rotation Configurations",
                container=dmc.ContainerId("pumps_space", "PumpSpecificationsContainer"),
                container_property_identifier="rotationConfigurations"
            )
        }
   )
)
```

The view definition doesn't contain unit details. The unit information is stored in the container definition and automatically inherited by the view.

**3. Create data model**

You use data models to organize collections of views. The example below shows how to create a data model using the Python SDK:

```python wrap theme={"languages":{"custom":["/_languages/kuiper.json"]}}
from cognite.client import CogniteClient
import cognite.client.data_classes.data_modeling as dmc
client = CogniteClient()

client.data_modeling.data_models.apply(
    dmc.DataModelApply(
        space="pumps_space",
        external_id="PumpManagementModel",
        name="Pump Management Model",
        version="1",
        views=[
            dmc.ViewId(
                space="pumps_space",
                external_id="PumpSpecificationsView",
                version="1"
            )
        ]
    )
)
```

Even though the DMS API is the recommended way to manage containers, views, and data models, it's possible to create them using the **Data modeling extension for GraphQL** (DML).

The equivalent representation in DML for this container/view definition would be:

```graphql wrap theme={"languages":{"custom":["/_languages/kuiper.json"]}}
type PumpSpecifications {
  "Maximum Pump Pressure"
  maxPressure: Float @unit(externalId: "pressure:bar", sourceUnit: "BAR")
  "Maximum Pump Temperature"
  maxTemperature: Float @unit(externalId: "temperature:deg_c")
  "Rotation Configurations"
  rotationConfigurations: [Float64] @unit(externalId: "angular_velocity:rev-per-min")
}
```

> [!NOTE]
>
> The `@unit` directive associates a property with a specific unit. This works precisely like assigning units using DMS; the `externalId` argument must correspond to an entry in the unit catalog.

See the [Data modeling extension for GraphQL (DML) documentation](https://docs.cognite.com/cdf/dm/dm_graphql/dm_data_modeling_language.md#specification-of-directives) for more information on using DML.

### Querying data models with units

#### Using the Data modeling API

When querying data, set `includeTyping` to `true` to retrieve information about the units associated with all properties included in the response.

To add unit conversion into a response, specify either `unit.externalId` or `unit.unitSystemName` for each property requiring conversion within the `targetUnits` argument. When you set `includeTyping` to `true`, the typing information will reflect the units of the data included in the request. For example, if `maxPressure` was stored as `pressure:bar`, a request to convert to `pressure:pa` will result in both the data and typing information being in `pressure:pa`.

When applying a filter to a property included in `targetUnits`, the filter will be unit-aware, ensuring that the value passed on the filter aligns with the unit of the data. For instance, if `maxPressure` was stored as `pressure:bar` and a request to convert to `pressure:pa` with a filter `greater than or equal to 3520000.0` will filter the data to instances where `maxPressure` is greater than or equal to `3520000.0 Pascal`.

> [!WARNING]
>
> Unit conversion may introduce minor floating-point rounding errors.
>
> For example, when you convert `1.69999999999` `meters` to `centimeters`, it results in `170` `centimeters`. This discrepancy becomes particularly evident with the application of filters. For instance, when applying a filter for `height less than 170 cm`, one might anticipate results including the original height value of `1.69999999999`, but due to rounding errors in conversion, these results might get excluded.

> [!WARNING]
>
> When you set target units with filters, ambiguity can arise when two different views map to the same property, `propertyA`, but each view uses a separate unit for the property. When you apply a filter on `propertyA` and reference the property through its container (for example, `[space, containerId, property]`), the system cannot determine the correct `source` and, therefore, the appropriate `targetUnit` for the filter.
>
> To avoid that, explicitly reference `propertyA` in the filter by specifying the view (for example, `[space, viewId/viewVersion, property]`).

All the data modeling query endpoints support typing information, unit conversion, and unit-aware filtering.

The following sections show examples of valid queries.

#### List instances with unit conversion and unit-aware filtering

The example below shows a list instance with unit conversion and unit-aware filtering.

```python wrap theme={"languages":{"custom":["/_languages/kuiper.json"]}}
from cognite.client import CogniteClient
import cognite.client.data_classes.data_modeling as dmc
import cognite.client.data_classes.filters as dmf
client = CogniteClient()

instances = client.data_modeling.instances.list(
    instance_type="node",
    sources=dmc.query.SourceSelector(
        source=dmc.ViewId(
            space="pumps_space",
            external_id="PumpSpecificationsView",
            version="1"
        ),
        target_units=[
            dmc.query.TargetUnit(
                property="maxPressure",
                unit=dmc.data_types.UnitReference(
                    external_id="pressure:pa"
                )
            ),
            dmc.query.TargetUnit(
                property="maxTemperature",
                unit=dmc.data_types.UnitSystemReference(
                    unit_system_name="SI"
                )
            ),
            dmc.query.TargetUnit(
                property="rotationConfigurations",
                unit=dmc.data_types.UnitSystemReference(
                    unit_system_name="SI"
                )
            )
        ]
    ),
    filter=dmf.Range(
        property=("pumps_space", "PumpSpecificationsView/1", "maxPressure"),
        gte=3520000.0
    ),
    include_typing=True
)
```

#### Query instances with unit conversion and unit-aware filtering

The example below shows a query instance with unit conversion and unit-aware filtering.

```python wrap theme={"languages":{"custom":["/_languages/kuiper.json"]}}
from cognite.client import CogniteClient
import cognite.client.data_classes.data_modeling as dmc
import cognite.client.data_classes.filters as dmf
client = CogniteClient()

instances = client.data_modeling.instances.query(
    query=dmc.query.Query(
        with_ = {
            "pumpsHighPressure": dmc.query.NodeResultSetExpression(
                filter=dmf.Range(
                    property=("pumps_space", "PumpSpecificationsView/1", "maxPressure"),
                    gte=3520000.0
                ),
                limit=1000
            )
        },
        select = {
            "pumpsHighPressure": dmc.query.Select(
                sources=[
                    dmc.query.SourceSelector(
                        source=dmc.ViewId("pumps_space", "PumpSpecificationsView", "1"),
                        properties=["maxPressure", "maxTemperature", "rotationConfigurations"],
                        target_units=[
                            dmc.query.TargetUnit(
                                property="maxPressure",
                                unit=dmc.data_types.UnitReference(
                                    external_id="pressure:pa"
                                )
                            ),
                            dmc.query.TargetUnit(
                                property="maxTemperature",
                                unit=dmc.data_types.UnitSystemReference(
                                    unit_system_name="SI"
                                )
                            ),
                            dmc.query.TargetUnit(
                                property="rotationConfigurations",
                                unit=dmc.data_types.UnitSystemReference(
                                    unit_system_name="SI"
                                )
                            )
                        ]
                    )
                ],
                limit=1000
            )
        }
    ),
    include_typing=False
)
```

#### Aggregate data across nodes/edges with unit conversion and unit-aware filtering

The example below shows aggregate data across nodes/edges with unit conversion and unit-aware filtering.

```python wrap theme={"languages":{"custom":["/_languages/kuiper.json"]}}
from cognite.client import CogniteClient
import cognite.client.data_classes.data_modeling as dmc
import cognite.client.data_classes.filters as dmf
client = CogniteClient()

aggregates = client.data_modeling.instances.aggregate(
    instance_type="node",
    view=dmc.ViewId(space="pumps_space", external_id="PumpSpecificationsView", version="1"),
    aggregates=[
        dmc.aggregations.Avg("maxPressure"),
        dmc.aggregations.Avg("maxTemperature"),
        dmc.aggregations.Count("rotationConfigurations")
    ],
    filter=dmf.Range(
        property=("pumps_space", "PumpSpecificationsView/1", "maxPressure"),
        gte=3520000.0
    ),
    target_units=[
        dmc.query.TargetUnit(
            property="maxPressure",
            unit=dmc.data_types.UnitReference(
                external_id="pressure:pa"
            )
        ),
        dmc.query.TargetUnit(
            property="maxTemperature",
            unit=dmc.data_types.UnitSystemReference(
                unit_system_name="SI"
            )
        ),
        dmc.query.TargetUnit(
            property="rotationConfigurations",
            unit=dmc.data_types.UnitSystemReference(
                unit_system_name="SI"
            )
        )
    ],
    limit=100
)
```

> [!WARNING]
>
> Query parameter values are not unit-aware and are considered in the same unit as the underlying property. No unit conversion will occur if you provide a filter property through query parameters.

#### Using GraphQL

Apart from querying data using the DMS endpoints, use GraphQL to fetch data from views and time series data points. The GraphQL API supports unit conversion and unit-aware filtering. A query can set `targetUnit` or `targetUnitSystem` on at most 10 fields. If you exceed that limit, the request fails with `targetUnits size must be between 1 and 10`. See [Limits and restrictions](https://docs.cognite.com/cdf/dm/dm_reference/dm_limits_and_restrictions.md#instance-query-limits).

The following examples include valid queries.

#### List query with unit conversion and unit-aware filtering

The example below shows a list query with unit conversion and unit-aware filtering.

```graphql wrap theme={"languages":{"custom":["/_languages/kuiper.json"]}}
query MyQuery {
  # filter on maxPressure -> only 1 entry has maxPressure >= 3520000.0 Pa
  listPumpSpecifications(filter: { maxPressure: { gte: 3520000.0 } }) {
    items {
      # return maxPressure and convert its values and the filter to Pa
      maxPressure(targetUnit: "pressure:pa")
      # return maxTemperature and convert its values to the SI unitSystem
      maxTemperature(targetUnitSystem: "SI")
      # return rotationConfigurations and convert its values to the SI unitSystem
      rotationConfigurations(targetUnitSystem: "SI")
    }
    # the units which are associated with the values returned in the request
    unitInfo {
      maxPressure {
        # externalId of the unit
        unitExternalId
        # free text unit alias
        sourceUnit
      }
      maxTemperature {
        unitExternalId
      }
      rotationConfigurations {
        unitExternalId
      }
    }
  }
}
```

#### Aggregate query with unit conversion and filtering

The example below shows an aggregate query with unit conversion and filtering.

```graphql wrap theme={"languages":{"custom":["/_languages/kuiper.json"]}}
query MyQuery {
  # filter on maxPressure -> only 1 entry has maxPressure >= 3520000.0 Pa
  aggregatePumpSpecifications(filter: {maxPressure: {gte: 3520000.0}}) {
    items {
      avg {
        maxPressure(targetUnit: "pressure:pa")
        maxTemperature(targetUnitSystem: "SI")
      }
      count {
        rotationConfigurations
      }
    }
    unitInfo {
      avg {
        maxPressure {
          unitExternalId
          sourceUnit
        }
        maxTemperature {
          sourceUnit
          unitExternalId
        }
        rotationConfigurations {
          sourceUnit
          unitExternalId
        }
      }
    }
  }
}
```

#### Time series data points query with unit conversion

The example below shows time series data points query with unit conversion.

```graphql wrap theme={"languages":{"custom":["/_languages/kuiper.json"]}}
query MyQuery($startTime: Int64!, $endTime: Int64!) {
  # select only one instance based on the externalId
  listSensorList( filter: { externalId: { eq: "TI-FORNEBU-02" } } ) {
    items {
      # externalId of the node
      externalId
      # property included in the view
      name
      sensor {
        # externalId of the "sensor" time series
        externalId
        # name of the "sensor" time series
        name
        # free text unit assigned to the "sensor" time series
        unit
        # unitExternalId of the "sensor" time series where the data is stored
        unitExternalId
        getDataPoints(targetUnitSystem: "SI", start: $startTime, end: $endTime, granularity: "1h", aggregates: AVERAGE ) {
          # unitExternalId of the datapoints returned in the request
          unitExternalId
          # datapoint items
          items {
            timestamp
            average
          }
        }
      }
    }
  }
}
```

For this query, the following view definition is used:

```graphql wrap theme={"languages":{"custom":["/_languages/kuiper.json"]}}
type SensorList @view(version: "1") {
    name: String
    sensor: TimeSeries
}
```

> [!NOTE]
>
> You need to define the variables `startTime` and `endTime` before executing the query.

For more detailed information, see:

* [Units API documentation](https://docs.cognite.com/api-reference/concepts/20230101/units.md)
* [Data models API documentation](https://docs.cognite.com/api-reference/concepts/20230101/data-models.md)

## Integration with records

Records store high-volume industrial data using the same container schemas as data modeling. You assign units to float properties when you create the container (using `used_for="record"`), and the Records API converts values at query time when you request a different unit.

### Creating record containers with units

Record containers use the same float-with-unit syntax as data modeling containers — `Float64(unit=UnitReference(...))` for 64-bit values and `Float32(unit=UnitReference(...))` for 32-bit values. Set `used_for="record"` to mark the container for use with the Records service. Unit assignment works for both scalar and list float properties.

```python wrap theme={"languages":{"custom":["/_languages/kuiper.json"]}}
from cognite.client import CogniteClient
import cognite.client.data_classes.data_modeling as dmc
client = CogniteClient()

container = client.data_modeling.containers.apply(
    dmc.ContainerApply(
        space="pumps_space",
        external_id="PumpSpecificationsContainer",
        name="PumpSpecifications",
        description="Container storing pump specifications",
        used_for="record",
        properties={
            "maxPressure": dmc.ContainerProperty(
                nullable=True,
                description="Maximum Pump Pressure",
                name="maxPressure",
                type=dmc.Float64(
                    unit=dmc.data_types.UnitReference(
                        external_id="pressure:bar",
                        source_unit="BAR"
                    )
                )
            ),
            "maxTemperature": dmc.ContainerProperty(
                nullable=True,
                description="Maximum Pump Temperature",
                name="maxTemperature",
                type=dmc.Float64(
                    unit=dmc.data_types.UnitReference(
                        external_id="temperature:deg_c"
                    )
                )
            ),
            "rotationConfigurations": dmc.ContainerProperty(
                nullable=True,
                description="Rotation Configurations",
                name="rotationConfigurations",
                type=dmc.Float64(
                    is_list=True,
                    unit=dmc.data_types.UnitReference(
                        external_id="angular_velocity:rev-per-min"
                    )
                )
            )
        }
    )
)
```

The `unit.externalId` must match an entry in the CDF unit catalog. The `source_unit` field is an optional free-text traceability field that captures the original unit name as it appeared in the source system. See [Containers, views, polymorphism, data models](https://docs.cognite.com/cdf/dm/dm_concepts/dm_containers_views_datamodels.md) for more information on container schemas.

> [!WARNING]
>
> The container `unit` provides typing information and enables query-time conversion only. The Records service **does not** convert values during ingestion — you must write data in the container unit. Updating `unit.externalId` on a container property does not retroactively convert existing records.

### Normalizing data before ingestion

Because the Records service does not convert values on write, you must normalize data to the container unit before or during ingestion. Two common patterns:

**In the extractor** — convert source-unit values to the container unit as part of the extraction pipeline before writing. This is the simplest approach when you control the extractor.

**Staging streams** — ingest raw data in source units into separate per-source staging containers (for example, a `pump_specifications_us` container using `pressure:psi` and `temperature:deg_f`). A downstream workflow reads from the staging container using `targetUnits` to align units, then writes normalized values to the canonical production container. This pattern works well when you receive data from heterogeneous sources you don't control. Because a property's unit is fixed at container creation time, each source unit needs its own staging container (or its own property within a shared staging container) — you can't write values in different units to the same property.

### Querying records with units

The `records/sync`, `records/filter`, and `records/aggregate` endpoints accept a top-level `targetUnits` parameter that converts both response values and filter inputs to the requested units. You can target a unit system for all eligible properties at once, or specify per-property overrides.

#### Convert with a unit system

Use `unitSystemName` to convert all float properties that have a unit defined on the container to the default unit for that system in one step.

```json theme={"languages":{"custom":["/_languages/kuiper.json"]}}
{
  "targetUnits": {
    "unitSystemName": "SI"
  },
  "sources": [
    {
      "source": {
        "type": "container",
        "space": "pumps_space",
        "externalId": "PumpSpecificationsContainer"
      },
      "properties": ["*"]
    }
  ],
  "filter": {
    "hasData": [
      {
        "type": "container",
        "space": "pumps_space",
        "externalId": "PumpSpecificationsContainer"
      }
    ]
  },
  "initializeCursor": "100d-ago",
  "limit": 1000
}
```

With data stored in bar, °C, and rev/min, the SI conversion returns values in pascal, kelvin, and rad/s respectively.

#### Convert per property

Use the `properties` form to specify a different target unit or unit system for each property individually.

```json theme={"languages":{"custom":["/_languages/kuiper.json"]}}
{
  "targetUnits": {
    "properties": [
      {
        "property": ["pumps_space", "PumpSpecificationsContainer", "maxPressure"],
        "unit": { "externalId": "pressure:pa" }
      },
      {
        "property": ["pumps_space", "PumpSpecificationsContainer", "maxTemperature"],
        "unit": { "unitSystemName": "SI" }
      },
      {
        "property": ["pumps_space", "PumpSpecificationsContainer", "rotationConfigurations"],
        "unit": { "unitSystemName": "SI" }
      }
    ]
  }
}
```

Each item's `unit` is a choice between `{ "externalId": "..." }` (specific unit) and `{ "unitSystemName": "..." }` (unit system default for that property's quantity). You can mix both forms in the same request. The `targetUnits.properties` list accepts up to 1000 entries.

#### Filter in the target unit

When you set `targetUnits`, the same conversion applies to filter values. You write filter thresholds in the target unit, and the service converts them to the container unit before comparison. This bidirectionality is a core design principle: filter inputs and response outputs always share the same unit.

The example below filters pumps where `maxPressure ≥ 3 520 000 Pa`. The stored values are in bar, so the service converts `3 520 000 Pa` to `35.2 bar` before evaluating the filter — and returns the matching records with `maxPressure` converted back to Pa.

```json theme={"languages":{"custom":["/_languages/kuiper.json"]}}
{
  "targetUnits": {
    "properties": [
      {
        "property": ["pumps_space", "PumpSpecificationsContainer", "maxPressure"],
        "unit": { "externalId": "pressure:pa" }
      }
    ]
  },
  "filter": {
    "and": [
      {
        "hasData": [
          {
            "type": "container",
            "space": "pumps_space",
            "externalId": "PumpSpecificationsContainer"
          }
        ]
      },
      {
        "range": {
          "property": ["pumps_space", "PumpSpecificationsContainer", "maxPressure"],
          "gte": 3520000.0
        }
      }
    ]
  },
  "initializeCursor": "100d-ago",
  "limit": 1000
}
```

> [!WARNING]
>
> Unit conversion may introduce minor floating-point rounding errors.
>
> For example, `1.69999999999 m` may convert to `170.0 cm`, causing a filter of `height < 170 cm` to unexpectedly exclude that value. Use a small tolerance margin in boundary filters when precision is critical.

Comparison filters (`equals`, `range`, `in`, `not_in`, and similar) are unit-aware when the filtered property is included in `targetUnits`. Properties not listed in `targetUnits` are compared in their stored container unit.

#### Aggregate in the target unit

The `/records/aggregate` endpoint applies the same `targetUnits` conversion to numeric aggregates. Functions such as `avg`, `sum`, `min`, and `max` operate on converted values; `count` is unit-independent and is unaffected. Filter values in the aggregate request are converted in the same way as for `/records/filter` — you write thresholds in the target unit and the service converts them to the container unit before evaluation.

> [!WARNING]
>
> Avoid `sum` (and other additive aggregates) when converting between units that have an offset, such as `temperature:deg_c → temperature:deg_f`. These conversions are affine (they add a constant), so summing converted values is not equivalent to converting the sum and produces results that aren't physically meaningful. When you need to perform additive aggregation involving units with an offset, aggregate in the storage unit and convert the scalar result yourself.

The example below shows aggregating pressure and temperature across all records, requesting results in Pa and the SI unit system:

```json theme={"languages":{"custom":["/_languages/kuiper.json"]}}
{
  "targetUnits": {
    "properties": [
      {
        "property": ["pumps_space", "PumpSpecificationsContainer", "maxPressure"],
        "unit": { "externalId": "pressure:pa" }
      },
      {
        "property": ["pumps_space", "PumpSpecificationsContainer", "maxTemperature"],
        "unit": { "unitSystemName": "SI" }
      },
      {
        "property": ["pumps_space", "PumpSpecificationsContainer", "rotationConfigurations"],
        "unit": { "unitSystemName": "SI" }
      }
    ]
  },
  "filter": {
    "hasData": [
      {
        "type": "container",
        "space": "pumps_space",
        "externalId": "PumpSpecificationsContainer"
      }
    ]
  },
  "aggregates": {
    "total_count": { "count": {} },
    "avg_pressure": { "avg": { "property": ["pumps_space", "PumpSpecificationsContainer", "maxPressure"] } },
    "avg_temperature": { "avg": { "property": ["pumps_space", "PumpSpecificationsContainer", "maxTemperature"] } },
    "count_rotation_config": { "count": { "property": ["pumps_space", "PumpSpecificationsContainer", "rotationConfigurations"] } }
  }
}
```

#### Inspect resolved units with `includeTyping`

Set `includeTyping: true` to include a `typing` block in the response. This block shows the schema and resolved unit for each property touched by the request. When `targetUnits` is set, the `type.unit.externalId` field in the typing block reflects the target unit, not the storage unit.

The service includes typing information for properties that are:

* returned in `items` (for `/filter` and `/sync` responses)
* referenced in the request `filter`
* referenced in `targetUnits`
* referenced in `aggregates` (for `/aggregate` responses)

```json theme={"languages":{"custom":["/_languages/kuiper.json"]}}
{
  "targetUnits": { "unitSystemName": "SI" },
  "sources": [ ... ],
  "filter": { ... },
  "initializeCursor": "100d-ago",
  "limit": 1000,
  "includeTyping": true
}
```

Example response snippet (typing block):

```json theme={"languages":{"custom":["/_languages/kuiper.json"]}}
{
  "items": [ ... ],
  "typing": {
    "pumps_space": {
      "PumpSpecificationsContainer": {
        "maxPressure": {
          "nullable": true,
          "type": {
            "type": "float64",
            "list": false,
            "unit": { "externalId": "pressure:pa" }
          }
        },
        "rotationConfigurations": {
          "nullable": true,
          "type": {
            "type": "float64",
            "list": true,
            "unit": { "externalId": "angular_velocity:rad-per-sec" }
          }
        },
        "maxTemperature": {
          "nullable": true,
          "type": {
            "type": "float64",
            "list": false,
            "unit": { "externalId": "temperature:k" }
          }
        }
      }
    }
  }
}
```

> [!NOTE]
>
> Unlike data modeling instances, the Records API returns typing information only for properties that appear in the current request or response, not for the full container schema. This keeps response sizes manageable for containers with many properties.

#### Paginate with `/records/sync`

When paginating with `/records/sync`, repeat `targetUnits` in every follow-up request. Cursors carry your position in the change stream but do not store conversion settings.

For more detailed information, see:

* [Records API documentation](https://docs.cognite.com/api-reference/concepts/20230101/records.md)
* [Units API documentation](https://docs.cognite.com/api-reference/concepts/20230101/units.md)
* [Time series API documentation](https://docs.cognite.com/api-reference/concepts/20230101/time-series-data.md)
