Gateway API Call & Command Guide

Technical integration blueprint for invoking Home Assistant devices via the Lumi local messaging gateway.

info 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.

devices 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:

GET / POST /api/device/[DEVICE_ID]/[ACTION]

Where:

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:

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.

vpn_key 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

GET /api/auth/otp/[YOUR_10_DIGIT_MOBILE]

Step 2: Verify the OTP code and retrieve the Bearer Token

GET /api/auth/verify/[YOUR_10_DIGIT_MOBILE]/[6_DIGIT_OTP_CODE]

On successful verification, the gateway returns a JSON response containing the Bearer JWT token.

lock 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"

sms 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"

verified 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"

code 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))