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

# Apply templates to points

> Look up the ontology, discover point groups, apply a templating plan to the points of your devices, track the operation, and clear it through the KODE API.

Templating gives the points of a device a meaning from the KODE ontology. You pick a canonical type for the devices, such as an air handler, and map each group of points to an ontology field, such as a discharge air temperature. KODE OS then names the points, sets their units, and sets the canonical type on the devices.

You work in three phases. You look up what the ontology offers, you discover how the points of your devices group, and you send one plan that maps each group to a field. The API runs the plan as an operation that you track until it finishes.

Each linked endpoint can switch the sample between cURL, JavaScript, Python, and PowerShell, or send the request for you.

> [!WARNING]
>
> Applying and clearing a plan rewrites live data on every matched point: names, units, ontology fields, and enum maps. There is no undo call. Always validate first, check how many points the plan reaches, and test on a non-critical device before you automate it.

## Before you begin

You need:

* An access token. See the [quickstart](https://docs.kodelabs.com/products/kode-api/quickstart.md).
* A service account whose role allows **Get device and point templating** and **Manage device and point templating**. Get covers discovery and reading operations. Manage covers validate, apply, and clear, including a dry run.
* The `buildingId` of the building, from [List buildings](https://docs.kodelabs.com/products/kode-api/api-reference/buildings/list-buildings.md).
* The ids of the devices to template, from [List building devices](https://docs.kodelabs.com/products/kode-api/api-reference/devices/list-building-devices.md). Devices in that list carry `_id`.

## Look up the ontology

The ontology endpoints do not depend on a building. They return the same catalog for your whole organization.

**1. List canonical types**

Call [List canonical types](https://docs.kodelabs.com/products/kode-api/api-reference/ontology/list-canonical-types.md) and pick the type that fits your devices.

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl "https://api.kodelabs.com/kodeos/api/v1/ontology/canonical-types" \
  --header "Authorization: Bearer ${KODE_ACCESS_TOKEN}"
```

Send a canonical type exactly as listed. Values are case-sensitive, so `Ahu` does not match `ahu`.

**2. List the fields of the type**

Call [List fields for a canonical type](https://docs.kodelabs.com/products/kode-api/api-reference/ontology/list-fields-for-a-canonical-type.md). Add `unitSystem=METRIC` for metric units. The default is `IMPERIAL`.

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl "https://api.kodelabs.com/kodeos/api/v1/ontology/canonical-types/ahu/fields?limit=200" \
  --header "Authorization: Bearer ${KODE_ACCESS_TOKEN}"
```

For each field, read:

* `field`: The identifier you send as `field` in a mapping.
* `supportedKinds`: The point kinds the field can take, from `Number`, `Str`, and `Bool`. A field applies only to points of a supported kind.
* `unit`: The unit `symbol` and `category`. A unit override must be in this category.
* `requiredEnums` and `optionalEnums`: The values a `Bool` or `Str` point can map to.
* `displayName`: The name the points get. A `{{prefix}}` or `{{suffix}}` in it is replaced with the instance number when a device has more than one point with the field.

Use `kind` to filter by point kind, and `q` to search by field identifier.

**3. List units, if you override a unit**

A mapping can override the unit of a field. Call [List units by category](https://docs.kodelabs.com/products/kode-api/api-reference/ontology/list-units-by-category.md) and choose a unit from the same category as the field's `unit.category`.

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl "https://api.kodelabs.com/kodeos/api/v1/ontology/units" \
  --header "Authorization: Bearer ${KODE_ACCESS_TOKEN}"
```

**4. List connections, if you link devices**

Templating does not use connections. You need them when you link devices that you template. [List connection keys](https://docs.kodelabs.com/products/kode-api/api-reference/ontology/list-connection-keys.md) returns every key. [List connections for a canonical type](https://docs.kodelabs.com/products/kode-api/api-reference/ontology/list-connections-for-a-canonical-type.md) returns the connections a device of one type can have, with `type` set to `INPUT` or `OUTPUT`, whether it accepts several devices in `multi`, and the canonical types allowed on the other end. A row's `key`, `multi`, and `type` are the `relationship` of a device reference.

## Template points

**1. Discover the point groups**

Send the devices to [Discover templating point groups](https://docs.kodelabs.com/products/kode-api/api-reference/templating/discover-templating-point-groups.md). Select 1 to 2,000 devices. Template devices of one canonical type together.

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl --request POST \
  "https://api.kodelabs.com/kodeos/api/v1/buildings/${BUILDING_ID}/templating/point-groups/query?limit=200" \
  --header "Authorization: Bearer ${KODE_ACCESS_TOKEN}" \
  --header "Content-Type: application/json" \
  --data '{
    "deviceIds": ["66f1a2b3c4d5e6f7a8b9c0d2"],
    "templateStatuses": ["NOT_TEMPLATED"]
  }'
```

A group holds the points of the selected devices that share a connector name, kind, writable flag, and unit (for `Number` points) or enum values (for `Bool` and `Str` points). Virtual and metadata points are not grouped. For each group, read:

* `groupKey`: The key you send in a mapping.
* `name`, `kind`, `writable`, `unitSymbol`, and `pointEnum`: The values that define the group.
* `pointsCount`: How many points the group holds.
* `templateStatus`: `NOT_TEMPLATED` or `TEMPLATED`.
* `enumStatus`: `N_A`, `UNMAPPED`, `PARTIALLY_MAPPED`, or `MAPPED`. `N_A` means the group has no enum to map.
* `currentFields`: The fields already applied, with a point count for each.

You can filter the groups with `templateStatuses`, `kinds`, and `enumStatuses` in the body, and with `q` on the connector name.

**2. Drill into a group**

To review a group, or to pick single points, call [List points in a point group](https://docs.kodelabs.com/products/kode-api/api-reference/templating/list-points-in-a-point-group.md). Send the `groupKey` and the same `deviceIds`. Repeat the `deviceIds` parameter for each device.

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl "https://api.kodelabs.com/kodeos/api/v1/buildings/${BUILDING_ID}/templating/point-groups/${GROUP_KEY}/points?deviceIds=66f1a2b3c4d5e6f7a8b9c0d2" \
  --header "Authorization: Bearer ${KODE_ACCESS_TOKEN}"
```

Each point shows the device it belongs to, the `field` it carries now, and its `enumStatus`. A `groupKey` that does not match a group of those devices returns `400`.

**3. Build the plan**

A plan names the `canonicalType`, the `deviceIds`, and one mapping for each group or set of points you want to change.

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "canonicalType": "ahu",
  "deviceIds": ["66f1a2b3c4d5e6f7a8b9c0d2"],
  "requestId": "ahu-1-apply-2026-11-10",
  "validateOnly": true,
  "mappings": [
    {
      "match": { "groupKey": "pg1.Yz3kR1a2B3c4D5e6F7g8H9" },
      "field": "dischargeAirTemp",
      "overrides": { "displayName": "Supply Air Temp", "precision": 1 }
    },
    {
      "match": { "pointIds": ["66f1a2b3c4d5e6f7a8b9c0d1"] },
      "field": "supplyFanRunCmd",
      "enumMapping": {
        "action": "SET",
        "readMap": [
          { "from": "Stopped", "to": "Off" },
          { "from": "Running", "to": "On" }
        ]
      }
    }
  ]
}
```

Each mapping has:

* `match`: Send exactly one of `groupKey`, a tuple (`name`, `kind`, `unitSymbol`, `writable`, `pointEnum`), or `pointIds`. A point-level mapping takes precedence over a group mapping that covers the same point.
* `field`: A `field` from the ontology, and a kind that fits the points.
* `overrides`: Optional. `displayName`, `unit`, `precision`, `covTolerance`, `defaultInGraph`, `prefix`, `suffix`, and `dataInterval` replace the field's defaults. Without `prefix` and `suffix`, the server numbers repeated fields on a device.
* `enumMapping`: Optional, for `Bool` and `Str` points. With `SET`, `readMap` pairs each of the point's own labels (`from`) with an enum value of the field (`to`). A `writeMap` pairs the other way and is accepted only for writable points. `UNSET` removes both maps. `KEEP` is the default and leaves them as they are.

A plan holds at most 1,000 mappings and covers at most 5,000 distinct point ids. The values in the example are illustrative. Use the fields and enum values that the ontology returns for your canonical type.

**4. Validate the plan**

Post the plan to [Apply a templating plan](https://docs.kodelabs.com/products/kode-api/api-reference/templating/apply-a-templating-plan.md) with `validateOnly` set to `true`. Nothing is written.

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl --request POST \
  "https://api.kodelabs.com/kodeos/api/v1/buildings/${BUILDING_ID}/templating:apply" \
  --header "Authorization: Bearer ${KODE_ACCESS_TOKEN}" \
  --header "Content-Type: application/json" \
  --data @plan.json
```

The response holds `validationReport`:

* `valid`: `true` when no issue has `ERROR` severity.
* `summary`: `matchedPoints` and `devicesAffected` show how many points and devices the apply would change. Check these numbers before you continue. `errors` and `warnings` count the issues.
* `mappings[]`: The outcome of each mapping, in the order you sent them. `status` is `OK`, `WARNING`, or `ERROR`.
* `issues`: Findings about the whole plan.
* `unmappedGroups`: Groups of your devices that no mapping covers.
* `etag`: A fingerprint of the resolved plan. Keep it for the next step.

Each issue has a `code`, a `pointer` into your request, and a `message`. Fix every `ERROR` and validate again until `valid` is `true`. Warnings do not stop an apply. Common issues:

| Code | Severity | Meaning |
| - | - | - |
| `UNKNOWN_FIELD` | Error | The field is not a field of this canonical type. |
| `FIELD_KIND_MISMATCH` | Error | The field does not support the kind of the matched points. |
| `UNIT_CATEGORY_MISMATCH` | Error | The unit override is not in the field's unit category. |
| `UNKNOWN_GROUP_KEY` | Error | The `groupKey` matches no group of the selected devices. |
| `DUPLICATE_GROUP_MAPPING` | Error | The same group is in more than one mapping. |
| `DEVICES_TEMPLATED_UNDER_OTHER_CANONICAL` | Error | A device already has points templated under another canonical type. Clear it first. |
| `MULTIPLE_CANONICAL_TYPES_IN_SELECTION` | Error | The selected devices are templated under more than one canonical type. |
| `ENUM_MAPPING_ON_NUMBER` | Error | An enum map was sent for a `Number` point. |
| `ENUM_MAP_KEY_NOT_IN_POINT_ENUM` | Error | A `readMap` `from` value is not one of the point's enum values. |
| `ENUM_MAP_VALUE_NOT_ALLOWED` | Error | A `to` value is not an enum value of the field. |
| `WRITE_MAP_ON_READONLY_POINT` | Error | A `writeMap` was sent for points that are not writable. |
| `PLAN_TOO_LARGE` | Error | The plan has too many mappings, devices, or points. |
| `NO_MATCH` | Warning | The mapping matched no points. |
| `REQUIRED_ENUMS_UNMAPPED` | Warning | Some required enum values of the field are not mapped. |
| `ENUM_MAPPING_RECOMMENDED` | Warning | The field has required enum values and the points have no read map. |

**5. Apply the plan**

Send the same body with `validateOnly` removed, and add the `etag` from the report:

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "canonicalType": "ahu",
  "deviceIds": ["66f1a2b3c4d5e6f7a8b9c0d2"],
  "requestId": "ahu-1-apply-2026-11-10",
  "etag": "sha256:9f86d0818...",
  "mappings": [ "...same as before" ]
}
```

The response holds `operation`. Keep the last segment of `operation.name` as the `operationId`.

* Only `validateOnly: true` makes a dry run. If you leave `validateOnly` out, the call starts a real run.
* The `requestId` is unique per building. If your request times out, send it again with the same `requestId` and body. The API returns the operation it already started instead of starting a second one. A different body with the same `requestId` returns `400`.
* If the plan changed since you validated it, the `etag` no longer matches and the call returns `400`. Validate again to get a new `etag`.
* The API does not stop two runs from touching the same points. The last write wins.

**6. Track the operation**

Call [Get a templating operation](https://docs.kodelabs.com/products/kode-api/api-reference/templating/get-a-templating-operation.md) every few seconds until `done` is `true`.

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl "https://api.kodelabs.com/kodeos/api/v1/buildings/${BUILDING_ID}/templating/operations/${OPERATION_ID}" \
  --header "Authorization: Bearer ${KODE_ACCESS_TOKEN}"
```

This response is the operation itself. It is not wrapped in `operation`. Read `metadata.state`:

| State | Meaning |
| - | - |
| `QUEUED` and `RUNNING` | The run has not finished. `metadata.progress` shows a percentage that updates after each batch. |
| `SUCCEEDED` | The run finished. `response.matchedPoints` and `response.devicesAffected` show what it changed. |
| `FAILED` | The run stopped with an error. `error.code` and `error.message` explain why. |
| `STALLED` | The run is `RUNNING` but has stopped reporting progress. It is not finished, so `done` stays `false`. |

Use `done`, not `progress`, to detect that a run is finished. After `SUCCEEDED`, graphics, device references, and device linking keep updating in the background, so they can lag behind the point data for a short time. Add `fields=done,metadata` to return less data while you poll.

**7. Clear the templating**

To remove the templating from points, call [Clear a templating plan](https://docs.kodelabs.com/products/kode-api/api-reference/templating/clear-a-templating-plan.md). It takes the same body as apply, including `requestId`, `etag`, and `validateOnly`. A clear mapping needs only `match`.

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl --request POST \
  "https://api.kodelabs.com/kodeos/api/v1/buildings/${BUILDING_ID}/templating:clear" \
  --header "Authorization: Bearer ${KODE_ACCESS_TOKEN}" \
  --header "Content-Type: application/json" \
  --data '{
    "canonicalType": "ahu",
    "deviceIds": ["66f1a2b3c4d5e6f7a8b9c0d2"],
    "requestId": "ahu-1-clear-2026-11-10",
    "validateOnly": true
  }'
```

* With `mappings`, only the points the mappings match are cleared.
* Without `mappings`, or with an empty list, **every** point templated under `canonicalType` on the selected devices is cleared. Validate first. `summary.matchedPoints` and `summary.devicesAffected` show how many points and devices a real clear reaches.

Validate, then send the clear without `validateOnly`, and track the operation as before.

## What apply and clear change

Both calls change stored point and device data. They do not send values to the equipment.

| | Apply | Clear |
| - | - | - |
| Point name | Set to the field's display name, with `{{prefix}}` and `{{suffix}}` replaced by the instance numbers. | Reset to the connector name. |
| Point display name | Set to the field's display name. | Reset to the connector name. |
| Ontology fields on the point | Sets `field`, `prefix`, `suffix`, and the field's measurement, component, descriptor, point type, aggregation, bounds, precision, COV tolerance, and graphing defaults. Your `overrides` replace the defaults. | Removes them. |
| Unit | Set to the field's unit, or to your `unit` override. | Reset to the unit the connector reported. |
| Enum maps | Follows `enumMapping`: `SET` replaces the read and write maps, `UNSET` removes them, `KEEP` leaves them. | Removes them. |
| Canonical type | Set on the matched devices and their points. | Removed from a device that has no templated point left, and from its points. A cleared point keeps it while its device has other templated points. |
| Entities | Set on the devices and points from the ontology. | Updated on the devices. Removed from the cleared points. |

Some things apply and clear leave alone:

* Virtual points. Apply does not create, change, or template virtual points, and the point groups do not list them.
* Points on platform-managed connectors.
* The point's value, its connector, and the write commands it accepts. Clear does not remove the `dataInterval` that apply set.
* Any point that the plan does not match.

After a run succeeds, KODE OS also updates device graphics, device references, and device linking for the changed devices. This runs in the background and can finish after the operation reports `SUCCEEDED`.

## Limits

| Limit | Value |
| - | - |
| Devices per request | 2,000 |
| Mappings per plan | 1,000 |
| Distinct point ids per plan | 5,000 |
| Groups, canonical types, fields, and `limit` per page | `limit` up to 200 |
| `requestId` and `etag` length | 128 characters |

Templating requests also count toward your [rate limits](https://docs.kodelabs.com/products/kode-api/fundamentals.md#rate-limits).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.