The OPNsense firewall exposes a REST API for automation and integration. This post shows how to use the API from Python to retrieve core information over HTTPS, using an ACME/Let’s Encrypt certificate.
What you’ll do
- Create a dedicated API user
- Call core endpoints: system status, firmware status, interface statistics
- Use Python
requests
with HTTP Basic authentication over TLS
Prerequisites
- OPNsense GUI reachable at
https://opnsense.maksonlee.com:8444
(adjust to your host/port) - Valid ACME/Let’s Encrypt certificate
- API key/secret from System → Access → Users (least-privilege permissions)
Create the API user (add privileges here, then generate the key)
- Go to System → Access → Users and click +.
- Username: e.g.,
api-automation
- Fill the required fields.
- Effective Privileges: open the picker now (while still on the create form) and add exactly:
- System: Status
- System: Firmware
- Diagnostics: Netstat
- Click Save.
- Username: e.g.,
- Back on the Users list, click the key icon next to your new user (tooltip: Create and download API key for this user).
- Copy/download the API key and API secret (these are the Basic Auth username/password for the API). Keep them safe.
Why these privileges?
They unlock the endpoints used below:
GET /api/core/system/status
GET /api/core/firmware/status
GET /api/diagnostics/interface/get_interface_statistics
Python example
Save as opnsense_api_example.py
:
import json
import requests
from requests.auth import HTTPBasicAuth
BASE = "https://opnsense.maksonlee.com:8444"
KEY = "YOUR_API_KEY_HERE"
SEC = "YOUR_API_SECRET_HERE"
session = requests.Session()
session.auth = HTTPBasicAuth(KEY, SEC)
session.headers.update({"Accept": "application/json"})
# 1) System status
resp = session.get(f"{BASE}/api/core/system/status", timeout=15, verify=True)
resp.raise_for_status()
print("[system/status]")
print(json.dumps(resp.json(), indent=2))
# 2) Firmware status
resp = session.get(f"{BASE}/api/core/firmware/status", timeout=15, verify=True)
resp.raise_for_status()
print("\n[firmware/status]")
print(json.dumps(resp.json(), indent=2))
# 3) Interface statistics (note the snake_case endpoint)
resp = session.get(f"{BASE}/api/diagnostics/interface/get_interface_statistics", timeout=15, verify=True)
resp.raise_for_status()
iface_stats = resp.json()
print("\n[diagnostics/interface/get_interface_statistics]")
print(json.dumps(iface_stats, indent=2))
# Optional: quick RX/TX summary if present
rows = iface_stats.get("rows") or iface_stats.get("interfaces") or []
if isinstance(rows, list) and rows:
print("\n[summary]")
for it in rows:
name = it.get("name") or it.get("interface") or "?"
rx = it.get("ibytes") or it.get("rxbytes") or it.get("rx") or "?"
tx = it.get("obytes") or it.get("txbytes") or it.get("tx") or "?"
print(f"{name}: RX={rx} TX={tx}")