Clients API
A client is the legacy, MAC-based subscriber record: one MAC address pinned to one fixed IPv4 address (and optionally an IPv6 address) on one of the router’s networks, served by DHCP, with an optional speed plan, a suspension flag and an optional end date. New integrations that provision ONTs use the Services API instead; clients remain for billings and networks keyed on the CPE’s MAC.
Base URL, authentication (X-API-Key), error format and the action API are described in the
API overview.
MAC addresses are accepted in any common form (aa-bb-cc-dd-ee-ff, aabbccddeeff,
AA:BB:CC:DD:EE:FF) and are stored upper-case with colons.
Reading
Section titled “Reading”GET /clients
Section titled “GET /clients”Lists the registered clients, each with the IPv6 addresses it is actually seen using and any prefix delegated to it.
| Name | In | Type | Notes |
|---|---|---|---|
network |
query | CIDR | Only clients whose IPv4 address is in this network, e.g. 10.110.0.0/21. |
q |
query | string | Case-insensitive search in MAC, IP, IPv6, hostname, comment, interface and network. |
curl -H "X-API-Key: YOUR_API_KEY" "http://ROUTER-IP:8880/clients?network=10.110.0.0/21"{ "count": 1, "clients": [ { "mac": "AA:BB:CC:DD:EE:FF", "ip": "10.110.0.2", "ipv6": null, "plan": "plan_100_50", "suspended": false, "auto_suspended": false, "expires": null, "hostname": "client-aabbccddeeff", "network": "10.110.0.0/21", "network6": null, "iface": "vlan100", "gateway": "10.110.0.1", "comment": "", "created": "2026-09-01 10:00:00", "ipv6_actual": "XXXX:XXXX:100::25", "ipv6_all": ["XXXX:XXXX:100::25"], "ipv6_state": "REACHABLE", "prefix6_delegated": [] } ]}GET /clients/{mac}
Section titled “GET /clients/{mac}”One client. {mac} may also be the client’s IPv4 address, its IPv6 address or its hostname
(case-insensitive).
| Name | In | Type | Notes |
|---|---|---|---|
mac |
path | string | MAC, IPv4, IPv6 or hostname. |
curl -H "X-API-Key: YOUR_API_KEY" http://ROUTER-IP:8880/clients/AA:BB:CC:DD:EE:FF{ "client": { "mac": "AA:BB:CC:DD:EE:FF", "ip": "10.110.0.2", "hostname": "client-aabbccddeeff", "network": "10.110.0.0/21", "iface": "vlan100", "gateway": "10.110.0.1", "plan": "plan_100_50", "suspended": false, "expires": null }}404 {"error": "Client not found"} when nothing matches.
GET /clients/active
Section titled “GET /clients/active”Who is online now: every client and every service with its
neighbour (ARP/NDP) state, plus the dynamic DHCP leases that belong to neither.
status is online (REACHABLE, DELAY, PROBE, PERMANENT), recent (STALE) or offline.
mac_mismatch is true when the address is answered by a different MAC than the one registered.
| Name | In | Type | Notes |
|---|---|---|---|
state |
query | string | Only online, recent or offline entries (the summary still counts everything). |
curl -H "X-API-Key: YOUR_API_KEY" "http://ROUTER-IP:8880/clients/active?state=online"{ "summary": { "registered": 120, "services": 340, "online": 401, "recent": 12, "offline": 47, "dynamic_active_leases": 3 }, "clients": [ { "mac": "AA:BB:CC:DD:EE:FF", "ip": "10.110.0.2", "hostname": "client-aabbccddeeff", "iface": "vlan100", "status": "online", "neigh_state": "REACHABLE", "seen_mac": "AA:BB:CC:DD:EE:FF", "mac_mismatch": false, "ipv6_actual": null, "ipv6_all": [], "prefix6_delegated": [] }, { "mac": "AA:BB:CC:00:11:22", "ip": "100.64.16.9", "hostname": "Customer name", "iface": "vlan116", "status": "online", "neigh_state": "REACHABLE", "service": "svc_1a2b3c4d", "service_state": "active", "seen_mac": "AA:BB:CC:00:11:22", "mac_mismatch": false } ], "dynamic_leases": [ { "ip": "10.120.0.50", "state": "active", "mac": "AA:BB:CC:33:44:55", "hostname": "cpe", "ends": "2026/09/28 12:00:00", "neigh_state": "STALE", "online": false } ]}GET /clients6
Section titled “GET /clients6”Every MAC seen on the wire over IPv6 — registered or not — with the IPv6 address it holds, its
DHCPv6 lease, its pinned prefix and the prefix delegated to it. The router’s own MACs are left
out; other routers (IPv6 router advertisements) are left out unless routers=1, or unless they
are registered or hold a lease.
| Name | In | Type | Notes |
|---|---|---|---|
iface |
query | string | Only this interface, e.g. vlan100. |
routers |
query | 1 |
Include neighbouring routers. |
curl -H "X-API-Key: YOUR_API_KEY" "http://ROUTER-IP:8880/clients6?iface=vlan100"{ "summary": { "total": 1, "online": 1, "registered": 1, "with_prefix": 1 }, "clients": [ { "mac": "AA:BB:CC:DD:EE:FF", "registered": true, "hostname": "client-aabbccddeeff", "ipv4": "10.110.0.2", "iface": "vlan100", "ipv6": "XXXX:XXXX:100::25", "ipv6_all": ["XXXX:XXXX:100::25"], "ipv6_reserved": null, "link_local": "fe80::aabb:ccff:fedd:eeff", "state": "REACHABLE", "online": true, "router": false, "lease6": ["XXXX:XXXX:100::25"], "prefix6_pinned": null, "prefix6_delegated": ["XXXX:XXXX:b:500::/64"] } ], "orphan_delegated_prefixes": []}GET /ips
Section titled “GET /ips”Address usage per network on the router: the registered addresses, unregistered addresses seen live on the wire, and the first ten free addresses.
| Name | In | Type | Notes |
|---|---|---|---|
network |
query | CIDR | One network, e.g. 10.110.0.0/21. Omit for all. |
curl -H "X-API-Key: YOUR_API_KEY" "http://ROUTER-IP:8880/ips?network=10.110.0.0/21"{ "ok": true, "code": 200, "count": 1, "networks": [ { "network": "10.110.0.0/21", "iface": "vlan100", "gateway": "10.110.0.1", "total_assignable": 2045, "used_count": 1, "free_count": 2044, "next_free": ["10.110.0.3", "10.110.0.4"], "used": [ { "ip": "10.110.0.2", "mac": "AA:BB:CC:DD:EE:FF", "hostname": "client-aabbccddeeff" } ], "unregistered_seen": [ { "ip": "10.110.0.77", "mac": "AA:BB:CC:66:77:88", "neigh_state": "STALE" } ] } ]}404 when the named network is not on this router.
Changing
Section titled “Changing”POST /clients
Section titled “POST /clients”Registers a client. The IPv4 address must lie in a network configured on one of the router’s interfaces (not its network, gateway or broadcast address); the network, interface and gateway are taken from there. An IPv6 address, when given, must be on the same interface.
| Name | In | Type | Notes |
|---|---|---|---|
mac |
body | string | Required. |
ip |
body | IPv4 | Required. |
ipv6 |
body | IPv6 | Optional fixed IPv6 address. |
hostname |
body | string | Letters, digits, dots, hyphens, max 63. Default client-<mac without colons>. |
comment |
body | string | Free text. |
plan |
body | string | An existing plan name, or none. |
expires |
body | date | End date (YYYY-MM-DD or YYYY-MM-DD HH:MM), or never. |
curl -X POST -H "X-API-Key: YOUR_API_KEY" -H "Content-Type: application/json" \ -d '{"mac":"AA:BB:CC:DD:EE:FF","ip":"10.110.0.2","hostname":"cpe-1","plan":"plan_100_50"}' \ http://ROUTER-IP:8880/clients{ "ok": true, "code": 201, "message": "Client added successfully", "client": { "mac": "AA:BB:CC:DD:EE:FF", "ip": "10.110.0.2", "ipv6": null, "plan": "plan_100_50", "suspended": false, "auto_suspended": false, "expires": null, "hostname": "cpe-1", "network": "10.110.0.0/21", "network6": null, "iface": "vlan100", "gateway": "10.110.0.1", "comment": "", "created": "2026-09-28 10:00:00" }}Errors: 400 invalid MAC/IP/IPv6/hostname or an address outside the router’s networks;
404 unknown plan; 409 MAC, IP, IPv6 or hostname already in use (for a duplicate MAC the
answer carries existing); 500 when DHCP does not reload — the client is then rolled back.
DELETE /clients/{mac}
Section titled “DELETE /clients/{mac}”| Name | In | Type | Notes |
|---|---|---|---|
mac |
path | string | Required. |
curl -X DELETE -H "X-API-Key: YOUR_API_KEY" http://ROUTER-IP:8880/clients/AA:BB:CC:DD:EE:FF{ "ok": true, "code": 200, "message": "Client deleted", "client": { "mac": "AA:BB:CC:DD:EE:FF", "ip": "10.110.0.2", "hostname": "cpe-1" }, "dhcp": { "ok": true, "message": "DHCP reloaded" }}404 when the client does not exist; 400 {"error": "Specify MAC"} without a MAC.
POST /plan
Section titled “POST /plan”| Name | In | Type | Notes |
|---|---|---|---|
mac |
body | string | Required. |
plan |
body | string | A plan name; empty or none removes the limit. |
curl -X POST -H "X-API-Key: YOUR_API_KEY" -H "Content-Type: application/json" \ -d '{"mac":"AA:BB:CC:DD:EE:FF","plan":"plan_200_100"}' \ http://ROUTER-IP:8880/plan{ "ok": true, "code": 200, "message": "Plan assigned", "mac": "AA:BB:CC:DD:EE:FF", "plan": "plan_200_100" }404 for an unknown client or plan.
POST /suspend
Section titled “POST /suspend”The MAC can be in the body or in the path (POST /suspend/{mac}). Suspending by hand clears
the automatic (end-date) suspension flag.
| Name | In | Type | Notes |
|---|---|---|---|
mac |
body or path | string | Required. |
curl -X POST -H "X-API-Key: YOUR_API_KEY" -H "Content-Type: application/json" \ -d '{"mac":"AA:BB:CC:DD:EE:FF"}' http://ROUTER-IP:8880/suspend{ "ok": true, "code": 200, "message": "Client suspended", "mac": "AA:BB:CC:DD:EE:FF", "suspended": true }POST /resume
Section titled “POST /resume”Same parameters as POST /suspend (MAC in the body or the path).
curl -X POST -H "X-API-Key: YOUR_API_KEY" http://ROUTER-IP:8880/resume/AA:BB:CC:DD:EE:FF{ "ok": true, "code": 200, "message": "Client resumed", "mac": "AA:BB:CC:DD:EE:FF", "suspended": false }POST /expires
Section titled “POST /expires”After the date is stored, the end dates of all clients are reconciled immediately (the router also does this every 5 minutes).
| Name | In | Type | Notes |
|---|---|---|---|
mac |
body | string | Required. |
expires |
body | date | YYYY-MM-DD or YYYY-MM-DD HH:MM; empty or never clears it. |
curl -X POST -H "X-API-Key: YOUR_API_KEY" -H "Content-Type: application/json" \ -d '{"mac":"AA:BB:CC:DD:EE:FF","expires":"2026-10-31"}' \ http://ROUTER-IP:8880/expires{ "ok": true, "code": 200, "message": "End date set", "mac": "AA:BB:CC:DD:EE:FF", "expires": "2026-10-31 00:00:00", "suspended": false}400 for a date it cannot read, 404 for an unknown client.
Action API equivalents
Section titled “Action API equivalents”All are GET /api?action=… with the parameters in the query string (see the
API overview).
| Action | Query parameters | Same as |
|---|---|---|
action=list |
[network] |
GET /clients |
action=search |
q |
GET /clients?q= (answer adds query) |
action=get |
mac |
GET /clients/{mac} |
action=add |
mac, ip, [hostname], [comment], [ipv6] |
POST /clients (no plan or end date) |
action=delete |
mac |
DELETE /clients/{mac} |
action=active / action=connected |
[state] |
GET /clients/active |
action=clients6 |
[iface], [routers=1] |
GET /clients6 |
action=ip-info |
[network] |
GET /ips |
action=set-plan |
mac, plan |
POST /plan |
action=suspend / action=resume |
mac |
POST /suspend / POST /resume |
action=set-expires |
mac, expires |
POST /expires |