openwrt-iac / uapi
A native, zero-footprint HTTP control plane for OpenWrt 25.12+. Translates standard REST verbs into ubus calls so modern edge routers become first-class targets for Infrastructure-as-Code.
v2.1 - signed APK feed - MIT licensed - OpenWrt 25.12 +
Runs inside the existing uhttpd process via ucode. No daemon to
supervise, no socket to wire, no extra memory at idle.
Talks to ubus directly; never touches
/etc/config/ behind uci's back.
Every HTTP write is one transaction: snapshot, validate, commit,
reload. If the daemon rejects the new config, uapi restores the
snapshot and returns 500 reload_failed_restored.
POST /batch extends this across N packages with
per-package locks acquired deadlock-free.
Stable resource IDs survive uci rewrites. Dependency-aware ETags
+ If-Match give Terraform-style providers the
optimistic-concurrency story they need. Conditional GET cuts
refresh bandwidth; cursor pagination keeps collections sane.
Bearer auth with hierarchical scopes, token expiry, source-IP
scoping, per-token rate limit, Prometheus /metrics,
syslog audit log, idempotency keys for safe retries, signed-tag
releases with SPDX SBOM.
The reference client is the
openwrt-iac/uapi Terraform provider.
uapi's wire surface (stable IDs, per-resource ETags, optimistic
If-Match concurrency, idempotency keys for safe POST
retries, atomic /batch across packages) was designed
around what an IaC provider needs.
terraform {
required_providers {
uapi = {
source = "openwrt-iac/uapi"
version = "~> 2.1"
}
}
}
provider "uapi" {
endpoint = "https://router.example.com/api/v2"
token = var.uapi_token # mint with `uapi-token create`
}
resource "uapi_firewall_rule" "ssh_mgmt" {
name = "allow-ssh-mgmt"
target = "ACCEPT"
match {
src_zone = "lan"
proto = ["tcp"]
dest_port = [22]
}
}
The provider is a separate project; see its registry page for the full resource catalog and version compatibility matrix.
apk add uapi
uapi-token create --name ci_bot --scope '*:rw' --expires-in 90d
curl -H "Authorization: Bearer $TOKEN" \
https://router/api/v2/firewall/rules
curl -H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-X POST https://router/api/v2/firewall/rules \
-d '{"name":"allow-mgmt","target":"ACCEPT",
"match":{"src_zone":"lan","dest_port":[22]}}'
network/ interfaces, devices, routes, rules, bridge-vlans, wireguard-peersfirewall/ zones, rules, redirects, forwardings, defaultswireless/ devices, interfacesdhcp/ hosts, leases, leases6, servers, dnsmasq, odhcpdsystem/ + timeservers + password + authorized_keysdropbear, uhttpd, unbound (server + srv + ext), sqm, snmpd, lldpd, vnstat, prometheus-node-exporterpackages/ installed + feeds (apk passthrough)
The long tail of OpenWrt config under
/api/v2/raw/<package>/<section>.
Same atomic-transaction recipe, same auth + scope model,
payloads pass through uci field names directly.
GET /healthz - subsystem statusGET /openapi.json - full specGET /schema/<pkg>/<res> - one resource's schemaGET /auth/whoami - token introspectionGET /tokens, POST /tokens - HTTP token rotationGET /metrics - Prometheus textGET /diagnostics - lock state + uptimePOST /batch - multi-package atomic transactions