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 requestswith 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}")
