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 mode | Access Point mode | |
|---|---|---|
| Scheme | https:// | http:// |
| Port | 443 | 80 |
| Host | ZiggyMeter-XXXX or the router-assigned IP | 192.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:
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:
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:
GET /api/v1/system/challengereturns a single-usenonceand the account'ssalt.POST /api/v1/system/loginsendsusername, the samenonce, and aproof:
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
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
# 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
| Status | Meaning |
|---|---|
400 Bad Request | Malformed JSON, or a field failed validation |
401 Unauthorized | Missing, invalid or expired token; or a bad login proof |
404 Not Found | Unknown route |
500 Internal Server Error | The 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/otamust be rate-limited or the device reports an error.--limit-rate 128kis a tested working value — see Firmware upgrade. - Reading takes time.
GET /api/v1/iec62056/readperforms a full meter conversation before it responds; allow up to 60 seconds. Use/iec62056/lastto 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.