Skip to content

VLANs, IPs and routes API

These endpoints manage the router’s VLAN interfaces, the addresses on its interfaces, the static routes it keeps across reboots and its dynamic (source) NAT pools. Base URL, authentication and the error format are described in the API overview.

Everything created here is persisted by the router and restored at boot. Changes are logged with the caller’s address. A 5xx answer can carry broken_data_files, naming router data files that do not parse.

A VLAN’s interface name follows from its parent and protocol: vlan100 for an 802.1Q VLAN on a port or bond, svlan500 for an 802.1ad S-VLAN, svlan500.20 for a C-VLAN inside an S-VLAN.

Every VLAN the router manages, with its live state.

Terminal window
curl -H "X-API-Key: YOUR_API_KEY" http://ROUTER-IP:8880/vlans
{
"count": 1,
"vlans": [
{
"name": "vlan110",
"parent": "bond0",
"vlan_id": 108,
"protocol": "802.1Q",
"ips": ["100.64.8.1/24"],
"ips6": ["XXXX:XXXX:110::1/64"],
"label": "OLT 1 PON 0/1/3",
"added": "2026-09-01 10:00:00",
"enabled": true,
"live": true,
"status": "UP",
"live_ips": ["100.64.8.1/24"],
"live_ips6": ["XXXX:XXXX:110::1/64"]
}
]
}

status is the link state, DISABLED for a disabled VLAN, or NOT CREATED when the interface does not exist.

Creates the VLAN, brings it up and adds its addresses. Unless serve is off, each address is then served: IPv4 with DHCP (POST /net), IPv6 with DHCPv6 and a pool (POST /net6, POST /net6/pool) and prefix delegation (POST /pd). Each serving step is reported on its own; a failed step does not undo the VLAN.

Name In Type Notes
parent body string Existing parent interface: a port, bond or S-VLAN.
vlan_id body integer 1–4094.
protocol body string 802.1Q (default) or 802.1ad.
ip body CIDR Optional IPv4 gateway address, e.g. 100.64.8.1/24. A network address is corrected to the first host (with a note).
ipv6 body CIDR Optional IPv6 address, e.g. XXXX:XXXX:110::1/64.
label body string Optional description.
serve body boolean 0, false or no creates the link only. Default on.
force body boolean Allow an address that overlaps one already on another interface.
Terminal window
curl -X POST -H "X-API-Key: YOUR_API_KEY" -H "Content-Type: application/json" \
-d '{"parent":"bond0","vlan_id":108,"ip":"100.64.8.1/24","ipv6":"XXXX:XXXX:110::1/64","label":"OLT 1 PON 0/1/3"}' \
http://ROUTER-IP:8880/vlans
{
"ok": true,
"code": 201,
"message": "VLAN vlan110 created on bond0 (100.64.8.1/24) (XXXX:XXXX:110::1/64)",
"name": "vlan110",
"parent": "bond0",
"vlan_id": 108,
"protocol": "802.1Q",
"ip": "100.64.8.1/24",
"ipv6": "XXXX:XXXX:110::1/64",
"served": {
"net": { "network": "100.64.8.0/24" },
"net6": { "network": "XXXX:XXXX:110::/64", "pool": "XXXX:XXXX:110::1000 - XXXX:XXXX:110::ffff" },
"pd": { "pool": "XXXX:XXXX:a:1000:: - XXXX:XXXX:a:10ff::", "capacity": 256 }
}
}

A failed serving step appears as {"error": "…", "retry": "dtvsol net add vlan110"} under its key, and warning says the VLAN is not fully served. With serve off, served is null and note says which commands serve it later.

Errors: 400 invalid ID, address or protocol; 404 parent not found; 409 VLAN already exists, tag already used on that parent, or address conflict (the answer carries conflict; pass force to override).

The VLAN name is taken from the body.

Name In Type Notes
name body string VLAN interface name, e.g. vlan110.
Terminal window
curl -X DELETE -H "X-API-Key: YOUR_API_KEY" -H "Content-Type: application/json" \
-d '{"name":"vlan110"}' http://ROUTER-IP:8880/vlans
{
"ok": true,
"code": 200,
"message": "VLAN vlan110 deleted",
"name": "vlan110",
"removed_subnets": ["subnet 100.64.8.0"],
"removed_pd_pool": null,
"removed_routes": [],
"removed_nat": null,
"removed_forwards": [],
"kept": { "rrd": "data/rrd/iface_vlan110.rrd (traffic history; delete by hand if not wanted)" }
}

Errors: 404 not a managed VLAN; 409 when another VLAN uses it as parent, clients are still registered on it, or CGNAT translates through it (the answer carries a fix).

Switches a VLAN off without releasing anything; POST /vlans/enable restores it exactly. GET /vlans/disable takes the parameters from the query string.

Name In Type Notes
name body string VLAN interface name.
reason body string Optional note kept with the VLAN.
Terminal window
curl -X POST -H "X-API-Key: YOUR_API_KEY" -H "Content-Type: application/json" \
-d '{"name":"vlan110","reason":"OLT maintenance"}' http://ROUTER-IP:8880/vlans/disable
{
"ok": true,
"code": 200,
"message": "VLAN vlan110 disabled",
"name": "vlan110",
"enabled": false,
"disabled_at": "2026-09-27 10:00:00",
"reason": "OLT maintenance",
"took_offline": ["dhcp4", "dhcp6", "radvd", "link down"],
"kept": "delegation range, subnet blocks, NAT pool, port forwards, routes, client reservations and addresses — all restored by: dtvsol vlan enable vlan110",
"forwards_still_active": []
}

Errors: 404 not found; 409 already disabled, an enabled VLAN runs on top of it, or CGNAT translates through it.

Name In Type Notes
name body string VLAN interface name.
Terminal window
curl -X POST -H "X-API-Key: YOUR_API_KEY" -H "Content-Type: application/json" \
-d '{"name":"vlan110"}' http://ROUTER-IP:8880/vlans/enable
{
"ok": true,
"code": 200,
"message": "VLAN vlan110 enabled",
"name": "vlan110",
"enabled": true,
"restored": ["link up", "addresses", "dhcp4", "dhcp6 + radvd"],
"forwards_active": [],
"was_disabled_at": "2026-09-27 10:00:00",
"was_disabled_reason": "OLT maintenance"
}

Errors: 404 not found; 409 already enabled or its parent VLAN is disabled.

Adds an IPv4 or IPv6 address to an existing interface. On a managed VLAN it is stored with the VLAN; on a physical port or bond it is stored in the router’s network configuration. Adding an address that is already live only persists it. The address is not served automatically — use POST /net or POST /net6.

Name In Type Notes
iface body string Interface name.
ip body CIDR IPv4 or IPv6 address with prefix length. A network address is corrected to the first host.
Terminal window
curl -X POST -H "X-API-Key: YOUR_API_KEY" -H "Content-Type: application/json" \
-d '{"iface":"vlan110","ip":"100.64.9.1/24"}' http://ROUTER-IP:8880/ip
{ "ok": true, "code": 201, "message": "IP 100.64.9.1/24 added to vlan110", "persisted": true }

When the address was corrected the answer also carries requested, assigned and note. Errors: 400 invalid address; 404 interface not found; 409 when the subnet is already on another interface (over REST there is no override; the action API accepts force=1).

Name In Type Notes
iface body string Interface name.
ip body CIDR Address to remove, as assigned.
Terminal window
curl -X DELETE -H "X-API-Key: YOUR_API_KEY" -H "Content-Type: application/json" \
-d '{"iface":"vlan110","ip":"100.64.9.1/24"}' http://ROUTER-IP:8880/ip
{
"ok": true,
"code": 200,
"message": "IP 100.64.9.1/24 removed from vlan110",
"warning": "no interface is served in that network any more, so subnet 100.64.9.0 in dhcpd.conf is now unused — kept because it may hold hand-tuned options; remove it deliberately if not wanted",
"unused_subnet": { "kind": "subnet", "block": "100.64.9.0", "file": "/etc/dhcp/dhcpd.conf" }
}

A DHCP subnet or delegation range left unused is named (unused_subnet, unused_pd_pool, pd_warning), never removed. Errors: 400 invalid address; 404 interface not found; 409 when the address is the gateway of a registered client. Other methods on /ip answer 400 Use POST /ip or DELETE /ip.

The routes the router manages (restored at boot) and the kernel’s live IPv4 and IPv6 routing tables.

Terminal window
curl -H "X-API-Key: YOUR_API_KEY" http://ROUTER-IP:8880/routes
{
"count": 1,
"managed": [
{ "prefix": "default", "via": "XXX.XXX.XXX.1", "dev": "vlan90", "family": "ipv4", "comment": "upstream", "created": "2026-09-01 10:00:00" }
],
"live_ipv4": ["default via XXX.XXX.XXX.1 dev vlan90"],
"live_ipv6": []
}
Name In Type Notes
prefix body string CIDR (10.50.0.0/24, XXXX:XXXX::/48) or default.
via body IP Gateway, same family as the prefix. via and/or dev is required.
dev body string Outgoing interface.
comment body string Optional.
Terminal window
curl -X POST -H "X-API-Key: YOUR_API_KEY" -H "Content-Type: application/json" \
-d '{"prefix":"default","via":"XXX.XXX.XXX.1","dev":"vlan90","comment":"upstream"}' \
http://ROUTER-IP:8880/routes
{
"ok": true,
"code": 201,
"message": "Route added",
"route": { "prefix": "default", "via": "XXX.XXX.XXX.1", "dev": "vlan90", "family": "ipv4", "comment": "upstream", "created": "2026-09-28 09:00:00" }
}

Errors: 400 invalid prefix, gateway or interface name; 409 Route already managed; 500 when the kernel refuses the route.

The first managed route with this prefix (and, when given, this via/dev) is removed.

Name In Type Notes
prefix body string Prefix of the route, or default.
via body IP Optional, to pick one of several routes.
dev body string Optional, to pick one of several routes.
Terminal window
curl -X DELETE -H "X-API-Key: YOUR_API_KEY" -H "Content-Type: application/json" \
-d '{"prefix":"default"}' http://ROUTER-IP:8880/routes
{
"ok": true,
"code": 200,
"message": "Route removed",
"route": { "prefix": "default", "via": "XXX.XXX.XXX.1", "dev": "vlan90", "family": "ipv4" },
"kernel_removed": true
}

Errors: 404 Route … is not managed by DTVSOL.

Source NAT for traffic leaving an interface, translated to one public address or a range. For carrier-grade NAT with deterministic port blocks, see the CGNAT API.

The configured pools and the live NAT POSTROUTING rules.

Terminal window
curl -H "X-API-Key: YOUR_API_KEY" http://ROUTER-IP:8880/nat
{
"count": 1,
"pools": [
{ "iface": "vlan90", "pool_start": "XXX.XXX.XXX.10", "pool_end": "XXX.XXX.XXX.20", "exempt": ["10.0.0.0/8"], "comment": "", "created": "2026-09-01 10:00:00" }
],
"live_postrouting": ["-P POSTROUTING ACCEPT", "-A POSTROUTING -o vlan90 -j SNAT --to-source XXX.XXX.XXX.10-XXX.XXX.XXX.20 …"]
}
Name In Type Notes
iface body string Outgoing interface.
pool_start body IPv4 First public address.
pool_end body IPv4 Optional last public address; omit for a single address.
exempt body list or string Optional IPv4 networks (CIDR) not translated; a list or a comma-separated string.
comment body string Optional.
Terminal window
curl -X POST -H "X-API-Key: YOUR_API_KEY" -H "Content-Type: application/json" \
-d '{"iface":"vlan90","pool_start":"XXX.XXX.XXX.10","pool_end":"XXX.XXX.XXX.20","exempt":["10.0.0.0/8"]}' \
http://ROUTER-IP:8880/nat
{
"ok": true,
"code": 201,
"message": "Dynamic NAT pool configured",
"pool": { "iface": "vlan90", "pool_start": "XXX.XXX.XXX.10", "pool_end": "XXX.XXX.XXX.20", "exempt": ["10.0.0.0/8"], "comment": "", "created": "2026-09-28 09:00:00" }
}

Errors: 400 invalid interface name, pool address, reversed range or exempt network; 404 interface not found; 500 when a rule cannot be installed.

Name In Type Notes
iface body string Interface whose pool is removed.
Terminal window
curl -X DELETE -H "X-API-Key: YOUR_API_KEY" -H "Content-Type: application/json" \
-d '{"iface":"vlan90"}' http://ROUTER-IP:8880/nat
{
"ok": true,
"code": 200,
"message": "NAT pool removed",
"iface": "vlan90",
"rules_removed": 1,
"pool": { "iface": "vlan90", "pool_start": "XXX.XXX.XXX.10", "pool_end": "XXX.XXX.XXX.20" }
}

Errors: 400 without iface; 404 No DTVSOL NAT pool on ….

Action Query parameters Same as
action=vlan-list — GET /vlans
action=vlan-add parent, vlan_id, protocol, ip, ipv6, label, force=1, serve=0 POST /vlans
action=vlan-del name DELETE /vlans
action=vlan-disable name, reason POST /vlans/disable
action=vlan-enable name POST /vlans/enable
action=ip-add iface, ip, force=1 POST /ip (with override)
action=ip-del iface, ip DELETE /ip
action=route-list — GET /routes
action=route-add prefix, via, dev, comment POST /routes
action=route-del prefix, via, dev DELETE /routes
action=nat-list — GET /nat
action=nat-add iface, pool_start, pool_end, exempt, comment POST /nat
action=nat-del iface DELETE /nat