External API

Control lighting programmatically via HTTP REST API

Overview

The External API allows you to control your Hackers Nightlight lighting from external scripts and applications. It uses a simple HTTP REST interface secured with API key authentication.

Enable the API

  1. Open the WebUI and navigate to Settings → API
  2. Toggle Enable External API to on
  3. Click Generate Key to create an API key
  4. Copy and save the key immediately - it will only be shown once

Base URL

http://<device-ip>

For devices connected to your network (Station mode), use the device’s IP address (e.g., http://192.168.1.100).

For devices in Access Point mode, use the AP IP address (e.g., http://192.168.4.1).

Authentication

All API requests require an X-API-Key header containing your 64-character hex API key:

X-API-Key: cce9f0dae76bc8dec2f5c2a608a015d45476d8df8b3356a7c26147ba0e5c13e9

Endpoints

GET /api/v1/status

Get the current light state including mode, color values, and brightness.

Response:

{
  "success": true,
  "state": {
    "mode": "rgb",
    "r": 255,
    "g": 128,
    "b": 64,
    "warm": 0,
    "cold": 0,
    "brightness": 200
  }
}

Mode values: "rgb" or "pwm"

State fields:

FieldTypeDescription
modestringCurrent mode: “rgb” (color) or “pwm” (warm/cold white)
rintRed value (0-255)
gintGreen value (0-255)
bintBlue value (0-255)
warmintWarm white PWM value (0-255)
coldintCold white PWM value (0-255)
brightnessintBrightness level (0-255)

POST /api/v1/color

Set RGB color mode with optional brightness.

Request body:

{
  "r": 255,
  "g": 128,
  "b": 64,
  "brightness": 200
}
ParameterTypeRangeRequiredDefault
rint0-255Yes-
gint0-255Yes-
bint0-255Yes-
brightnessint0-255No255

Example response:

{"success": true}

POST /api/v1/pwm

Set warm/cold white PWM channels for color temperature control.

Request body:

{
  "warm": 200,
  "cold": 50,
  "brightness": 255
}
ParameterTypeRangeRequiredDefault
warmint0-255Yes-
coldint0-255Yes-
brightnessint0-255No255

Example response:

{"success": true}

POST /api/v1/brightness

Set the overall brightness level (affects both RGB and PWM modes).

Request body:

{
  "brightness": 180
}
ParameterTypeRangeRequiredDefault
brightnessint0-255Yes-

Example response:

{"success": true}

POST /api/v1/effect

Start or stop a lighting effect.

Request body:

{
  "effect": "aurora",
  "action": "start"
}
ParameterTypeRequiredDescription
effectstringYesEffect name (see table below)
actionstringYes“start” or “stop”

Available Effects:

EffectDescription
noneStop all effects, return to solid color
rainbowClassic rainbow color cycle
rainbow_fadeSmooth rainbow fade transition
auroraAurora borealis / northern lights effect
strobeStrobe/flashing light
discoDisco ball / music reactive style
ember_hearthWarm flickering fireplace embers

Example response:

{"success": true}

POST /api/v1/off

Turn off all lights immediately (sets all values to 0).

Request body: None required

Example response:

{"success": true}

GET /api/v1/info

Get API version and available endpoints.

Response:

{
  "success": true,
  "api_version": "1.0",
  "endpoints": ["status", "color", "pwm", "brightness", "effect", "off"]
}

GET /api/v1/settings

Get API enable/disable state and key status. Does not require API key.

Response:

{
  "enabled": true,
  "has_key": true,
  "key_shown": false
}

POST /api/v1/key/generate

Generate a new API key. Does not require existing API key.

Response:

{
  "success": true,
  "key": "cce9f0dae76bc8dec2f5c2a608a015d45476d8df8b3356a7c26147ba0e5c13e9"
}

Warning: Generating a new key invalidates the previous key.


POST /api/v1/settings

Enable or disable the API.

Request body:

{
  "enabled": true
}
ParameterTypeRequiredDescription
enabledboolYestrue to enable, false to disable

Example response:

{"success": true}

Python Example

import requests

class NightlightAPI:
    def __init__(self, host, api_key):
        self.base_url = f"http://{host}"
        self.headers = {"X-API-Key": api_key}

    def status(self):
        """Get current light state."""
        return requests.get(f"{self.base_url}/api/v1/status", headers=self.headers).json()

    def set_color(self, r, g, b, brightness=255):
        """Set RGB color with optional brightness (0-255)."""
        return requests.post(
            f"{self.base_url}/api/v1/color",
            headers=self.headers,
            json={"r": r, "g": g, "b": b, "brightness": brightness}
        ).json()

    def set_pwm(self, warm, cold, brightness=255):
        """Set warm/cold white PWM channels (0-255)."""
        return requests.post(
            f"{self.base_url}/api/v1/pwm",
            headers=self.headers,
            json={"warm": warm, "cold": cold, "brightness": brightness}
        ).json()

    def set_brightness(self, brightness):
        """Set overall brightness (0-255)."""
        return requests.post(
            f"{self.base_url}/api/v1/brightness",
            headers=self.headers,
            json={"brightness": brightness}
        ).json()

    def set_effect(self, effect, action="start"):
        """Start or stop an effect.

        Effects: 'none', 'rainbow', 'rainbow_fade', 'aurora',
                 'strobe', 'disco', 'ember_hearth'
        Actions: 'start', 'stop'
        """
        return requests.post(
            f"{self.base_url}/api/v1/effect",
            headers=self.headers,
            json={"effect": effect, "action": action}
        ).json()

    def turn_off(self):
        """Turn off all lights."""
        return requests.post(f"{self.base_url}/api/v1/off", headers=self.headers).json()

    def info(self):
        """Get API version and available endpoints."""
        return requests.get(f"{self.base_url}/api/v1/info", headers=self.headers).json()


# Usage Examples
if __name__ == "__main__":
    # Connect to device
    light = NightlightAPI("192.168.4.1", "your-api-key-here")

    # Check current state
    print(light.status())

    # Set warm orange color
    light.set_color(255, 140, 50)

    # Set to warm white
    light.set_pwm(warm=200, cold=50)

    # Start aurora effect
    light.set_effect("aurora")

    # Stop effect
    light.set_effect("none")

    # Turn off
    light.turn_off()

curl Examples

Get status:

curl -H "X-API-Key: your-key" http://192.168.4.1/api/v1/status

Set color (orange):

curl -X POST \
  -H "X-API-Key: your-key" \
  -H "Content-Type: application/json" \
  -d '{"r":255,"g":140,"b":50}' \
  http://192.168.4.1/api/v1/color

Set PWM (warm white):

curl -X POST \
  -H "X-API-Key: your-key" \
  -H "Content-Type: application/json" \
  -d '{"warm":200,"cold":50}' \
  http://192.168.4.1/api/v1/pwm

Start effect:

curl -X POST \
  -H "X-API-Key: your-key" \
  -H "Content-Type: application/json" \
  -d '{"effect":"aurora","action":"start"}' \
  http://192.168.4.1/api/v1/effect

Stop effect:

curl -X POST \
  -H "X-API-Key: your-key" \
  -H "Content-Type: application/json" \
  -d '{"effect":"none","action":"start"}' \
  http://192.168.4.1/api/v1/effect

Turn off:

curl -X POST -H "X-API-Key: your-key" http://192.168.4.1/api/v1/off

Generate new API key:

curl -X POST http://192.168.4.1/api/v1/key/generate

JavaScript / Node.js Example

class NightlightAPI {
  constructor(host, apiKey) {
    this.baseUrl = `http://${host}`;
    this.headers = { "X-API-Key": apiKey };
  }

  async status() {
    const res = await fetch(`${this.baseUrl}/api/v1/status`, { headers: this.headers });
    return res.json();
  }

  async setColor(r, g, b, brightness = 255) {
    const res = await fetch(`${this.baseUrl}/api/v1/color`, {
      method: "POST",
      headers: { ...this.headers, "Content-Type": "application/json" },
      body: JSON.stringify({ r, g, b, brightness })
    });
    return res.json();
  }

  async setPwm(warm, cold, brightness = 255) {
    const res = await fetch(`${this.baseUrl}/api/v1/pwm`, {
      method: "POST",
      headers: { ...this.headers, "Content-Type": "application/json" },
      body: JSON.stringify({ warm, cold, brightness })
    });
    return res.json();
  }

  async setEffect(effect, action = "start") {
    const res = await fetch(`${this.baseUrl}/api/v1/effect`, {
      method: "POST",
      headers: { ...this.headers, "Content-Type": "application/json" },
      body: JSON.stringify({ effect, action })
    });
    return res.json();
  }

  async turnOff() {
    const res = await fetch(`${this.baseUrl}/api/v1/off`, {
      method: "POST",
      headers: this.headers
    });
    return res.json();
  }
}

// Usage
const light = new NightlightAPI("192.168.4.1", "your-api-key-here");
await light.setColor(255, 140, 50);
await light.setEffect("aurora");
await light.turnOff();

Error Responses

HTTP StatusMeaningLikely Cause
200SuccessRequest completed successfully
400Bad RequestInvalid JSON or missing required fields
401UnauthorizedMissing or invalid API key
403ForbiddenAPI is disabled in settings

Error response body:

{
  "success": false,
  "message": "invalid json"
}

Or for auth errors:

{
  "error": "Missing API key"
}
{
  "error": "Invalid API key"
}

Security Notes

  • Keep your API key secret - anyone with the key can control your lights
  • Regenerate the key immediately if you suspect it has been compromised
  • Disable the API when not in use via Settings → API
  • The API is designed for local network use only - do not expose to the internet
  • API keys are stored in non-volatile flash memory and persist across reboots

Troubleshooting

“Missing API key” error:

  • Ensure you’re sending the X-API-Key header with every request
  • Check that the header name is exactly X-API-Key (case-sensitive)

“Invalid API key” error:

  • Verify the key matches exactly (64 hex characters)
  • Try generating a new key if the old one is not working

Requests seem to succeed but lights don’t change:

  • Ensure the API is enabled in Settings → API
  • Check that the device is still connected to the network

Connection refused:

  • Verify the device IP address is correct
  • Ensure the device is in the same network segment
  • Check if the device is in AP mode (use 192.168.4.1)