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

# Clarify (Searis)

> Client-mode REST API connector for Clarify by Searis: push MQTT readings into Clarify as time series, create and update signal metadata, publish signals as items, list signals and items, and read raw, rolled-up or calculated data frames back, through the Clarify JSON-RPC API v1.1

Technical Preview

## Overview

[Clarify](https://www.clarify.io/) by Searis is a cloud time-series tool where operators and engineers explore and annotate industrial data together. This connector lets the broker push readings from the unified namespace straight into Clarify signals and read rolled-up or calculated data back onto MQTT, without a separate collector.

The template has one client connector, **ClarifyApi** (`ClarifyRestApi.lot`) on `https://api.clarify.io`, with 8 events: the OAuth 2.0 token call, 3 writes and 4 reads. Nothing is deleted.

> [!NOTE]
>
> Clarify is a JSON-RPC 2.0 API, not a classic REST API. Every method is a `POST` to `/v1/rpc` with the method name in the body, and the `X-API-Version: 1.1` header selects the API version. Each event in this template sends the payload field `request` (the full JSON-RPC object) unchanged, so put the method that matches the topic in it. The events follow the public [Clarify API reference 1.1](https://docs.clarify.io/api/1.1/).

## Events

| Event | Topic | Clarify method | Kind | What it does |
| - | - | - | - | - |
| `GetAccessToken` | `historian/clarify/token/get` | `POST /v1/oauth/token` | Token | OAuth 2.0 client-credentials token (form body); reply access\_token, token\_type Bearer, expires\_in 86400. Token calls are rate limited, so reuse the token until it expires |
| `InsertData` | `historian/clarify/data/insert` | `integration.insert` | Write | insert time series into the integration's own signals (data frame: times\[] + series\{input key: values\[]}); unknown input keys create signals. 1 request/s |
| `SaveSignals` | `historian/clarify/signals/save` | `integration.saveSignals` | Write | create or update signal metadata (name, description, labels, engUnit, valueType, sampleInterval, gapDetection) by input key. 1 request/s |
| `SelectSignals` | `historian/clarify/signals/select` | `admin.selectSignals` | Read | List signals of an integration with a resource query (filter, limit up to 1000, skip, total); include item to side-load published items. Needs the admin namespace |
| `PublishSignals` | `historian/clarify/items/publish` | `admin.publishSignals` | Write | create or update items (the organisation-visible view in Clarify) from signal IDs, so new signals show up for users. Needs the admin namespace |
| `SelectItems` | `historian/clarify/items/select` | `clarify.selectItems` | Read | List published items organisation wide with a resource query (filter, limit up to 1000). Needs the clarify namespace |
| `DataFrame` | `historian/clarify/data-frame/query` | `clarify.dataFrame` | Read | Raw or rolled-up item data (count, min, max, sum, avg; state histograms for enums) for up to 50 items; 40-day window for raw data. 1 request/s |
| `Evaluate` | `historian/clarify/evaluate/run` | `clarify.evaluate` | Read | Server-side aggregation plus formulas over up to 50 items (for example hourly OEE-style ratios). 1 request/s |

Replies arrive on the same topic with `/response` appended.

## Authentication

> [!WARNING]
>
> Credentials of an integration can write data into Clarify, and with the admin or clarify namespace granted they can read organisation-wide data. Keep the client secret and passwords in Hub secrets with `GET SECRET`, keep the access token on an internal topic, and grant the integration only the namespaces it needs.

Create an integration in Clarify under **Admin panel**, then **Integrations**, and create credentials on its **Access control** tab. Clarify offers two kinds:

* **OAuth 2.0 client credentials (recommended):** publish `client_id` and `client_secret` from `clarify-credentials.json` to `historian/clarify/token/get`. The reply carries `access_token`, valid for about 24 hours (`expires_in` 86400). Send it as `Bearer <access_token>`. Token calls are rate limited, so reuse the token until it expires.
* **Basic Auth:** send `Basic <base64 of username:password>`. The user name is also the integration ID.

Every `/v1/rpc` event sends the payload field `authorization` as the `Authorization` header.

```json theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
{
  "client_id": "<clientId from clarify-credentials.json>",
  "client_secret": "<clientSecret from clarify-credentials.json>"
}
```

### Namespaces

`integration.insert` and `integration.saveSignals` work for every integration and only touch its own signals. `admin.selectSignals` and `admin.publishSignals` need the **admin** namespace, and `clarify.selectItems`, `clarify.dataFrame` and `clarify.evaluate` need the **clarify** namespace. Grant them on the integration in the Clarify admin panel.

## Payload conventions

* `request` is the JSON-RPC 2.0 object: `jsonrpc` `"2.0"`, `method`, `id` and `params`. Set `id` (for example `"1"`) to get a reply; without it Clarify treats the call as a notification and answers `204` with no body.
* `integration` in `params` is the integration ID from the credentials file.
* `InsertData` takes a data frame: `times` is a list of RFC 3339 timestamps and `series` maps each signal input key to a list of values of the same length (`null` for gaps). An unknown input key creates a new signal.
* Resource queries (`query`) use Clarify's filter syntax, for example `{"labels.line": {"$in": ["line1"]}}`, plus `limit`, `skip` and `total`.
* Data queries (`data`) take `filter.times` with `$gte` / `$lt` and an ISO 8601 `rollup` such as `PT1H` or `P1D`.

Insert two readings:

```json theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
{
  "authorization": "<Bearer access_token from GetAccessToken, or Basic base64(username:password) from Basic Auth credentials>",
  "request": {
    "jsonrpc": "2.0",
    "method": "integration.insert",
    "id": "1",
    "params": {
      "integration": "<YOUR_INTEGRATION_ID>",
      "data": {
        "times": [
          "2026-10-06T06:00:00Z",
          "2026-10-06T06:01:00Z"
        ],
        "series": {
          "porto/line1/press01/pressure": [
            101.3,
            101.7
          ],
          "porto/line1/press01/temperature": [
            61.2,
            null
          ]
        }
      }
    }
  }
}
```

Hourly rollup for the last day:

```json theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
{
  "authorization": "<Bearer access_token from GetAccessToken, or Basic base64(username:password) from Basic Auth credentials>",
  "request": {
    "jsonrpc": "2.0",
    "method": "clarify.dataFrame",
    "id": "1",
    "params": {
      "query": {
        "filter": {
          "labels.line": {
            "$in": [
              "line1"
            ]
          }
        },
        "limit": 10
      },
      "data": {
        "filter": {
          "times": {
            "$gte": "2026-10-05T00:00:00Z",
            "$lt": "2026-10-06T00:00:00Z"
          }
        },
        "rollup": "PT1H"
      },
      "include": [
        "item"
      ]
    }
  }
}
```

> [!WARNING]
>
> Clarify answers HTTP `200` for JSON-RPC errors such as invalid params (`-32602`), forbidden (`-32003`, often a namespace that is not granted) or too many requests (`-32011`). Always check for an `error` object in the `/response` payload, not only the HTTP status.

## Limits

Clarify rate-limits each method: `integration.insert`, `integration.saveSignals`, `clarify.dataFrame` and `clarify.evaluate` allow 1 request per second, `admin.publishSignals` 5, and `admin.selectSignals` and `clarify.selectItems` 10. Requests are capped at about 8 MiB of JSON. Signals should average no more than one sample per minute (bursts up to one per second), so batch several readings into one `InsertData` call. `clarify.dataFrame` and `clarify.evaluate` read at most 50 items per call, and raw data (no rollup) is limited to a 40-day window.

## Connector

```lot theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
# Coreflux REST_API template — Clarify (Searis) (beta): push MQTT readings into Clarify as time series, create and
#   update signal metadata, publish signals as items, list signals and items, and read raw, rolled-up or calculated
#   data frames back, over the Clarify JSON-RPC 2.0 API v1.1 (POST /v1/rpc), plus the OAuth 2.0 token call.
# Hugo catalog #162, Tier 3 (historian). 8 events (3 writes). Hand-curated from the public docs.clarify.io API 1.1
#   reference (no public OpenAPI file), cached in openapi-cache/clarify/.
# Docs: https://docs.coreflux.org/latest/lot-language/routes/rest-api-apps/clarify
#
# Auth: publish "authorization" as "Bearer <access_token>" (GetAccessToken with clientId / clientSecret from the
#   integration's credentials file, valid about 24 h) or "Basic <base64 username:password>" (Basic Auth credentials).
#   Keep credentials in Hub secrets (GET SECRET) and the token in an internal topic. Never commit them.
# Payloads: "request" is the complete JSON-RPC 2.0 object sent unchanged, {"jsonrpc": "2.0", "method": "<method>",
#   "id": "1", "params": {...}}. Use the method that matches the topic (InsertData -> integration.insert, ...).
#   Clarify answers HTTP 200 even for RPC errors: check "error" in the /response body. Without "id" Clarify returns
#   204 with no body (notification).
#     InsertData  {"authorization": "...", "request": {"jsonrpc": "2.0", "method": "integration.insert", "id": "1",
#                  "params": {"integration": "<id>", "data": {"times": ["2026-10-06T06:00:00Z"], "series": {"press01/pressure": [101.3]}}}}}
# Namespaces: integration.* works for every integration; admin.* and clarify.* need that namespace granted to the
#   integration in Clarify Admin panel -> Integrations. Rate limits: insert, saveSignals, dataFrame, evaluate 1 req/s.

DEFINE ROUTE ClarifyApi WITH TYPE REST_API
    ADD REST_API_CONFIG
        WITH BASE_ADDRESS "https://api.clarify.io"
        WITH ENABLE_CLIENT "true"
        WITH USE_SSL "true"
        WITH REQUEST_TIMEOUT '30'
        WITH DEFAULT_CONTENT_TYPE "application/json"
        # Smoke: GetAccessToken -> SaveSignals -> InsertData -> SelectSignals -> PublishSignals -> SelectItems -> DataFrame. Never commit client secrets, passwords or tokens.
    # OAuth 2.0 client-credentials token (form body); reply access_token, token_type Bearer, expires_in 86400. Token calls are rate limited, so reuse the token until it expires
    ADD EVENT GetAccessToken
        WITH SOURCE_TOPIC "historian/clarify/token/get"
        WITH DESTINATION_TOPIC "historian/clarify/token/get/response"
        WITH METHOD "POST"
        WITH ENDPOINT "/v1/oauth/token"
        WITH HEADERS
            ADD "Accept" "application/json"
            ADD "Content-Type" "application/x-www-form-urlencoded"
        WITH BODY "grant_type=client_credentials&client_id={value.json.client_id}&client_secret={value.json.client_secret}&audience=https%3A%2F%2Fapi.clarify.io%2Fv1%2F"
    # WRITE: insert time series into the integration's own signals (data frame: times[] + series{input key: values[]}); unknown input keys create signals. 1 request/s
    ADD EVENT InsertData
        WITH SOURCE_TOPIC "historian/clarify/data/insert"
        WITH DESTINATION_TOPIC "historian/clarify/data/insert/response"
        WITH METHOD "POST"
        WITH ENDPOINT "/v1/rpc"
        WITH HEADERS
            ADD "Accept" "application/json"
            ADD "Authorization" "{value.json.authorization}"
            ADD "Content-Type" "application/json"
            ADD "X-API-Version" "1.1"
        WITH BODY "{value.json.request}"
    # WRITE: create or update signal metadata (name, description, labels, engUnit, valueType, sampleInterval, gapDetection) by input key. 1 request/s
    ADD EVENT SaveSignals
        WITH SOURCE_TOPIC "historian/clarify/signals/save"
        WITH DESTINATION_TOPIC "historian/clarify/signals/save/response"
        WITH METHOD "POST"
        WITH ENDPOINT "/v1/rpc"
        WITH HEADERS
            ADD "Accept" "application/json"
            ADD "Authorization" "{value.json.authorization}"
            ADD "Content-Type" "application/json"
            ADD "X-API-Version" "1.1"
        WITH BODY "{value.json.request}"
    # List signals of an integration with a resource query (filter, limit up to 1000, skip, total); include item to side-load published items. Needs the admin namespace
    ADD EVENT SelectSignals
        WITH SOURCE_TOPIC "historian/clarify/signals/select"
        WITH DESTINATION_TOPIC "historian/clarify/signals/select/response"
        WITH METHOD "POST"
        WITH ENDPOINT "/v1/rpc"
        WITH HEADERS
            ADD "Accept" "application/json"
            ADD "Authorization" "{value.json.authorization}"
            ADD "Content-Type" "application/json"
            ADD "X-API-Version" "1.1"
        WITH BODY "{value.json.request}"
    # WRITE: create or update items (the organisation-visible view in Clarify) from signal IDs, so new signals show up for users. Needs the admin namespace
    ADD EVENT PublishSignals
        WITH SOURCE_TOPIC "historian/clarify/items/publish"
        WITH DESTINATION_TOPIC "historian/clarify/items/publish/response"
        WITH METHOD "POST"
        WITH ENDPOINT "/v1/rpc"
        WITH HEADERS
            ADD "Accept" "application/json"
            ADD "Authorization" "{value.json.authorization}"
            ADD "Content-Type" "application/json"
            ADD "X-API-Version" "1.1"
        WITH BODY "{value.json.request}"
    # List published items organisation wide with a resource query (filter, limit up to 1000). Needs the clarify namespace
    ADD EVENT SelectItems
        WITH SOURCE_TOPIC "historian/clarify/items/select"
        WITH DESTINATION_TOPIC "historian/clarify/items/select/response"
        WITH METHOD "POST"
        WITH ENDPOINT "/v1/rpc"
        WITH HEADERS
            ADD "Accept" "application/json"
            ADD "Authorization" "{value.json.authorization}"
            ADD "Content-Type" "application/json"
            ADD "X-API-Version" "1.1"
        WITH BODY "{value.json.request}"
    # Raw or rolled-up item data (count, min, max, sum, avg; state histograms for enums) for up to 50 items; 40-day window for raw data. 1 request/s
    ADD EVENT DataFrame
        WITH SOURCE_TOPIC "historian/clarify/data-frame/query"
        WITH DESTINATION_TOPIC "historian/clarify/data-frame/query/response"
        WITH METHOD "POST"
        WITH ENDPOINT "/v1/rpc"
        WITH HEADERS
            ADD "Accept" "application/json"
            ADD "Authorization" "{value.json.authorization}"
            ADD "Content-Type" "application/json"
            ADD "X-API-Version" "1.1"
        WITH BODY "{value.json.request}"
    # Server-side aggregation plus formulas over up to 50 items (for example hourly OEE-style ratios). 1 request/s
    ADD EVENT Evaluate
        WITH SOURCE_TOPIC "historian/clarify/evaluate/run"
        WITH DESTINATION_TOPIC "historian/clarify/evaluate/run/response"
        WITH METHOD "POST"
        WITH ENDPOINT "/v1/rpc"
        WITH HEADERS
            ADD "Accept" "application/json"
            ADD "Authorization" "{value.json.authorization}"
            ADD "Content-Type" "application/json"
            ADD "X-API-Version" "1.1"
        WITH BODY "{value.json.request}"
```

## Example actions

These actions refresh the token twice a day, keep it on an internal topic, push a press pressure reading into Clarify, and request an hourly rollup.

```lot theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
DEFINE ACTION ClarifyTokenRefresh
ON EVERY 12 HOURS DO
    SET "cid" WITH GET SECRET "CLARIFY_CLIENT_ID"
    SET "csecret" WITH GET SECRET "CLARIFY_CLIENT_SECRET"
    PUBLISH TOPIC "historian/clarify/token/get" WITH { "client_id": {cid}, "client_secret": {csecret} }

DEFINE ACTION ClarifyKeepToken
ON TOPIC "historian/clarify/token/get/response" DO
    SET "tok" WITH (GET JSON "access_token" IN PAYLOAD AS STRING)
    KEEP TOPIC "internal/clarify/authorization" WITH "Bearer " + {tok}

DEFINE ACTION ClarifyInsertPressure
ON TOPIC "porto/line1/press01/pressure" DO
    SET "auth" WITH GET TOPIC "internal/clarify/authorization" AS STRING
    SET "integ" WITH GET SECRET "CLARIFY_INTEGRATION_ID"
    SET "v" WITH PAYLOAD AS DOUBLE
    SET "ts" WITH TIMESTAMP "ISO"
    PUBLISH TOPIC "historian/clarify/data/insert" WITH { "authorization": {auth}, "request": { "jsonrpc": "2.0", "method": "integration.insert", "id": "1", "params": { "integration": {integ}, "data": { "times": [{ts}], "series": { "porto/line1/press01/pressure": [{v}] } } } } }

DEFINE ACTION ClarifyHourlyRollup
ON EVERY 1 HOUR DO
    SET "auth" WITH GET TOPIC "internal/clarify/authorization" AS STRING
    PUBLISH TOPIC "historian/clarify/data-frame/query" WITH { "authorization": {auth}, "request": { "jsonrpc": "2.0", "method": "clarify.dataFrame", "id": "1", "params": { "query": { "filter": { "labels.line": { "$in": ["line1"] } }, "limit": 10 }, "data": { "rollup": "PT1H" } } } }
```

## Status

Technical preview, **blocked**. The template passes 73 smoke checks: static checks of the connector, every event against a local mock of the Clarify JSON-RPC API built from the public reference, negative controls, and three unauthenticated calls to the real `api.clarify.io` that confirm the token endpoint and the RPC endpoint reject invalid credentials. No call has run yet with real Clarify credentials.

To finish testing, create an integration with OAuth client credentials, set `CLARIFY_CLIENT_ID`, `CLARIFY_CLIENT_SECRET` and `CLARIFY_INTEGRATION_ID`, run `python3 smoke_clarify.py` with `CLARIFY_ALLOW_WRITE=1` (and `CLARIFY_READ=1` and `CLARIFY_ADMIN=1` if those namespaces are granted), then deploy the connector on a broker and do an MQTT round trip.

## Related

* [REST API connectors](/latest/lot-language/routes/rest-api-routes)
* [REST API app templates](/latest/lot-language/routes/rest-api-apps/index)
* [Clarify API reference](https://docs.clarify.io/api/1.1/)


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