Authentication - GlMeter API

Team
Utilities Team
Last Updated
August 7, 2026

The GlMeter API uses the GL-HMAC authentication scheme to secure all requests. This guide covers credential management, the authentication mechanism, and security best practices.

Obtaining credentials

Credentials are created by calling the management API directly, authenticated via your own Dashboard user session (email/password login—the same account used at https://utilities.grouplinkone.com/).

Prerequisites

1. Account Group Link: active account with the organization's admin profile.

2. HTTP Client: curl, Postman, or similar — there is no dedicated UI for this step today (nor is one needed).

Step 1 — Log in and obtain the session token

Bash

1curl -X POST "https://grouplink-api.grouplinkone.com/login" \
2 -H "Content-Type: application/json" \
3 -d '{"email": "[email protected]", "password": "your-password"}'

Response:

1{ "access_token": "eyJhbGciOi..." }

Use this access_token as a Bearer token (Authorization: Bearer <access_token>) in all subsequent calls.

Step 2 — Check the available scope (optional)

1curl -X GET "https://grouplink-api.grouplinkone.com/org-api-credentials/available-scopes" \
2 -H "Authorization: Bearer <access_token>"

Confirm that the response includes glmeter:consumption:read for your organization's use case.

Step 3 — Create the credential

1curl -X POST "https://grouplink-api.grouplinkone.com/org-api-credentials" \
2 -H "Authorization: Bearer <access_token>" \
3 -H "Content-Type: application/json" \
4 -d '{
5 "name": "Integration ERP - Consumption",
6 "type": "secret",
7 "scopes": ["glmeter:consumption:read"]
8 }'
9

Response:

1{
2 "id": "b3f1...",
3 "access_key": "sk_live_abc123def456",
4 "secret_key": "your_secret_key_from_credential_creation",
5 "type": "secret",
6 "scopes": ["glmeter:consumption:read"],
7 "allowed_cidrs": [],
8 "created_at": "2026-07-28T12:00:00.000Z"
9}
10

IMPORTANT: save the access_key and secret_key immediately. — The secret_key is only returned in this response; it can never be retrieved again. If lost, revoke the credential and create a new one.

Credential management
Using the same Bearer session token:

GL-HMAC Authentication

The GlMeter API uses GL-HMAC authentication with secret credentials (`sk_live_*`) to ensure request integrity and prevent tampering. All requests require HMAC-SHA256 signatures.

Authorization header format:

1Authorization: GL-HMAC access_key=<key> signature=<sig> timestamp=<ts>
  • access_key: your access key, `sk_live_*` (secret) or `pk_live_*` (public).
  • signature: HMAC-SHA256 signature (hex, lowercase) — or the literal string `public` for `pk_live_*` credentials.
  • timestamp: Unix timestamp in seconds.

Step 1: Calculate the body hash

Calculate the SHA-256 hash of the request body. For `GET` requests (no body), use the hash of the empty string.

1body_hash = SHA256(request_body)

Examples:

  • GET request: SHA256("") = e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
  • POST with JSON: SHA256('{"device_ids":[123,456]}') = <calculated_hash>

Step 2: Build the signing string

Build the signing string by joining these components with a line break (`\n`):

1signing_string = METHOD + "\n" +
2 PATH + "\n" +
3 QUERY + "\n" +
4 BODY_HASH + "\n" +
5 TIMESTAMP

Components:

  • METHOD: HTTP verb in uppercase (`GET`, `POST`).
  • PATH: URL path without the query string (e.g. `/glmeter/v1/devices`).
  • QUERY: query string without the `?` (e.g. `ipp=25&next_page_token=...`).
  • BODY_HASH: SHA-256 hash from Step 1 (hex, lowercase).
  • TIMESTAMP: Unix timestamp in seconds (as a string).

Important: if there is no query string, use an empty string (still include the line). Build the query string in exactly the same order/encoding that will be used in the actual request URL — signing and sending must use the same bytes.

Step 3: Generate the HMAC signature

Calculate the HMAC-SHA256 using your secret key:

1signature = HMAC_SHA256(secret_key, signing_string)

The result must be hex, lowercase.

Step 4: Build the Authorization header

1Authorization: GL-HMAC access_key=sk_live_abc123 signature=a1b2c3... timestamp=1706356800

Timestamp validation

  • Format: Unix timestamp in seconds (integer).
  • Maximum deviation: ±5 minutes (300 seconds) — the same GL-HMAC scheme used by the API Gateway across all integrations (including Sonarprint).
  • Validation: the request timestamp must be within 5 minutes of the server time.

Make sure your system clock is synchronized (NTP) to avoid authentication failures.

Complete example (Node.js)

Same logic as the step-by-step above, just in actual code — this is what it looks like in a real integration:

1const crypto = require('crypto');
2const https = require('https');
3
4const ACCESS_KEY = 'sk_live_abc123def456';
5const SECRET_KEY = 'your_secret_key_from_credential_creation';
6const API_BASE_URL = 'https://api-gateway.grouplinkone.com';
7
8function signRequest(method, path, queryString, bodyString) {
9 const timestamp = Math.floor(Date.now() / 1000).toString();
10
11 // Step 1: SHA-256 of the body — comes from the native "crypto" module
12 const bodyHash = crypto.createHash('sha256').update(bodyString).digest('hex');
13
14 // Step 2: build the signing string (here "\n" inside the template string IS already a real line break)
15 const signingString = `${method}\n${path}\n${queryString}\n${bodyHash}\n${timestamp}`;
16
17 // Step 3: HMAC-SHA256 — also comes from the native "crypto" module
18 const signature = crypto.createHmac('sha256', SECRET_KEY).update(signingString).digest('hex');
19
20 return { timestamp, signature };
21}
22
23const path = '/glmeter/v1/devices';
24const queryString = 'ipp=25';
25const { timestamp, signature } = signRequest('GET', path, queryString, '');
26
27// Step 4: build the header
28const authHeader = `GL-HMAC access_key=${ACCESS_KEY} signature=${signature} timestamp=${timestamp}`;
29
30// Step 5: make the request
31https.get(`${API_BASE_URL}${path}?${queryString}`, { headers: { Authorization: authHeader } }, (res) => {
32 let data = '';
33 res.on('data', (chunk) => (data += chunk));
34 res.on('end', () => console.log(JSON.parse(data)));
35});

API endpoints reference

Base URL:

1https://api-gateway.grouplinkone.com

Endpoints (see request/response examples for each route below):

1GET /glmeter/v1/devices
2POST /glmeter/v1/dashboard
3GET /glmeter/v1/history
4GET /glmeter/v1/history/3m
5GET /glmeter/v1/projection/bulk
6GET /glmeter/v1/projection/{deviceId}

Complete example (Bash/cURL)

1#!/bin/bash
2set -e
3
4# Configuration (replace with your credentials from the creation response at /org-api-credentials)
5ACCESS_KEY="sk_live_abc123def456"
6SECRET_KEY="your_secret_key_from_credential_creation"
7API_BASE_URL="https://api-gateway.grouplinkone.com"
8
9# Request details
10METHOD="GET"
11PATH="/glmeter/v1/devices"
12QUERY="ipp=25"
13
14# Step 1: Calculate the body hash (empty for GET)
15BODY_HASH=$(echo -n "" | sha256sum | awk '{print $1}')
16
17# Step 2: Get the current timestamp
18TIMESTAMP=$(date +%s)
19
20# Step 3: Build the signing string
21SIGNING_STRING="${METHOD}\n${PATH}\n${QUERY}\n${BODY_HASH}\n${TIMESTAMP}"
22
23# Step 4: Generate the HMAC signature
24SIGNATURE=$(echo -n -e "${SIGNING_STRING}" | openssl dgst -sha256 -hmac "${SECRET_KEY}" | awk '{print $2}')
25
26# Step 5: Build the authorization header
27AUTH_HEADER="GL-HMAC access_key=${ACCESS_KEY} signature=${SIGNATURE} timestamp=${TIMESTAMP}"
28
29# Step 6: Make the request
30curl -X GET "${API_BASE_URL}${PATH}?${QUERY}" \
31 -H "Authorization: ${AUTH_HEADER}"

Expected response:

1{
2 "rows": [
3 { "id": 123, "name": "Device A" }
4 ],
5 "has_more": false,
6 "next_page_token": null
7}

Request limit (rate limit)

Rate Limit:

  • Limit: 60 requests/minute per credential.
  • Response code: 429 Too Many Requests when exceeded.

When the limit is exceeded:

1HTTP/1.1 429 Too Many Requests
2Retry-After: 6

Always respect the Retry-After header in your retry logic.

Common authentication errors

CodeSituationSolution
401Invalid signatureCheck the signing string construction and the HMAC calculation (order of method/path/query/body-hash/timestamp, encoding)
401Timestamp deviation too largeSynchronize the system clock (must be within ±5 minutes)
401Invalid or missing access key Check the access_key via GET /org-api-credentials, or whether the Authorization header is present
401Revoked credentialThe credential has been revoked; create a new one
403Missing scope / IP not allowedThe credential does not have the glmeter:consumption:read scope, or the source IP is outside the configured allowed_cidrs
429Request limit exceededWait for the time indicated in the Retry-After header


Security best practices

1. Never expose secret keys: do not commit them to repositories or share them publicly.

2. Use environment variables: store credentials outside the source code, securely.

3. Rotate credentials periodically: create new credentials periodically and migrate the integration.

4. Revoke compromised credentials: revoke immediately if exposed.

5. Prefer secret credentials in production: `public` credentials skip HMAC validation entirely.

6. Restrict allowed_cidrs whenever the source IPs are known and stable.


Endpoint examples

Each example below shows a complete request and a response for that route.

List devices

GET /glmeter/v1/devices

1curl -X GET "https://api-gateway.grouplinkone.com/glmeter/v1/devices?limit=25" \
2 -H "Authorization: GL-HMAC access_key=<access_key> signature=<signature> timestamp=<timestamp>"

Response:

1{
2 "devices": [
3 { "device_id": "987654321", "activated_at": "2026-06-11T17:08:41.589Z", "channels": 1 }
4 ],
5 "next_cursor": null
6}

Consolidated dashboard

POST /glmeter/v1/dashboard

1curl -X POST "https://api-gateway.grouplinkone.com/glmeter/v1/dashboard" \
2 -H "Authorization: GL-HMAC access_key=<access_key> signature=<signature> timestamp=<timestamp>" \
3 -H "Content-Type: application/json" \
4 -d '{"devices": [{"device_id": 987654321}]}'

Response:

1{
2 "results": [
3 {
4 "device_id": 987654321,
5 "status": "SUCCESS",
6 "panels": [
7 { "type": "CURRENT_CONSUMPTION", "title": "Current consumption", "value": 279.5, "display_value": "279.5", "unit": "m³", "subtitle": "so far", "serie": "water", "channel": 0, "alerts": null },
8 { "type": "DAILY_ESTIMATE", "title": "Daily estimate", "value": 60.8, "display_value": "60.8", "unit": "m³", "serie": "water", "channel": 0, "alerts": null },
9 { "type": "LAST_READING", "title": "Last reading", "value": 34909.8, "display_value": "34,909.8", "unit": "m³", "serie": "water", "channel": 0, "alerts": null },
10 { "type": "MONTHLY_PROJECTION", "title": "Consumption projection", "value": 1884.0, "display_value": "1,884.0", "unit": "m³", "subtitle": "by end of month", "serie": "water", "channel": 0, "alerts": null }
11 ]
12 }
13 ]
14}

Consumption history

GET /glmeter/v1/history

1curl -X GET "https://api-gateway.grouplinkone.com/glmeter/v1/history?device_id=987654321&period=CURRENT_MONTH" \
2 -H "Authorization: GL-HMAC access_key=<access_key> signature=<signature> timestamp=<timestamp>" \
3 -H "X-Timezone: America/Sao_Paulo"

Response:

1{
2 "device_id": 987654321,
3 "period": "CURRENT_MONTH",
4 "time_zone": "America/Sao_Paulo",
5 "chart_type": "LINE",
6 "series": [
7 {
8 "serie": "water",
9 "label": "Water Consumption",
10 "unit": "m³",
11 "decimal_places": 3,
12 "data_type": "consumption",
13 "description": "Daily consumption values",
14 "data_points": [
15 { "date": "2026-08-01 00:00:00.000", "value": "45.000", "value_fmt": "45.000", "alerts": [] }
16 ],
17 "projection": { "date": "2026-08-31", "value": 1884.024 }
18 }
19 ],
20 "summary": "Approximately 55.9 m³/day. Projected for the month: 1884.024 m³"
21}

Note: chart_type depends on period"BAR" for 7D, 15D and past months (YYYY-MM); "LINE" for CURRENT_MONTH (to better show the trend/projection).

Last 3 months (monthly aggregated)

GET /glmeter/v1/history/3m

1curl -X GET "https://api-gateway.grouplinkone.com/glmeter/v1/history/3m?device_id=987654321" \
2 -H "Authorization: GL-HMAC access_key=<access_key> signature=<signature> timestamp=<timestamp>" \
3 -H "X-Timezone: America/Sao_Paulo"

Response:

1{
2 "device_id": 987654321,
3 "time_zone": "America/Sao_Paulo",
4 "series": [
5 {
6 "serie": "water",
7 "label": "Water consumption",
8 "unit": "m³",
9 "decimal_places": 2,
10 "rows": [
11 {
12 "date": "2026-07-01",
13 "value": "45.12",
14 "value_fmt": "45.12 m³",
15 "alerts": null,
16 "by_days": [
17 { "date": "2026-07-01", "value": "1.20", "value_fmt": "1.20 m³", "alerts": null }
18 ]
19 }
20 ]
21 }
22 ]
23}

Current month projection (single device)

GET /glmeter/v1/projection

1curl -X GET "https://api-gateway.grouplinkone.com/glmeter/v1/projection?device_id=987654321" \
2 -H "Authorization: GL-HMAC access_key=<access_key> signature=<signature> timestamp=<timestamp>" \
3 -H "X-Timezone: America/Sao_Paulo"

Response:

1{
2 "device_id": "987654321",
3 "projected_value": 48.6,
4 "projected_cost": 152.30,
5 "unit": "m³",
6 "currency_symbol": "R$",
7 "days_used_for_calculation": 20,
8 "total_days_in_month": 31,
9 "average_daily_consumption": 1.57,
10 "calculation_date": "2026-07-20",
11 "projection_date": "2026-07-31"
12}

Current month projections (bulk)

GET /glmeter/v1/projection/bulk

1curl -X GET "https://api-gateway.grouplinkone.com/glmeter/v1/projection/bulk?device_ids=987654321,987654322" \
2 -H "Authorization: GL-HMAC access_key=<access_key> signature=<signature> timestamp=<timestamp>" \
3 -H "X-Timezone: America/Sao_Paulo"

Response:

1{
2 "projections": [
3 {
4 "device_id": "987654321",
5 "projected_value": 48.6,
6 "unit": "m³",
7 "days_used_for_calculation": 20,
8 "total_days_in_month": 31,
9 "average_daily_consumption": 1.57,
10 "calculation_date": "2026-07-20",
11 "projection_date": "2026-07-31"
12 }
13 ],
14 "errors": []
15}