Authentication - GlMeter API
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:
- List: GET https://grouplink-api.grouplinkone.com/org-api-credentials
- Revoke: PATCH https://grouplink-api.grouplinkone.com/org-api-credentials/{id}/revoke
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');34const ACCESS_KEY = 'sk_live_abc123def456';5const SECRET_KEY = 'your_secret_key_from_credential_creation';6const API_BASE_URL = 'https://api-gateway.grouplinkone.com';78function signRequest(method, path, queryString, bodyString) {9 const timestamp = Math.floor(Date.now() / 1000).toString();1011 // Step 1: SHA-256 of the body — comes from the native "crypto" module12 const bodyHash = crypto.createHash('sha256').update(bodyString).digest('hex');1314 // 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}`;1617 // Step 3: HMAC-SHA256 — also comes from the native "crypto" module18 const signature = crypto.createHmac('sha256', SECRET_KEY).update(signingString).digest('hex');1920 return { timestamp, signature };21}2223const path = '/glmeter/v1/devices';24const queryString = 'ipp=25';25const { timestamp, signature } = signRequest('GET', path, queryString, '');2627// Step 4: build the header28const authHeader = `GL-HMAC access_key=${ACCESS_KEY} signature=${signature} timestamp=${timestamp}`;2930// Step 5: make the request31https.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/devices2POST /glmeter/v1/dashboard3GET /glmeter/v1/history4GET /glmeter/v1/history/3m5GET /glmeter/v1/projection/bulk6GET /glmeter/v1/projection/{deviceId}
Complete example (Bash/cURL)
1#!/bin/bash2set -e34# 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"89# Request details10METHOD="GET"11PATH="/glmeter/v1/devices"12QUERY="ipp=25"1314# Step 1: Calculate the body hash (empty for GET)15BODY_HASH=$(echo -n "" | sha256sum | awk '{print $1}')1617# Step 2: Get the current timestamp18TIMESTAMP=$(date +%s)1920# Step 3: Build the signing string21SIGNING_STRING="${METHOD}\n${PATH}\n${QUERY}\n${BODY_HASH}\n${TIMESTAMP}"2223# Step 4: Generate the HMAC signature24SIGNATURE=$(echo -n -e "${SIGNING_STRING}" | openssl dgst -sha256 -hmac "${SECRET_KEY}" | awk '{print $2}')2526# Step 5: Build the authorization header27AUTH_HEADER="GL-HMAC access_key=${ACCESS_KEY} signature=${SIGNATURE} timestamp=${TIMESTAMP}"2829# Step 6: Make the request30curl -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": null7}
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 Requests2Retry-After: 6
Always respect the Retry-After header in your retry logic.
Common authentication errors
| Code | Situation | Solution |
|---|---|---|
| 401 | Invalid signature | Check the signing string construction and the HMAC calculation (order of method/path/query/body-hash/timestamp, encoding) |
| 401 | Timestamp deviation too large | Synchronize the system clock (must be within ±5 minutes) |
| 401 | Invalid or missing access key | Check the access_key via GET /org-api-credentials, or whether the Authorization header is present |
| 401 | Revoked credential | The credential has been revoked; create a new one |
| 403 | Missing scope / IP not allowed | The credential does not have the glmeter:consumption:read scope, or the source IP is outside the configured allowed_cidrs |
| 429 | Request limit exceeded | Wait 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": null6}
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}