API Reference - fulviofreitas/eero-ui GitHub Wiki
The backend exposes these REST endpoints. Full interactive docs are available at /api/docs in debug mode.
Conventions that apply to every route below:
-
Auth — every route requires an authenticated dashboard session unless marked no. An unauthenticated call gets
401; a session the eero cloud has rejected gets401 {"reason": "expired"}and the stored token is cleared. -
CSRF — every
POST/PUT/PATCH/DELETEunder/api/must carryX-Requested-With: eero-ui, else403 {"type": "csrf"}. -
Gate —
experimentalmeans the route depends onEERO_DASHBOARD_EXPERIMENTAL_WRITES(closed:403 {"type": "experimental_disabled"});identitymeans it additionally depends onEERO_DASHBOARD_ACCOUNT_IDENTITY_WRITES(closed:403 {"type": "account_identity_disabled"}). - Limit — slowapi limit per client IP. A named scope is shared by every route carrying it.
-
Path ids —
{network_id},{eero_id},{profile_id},{device_id}are validated before any SDK call; a malformed value is400 {"detail": "Invalid <name>."}. -
Mapped errors (any route) —
402 premium_required,403access denied,404not found,409 feature_unavailable,422 {field, message}validation,429(withRetry-After: 60),502cloud error,503unreachable/timeout. See Architecture#exception-layer. - Many routes accept an optional
?network_id=query; when omitted the preferred network is used.
| Method | Endpoint | Auth | Response | Notes |
|---|---|---|---|---|
GET |
/api/health |
no | {status, version, eero_client_version, experimental_writes} |
experimental_writes lets the UI hide gated controls |
| Method | Endpoint | Auth | Limit | Request | Response | Notes |
|---|---|---|---|---|---|---|
GET |
/api/auth/status |
no | — | — | AuthStatusResponse |
reason: null (live), "none" (no token), "expired" (probe failed; token cleared) |
POST |
/api/auth/login |
no | 5/minute | LoginRequest {identifier} |
LoginResponse |
401 bad identifier, 503 cloud unreachable; never reported as "expired" |
POST |
/api/auth/verify |
no | 5/minute | VerifyRequest {code} |
VerifyResponse {success, message, preferred_network_id} |
401 wrong code |
POST |
/api/auth/logout |
no | — | — | {success, message} |
clears local state even if the cloud call fails |
| Method | Endpoint | Response | Notes |
|---|---|---|---|
GET |
/api/networks |
list[NetworkSummary] |
|
GET |
/api/networks/{network_id} |
NetworkDetail |
|
GET |
/api/networks/{network_id}/speedtests |
list[SpeedTestResult] |
most recent first; ?limit= bounded, 400 if out of range |
GET |
/api/networks/{network_id}/guest |
GuestNetworkStatus |
password never returned |
GET |
/api/networks/{network_id}/dns |
DnsSettings |
per-family IPv4/IPv6 view, ISP upstream, provider catalogue |
GET |
/api/networks/{network_id}/entitlements |
NetworkEntitlements |
features, upsell, is_premium, premium_status, capabilities, experimental_writes; each source fail-soft |
GET |
/api/networks/{network_id}/scan |
NetworkScanResponse |
channel/neighbour scan |
GET |
/api/networks/{network_id}/insights |
InsightsResponse |
`?insight_type=adblock |
GET |
/api/networks/{network_id}/data-usage |
DataUsageResponse |
premium |
GET |
/api/networks/{network_id}/data-usage/breakdown |
DataUsageResponse |
premium |
GET |
/api/networks/{network_id}/data-usage/devices |
DataUsageResponse |
premium |
GET |
/api/networks/{network_id}/data-usage/devices/{device_mac} |
DataUsageResponse |
400 malformed MAC; premium |
GET |
/api/networks/{network_id}/data-usage/eeros/summary |
DataUsageResponse |
premium |
GET |
/api/networks/{network_id}/data-usage/eeros/{eero_id} |
DataUsageResponse |
premium |
GET |
/api/networks/{network_id}/data-usage/profiles/{profile_id} |
DataUsageResponse |
premium |
GET |
/api/networks/{network_id}/events |
AppEventsResponse |
?page_size=; 400 out of range |
GET |
/api/networks/{network_id}/channel-utilization |
raw (stripped) |
?eero_id=&granularity=; 400/422 on bad params; not cached |
GET |
/api/networks/{network_id}/permissions |
PermissionsResponse |
|
GET |
/api/networks/{network_id}/members |
MembersResponse |
a 403 from the cloud is reported as partial |
GET |
/api/networks/{network_id}/invites |
InvitesResponse |
|
GET |
/api/networks/{network_id}/backup-internet |
BackupInternetResponse |
status, cellular usage and events |
GET |
/api/networks/{network_id}/backup-access-points |
BackupAccessPointsResponse |
|
GET |
/api/networks/{network_id}/security |
SecuritySettingsResponse |
WPA3, band steering, UPnP, IPv6, per-band WPA3, Passpoint |
GET |
/api/networks/{network_id}/subnets |
SubnetsResponse |
|
GET |
/api/networks/{network_id}/multistaticip |
MultiStaticIpResponse |
404 from the cloud is reported as no config |
GET |
/api/networks/{network_id}/advanced |
AdvancedNetworkSettings |
DHCP, connection mode, power saving, DDNS, NAT port randomization, MLO mode (proxied_nodes_enabled always null - no reliable source) |
GET |
/api/networks/{network_id}/power-saving/schedules |
PowerSavingSchedulesResponse |
|
GET |
/api/networks/{network_id}/notifications |
NotificationsResponse |
settings and unread flag |
GET |
/api/networks/{network_id}/notifications/history |
NotificationHistoryResponse |
400 bad paging |
GET |
/api/networks/{network_id}/forwards |
ForwardsResponse |
|
GET |
/api/networks/{network_id}/reservations |
ReservationsResponse |
|
GET |
/api/networks/{network_id}/content-filter |
ContentFilterResponse |
premium |
| Method | Endpoint | Limit | Request | Response | Notes |
|---|---|---|---|---|---|
POST |
/api/networks/{network_id}/set-preferred |
— | — | {success, preferred_network_id} |
in-memory only |
POST |
/api/networks/{network_id}/speedtest |
2/minute speedtest
|
— | 202 SpeedTestStartedResponse {status: "started", started_at} |
409 {"type": "speedtest_in_progress"} within 90 s of a previous start on the same network; read the result from /speedtests
|
PUT |
/api/networks/{network_id}/guest-network |
— | query ?enabled=<bool>[&name=]
|
{success, ...} |
disconnects guest clients |
PUT |
/api/networks/{network_id}/guest/password |
5/minute guest_password
|
GuestPasswordRequest {password} |
GuestPasswordResponse |
read-back returned; password never echoed |
DELETE |
/api/networks/{network_id}/guest/password |
5/minute guest_password
|
— | GuestPasswordResponse |
| Method | Endpoint | Request | Response | Notes |
|---|---|---|---|---|
PUT |
/api/networks/{network_id}/dns |
DnsUpdateRequest |
DnsUpdateResponse {success, changed, ...} |
server-side no-op guard on parsed addresses; changed: false means nothing was written; 422 {field, message} on a bad server; proven to reboot every eero; the only settings-class write that is not gated (live-verified) |
Each route depends on its own gate constant in networks.py (_SQM_GATE, _DHCP_GATE, …), all currently aliasing the experimental gate.
| Method | Endpoint | Request | Response | Notes |
|---|---|---|---|---|
PUT |
/api/networks/{network_id}/name |
NetworkRenameRequest {name} |
{success, changed, name} |
_NETWORK_NAME_GATE; 400 empty, 422 on control/format characters or > 32 bytes; read-first no-op guard; assumed to reboot the mesh |
PUT |
/api/networks/{network_id}/sqm |
SqmUpdateRequest |
SqmUpdateResponse |
no-op guard |
PUT |
/api/networks/{network_id}/dhcp |
DhcpUpdateRequest {mode, custom?} |
DhcpUpdateResponse |
422 invalid range; custom_v2 not exposed |
PUT |
/api/networks/{network_id}/connection-mode |
ConnectionModeRequest {mode: BRIDGE|NAT, acknowledge_disables_routing?} |
ConnectionModeResponse |
mode: BRIDGE requires acknowledge_disables_routing: true (422 otherwise) — bridge mode disables the network's own DHCP/NAT |
PUT |
/api/networks/{network_id}/nat-port-randomization |
NatPortRandomizationRequest {enabled} |
NatPortRandomizationResponse |
best-effort no-op guard |
PUT |
/api/networks/{network_id}/wpa3 |
Wpa3PerBandRequest |
Wpa3PerBandResponse |
422; no 6 GHz field in v8.0.3 |
PUT |
/api/networks/{network_id}/security |
SecurityUpdateRequest (exactly one of wpa3, band_steering, upnp, ipv6) |
SecurityUpdateResponse |
422 if zero or more than one field |
PUT |
/api/networks/{network_id}/mlo |
MloUpdateRequest |
MloUpdateResponse |
best-effort no-op guard (no getter) |
PUT |
/api/networks/{network_id}/fast-transition |
FastTransitionRequest {enabled} |
FastTransitionResponse |
|
PUT |
/api/networks/{network_id}/passpoint |
PasspointRequest {enabled} |
PasspointResponse |
best-effort no-op guard |
PUT |
/api/networks/{network_id}/proxied-nodes |
ProxiedNodesRequest {enabled} |
ProxiedNodesResponse |
no no-op guard — always writes |
PUT |
/api/networks/{network_id}/power-saving |
PowerSavingRequest |
PowerSavingResponse |
422 |
PUT |
/api/networks/{network_id}/subnets |
SubnetConfigRequest (strict) |
SubnetConfigResponse |
422; password never echoed |
DELETE |
/api/networks/{network_id}/subnets/{subnet_type} |
— | SubnetConfigResponse |
400 unknown type |
PUT |
/api/networks/{network_id}/multistaticip |
MultiStaticIpRequest |
MultiStaticIpUpdateResponse |
|
PUT |
/api/networks/{network_id}/secondary-wan |
SecondaryWanConfigRequest (bulk) |
SecondaryWanConfigResponse |
422; no no-op guard for the bulk form |
POST |
/api/networks/{network_id}/updates/apply |
— | NetworkUpdateApplyResponse |
409 {"type": "no_update_available"} when nothing is pending; 409 {"type": "update_in_progress"} when a previous apply is still within its window; reboots every node |
PUT |
/api/networks/{network_id}/updates/preferred-hour |
PreferredUpdateHourRequest {hour: 0-23} |
PreferredUpdateHourResponse |
422 outside 0-23; no-op guard against get_updates; nothing restarts |
PUT |
/api/networks/{network_id}/hide-5g |
Hide5gRequest {enabled} |
Hide5gResponse |
no-op guard against temporary_flags.hide_5g; temporary flag the cloud expires; disconnects 5 GHz clients |
PUT |
/api/networks/{network_id}/password |
NetworkPasswordRequest {password} |
NetworkPasswordResponse |
limit 2/minute network_password; 422; disconnects every client; never logged or echoed |
DELETE |
/api/networks/{network_id}/password |
NetworkPasswordClearRequest {confirm_open_network: true} |
NetworkPasswordResponse |
limit 2/minute network_password; 422 unless confirm_open_network is true — an empty-body DELETE can never open the network |
Unverified, non-settings writes (gate: experimental; limit 10/minute experimental_writes unless noted)
| Method | Endpoint | Request | Response | Notes |
|---|---|---|---|---|
PUT |
/api/networks/{network_id}/ddns |
DdnsUpdateRequest {enabled} |
DdnsUpdateResponse |
|
PUT |
/api/networks/{network_id}/backup-internet |
BackupInternetToggleRequest {enabled} |
BackupInternetToggleResponse |
premium |
PUT |
/api/networks/{network_id}/thread |
ThreadUpdateRequest |
ThreadUpdateResponse |
|
POST |
/api/networks/{network_id}/thread/regenerate |
— | {success, ...} |
limit 1/minute thread_regenerate
|
PUT |
/api/networks/{network_id}/notifications |
NotificationSettingsUpdateRequest |
NotificationsResponse |
400/422
|
POST |
/api/networks/{network_id}/notifications/mark-read |
— | {success} |
|
POST |
/api/networks/{network_id}/invites |
InviteCreateRequest {role: owner|admin} |
201 InviteCreateResponse {success, role} |
limit 2/minute invite_create; 422 unknown role; returns neither the invite id nor invite_url — re-list /invites
|
PUT |
/api/networks/{network_id}/invites/{invite_id} |
InviteUpdateRequest {name} |
InviteSummary |
422 |
DELETE |
/api/networks/{network_id}/invites/{invite_id} |
— | {success} |
|
POST |
/api/networks/{network_id}/members/{member_id}/promote |
— | {success} |
|
DELETE |
/api/networks/{network_id}/admins/{user_id} |
— | {success} |
|
POST |
/api/networks/{network_id}/pending-admin/cancel |
— | {success} |
|
POST |
/api/networks/{network_id}/backup-access-points |
BackupAccessPointCreateRequest |
201 BackupAccessPoint |
|
PUT |
/api/networks/{network_id}/backup-access-points/order |
BackupAccessPointOrderRequest |
{success} |
|
PUT |
/api/networks/{network_id}/backup-access-points/{ap_id} |
BackupAccessPointUpdateRequest |
BackupAccessPoint |
|
DELETE |
/api/networks/{network_id}/backup-access-points/{ap_id} |
— | {success} |
|
POST |
/api/networks/{network_id}/backup-access-points/discover |
— | BackupSsidDiscoveryResponse |
starts discovery and returns the result |
POST |
/api/networks/{network_id}/backup-access-points/check |
— | {success, ...} |
connectivity check |
POST |
/api/networks/{network_id}/forwards |
ForwardCreateRequest |
201 ForwardSummary |
|
PUT |
/api/networks/{network_id}/forwards/{forward_id} |
ForwardUpdateRequest |
ForwardSummary |
|
DELETE |
/api/networks/{network_id}/forwards/{forward_id} |
— | {success} |
|
POST |
/api/networks/{network_id}/reservations |
ReservationCreateRequest |
201 ReservationSummary |
422 |
PUT |
/api/networks/{network_id}/reservations/{reservation_id} |
ReservationUpdateRequest |
ReservationSummary |
422 |
DELETE |
/api/networks/{network_id}/reservations/{reservation_id} |
— | {success} |
?delete_forwards= |
POST / DELETE
|
/api/networks/{network_id}/content-filter/allow |
DomainRequest {domain} |
ContentFilterResponse |
premium |
POST / DELETE
|
/api/networks/{network_id}/content-filter/block |
DomainRequest {domain} |
ContentFilterResponse |
premium |
POST / DELETE
|
/api/networks/{network_id}/content-filter/allow-for-profiles |
DomainForProfilesRequest |
{success} |
premium |
POST / DELETE
|
/api/networks/{network_id}/content-filter/block-for-profiles |
DomainBlockForProfilesRequest |
{success} |
premium |
POST |
/api/networks/{network_id}/power-saving/schedules |
PowerSavingScheduleCreateRequest |
PowerSavingScheduleActionResponse |
422 |
PUT |
/api/networks/{network_id}/power-saving/schedules/{schedule_id} |
PowerSavingScheduleUpdateRequest |
PowerSavingScheduleActionResponse |
400/422
|
DELETE |
/api/networks/{network_id}/power-saving/schedules/{schedule_id} |
— | PowerSavingScheduleActionResponse |
400 |
| Method | Endpoint | Gate | Limit | Request | Response | Notes |
|---|---|---|---|---|---|---|
GET |
/api/devices |
— | — | — | list[DeviceSummary] |
|
GET |
/api/devices/{device_id} |
— | — | — | DeviceDetail |
|
POST |
/api/devices/{device_id}/block |
— | 10/minute device_writes
|
— | DeviceAction |
MAC resolved server-side; 422 if the device has no MAC; verified (live-verified 2026-10-06) |
POST |
/api/devices/{device_id}/unblock |
— | — | — | DeviceAction |
MAC resolved server-side; 422 without MAC; verified |
PUT |
/api/devices/{device_id}/nickname |
— | — | NicknameRequest {nickname} |
DeviceAction |
stripped; 422 on control/format characters; verified |
PUT |
/api/devices/{device_id}/type |
— | 10/minute device_type
|
DeviceTypeRequest {device_type} |
DeviceAction |
422 unknown type; verified |
PUT |
/api/devices/{device_id}/secondary-wan-access |
experimental (_SECONDARY_WAN_ACCESS_GATE) |
2/minute settings_writes
|
SecondaryWanAccessRequest {denied} |
SecondaryWanAccessResponse |
settings-class; best-effort no-op guard |
GET |
/api/devices/{device_id}/insights |
— | — | — | InsightsResponse |
premium |
| Method | Endpoint | Gate | Limit | Request | Response | Notes |
|---|---|---|---|---|---|---|
GET |
/api/eeros |
— | — | — | list[EeroSummary] |
|
GET |
/api/eeros/{eero_id} |
— | — | — | EeroDetail |
|
POST |
/api/eeros/{eero_id}/reboot |
— | — | — | EeroAction |
verified; reboots that node only |
GET |
/api/eeros/{eero_id}/led |
— | — | — | LedStatus {led_on, led_brightness} |
|
POST |
/api/eeros/{eero_id}/led |
— | — | {enabled} |
EeroAction |
verified (was a silent no-op before v8) |
PUT |
/api/eeros/{eero_id}/led/brightness |
— | 10/minute led_brightness
|
{brightness} |
EeroLedBrightnessAction |
read-back returned; verified |
GET |
/api/eeros/{eero_id}/connections |
— | — | — | EeroConnectionsResponse |
|
PUT |
/api/eeros/{eero_id}/location |
experimental | 10/minute experimental_writes
|
LocationUpdateRequest {location} |
LocationUpdateResponse |
422 |
POST |
/api/eeros/{eero_id}/node-action |
experimental | 2/minute node_actions
|
NodeActionRequest {action} |
NodeActionResponse {reboots_node, ...} |
POWER_CYCLE_ALL_PORTS_AND_REBOOT reboots the node; 422
|
POST |
/api/eeros/{eero_id}/ports/{port_number}/action |
experimental | 2/minute node_actions
|
PortActionRequest {action} |
PortActionResponse |
422 {"type": "port_protected"} when the target is the gateway's WAN/uplink port |
| Method | Endpoint | Gate | Limit | Request | Response | Notes |
|---|---|---|---|---|---|---|
GET |
/api/profiles |
— | — | — | list[ProfileSummary] |
|
GET |
/api/profiles/{profile_id} |
— | — | — | ProfileSummary |
|
POST |
/api/profiles |
experimental | 10/minute experimental_writes
|
ProfileCreateRequest {name} |
201 ProfileSummary |
400 |
PATCH |
/api/profiles/{profile_id} |
experimental | 10/minute experimental_writes
|
ProfileRenameRequest {name} |
ProfileSummary |
400 |
DELETE |
/api/profiles/{profile_id} |
experimental | 10/minute experimental_writes
|
— | ProfileAction |
devices become unassigned |
POST |
/api/profiles/{profile_id}/pause |
experimental | 10/minute experimental_writes
|
— | ProfileAction |
SDK logs a WARNING |
POST |
/api/profiles/{profile_id}/unpause |
experimental | 10/minute experimental_writes
|
— | ProfileAction |
|
POST |
/api/profiles/{profile_id}/assign-devices |
experimental | 10/minute experimental_writes
|
AssignDevicesRequest {device_ids} |
AssignDevicesResponse |
merges with the existing list |
GET |
/api/profiles/{profile_id}/insights |
— | — | — | InsightsResponse |
premium |
GET |
/api/profiles/{profile_id}/schedules |
— | — | — | list[ScheduleSummary] |
|
POST |
/api/profiles/{profile_id}/schedules |
experimental | 10/minute experimental_writes
|
ScheduleCreateRequest |
201 ScheduleSummary |
|
PUT |
/api/profiles/{profile_id}/schedules/{schedule_id} |
experimental | 10/minute experimental_writes
|
ScheduleUpdateRequest |
ScheduleSummary |
|
DELETE |
/api/profiles/{profile_id}/schedules/{schedule_id} |
experimental | 10/minute experimental_writes
|
— | {success} |
|
DELETE |
/api/profiles/{profile_id}/schedules |
experimental | 10/minute experimental_writes
|
— | {success} |
clears every schedule |
POST |
/api/profiles/{profile_id}/bedtime |
experimental | 10/minute experimental_writes
|
BedtimeCreateRequest |
201 ScheduleSummary |
|
GET |
/api/profiles/{profile_id}/blocked-applications |
— | — | — | BlockedApplicationsResponse |
premium |
PUT |
/api/profiles/{profile_id}/blocked-applications |
experimental | 10/minute experimental_writes
|
SetBlockedApplicationsRequest |
BlockedApplicationsResponse |
premium |
All account writes share the account_writes scope at 10/minute.
| Method | Endpoint | Gate | Request | Response | Notes |
|---|---|---|---|---|---|
GET |
/api/account/sms-countries |
— | — | SmsCountriesResponse |
|
PUT |
/api/account/name |
experimental | AccountNameRequest {name} |
AccountActionResponse |
|
PUT |
/api/account/consents |
experimental | AccountConsentsRequest |
AccountActionResponse |
marketing e-mail consent |
PUT |
/api/account/email |
experimental + identity | AccountEmailRequest {email} |
AccountActionResponse |
inactive until verified |
POST |
/api/account/email/verify |
experimental + identity | VerificationCodeRequest {code} |
AccountActionResponse |
|
PUT |
/api/account/phone |
experimental + identity | AccountPhoneRequest {phone} |
AccountActionResponse |
inactive until verified |
POST |
/api/account/phone/verify |
experimental + identity | VerificationCodeRequest {code} |
AccountActionResponse |
All routes require a session (router-level require_auth) and validate every path id at router level (validate_request_path_ids) before it can reach a PromQL label selector; the network_id query parameter is validated the same way. 503 "Metrics service unavailable" when VictoriaMetrics cannot be reached. See Metrics.
| Method | Endpoint | Query | Response |
|---|---|---|---|
GET |
/api/metrics/health |
— | {victoria_metrics, status, last_successful_write, collector_errors_total} |
GET |
/api/metrics/speedtest/history |
start, end, step=5m, network_id?
|
{download, upload} (VictoriaMetrics query_range results) |
GET |
/api/metrics/devices/{device_id}/signal |
start, end, step=1m
|
{signal_strength, connection_score}; 400 on a malformed id |
GET |
/api/metrics/network/client_count |
start, end, step=5m
|
{total, wireless, wired, client_count} |
GET |
/api/metrics/roaming |
network_id, range=24h (1h/6h/24h/7d), device_id?
|
RoamingResponse {network_id, range, start, end, resolution_seconds, total_events, truncated, events[], top_roamers[]}; newest first, at most 500 events. 400 on a malformed id or unknown range, 404 when network_id is not on the session's account, 422 when the range holds more than 2,000 device series. Limit: metrics_history scope, 30/minute |
| Route | Why |
|---|---|
GET /metrics (Prometheus proxy) |
the exporter is no longer embedded; eero-ui is not a scrape target. Returns 404, not the SPA |
GET /api/metrics/query |
raw PromQL passthrough with no frontend caller and an unbounded query surface |
GET /api/metrics/query_range |
same |
GET /api/metrics/eeros/{serial}/quality |
no frontend caller |
GET /api/metrics/devices/{mac}/bandwidth |
no frontend caller; the exporter never produced per-device bandwidth |
Also gone: exporter_version in GET /api/health, and POST /api/networks/{id}/speedtest no longer returns the result inline (it returns 202; read /speedtests).