Gateway API Call & Command Guide
Technical integration blueprint for invoking Home Assistant devices via the Lumi local messaging gateway.
Introduction
The Lumi Gateway enables external devices, microcontrollers, Home Assistant dashboard buttons, Tasker widgets, or scripts to securely trigger configured HA smart home actions locally. Endpoints accept requests using standard JWT tokens, simple OTP query parameters, or stateless HMAC-SHA256 signatures.
Device Endpoints & Client Templates
Instead of hardcoding a separate API route for every single device, the Lumi Gateway uses a single dynamic routing endpoint parameter structure. All devices are triggered through this unified route:
Where:
[DEVICE_ID]is the unique ID assigned to the device (e.g.dev-12345), found in the Admin Portal under the Devices or Generated APIs tab.[ACTION]is the dynamic service action you wish to perform (e.g.on,off,toggle,open,close,lock,unlock,run,press).
Managing Multiple Devices via Client Templates
If you are integrating multiple devices into automation platforms like Tasker, iOS Shortcuts, Node-RED, or custom scripts, you do not need to duplicate your setup for every device. You can create a reusable request template utilizing variables:
General Client Request Template Structure:
http://[GATEWAY_IP]:9875/api/device/{{device_id}}/{{action}}?mobile={{mobile_number}}&otp={{otp_code}}
Example Tasker Action (Android):
Create a single task with an HTTP Request action:
- Method: GET
- URL:
http://192.168.1.100:9875/api/device/%device_id/%action?mobile=%mobile_number&otp=%otp_code
By mapping widget clicks or voice triggers to assign different values to the %device_id and %action variables, a single template task can control all your home devices dynamically.
OTP Authentication Flow
Before using JWT session authentication, clients must verify their registered mobile number. This triggers a 6-digit OTP code sent via WhatsApp.
Step 1: Request an OTP code
Step 2: Verify the OTP code and retrieve the Bearer Token
On successful verification, the gateway returns a JSON response containing the Bearer JWT token.
JWT Bearer Token Authentication (Option A)
Standard JWT Bearer token sessions. You can send the token in request headers or directly append it as a URL query parameter.
Example GET command (Browser / Tasker URL)
curl -X GET "http://[GATEWAY_IP]:9875/api/device/dev-12345/toggle?token=your_jwt_token"
Example POST command (API Request Headers)
curl -X POST "http://[GATEWAY_IP]:9875/api/device/dev-12345/toggle" \ -H "Authorization: Bearer your_jwt_token" \ -H "Content-Type: application/json"
Mobile & OTP Verification (Option B)
A stateless verification flow. If you prefer not to manage local JWT storage, you can directly append active mobile and otp credentials with every device command.
Example GET command
curl -X GET "http://[GATEWAY_IP]:9875/api/device/dev-12345/toggle?mobile=9876543210&otp=768777"
Example POST command
curl -X POST "http://[GATEWAY_IP]:9875/api/device/dev-12345/toggle?mobile=9876543210&otp=768777" \ -H "Content-Type: application/json"
Stateless HMAC Request Signatures (Option C)
Stateless signed commands. This uses the server's JWT_SECRET to hash request details on the client-side. The gateway validates the signature and automatically authorizes the command.
To prevent replay attacks, the gateway compares the signature's timestamp parameter against the server time. The request expires if it is older than the allowed drift seconds configured in settings.
Signature Message Structure:
TIMESTAMP:METHOD:PATH:BODY
Where TIMESTAMP is the Unix epoch time in milliseconds, METHOD is POST or GET, PATH is the relative endpoint path (e.g. /api/device/dev-12345/toggle), and BODY is the stringified JSON request body (or empty string if no body is sent).
Example A: GET (Direct Call via Query Parameters - No Headers Required)
If you cannot set custom headers (e.g., when invoking from a standard web browser, Siri Shortcuts, or simple widgets), you can append both parameters directly to the URL query string:
curl -X GET "http://[GATEWAY_IP]:9875/api/device/dev-12345/toggle?timestamp=unix_epoch_milliseconds&signature=computed_hmac_hex"
Example B: POST (Using Custom Request Headers)
curl -X POST "http://[GATEWAY_IP]:9875/api/device/dev-12345/toggle" \ -H "Content-Type: application/json" \ -H "x-signature: computed_hmac_hex" \ -H "x-timestamp: unix_epoch_milliseconds"
Client-Side Request Signing Templates
Node.js (JavaScript) - GET Request Template (Auto-Parses pasted URL):
const crypto = require('crypto');
const axios = require('axios');
// Configure your gateway secret key
const JWT_SECRET = 'your_jwt_secret';
// Paste any device URL copied from the Admin portal (e.g. without signature/token)
const targetUrl = 'http://gateway-ip:9875/api/device/dev-12345/toggle';
try {
// Parse the URL automatically to extract the path and origin
const parsedUrl = new URL(targetUrl);
const gatewayOrigin = parsedUrl.origin;
const path = parsedUrl.pathname;
const timestamp = Date.now().toString();
// Message structure for GET (empty body): TIMESTAMP:GET:PATH:
const message = `${timestamp}:GET:${path}:`;
// Compute HMAC signature
const signature = crypto.createHmac('sha256', JWT_SECRET).update(message).digest('hex');
// Print HMAC details to console
console.log('--- HMAC Signature Details (GET) ---');
console.log('Timestamp (ms): ', timestamp);
console.log('HMAC Input Message: ', `"${message}"`);
console.log('Computed Signature: ', signature);
// Build signed URL with signature and timestamp query parameters (timestamp first to prevent HTML entity copy bugs)
const signedUrl = `${gatewayOrigin}${path}?timestamp=${timestamp}&signature=${signature}`;
console.log('Signed GET URL: ', signedUrl);
console.log('------------------------------------');
// Send request
axios.get(signedUrl)
.then(res => console.log('Response:', res.data))
.catch(err => console.error('API Error:', err.response ? err.response.data : err.message));
} catch (e) {
console.error('Invalid target URL:', e.message);
}
Node.js (JavaScript) - POST Request Template (Auto-Parses pasted URL):
const crypto = require('crypto');
const axios = require('axios');
// Configure your gateway secret key
const JWT_SECRET = 'your_jwt_secret';
// Paste any device URL copied from the Admin portal (e.g. without signature/token)
const targetUrl = 'http://gateway-ip:9875/api/device/dev-12345/set_speed';
// Define the request body to send (empty object {} for toggle, or actual payload)
const body = { };
try {
// Parse the URL automatically to extract the path and origin
const parsedUrl = new URL(targetUrl);
const gatewayOrigin = parsedUrl.origin;
const path = parsedUrl.pathname;
const timestamp = Date.now().toString();
// Format body as string if it has keys, otherwise empty string
const bodyStr = Object.keys(body).length ? JSON.stringify(body) : '';
// Message structure for POST: TIMESTAMP:POST:PATH:BODY
const message = `${timestamp}:POST:${path}:${bodyStr}`;
// Compute HMAC signature
const signature = crypto.createHmac('sha256', JWT_SECRET).update(message).digest('hex');
// Print HMAC details to console
console.log('--- HMAC Signature Details (POST) ---');
console.log('Timestamp (ms): ', timestamp);
console.log('HMAC Input Message: ', `"${message}"`);
console.log('Computed Signature: ', signature);
// Build target URL (clean URL, no query parameters)
const targetSignedUrl = `${gatewayOrigin}${path}`;
console.log('Target POST URL: ', targetSignedUrl);
console.log('Headers Sent: ', JSON.stringify({
'Content-Type': 'application/json',
'x-signature': signature,
'x-timestamp': timestamp
}, null, 2));
console.log('-------------------------------------');
// Send POST request
axios.post(targetSignedUrl, body, {
headers: {
'Content-Type': 'application/json',
'x-signature': signature,
'x-timestamp': timestamp
}
})
.then(res => console.log('Response:', res.data))
.catch(err => console.error('API Error:', err.response ? err.response.data : err.message));
} catch (e) {
console.error('Invalid target URL:', e.message);
}
Python - GET Request Template (Auto-Parses pasted URL):
import time, hmac, hashlib, requests
from urllib.parse import urlparse
# Configure your gateway secret key
JWT_SECRET = 'your_jwt_secret'
# Paste any device URL copied from the Admin portal (e.g. without signature/token)
target_url = 'http://gateway-ip:9875/api/device/dev-12345/toggle'
try:
# Parse the URL automatically to extract the path and origin
parsed_url = urlparse(target_url)
gateway_origin = f"{parsed_url.scheme}://{parsed_url.netloc}"
path = parsed_url.path
timestamp = str(int(time.time() * 1000))
# Message structure for GET (empty body): TIMESTAMP:GET:PATH:
message = f'{timestamp}:GET:{path}:'
# Compute HMAC signature
signature = hmac.new(
JWT_SECRET.encode('utf-8'),
message.encode('utf-8'),
hashlib.sha256
).hexdigest()
# Print HMAC details to console
print('--- HMAC Signature Details (GET) ---')
print('Timestamp (ms): ', timestamp)
print('HMAC Input Message: ', f'"{message}"')
print('Computed Signature: ', signature)
# Build signed URL with signature and timestamp query parameters (timestamp first to prevent HTML entity copy bugs)
signed_url = f'{gateway_origin}{path}?timestamp={timestamp}&signature={signature}'
print('Signed GET URL: ', signed_url)
print('------------------------------------')
# Send request
res = requests.get(signed_url)
print('Response:', res.json())
except Exception as e:
print('URL Parsing/Request Error:', str(e))
Python - POST Request Template (Auto-Parses pasted URL):
import time, hmac, hashlib, json, requests
from urllib.parse import urlparse
# Configure your gateway secret key
JWT_SECRET = 'your_jwt_secret'
# Paste any device URL copied from the Admin portal (e.g. without signature/token)
target_url = 'http://gateway-ip:9875/api/device/dev-12345/set_speed'
# Define the request body to send
body = { }
try:
# Parse the URL automatically to extract the path and origin
parsed_url = urlparse(target_url)
gateway_origin = f"{parsed_url.scheme}://{parsed_url.netloc}"
path = parsed_url.path
timestamp = str(int(time.time() * 1000))
# Format body as string if it has keys, otherwise empty string
body_str = json.dumps(body) if body else ''
# Message structure for POST: TIMESTAMP:POST:PATH:BODY
message = f'{timestamp}:POST:{path}:{body_str}'
# Compute HMAC signature
signature = hmac.new(
JWT_SECRET.encode('utf-8'),
message.encode('utf-8'),
hashlib.sha256
).hexdigest()
# Print HMAC details to console
print('--- HMAC Signature Details (POST) ---')
print('Timestamp (ms): ', timestamp)
print('HMAC Input Message: ', f'"{message}"')
print('Computed Signature: ', signature)
# Build target URL (clean URL, no query parameters)
target_signed_url = f'{gateway_origin}{path}'
print('Target POST URL: ', target_signed_url)
# Send POST request with headers
headers = {
'Content-Type': 'application/json',
'x-signature': signature,
'x-timestamp': timestamp
}
res = requests.post(target_signed_url, json=body, headers=headers)
print('Response:', res.json())
except Exception as e:
print('URL Parsing/Request Error:', str(e))