Get started

The DTVSOL Router API manages a DTVSOL DHCP router: static DHCP client leases (IPv4 + IPv6), auto-detected networks and interfaces, per-MAC firewall rules, QinQ VLAN provisioning and DHCP service control.

The API runs on the router itself, port 8880. Networks, interfaces and gateways are auto-detected from the system — no manual OLT/VLAN mapping is needed. Adding a client automatically writes the ISC DHCP host entry and reloads the DHCP service.

Base URL: http://ROUTER-IP:8880

To use this API you need an API key (configured in /opt/dtvsol/etc/config.php on the router).

Router IP / host:

API key:

Send the key in the X-API-Key HTTP header (recommended) or as an ?api_key= query parameter:
/usr/bin/curl -ss -X GET "http://ROUTER-IP:8880/status" -H "accept: application/json" -H "X-API-Key: "

Network protection: port 8880 is additionally guarded by iptables — only source networks added via the Protect endpoints (or dtvsol protect add on the router CLI) can reach the API at all. Everyone else is dropped before authentication.

STATUS

get status

Base url:
/usr/bin/curl -ss -X GET "http://ROUTER-IP:8880/status" -H "X-API-Key: "


RETURN PARAMETERS

FieldTypeDescription
apiStringAPI name and version.
dhcp_serviceString[running|stopped] — state of the ISC DHCP service.
total_clientsIntegerNumber of registered static clients.
per_networkObjectClient count per detected network (CIDR ⇒ count).
interfacesIntegerNumber of detected DHCP-serving interfaces.
server_timeStringCurrent router time (Y-m-d H:i:s).

EXAMPLE RESPONSE

{
    "api": "DTVSOL DHCP API v1.0",
    "dhcp_service": "running",
    "total_clients": 5,
    "per_network": {
        "100.67.0.0/24": 1,
        "100.67.1.0/24": 1,
        "100.67.2.0/24": 1
    },
    "interfaces": 6,
    "server_time": "2026-07-23 15:31:41"
}

CLIENTS

Static DHCP leases. Creating a client writes the host entry (IPv4 and, optionally, IPv6), assigns it to the auto-detected network of the given IP, and reloads DHCP.

list / search clients

Base url:
/usr/bin/curl -ss -X GET "http://ROUTER-IP:8880/clients" -H "X-API-Key: "


QUERY PARAMETERS

FieldTypeDescription
networkString(optional) Only clients of this network, e.g. 100.67.0.0/24.
qString(optional) Free-text search over MAC, IP, hostname and comment.

EXAMPLE RESPONSE

{
    "count": 1,
    "clients": [
        {
            "mac": "1C:61:B4:60:7F:92",
            "ip": "100.67.0.10",
            "ipv6": null,
            "hostname": "NOC",
            "network": "100.67.0.0/24",
            "network6": null,
            "iface": "vlan108",
            "gateway": "100.67.0.1",
            "comment": "prueba",
            "created": "2026-06-25 15:49:28"
        }
    ]
}

get one client

/usr/bin/curl -ss -X GET "http://ROUTER-IP:8880/clients/AA:BB:CC:DD:EE:FF" -H "X-API-Key: "

Returns {"client": {...}} or HTTP 404 if the MAC is not registered.

client activity — active / connected clients NEW

/usr/bin/curl -ss -X GET "http://ROUTER-IP:8880/clients/active" -H "X-API-Key: "

Live view of who is actually connected: every registered client is correlated with the kernel ARP/NDP neighbor tables (status: online = reachable now, recent = seen recently, offline), including a mac_mismatch warning when an IP answers from a different MAC than registered. Active dynamic leases from the DHCP pools are listed separately. Filter with ?state=online|recent|offline. Also available as /api?action=active (alias connected). A totals summary is included, and GET /status now reports online_clients too.

add client

/usr/bin/curl -ss -X POST "http://ROUTER-IP:8880/clients" -H "Content-Type: application/json" -H "X-API-Key: " -d '{"mac":"AA:BB:CC:DD:EE:FF","ip":"100.67.0.25","hostname":"client1","comment":"installed by tech 7"}'


BODY PARAMETERS (JSON)

FieldTypeDescription
macStringClient MAC address (any common format, normalized to AA:BB:CC:DD:EE:FF).
ipStringIPv4 address to reserve. Must belong to one of the detected networks.
hostnameString(optional) Hostname for the lease.
commentString(optional) Free-text note.
ipv6String(optional) IPv6 address to reserve as well (DHCPv6).

delete client

/usr/bin/curl -ss -X DELETE "http://ROUTER-IP:8880/clients/AA:BB:CC:DD:EE:FF" -H "X-API-Key: "

Removes the reservation (IPv4 + IPv6) and reloads DHCP.

NETWORKS

list detected networks

/usr/bin/curl -ss -X GET "http://ROUTER-IP:8880/networks" -H "X-API-Key: "

Networks are auto-detected from the router's interfaces. IPv6 prefix info is merged per interface when present.

EXAMPLE RESPONSE

{
    "count": 6,
    "networks": [
        {
            "iface": "vlan108",
            "gateway": "100.67.0.1",
            "subnet": "100.67.0.0",
            "mask": "255.255.255.0",
            "cidr": 24,
            "network": "100.67.0.0/24",
            "bcast": "100.67.0.255",
            "ipv6": null,
            "gateway6": null
        }
    ]
}

DHCP CONTROL

reload DHCP service

/usr/bin/curl -ss -X POST "http://ROUTER-IP:8880/dhcp/reload" -H "X-API-Key: "

Regenerates host files and restarts the ISC DHCP service. Returns {"ok": true} on success (HTTP 500 with detail on failure). Normally not needed — client add/delete reloads automatically.

FIREWALL

Per-client (MAC-based) FORWARD rules on the router.

list MAC rules

/usr/bin/curl -ss -X GET "http://ROUTER-IP:8880/firewall" -H "X-API-Key: "

QUERY PARAMETERS

FieldTypeDescription
networkString(optional) Only rules whose client belongs to this network.

full FORWARD chain

/usr/bin/curl -ss -X GET "http://ROUTER-IP:8880/firewall/full" -H "X-API-Key: "

Returns the complete iptables FORWARD chain as seen on the router (diagnostic view).

API PROTECTION

Manages which source networks may reach the API port at all (iptables INPUT rules, applied immediately and re-applied on service start).

list allowed networks

/usr/bin/curl -ss -X GET "http://ROUTER-IP:8880/protect" -H "X-API-Key: "

allow a network

/usr/bin/curl -ss -X POST "http://ROUTER-IP:8880/protect" -H "Content-Type: application/json" -H "X-API-Key: " -d '{"network":"10.0.0.0/24","comment":"NOC"}'

remove a network

/usr/bin/curl -ss -X DELETE "http://ROUTER-IP:8880/protect" -H "Content-Type: application/json" -H "X-API-Key: " -d '{"network":"10.0.0.0/24"}'

Careful: removing the network you are calling from locks you out of the API (router CLI dtvsol protect add can recover).

INTERFACES

DHCP-serving interfaces

/usr/bin/curl -ss -X GET "http://ROUTER-IP:8880/interfaces" -H "X-API-Key: "

Interfaces with their detected network, gateway and whether they are currently included in the DHCP configuration.

all system interfaces

/usr/bin/curl -ss -X GET "http://ROUTER-IP:8880/iface" -H "X-API-Key: "

Raw list of every interface on the router (including ones without IPs), useful before creating VLANs or assigning IPs. Each entry includes live statistics: status (UP/DOWN), mtu, speed_mbps (physical links), and traffic counters rx_bytes/tx_bytes.

DHCP NETWORKS

Enable or disable DHCP service (IPv4: /net, IPv6: /net6) on an interface. The subnet declaration is generated automatically from the interface's address.

add interface to DHCP (IPv4)

/usr/bin/curl -ss -X POST "http://ROUTER-IP:8880/net" -H "Content-Type: application/json" -H "X-API-Key: " -d '{"iface":"vlan108","label":"OLT 1 clients"}'

remove interface from DHCP (IPv4)

/usr/bin/curl -ss -X DELETE "http://ROUTER-IP:8880/net" -H "Content-Type: application/json" -H "X-API-Key: " -d '{"iface":"vlan108"}'

add / remove interface for DHCPv6

/usr/bin/curl -ss -X POST "http://ROUTER-IP:8880/net6" -H "Content-Type: application/json" -H "X-API-Key: " -d '{"iface":"svlan2700.2795"}'

Same with DELETE to remove. Also configures Router Advertisements (radvd) for the IPv6 prefix.

VLANS / QinQ

Provision 802.1Q or 802.1ad (QinQ) VLAN interfaces. Created VLANs persist and can immediately get an IP and DHCP service.

list VLANs

/usr/bin/curl -ss -X GET "http://ROUTER-IP:8880/vlans" -H "X-API-Key: "

add VLAN

/usr/bin/curl -ss -X POST "http://ROUTER-IP:8880/vlans" -H "Content-Type: application/json" -H "X-API-Key: " -d '{"parent":"ens5f0","vlan_id":2700,"protocol":"802.1ad","ip":"100.67.5.1/24","label":"OLT 5"}'


BODY PARAMETERS (JSON)

FieldTypeDescription
parentStringParent interface (e.g. ens5f0) or existing S-VLAN for QinQ inner tag.
vlan_idIntegerVLAN ID (1-4094).
protocolString(optional) 802.1Q (default) or 802.1ad for the outer QinQ tag.
ipString(optional) IPv4 address/CIDR to assign to the new VLAN interface.
ipv6String(optional) IPv6 address/prefix to assign.
labelString(optional) Free-text label stored in the VLAN database.

delete VLAN

/usr/bin/curl -ss -X DELETE "http://ROUTER-IP:8880/vlans" -H "Content-Type: application/json" -H "X-API-Key: " -d '{"name":"svlan2700.2795"}'

INTERFACE IPS

add IP to interface

/usr/bin/curl -ss -X POST "http://ROUTER-IP:8880/ip" -H "Content-Type: application/json" -H "X-API-Key: " -d '{"iface":"vlan108","ip":"100.67.9.1/24"}'

remove IP from interface

/usr/bin/curl -ss -X DELETE "http://ROUTER-IP:8880/ip" -H "Content-Type: application/json" -H "X-API-Key: " -d '{"iface":"vlan108","ip":"100.67.9.1/24"}'

Addresses are persisted so they survive reboot.

ROUTES V4/V6

Static route management for both IPv4 and IPv6 (family auto-detected from the prefix). Managed routes are persisted in data/routes.json and re-applied automatically at boot, after the VLAN interfaces are created.

list routes

/usr/bin/curl -ss -X GET "http://ROUTER-IP:8880/routes" -H "X-API-Key: "

Returns the DTVSOL-managed routes plus the live kernel routing tables (live_ipv4, live_ipv6) for comparison.

add route

/usr/bin/curl -ss -X POST "http://ROUTER-IP:8880/routes" -H "Content-Type: application/json" -H "X-API-Key: " -d '{"prefix":"2001:db8:100::/48","via":"2001:db8::2","comment":"customer prefix"}'


BODY PARAMETERS (JSON)

FieldTypeDescription
prefixStringDestination in CIDR (10.50.0.0/24, 2001:db8::/48) or default (default route; requires via).
viaString(optional*) Gateway address — must match the prefix family (IPv4 via for IPv4 prefix, IPv6 for IPv6).
devString(optional*) Output interface. *At least one of via/dev is required.
commentString(optional) Free-text note.

The route is applied immediately (ip route replace) and persisted for boot. Errors: 400 invalid prefix/family mismatch/unknown interface, 409 already managed, 500 kernel rejected the route.

delete route

/usr/bin/curl -ss -X DELETE "http://ROUTER-IP:8880/routes" -H "Content-Type: application/json" -H "X-API-Key: " -d '{"prefix":"2001:db8:100::/48"}'

Optionally include via/dev to disambiguate when several managed routes share a prefix. Removes the route from the kernel and from persistence. Only DTVSOL-managed routes can be deleted (404 otherwise).

CLI equivalent

dtvsol route list  |  dtvsol route add <prefix|default> [via <gw>] [dev <iface>] [comment]  |  dtvsol route del <prefix>

DYNAMIC NAT

Dynamic source NAT (SNAT) pools per WAN interface. Traffic leaving the interface is translated to a pool of public IPs (or a single IP) with per-flow port randomization (--random-fully) and client stickiness (--persistent — the same client always maps to the same pool IP). Exempt networks (clients with real public addresses) and the router's own traffic bypass NAT. Rules are tagged DTVSOL_NAT_<iface>, persisted with iptables and restored at boot.

list NAT pools

/usr/bin/curl -ss -X GET "http://ROUTER-IP:8880/nat" -H "X-API-Key: "

Returns managed pools plus the live POSTROUTING chain for comparison.

configure NAT pool

/usr/bin/curl -ss -X POST "http://ROUTER-IP:8880/nat" -H "Content-Type: application/json" -H "X-API-Key: " -d '{"iface":"vlan601","pool_start":"204.199.171.17","pool_end":"204.199.171.30","exempt":["200.215.228.192/29"],"comment":"clients NAT"}'


BODY PARAMETERS (JSON)

FieldTypeDescription
ifaceStringWAN (output) interface the NAT applies to.
pool_startStringFirst public IP of the SNAT pool.
pool_endString(optional) Last pool IP — omit for single-IP NAT.
exemptArray|String(optional) Source networks (CIDR) that bypass NAT — array or comma-separated string.
commentString(optional) Free-text note.

Re-POSTing for the same interface replaces its pool configuration (rules are rebuilt atomically).

remove NAT pool

/usr/bin/curl -ss -X DELETE "http://ROUTER-IP:8880/nat" -H "Content-Type: application/json" -H "X-API-Key: " -d '{"iface":"vlan601"}'

Removes every DTVSOL-tagged NAT rule for that interface. Unmanaged (hand-made) NAT rules are never touched.

CLI equivalent

dtvsol nat list  |  dtvsol nat add <iface> <pool_start> [pool_end] [exempt <cidr,cidr>] [comment]  |  dtvsol nat del <iface>

ACTION API (GET)

Browser-friendly alternative: every operation is also available as a plain GET request on /api with an action parameter and &api_key=. Same behavior as the REST endpoints.

ActionParametersREST equivalent
list[network]GET /clients
searchqGET /clients?q=
getmacGET /clients/{mac}
addmac, ip, [hostname], [comment], [ipv6]POST /clients
deletemacDELETE /clients/{mac}
networksGET /networks
reloadPOST /dhcp/reload
statusGET /status
firewall[network]GET /firewall
firewall-fullGET /firewall/full
protect-listGET /protect
protect-addnetwork, [comment]POST /protect
protect-deletenetworkDELETE /protect
interfacesGET /interfaces
iface-listGET /iface
add-netiface, [label]POST /net
remove-netifaceDELETE /net
net6-addifacePOST /net6
net6-delifaceDELETE /net6
vlan-listGET /vlans
vlan-addparent, vlan_id, [protocol], [ip], [ipv6], [label]POST /vlans
vlan-delnameDELETE /vlans
ip-addiface, ipPOST /ip
ip-deliface, ipDELETE /ip
route-listGET /routes
route-addprefix, [via], [dev], [comment]POST /routes
route-delprefix, [via], [dev]DELETE /routes
nat-listGET /nat
nat-addiface, pool_start, [pool_end], [exempt], [comment]POST /nat
nat-delifaceDELETE /nat
active / connected[state]GET /clients/active

Example:
http://ROUTER-IP:8880/api?action=add&mac=AA:BB:CC:DD:EE:FF&ip=100.67.0.25&hostname=client1&api_key=

Errors

All responses are JSON. Errors carry an error field with a human-readable message.

HTTP codeMeaning
200OK — request succeeded.
400Bad request — missing or invalid parameter (e.g. malformed MAC, IP outside detected networks).
401Unauthorized — missing or wrong API key.
404Not found — unknown endpoint or client MAC not registered.
405Method not allowed on this resource.
409Conflict — e.g. MAC or IP already registered.
500Server error — e.g. DHCP reload failed; the message contains the service error.

Note: if the API does not answer at all (connection timeout), your source network is probably not in the allowed networks list (iptables drops the connection before HTTP).