Cloud Integration (Partner API) – Integration Guide

Cloud Integration (Partner API)

Backend-to-backend integration via the Partner API — a GraphQL interface for linking customer accounts and pulling or streaming meter readings from IOmeter Cloud.

Cloud Integration (Partner API)

The Partner API is a GraphQL interface offered by IOmeter Cloud. It's a backend-to-backend interface — it is not intended to be called directly from frontend/mobile clients — and requires an API key issued by Neometer GmbH.

It lets a partner:

Endpoints

Transport URL
Queries & Mutations https://partner.prod.iometer.cloud/api/v1/query
Subscriptions wss://partner.prod.iometer.cloud/api/v1/query

Operations at a glance

Operation Transport Use when
Account linking (connectUrl) GraphQL query Onboarding a new customer installation
Installations / historical readings GraphQL query Listing linked installations, backfilling or auditing past data
Readings stream GraphQL subscription (WebSocket) Continuously ingesting readings as they're recorded

Where to start

  1. Read Authentication to understand how requests are signed and how GraphQL is transported (HTTP vs WebSocket).
  2. Walk through Account Linking to get your first installation_id.
  3. Use Installations & Readings and Subscriptions to pull data.
  4. For the exhaustive, field-by-field schema, see the Full GraphQL Reference.

Authentication and Transport

Authentication

All requests require HTTP Basic Authentication.

The Authorization header must be set on:

  • Every HTTP request
  • The WebSocket connection (as an HTTP header during the handshake)

Header format:

Authorization: Basic <base64(partner_id:api_key)>

Or, if the API key you were given is already a pre-encoded token, pass it directly as configured with IOmeter during onboarding.

For WebSocket subscriptions using the graphql-ws protocol, credentials may alternatively be passed in the connection_init payload:

{
  "type": "connection_init",
  "payload": {
    "Authorization": "Basic <token>"
  }
}

The full reference also documents WSSE as an alternative authentication scheme. See the Full GraphQL Reference if your integration requires it.

GraphQL over HTTP

Send a POST request to the query endpoint with a JSON body:

POST https://partner.prod.iometer.cloud/api/v1/query
Content-Type: application/json
Authorization: Basic <token>

{
  "query": "query { partner { id } }"
}

With variables:

{
  "query": "query GetReadings($limit: Int) { partner { readings(limit: $limit) { readings { time } cursor } } }",
  "variables": { "limit": 10 }
}

See also: graphql.org — Serving over HTTP.

GraphQL over WebSocket (subscriptions)

Subscriptions support the graphql-ws and graphql-sse protocols.

  1. Open a WebSocket connection to wss://partner.prod.iometer.cloud/api/v1/query with subprotocol graphql-ws.

  2. Send connection_init with the auth token in the payload (required by some resolvers):

{ "type": "connection_init", "payload": { "Authorization": "Basic <token>" } }

  1. Wait for connection_ack:

{ "type": "connection_ack" }

  1. Send a start message with your subscription query:

{ "type": "start", "id": "1", "payload": { "query": "subscription { meterReadings { time values { obisCode value unit } } }" } }

  1. Receive data messages:

{ "type": "data", "id": "1", "payload": { "data": { ... } } }

  1. To stop the subscription:

{ "type": "stop", "id": "1" }

See Subscriptions for the specific meterReadings and liveReadings payloads.

Account Linking

Purpose: generate a URL that the customer opens in the IOmeter app to grant your platform access to their meter data.

1. Get a connect URL

query {
  partner {
    connectUrl(redirectUrl: "https://your-app.example.com/callback")
  }
}

Parameters:

  • redirectUrl (String, required) — the URL the IOmeter app calls after the user accepts or denies linking. A query parameter is appended to it.
  • state (String, optional) — an opaque string returned verbatim as a state query parameter on the redirect, so you can restore client-side state.

Response:

{
  "data": {
    "partner": {
      "connectUrl": "https://partner.prod.iometer.cloud/connect?partner_id=e1c3c551-7f58-4e78-ab8b-e43c9b7e4659&redirect_url=https%3A%2F%2Fyour-app.example.com%2Fcallback"
    }
  }
}

The connectUrl must be opened as a universal link (deep link) that launches the IOmeter app directly — not as a regular browser URL. The app shows the user a consent screen with "Yes" / "No" options to allow or deny data access for the requesting partner.

iOS:

let url = URL(string: connectUrl)!
UIApplication.shared.open(url, options: [.universalLinksOnly: true]) { success in
    if !success { UIApplication.shared.open(url) }
}

Android:

val intent = Intent(Intent.ACTION_VIEW, Uri.parse(connectUrl))
    .addFlags(Intent.FLAG_ACTIVITY_REQUIRE_NON_BROWSER)
try { startActivity(intent) }
catch (e: ActivityNotFoundException) { startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(connectUrl))) }

3. Handle the redirect

Outcome Redirect
User granted access {redirectUrl}?installation_id=<uuid>
User denied access {redirectUrl}?error=access_denied
Partner not authorized {redirectUrl}?error=unauthorized_client
Server failure {redirectUrl}?error=server_error

The installation_id from a successful redirect is the persistent identifier used for all subsequent data access — store it. See Installations & Readings for what to do next.

Installations & Historical Readings

List installations

Purpose: retrieve all IOmeter installations linked to your partner account, with their devices.

query {
  partner {
    id
    installations {
      id
      devices {
        id
      }
    }
  }
}

Response:

{
  "data": {
    "partner": {
      "id": "e1c3c551-7f58-4e78-ab8b-e43c9b7e4659",
      "installations": [
        {
          "id": "22231002-eee7-41b3-ac68-6d90f4be5db0",
          "devices": [
            { "id": "f0f2d298-aa67-4b22-9d3d-f099eca3066f" }
          ]
        }
      ]
    }
  }
}

Each installation.id corresponds to an installation_id obtained via account linking. One installation can have one or more devices.

Query scheduled readings

Purpose: query scheduled meter readings stored in IOmeter's database. Readings are taken every 15 minutes (at :00, :15, :30, :45).

Access to database-stored readings requires the historic-readings feature to be enabled for your partner account. The API is rate-limited at 1 request/second.

Paging: responses include a cursor. Pass it in the next request to get the next page. Maximum page size is 200.

All readings (paginated)

query {
  partner {
    readings(limit: 10) {
      readings {
        time
        meter {
          number
          installation { id }
        }
        values { obisCode unit value }
      }
      cursor
    }
  }
}

With a time range and cursor:

query {
  partner {
    readings(
      limit: 100,
      startTime: "2026-03-01T00:00:00Z",
      endTime: "2026-03-02T00:00:00Z",
      cursor: "<cursor-from-previous-response>"
    ) {
      readings {
        time
        meter { number installation { id } }
        values { obisCode unit value }
      }
      cursor
    }
  }
}

Scoped to one installation

query {
  partner {
    installation(id: "22231002-eee7-41b3-ac68-6d90f4be5db0") {
      readings(startTime: "2026-03-01T00:00:00Z", endTime: "2026-03-02T00:00:00Z", limit: 100) {
        readings {
          time
          meter { number }
          values { obisCode unit value }
        }
        cursor
      }
    }
  }
}

Scoped to one meter number

query {
  partner {
    meter(number: "1ESY1161338362") {
      readings(startTime: "2026-03-01T00:00:00Z", endTime: "2026-03-02T00:00:00Z", limit: 100) {
        readings {
          time
          values { obisCode unit value }
        }
        cursor
      }
    }
  }
}

Scoped to one installation and meter

query {
  partner {
    installation(id: "22231002-eee7-41b3-ac68-6d90f4be5db0") {
      meter(number: "1ESY1161338362") {
        readings(startTime: "2026-03-01T00:00:00Z", endTime: "2026-03-02T00:00:00Z", limit: 100) {
          readings {
            time
            values { obisCode unit value }
          }
          cursor
        }
      }
    }
  }
}

Example response:

{
  "data": {
    "partner": {
      "readings": {
        "readings": [
          {
            "time": "2026-03-18T13:15:00Z",
            "meter": {
              "number": "1ESY1161338362",
              "installation": { "id": "22231002-eee7-41b3-ac68-6d90f4be5db0" }
            },
            "values": [
              { "obisCode": "1-0:1.8.0", "value": 6285980, "unit": "Wh" },
              { "obisCode": "1-0:16.7.0", "value": 164.96, "unit": "W" },
              { "obisCode": "1-0:36.7.0", "value": 164.96, "unit": "W" }
            ]
          }
        ],
        "cursor": "eyJsYXN0SWQiOiI..."
      }
    }
  }
}

For what each OBIS code means, see the OBIS Codes reference.

Field reference

Field Type Description
installation.id UUID string Unique identifier for a linked IOmeter installation
device.id UUID string Unique identifier for an IOmeter device
meter.number String Human-readable meter identifier (DIN 43849 format, e.g. 1ESY1161338362). May be a hex string for legacy meters.
time ISO 8601 string UTC timestamp of the reading
values[].obisCode String OBIS code identifying the measurement
values[].value Float Numeric measurement value
values[].unit String Unit of measurement (e.g. Wh, W)
cursor String Opaque pagination cursor for the next page of results

Next: Subscriptions for continuous/real-time delivery instead of point-in-time queries.

Subscriptions

Both subscriptions below use the WebSocket / graphql-ws mechanics described in Authentication and Transport.

Scheduled readings stream

Purpose: receive all scheduled meter readings of all your linked installations, continuously, as they're recorded.

subscription {
  meterReadings {
    meter { number }
    time
    values { obisCode value unit }
  }
}

Example event payload:

{
  "data": {
    "meterReadings": {
      "meter": { "number": "1ESY1161338362" },
      "time": "2026-03-18T13:15:00Z",
      "values": [
        { "obisCode": "1-0:1.8.0", "value": 6285980, "unit": "Wh" },
        { "obisCode": "1-0:16.7.0", "value": 164.96, "unit": "W" }
      ]
    }
  }
}

Live readings (10-second interval)

Purpose: trigger real-time electrical power readings for a specific installation at 10-second intervals — useful for showing current power consumption to a user.

Important: opening this subscription puts the IOmeter Core into a high-frequency mode that uses significantly more battery. It stops automatically when the subscription is closed by the client, or after a 30-minute safety timeout. Live readings are not stored in the database and do not appear in scheduled-reading queries.

subscription LiveReadings($installationId: ID!, $obisCodes: [ObisCode]) {
  liveReadings(installationId: $installationId, obisCodes: $obisCodes) {
    meter {
      id
      number
      installation { id }
    }
    time
    values { obisCode value unit }
  }
}

Variables:

{
  "installationId": "22231002-eee7-41b3-ac68-6d90f4be5db0",
  "obisCodes": ["1-0:16.7.0"]
}

obisCodes is optional — omit it to receive all available OBIS codes.

Example event payload:

{
  "data": {
    "liveReadings": {
      "meter": { "number": "1ESY1161338362" },
      "time": "2026-03-18T13:27:41Z",
      "values": [
        { "obisCode": "1-0:16.7.0", "value": 166.68, "unit": "W" }
      ]
    }
  }
}

Full GraphQL Reference

The guides in this section are a narrative walkthrough of the operations you'll actually use. For the exhaustive, field-by-field schema — every type, every argument, every scalar, kept automatically in sync with the live API — use the auto-generated reference:

partner.prod.iometer.cloud/api

It documents:

  • Both supported authentication methods (Basic Auth and WSSE)
  • Every query, mutation, and subscription with full argument lists
  • Every object and scalar type, including OBIS code format variants

Use it as your source of truth for exact field names and types; use the guides here for why and when.