Services API
A service is one subscriber on the fibre network: an ONT on a PON port of a registered OLT, a fixed IPv4 address (and optionally IPv6 with a delegated prefix) on that port’s network, a speed plan, an optional end date. This is the API a billing system uses. No CPE MAC address is needed: the router recognises the ONT by the port and ONT id the OLT stamps on its DHCP requests (Option 82).
Numbering is derived, never chosen: the subscribers of one PON port share that port’s C-VLAN,
100 + card × 16 + pon (port 0/1/0 → C-VLAN 116) inside the router’s S-VLAN, and its address
block. The legacy MAC-based records are the Clients API.
Base URL, authentication (X-API-Key), error format and the action API are described in the
API overview.
The billing flow
Section titled “The billing flow”- The technician installs the ONU; the billing asks
GET /services/unregisteredand shows the serials found. - The technician picks one; the billing calls
POST /serviceswith its ownref, thesn,olt,pon, plan and name. - The answer (
201) carriesservice.id— store it — andtechnician.user_vlan: set the ONU’s WAN to that VLAN with DHCP. - Afterwards, by id: change the plan or end date (
POST /services/{id}), suspend/resume, delete, status, traffic graph.
Service states
Section titled “Service states”provisioning (being created), active, suspended, error (the OLT side was done but the
router side failed — fix the cause and send {"retry": true}).
Reading
Section titled “Reading”GET /services
Section titled “GET /services”Lists services (deleted ones are never listed), each with a live block unless fast=1.
states counts every service by state.
| Name | In | Type | Notes |
|---|---|---|---|
state |
query | string | active, suspended, error, provisioning. |
olt |
query | string | Only services on this OLT. |
q |
query | string | Case-insensitive text search in the whole record. |
fast |
query | 1 |
Skip the live probe (link, neighbour, lease). |
curl -H "X-API-Key: YOUR_API_KEY" "http://ROUTER-IP:8880/services?state=active&olt=olt-1&fast=1"{ "ok": true, "code": 200, "count": 1, "states": { "active": 340, "suspended": 12 }, "services": [ { "id": "svc_1a2b3c4d", "ref": "billing-000812", "name": "Customer name", "olt": "olt-1", "pon": "0/1/0", "ont_id": 7, "state": "active" } ]}GET /services/{id}
Section titled “GET /services/{id}”One service with its live state and its plan. {id} accepts the service id, the billing’s
ref, the ONT serial, the contract, the IPv4 address or the interface name.
| Name | In | Type | Notes |
|---|---|---|---|
id |
path | string | Id, ref, serial, contract, address or interface. |
curl -H "X-API-Key: YOUR_API_KEY" http://ROUTER-IP:8880/services/svc_1a2b3c4d{ "ok": true, "code": 200, "service": { "id": "svc_1a2b3c4d", "ref": "billing-000812", "name": "Customer name", "contract": "C-00812", "comment": null, "olt": "olt-1", "pon": "0/1/0", "ont_id": 7, "sn": "0123456789ABCDEF", "svlan": 500, "cvlan": 116, "user_vlan": 116, "iface": "v500.116", "ipv4": { "network": "100.64.16.0/24", "gateway": "100.64.16.1", "address": "100.64.16.9" }, "ipv6": { "link": "XXXX:XXXX:a:183::/64", "pd": "XXXX:XXXX:b:8300::/56" }, "plan": "plan_200_200", "iptv": false, "expires": "2026-10-31 00:00:00", "state": "active", "created": "2026-09-25 10:30:00", "updated": "2026-09-25 10:30:00", "olt_service_ports": { "internet": 1234, "iptv": null }, "live": { "iface_exists": true, "link": "up", "online": true, "mac": "AA:BB:CC:DD:EE:FF", "neigh_state": "REACHABLE", "lease": { "state": "active", "mac": "AA:BB:CC:DD:EE:FF", "ends": "2026/09/28 12:00:00", "hostname": null } } }, "plan": { "name": "plan_200_200", "down_mbps": 200, "up_mbps": 200 }}404 when no service matches. The traffic graph of a service is
GET /services/{id}/graph — see the System API.
GET /services/unregistered
Section titled “GET /services/unregistered”Asks every registered OLT (or one) for the ONTs it sees connected and not yet registered — the
technician’s pick list. Each OLT is asked in turn. errors names the OLTs that could not be
asked; cvlan is the C-VLAN the ONT’s port uses.
| Name | In | Type | Notes |
|---|---|---|---|
olt |
query | string | Ask only this OLT. |
curl -H "X-API-Key: YOUR_API_KEY" "http://ROUTER-IP:8880/services/unregistered?olt=olt-1"{ "ok": true, "code": 200, "count": 1, "onts": [ { "olt": "olt-1", "pon": "0/1/0", "sn": "0123456789ABCDEF", "vendor": "ABCD", "model": "ONT-MODEL", "software": null, "seen_at": "2026-09-25 10:12:03+00:00", "svlan": 500, "cvlan": 116 } ], "errors": {}, "next": "POST /services {ref, sn, olt, pon, plan|down_mbps+up_mbps, name, ...}"}Changing
Section titled “Changing”POST /services
Section titled “POST /services”Creates a service in one synchronous call; when it answers 201 the service is live.
With ref, the call is idempotent: repeating it returns the existing service (200,
"existing": true) instead of creating a second one.
| Name | In | Type | Notes |
|---|---|---|---|
ref |
body | string | Recommended: the billing’s own id for this service. |
sn |
body | string | ONT serial, 16 hex digits. Required with an OLT. |
olt |
body | string | Registered OLT name; required when more than one OLT is registered. |
pon |
body | string | PON port frame/slot/port. If omitted, the serial is looked up in the OLT’s autofind table. |
plan |
body | string | Existing plan name… |
down_mbps, up_mbps |
body | integer | …or the speeds: plan plan_<down>_<up> is created when missing. |
name |
body | string | Required: the customer. |
contract, comment |
body | string | Free text. |
user_vlan |
body | integer | 1–4094: the VLAN the ONU sends, when it is not the port’s C-VLAN (the OLT translates it). |
ipv6 |
body | boolean | Default true when the router has a service IPv6 pool configured. |
iptv |
body | boolean | Put the ONT’s IPTV port on the OLT’s IPTV VLAN (when the OLT has one). |
expires |
body | date | End date; the router suspends the service then, and resumes it when a later date is set. never clears it. |
svlan, ont_id |
body | integer | Only with "olt": "none" (an ONT provisioned by hand): required together with pon. |
curl -X POST -H "X-API-Key: YOUR_API_KEY" -H "Content-Type: application/json" \ -d '{"ref":"billing-000812","sn":"0123456789ABCDEF","olt":"olt-1","pon":"0/1/0", "down_mbps":200,"up_mbps":200,"name":"Customer name","contract":"C-00812", "expires":"2026-10-31"}' \ http://ROUTER-IP:8880/services{ "ok": true, "code": 201, "message": "Service created", "service": { "id": "svc_1a2b3c4d", "ref": "billing-000812", "pon": "0/1/0", "ont_id": 7, "svlan": 500, "cvlan": 116, "user_vlan": 116, "iface": "v500.116", "ipv4": { "network": "100.64.16.0/24", "gateway": "100.64.16.1", "address": "100.64.16.9" }, "plan": "plan_200_200", "state": "active" }, "technician": { "user_vlan": 116, "note": "set the ONU's WAN to VLAN 116, DHCP; it receives 100.64.16.9" }}| Code | Meaning |
|---|---|
200 |
A service with this ref already exists; it is returned with "existing": true. |
400 |
A field is missing or invalid (error says which). |
404 |
Unknown OLT or plan, or the serial is not in the OLT’s autofind table. |
409 |
The serial (or that port’s ONT id) already belongs to a service — returned in service — or the port cannot be used (error says why). |
502 |
The OLT refused; olt_raw has the last lines of its answer. Nothing was created on the router. |
500 |
The OLT side succeeded, the router side failed: the service exists in state error; retry with POST /services/{id} {"retry": true}. |
507 |
No address left for this ONT on its port. |
POST /services/{id}
Section titled “POST /services/{id}”Changes one or more fields. PUT is accepted the same way. The answer’s olt says whether the
OLT side of a plan change succeeded.
| Name | In | Type | Notes |
|---|---|---|---|
id |
path | string | Id, ref, serial, contract, address or interface. |
plan or down_mbps + up_mbps |
body | string / integer | New plan (created from the speeds when missing). |
name, contract, comment, ref |
body | string | New values. |
expires |
body | date | New end date, or never. |
retry |
body | boolean | Re-run the router side (after a 500 on create). |
curl -X POST -H "X-API-Key: YOUR_API_KEY" -H "Content-Type: application/json" \ -d '{"plan":"plan_300_300"}' http://ROUTER-IP:8880/services/svc_1a2b3c4d{ "ok": true, "code": 200, "message": "Service updated", "changed": ["plan"], "olt": "ONT moved to the new plan on the OLT", "service": { "id": "svc_1a2b3c4d", "plan": "plan_300_300", "state": "active" }}400 when nothing is given to change; 404 for an unknown service.
POST /services/{id}/suspend
Section titled “POST /services/{id}/suspend”The OLT step can be turned off in the router’s configuration, leaving only the router-side block.
| Name | In | Type | Notes |
|---|---|---|---|
id |
path | string | Id, ref, serial, contract, address or interface. |
curl -X POST -H "X-API-Key: YOUR_API_KEY" http://ROUTER-IP:8880/services/svc_1a2b3c4d/suspend{ "ok": true, "code": 200, "message": "Service suspended", "olt": "ONT deactivated on the OLT", "service": { "id": "svc_1a2b3c4d", "state": "suspended" }}502 when the router side was done but the OLT did not follow (the answer says so): retry.
POST /services/{id}/resume
Section titled “POST /services/{id}/resume”Same parameters and answers as suspend ("message": "Service resumed", "olt": "ONT activated on the OLT").
curl -X POST -H "X-API-Key: YOUR_API_KEY" http://ROUTER-IP:8880/services/svc_1a2b3c4d/resumeDELETE /services/{id}
Section titled “DELETE /services/{id}”The record is removed on the router even when the OLT step fails (the answer’s olt says so).
The port’s interface stays for the other subscribers on it.
| Name | In | Type | Notes |
|---|---|---|---|
id |
path | string | Id, ref, serial, contract, address or interface. |
keep_ont |
query or body | boolean | Leave the ONT registered on the OLT. |
curl -X DELETE -H "X-API-Key: YOUR_API_KEY" "http://ROUTER-IP:8880/services/svc_1a2b3c4d?keep_ont=1"{ "ok": true, "code": 200, "message": "Service deleted", "olt": null, "service": { "id": "svc_1a2b3c4d", "name": "Customer name", "state": "active" }}POST /services/{id}/migrate
Section titled “POST /services/{id}/migrate”Operator use when renumbering (not part of the billing flow): moves a service created under an older numbering onto its port’s C-VLAN and address, recreating the service-port on the OLT without touching the ONU. The id stays; the ONU takes the new address at its next DHCP. The old per-subscriber interface is removed once nothing else uses it.
| Name | In | Type | Notes |
|---|---|---|---|
id |
path | string | Id, ref, serial, contract, address or interface. |
curl -X POST -H "X-API-Key: YOUR_API_KEY" http://ROUTER-IP:8880/services/svc_1a2b3c4d/migrate{ "ok": true, "code": 200, "message": "Service moved to its PON port", "olt": "service-port recreated: user-vlan 116 -> S-VLAN 500 / C-VLAN 116", "service": { "id": "svc_1a2b3c4d", "iface": "v500.116", "state": "active" }, "note": "the ONU keeps its settings; it takes the new address at its next DHCP (reboot the ONT to make that now)"}Answers "message": "Already on its port" when there is nothing to move; 409 when the port,
address or C-VLAN is held by something else; 502 when the OLT refuses (router side unchanged).
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=service-list |
[state], [olt], [q], [fast=1] |
GET /services |
action=service-get |
id |
GET /services/{id} |
action=service-unregistered |
[olt] |
GET /services/unregistered |
action=service-add |
sn, olt, pon, plan or down_mbps+up_mbps, name, [ref], [contract], [ipv6], [iptv], [expires] |
POST /services |
action=service-set |
id, [plan], [name], [expires], [retry=1] … |
POST /services/{id} |
action=service-suspend / action=service-resume |
id |
POST /services/{id}/suspend / resume |
action=service-del |
id, [keep_ont=1] |
DELETE /services/{id} |
action=service-expiry |
— | Runs the end-date check now (suspends what expired, resumes what was extended); answers {"changed": [...]}. |