Skip to content

REST API ​

ZiggyMeter exposes a REST API for configuration, status and integration. It is the same API the built-in web interface uses, so anything you can do in a browser you can do programmatically.

The complete list of routes is in the Endpoint reference.

Transport ​

Client modeAccess Point mode
Schemehttps://http://
Port44380
HostZiggyMeter-XXXX or the router-assigned IP192.168.4.1

Requests and responses are JSON. Send Content-Type: application/json and expect JSON back unless a route says otherwise.

WARNING

In client mode the device uses a self-signed certificate. curl will refuse it until you either point it at the certificate with --cacert <ca.pem>, add the certificate to your trust store, or — for testing only — pass -k. You can also install a certificate of your own, see HTTPS certificate.

Authentication ​

Every route except the login challenge requires a bearer token:

http
Authorization: Bearer <token>

Tokens are time-limited. The login response reports the remaining validity in expires_in_secs (1800 seconds at the time of writing). Refresh before expiry, or handle 401 Unauthorized by logging in again.

Long-lived tokens ​

For scripts and integrations, create a long-lived token instead — in the web interface under System → Long-lived access tokens, or with POST /api/v1/system/tokens. It never expires and is sent the same way, so the program needs neither the password nor the login exchange:

bash
curl -s https://<host>/api/v1/system/status   -H "Authorization: Bearer <long-lived token>"

Up to three can exist at a time. Each can be revoked on its own, so give every program its own token.

Challenge-response login ​

The password is never sent. Login is a two-step exchange:

  1. GET /api/v1/system/challenge returns a single-use nonce and the account's salt.
  2. POST /api/v1/system/login sends username, the same nonce, and a proof:
text
proof = SHA256( SHA256(salt ‖ password) ‖ nonce )

The inner hash — SHA256(salt ‖ password) — is exactly what the device stores, so it can verify the proof without knowing the password. Both hashes are computed over raw bytes: the inner SHA-256 digest is concatenated with the UTF-8 bytes of the nonce, not with its hex representation.

The nonce is consumed on first use and expires after 60 seconds, so a captured proof cannot be replayed. Changing the password works the same way — see POST /api/v1/system/password.

INFO

The older plaintext form ({"username", "password"}) still works over HTTPS for backwards compatibility, but is rejected while the device serves over plain HTTP in Access Point mode. New code should use challenge-response, which works in both modes.

Quick start ​

Python ​

python
import hashlib, json, urllib.request

BASE = "https://ziggymeter-1234.local"   # or http://192.168.4.1 in AP mode
USERNAME = "admin"
PASSWORD = "<your administrator password>"

def api(path, body=None, token=None):
    req = urllib.request.Request(
        BASE + path,
        data=json.dumps(body).encode() if body is not None else None,
        method="POST" if body is not None else "GET",
    )
    req.add_header("Content-Type", "application/json")
    if token:
        req.add_header("Authorization", "Bearer " + token)
    return json.loads(urllib.request.urlopen(req, timeout=30).read())

# 1. Obtain a challenge
challenge = api("/api/v1/system/challenge")

# 2. Compute the proof and log in
inner = hashlib.sha256((challenge["salt"] + PASSWORD).encode()).digest()
proof = hashlib.sha256(inner + challenge["nonce"].encode()).hexdigest()
token = api("/api/v1/system/login", {
    "username": USERNAME,
    "nonce": challenge["nonce"],
    "proof": proof,
})["token"]

# 3. Use it
print(api("/api/v1/system/status", token=token))

curl ​

bash
# Challenge
curl -s https://<host>/api/v1/system/challenge

# Login (compute the proof yourself — see above)
curl -s -X POST https://<host>/api/v1/system/login \
  -H "Content-Type: application/json" \
  -d '{"username":"admin","nonce":"<nonce>","proof":"<proof>"}'

# Authenticated call
curl -s https://<host>/api/v1/system/status \
  -H "Authorization: Bearer <token>"

# Logout — invalidates the token immediately
curl -s -X POST https://<host>/api/v1/system/logout \
  -H "Authorization: Bearer <token>"

Errors ​

StatusMeaning
400 Bad RequestMalformed JSON, or a field failed validation
401 UnauthorizedMissing, invalid or expired token; or a bad login proof
404 Not FoundUnknown route
500 Internal Server ErrorThe device could not complete the operation

Practical notes ​

  • The device is small. It serves one request at a time comfortably; do not open many parallel connections. The web interface itself queues its requests.
  • Throttle firmware uploads. POST /api/v1/system/ota must be rate-limited or the device reports an error. --limit-rate 128k is a tested working value — see Firmware upgrade.
  • Reading takes time. GET /api/v1/iec62056/read performs a full meter conversation before it responds; allow up to 60 seconds. Use /iec62056/last to fetch the previous result instantly.
  • Secrets are write-only. Passwords and DLMS keys are never returned; the API reports only whether one is set.
  • Do not expose the device to the internet. See Custom integrations.

Next ​