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:
- Link IOmeter customer accounts to their own platform (account linking)
- List linked IOmeter installations and their devices (Installations & Readings)
- Query historical (scheduled) meter readings from IOmeter's database (Installations & Readings)
- Receive a continuous stream of scheduled readings, or trigger near real-time live readings (Subscriptions)
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
- Read Authentication to understand how requests are signed and how GraphQL is transported (HTTP vs WebSocket).
- Walk through Account Linking to get your first
installation_id. - Use Installations & Readings and Subscriptions to pull data.
- 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.
-
Open a WebSocket connection to
wss://partner.prod.iometer.cloud/api/v1/querywith subprotocolgraphql-ws. -
Send
connection_initwith the auth token in the payload (required by some resolvers):
{ "type": "connection_init", "payload": { "Authorization": "Basic <token>" } }
- Wait for
connection_ack:
{ "type": "connection_ack" }
- Send a
startmessage with your subscription query:
{
"type": "start",
"id": "1",
"payload": {
"query": "subscription { meterReadings { time values { obisCode value unit } } }"
}
}
- Receive
datamessages:
{ "type": "data", "id": "1", "payload": { "data": { ... } } }
- 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 astatequery 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"
}
}
}
2. Open it as a universal link
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.
← Zurück zum Integration Guide · Weiter zu: LAN Integration · Push Integration