Integração com o Lumina ACS
Tudo o que um ERP, um app de técnicos ou um sistema do provedor precisa para falar com o Lumina ACS: a API REST, a NBI compatível com o GenieACS, o conector de ERP e os webhooks.
API /api/v1NBI /nbiTR-069 · TR-181 e TR-098Revisão de 14/10/2026
Os quatro caminhos
O Lumina conversa com o ERP do provedor por quatro caminhos independentes, que podem ser combinados. Em dois deles o ERP chama o Lumina; nos outros dois o Lumina vai até o ERP.
ERP do provedorsistema de gestão
/api/v1 · /nbi →← conector · webhooks
Lumina ACSAPI · NBI · worker
CWMP ⇄Inform e Connection Request
CPEs e ONUsTR-181 · TR-098
1 · ERP → Lumina · Bearer lmk_…
API REST
Para quem vai desenvolver: configuração do assinante, Wi-Fi por banda, hosts, diagnósticos, histórico, lotes, firmware. pronto
2 · ERP → Lumina · Basic, senha = lmk_…
NBI compatível com o GenieACS
Para o ERP que já tem integração pronta com ACS do tipo GenieACS e não quer desenvolver nada. a homologar
3 · Lumina → ERP · credencial do ERP
Conector de ERP
O contrato do cliente na tela da CPE, e o PPPoE e o Wi-Fi do contrato gravados nela, com conectores prontos para os ERPs mais usados. pronto
4 · Lumina → ERP · HMAC-SHA256
Webhooks
O ERP é avisado quando a CPE cai, volta, sai de fábrica, quando uma tarefa termina ou um alerta abre. pronto
Combinação típica: o conector de ERP ligado (o atendimento vê o cliente na CPE e o vínculo grava PPPoE e Wi-Fi do contrato), a NBI apontada no módulo de ACS do próprio ERP (o atendente reinicia e consulta sem sair do ERP) e webhooks para quedas, resets de fábrica e tarefas. A API REST entra quando o provedor quer automações próprias ou um app de instalação.
Por onde começar
Cinco regras que evitam a maioria dos problemas
- Ações em CPE são assíncronas. A resposta
202 quer dizer “aceito”, não “feito”. Veja Tarefas e o modelo assíncrono.
- Provisione pela configuração do assinante (
PUT …/config ou o vínculo com o ERP), não por set_values solto: só ela volta sozinha depois de um reset de fábrica.
- Não faça polling de CPE. Assine webhooks; o Lumina já sabe quando algo muda.
- Use a chave da CPE exatamente como a API devolve, codificada na URL. Nunca monte a chave no ERP a partir de pedaços.
- Trate
null. Nem toda CPE expõe RX óptico, CPU, memória ou clientes Wi-Fi. Ausência não é zero.
Reset de fábrica não existe pelo ACS. A ação, a tarefa e a operação da NBI são sempre recusadas. Reset só manual, com a CPE em mãos.
Primeiros passos
Da chave criada à primeira ação numa CPE, em cinco minutos.
1. Crie uma chave de API
No console, um administrador vai em Integrações → Chaves de API → Nova chave, dá um nome (por exemplo “ERP — testes”), marca os escopos devices:read, devices:safe-ops e tasks:read e salva. A chave (lmk_ seguido de 32 caracteres) aparece uma única vez: copie-a agora. Detalhes em Chaves de API e escopos.
2. Prepare as variáveis
Todos os exemplos de curl desta wiki assumem estas três variáveis de shell (e jq onde aparece):
ACS=https://acs.provedor.com.br
KEY="Authorization: Bearer lmk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
CPE=40ED00-XX230v-2249B1000123
ACS é o mesmo endereço do console. No ACS hospedado pela Lumina, é o subdomínio do provedor.
3. Três chamadas
# 1 — a chave funciona? (devices:read)
curl -s -H "$KEY" "$ACS/api/v1/dashboard/summary"
# 2 — achar a CPE do cliente pelo login PPPoE
curl -sG -H "$KEY" "$ACS/api/v1/devices" --data-urlencode "pppoe=cliente@isp" | jq -r '.items[0].key'
# 3 — reiniciar (devices:safe-ops)
curl -s -X POST -H "$KEY" "$ACS/api/v1/devices/$CPE/actions/reboot"
# 202 {"id":"b32fqgm2ejgjkahhetfa","status":"pending","connection_request":true}
4. Acompanhe a tarefa
curl -s -H "$KEY" "$ACS/api/v1/tasks/b32fqgm2ejgjkahhetfa" | jq -r .status
# pending → running → rebooting → done
Em produção, troque a consulta pelo webhook task.completed/task.failed. Se a CPE estiver inalcançável, a tarefa fica pending e executa no próximo Inform: não é erro.
Próximos passos
Chaves de API e escopos
Toda integração usa uma chave de API do tenant, com escopos, lista de IPs, limite por minuto e validade.
Criar uma chave
No console, em Integrações → Chaves de API → Nova chave, por um usuário administrador. Também por POST /api/v1/api-keys com a sessão desse usuário (nunca com outra chave). O formato é lmk_ seguido de 32 caracteres. O Lumina guarda só o hash e depois mostra apenas o prefixo (os 12 primeiros caracteres).
POST/api/v1/api-keysescopo integrations:write · sessão de administrador
{ "name": "ERP — módulo de ACS",
"scopes": ["devices:read","devices:safe-ops","devices:write","tasks:read","firmware:write"],
"ip_allowlist": ["200.150.10.0/24"],
"rate_per_minute": 600,
"expires_at": null }
→ 201
{ "item": { "id":"…", "name":"ERP — módulo de ACS", "prefix":"lmk_2f9cq7ab", "scopes":[…],
"ip_allowlist":[…], "rate_per_minute":600, "active":true, "expires_at":null, … },
"key": "lmk_2f9c…" // aparece só aqui }
name e ao menos um escopo são obrigatórios. Escopo fora da lista, IP/CIDR malformado ou rate_per_minute fora de 0–100 000 dão 400. GET /api/v1/api-keys devolve {"items":[…],"scopes":[…]} com last_used_at, last_used_ip e revoked_at de cada chave. DELETE /api/v1/api-keys/{id} revoga na hora.
Como autenticar
# API REST: Bearer
curl -s -H "Authorization: Bearer lmk_2f9c…" "$ACS/api/v1/devices?limit=1"
# NBI: Basic, usuário livre e a chave como senha (Bearer também vale)
curl -sG -u "erp:lmk_2f9c…" "$ACS/nbi/devices" \
--data-urlencode 'query={"_deviceId._SerialNumber":"2249B1000123"}'
Requisições com chave não passam pela proteção de CSRF (que só vale para o cookie do console) e não precisam de Origin. A lista de IPs de gerência do tenant vale para as sessões do console; uma chave obedece só à própria lista de IPs. As ações feitas com uma chave entram na auditoria e nas tarefas como apikey:<nome da chave>.
GET /api/v1/auth/me confirma a chave (role = api, email = apikey:…, tenant). O campo permissions volta vazio para chaves: os escopos valem, só não aparecem ali.
Escopos concedíveis
Uma chave recebe só os nove escopos abaixo, nunca os de usuários, integrações ou configurações (users:write, integrations:write, settings:write). Escopo ausente devolve 403 com error começando por sem permissão.
| Escopo | Permite |
devices:read | Listar e ler CPEs, parâmetros, eventos, tarefas da CPE, hosts, Wi-Fi, LAN, NAT, mesh, relógio, histórico, assinante e configuração salva (senhas mascaradas), alertas, regras e canais (sem segredos), perfis, presets, firmwares e campanhas; exportação CSV, painel, SSE, logins padrão, NTP do tenant e contexto dos guias. |
devices:safe-ops | Ações seguras: reboot, refresh*, Connection Request, Wi-Fi, otimização de canais, diagnósticos; tarefas get_values/get_names; vincular o assinante pelo login ou serial da própria CPE; consultar contrato no ERP; ler as senhas guardadas (GET …/secrets, auditado). |
devices:write | Escrita: set_values, add_object/delete_object, download, WAN/PPPoE, LAN, reservas DHCP, NAT, relógio, etiquetas; gravar ou apagar a configuração do assinante; vincular a outro login/serial; desvincular; remover e readmitir CPEs. |
tasks:read | Consultar tarefas (/tasks) e lotes (/batches). Com o escopo de criar aquele tipo de tarefa, também cancelar e repetir. |
bulk:write | Criar, cancelar e continuar lotes. A chave precisa também do escopo do tipo de tarefa do lote. |
presets:write | Criar, validar, editar e apagar presets de provisionamento. |
firmware:write | Enviar e editar firmware, baixar do catálogo, atualizar uma CPE (actions/firmware), criar e cancelar campanhas, registrar o resultado de uma atualização; tarefa download pela NBI. |
alerts:write | Resolver alertas; criar, editar e apagar regras e canais; testar canal. |
audit:read | Aceito na chave, mas nenhuma rota o exige hoje: não há endpoint de leitura da trilha de auditoria. |
Escopos por tipo de integração
| Integração | Escopos |
| Consulta para o atendimento (só leitura) | devices:read |
| Atendimento com reboot, Wi-Fi e diagnóstico | devices:read devices:safe-ops tasks:read |
| App de instalação | devices:read devices:safe-ops devices:write tasks:read |
| Módulo de ACS do ERP (NBI) | devices:read devices:safe-ops devices:write tasks:read, mais firmware:write se o ERP atualizar firmware |
| Abertura de OS por alerta | devices:read (os alertas chegam por webhook) |
Restrições por chave
- IPs permitidos
- Lista de IPs ou CIDRs. Fora da lista:
403 IP não autorizado para esta chave. Vazia = qualquer IP.
- Limite por minuto
- Janela deslizante de 60 s por chave (compartilhada entre réplicas quando há cache Valkey). Excedido:
429 limite de requisições da chave excedido com Retry-After em segundos. Aceita de 1 a 100 000; 0 ou ausente vira 600.
- Validade
expires_at opcional (RFC 3339). Vencida, a chave responde 401 chave de API inválida.
- Teto de tarefas de escrita
- Além do limite da chave, cada principal (usuário ou chave) cria no máximo 120 tarefas que gravam ou reiniciam por minuto fora dos lotes (
LUMINA_WRITE_TASKS_PER_MINUTE). Excedido: 429 com Retry-After.
Uma chave por integração. Com o mínimo de escopos e a lista de IPs preenchida, fica fácil revogar a chave do ERP sem derrubar a automação própria.
Convenções da API
Identificação da CPE, formato, erros, datas, paginação e segredos: o que vale para todas as rotas.
A chave da CPE
Toda CPE é identificada por {OUI}-{ProductClass}-{SerialNumber}, o mesmo formato de id do GenieACS. Por isso o id que o ERP já guardava de um GenieACS continua valendo. Exemplo: 40ED00-XX230v-2249B1000123. Onde esta wiki escreve {key}, é esse valor.
Leia a chave, não a monte. As partes vêm com escape de URL quando têm caracteres especiais. Use a key exatamente como a API a devolve e codifique-a ao montar a URL (encodeURIComponent). OUI e ProductClass vêm da própria CPE e não batem necessariamente com o que o ERP tem cadastrado.
Regras gerais
| Assunto | Regra |
| Formato | JSON na requisição e na resposta (Content-Type: application/json). Corpo limitado a 1 MB; tarefa da NBI a 256 KB; firmware vai por multipart, até 512 MB. |
| Erros | {"error":"mensagem em português"} com o status HTTP. Alguns erros trazem campos a mais para o programa decidir: count (lote), confirm_required, blocked e downgrade (firmware), mesh_agent (CPE agente EasyMesh). Na NBI o corpo do erro é uma string JSON, como no GenieACS. |
| Datas | RFC 3339 com o fuso do servidor (2026-09-17T10:31:07-03:00). Compare como instante, não como texto. A NBI devolve em UTC com milissegundos e aceita também milissegundos desde a época nas comparações. |
| Listas | {"items":[…]}. Listagens paginadas trazem next_cursor, que some na última página: repita a chamada com ?cursor=…. /devices, /tasks, /alerts e /devices/{key}/events trazem também total (contagem com os mesmos filtros). |
| Segredos | Senhas e tokens voltam como ••••••••. Reenviar o valor mascarado num PUT/PATCH preserva o segredo guardado. Senhas no payload das tarefas também saem mascaradas. |
| Tempo | 60 s por requisição; passou disso, 504. Ficam fora desse teto o SSE (ciclo próprio de 30 min) e a exportação CSV (prazo próprio de 10 min). |
| Tenant | A chave já pertence a um tenant; nada precisa ser informado. |
| Rastreio | Toda resposta traz X-Request-Id. Cite-o ao abrir chamado: é por ele que o erro é achado no log do ACS. |
| Nulos | Campos numéricos vêm null quando a CPE não expõe a grandeza. Trate a ausência, não presuma zero. |
Paginação na prática
URL="$ACS/api/v1/devices?limit=500"; CUR=""
while :; do
PAGE=$(curl -s -H "$KEY" "$URL${CUR:+&cursor=$CUR}")
echo "$PAGE" | jq -c '.items[] | {key, serial_number, pppoe_username, status}'
CUR=$(echo "$PAGE" | jq -r '.next_cursor // empty')
[ -n "$CUR" ] || break
done
O cursor é opaco: não o interprete nem o monte. Ele só vale com os mesmos filtros da chamada que o devolveu.
Tarefas e o modelo assíncrono
Por que uma ação responde 202, o que acontece até a CPE executar e como acompanhar.
Por que é assíncrono
O TR-069 é iniciado pela CPE: o ACS só fala com ela quando ela abre uma sessão (o Inform). Quando você pede uma ação, o Lumina:
- cria uma tarefa;
- dispara um Connection Request, por HTTP na porta da CPE ou, com o servidor STUN do Lumina ligado, por UDP para a CPE atrás de NAT;
- responde na hora, sem esperar a CPE.
HTTP/1.1 202 Accepted
{ "id": "b32fqgm2ejgjkahhetfa", "status": "pending", "connection_request": true }
Se a CPE estiver inalcançável (CGNAT, porta bloqueada, desligada), a tarefa fica pendente e executa no próximo Inform periódico. Isso não é erro.
Estados de uma tarefa
| status | Significa |
pending | Na fila, esperando a CPE abrir sessão. |
running | Em execução numa sessão. |
transferring | Download aceito pela CPE; aguarda o TransferComplete. |
rebooting | Reboot aceito; aguarda o 1 BOOT. |
diagnosing | Diagnóstico pedido; aguarda o 8 DIAGNOSTICS COMPLETE. |
done | Concluída. result traz o que a CPE devolveu. |
failed | Falhou; result.error ou result.fault dizem por quê. |
expired | Cancelada, ou venceu sem executar. |
Como acompanhar
| Meio | Quando usar |
Webhooks task.completed / task.failed | Integração servidor a servidor. É o recomendado: tem fila e retentativa. Ver Webhooks. |
GET /api/v1/tasks/{id} (tasks:read) | Um app que precisa mostrar o andamento na tela. Consulte a cada 3 a 5 s por até um minuto; se seguir pending, a CPE executa no próximo Inform. |
GET /api/v1/devices/{key}/tasks (devices:read) | Chave sem tasks:read: as tarefas da CPE, mais novas primeiro. |
SSE task.update | Tela de atendimento aberta. Ver Tempo real. |
# GET $ACS/api/v1/tasks/b32fqgm2ejgjkahhetfa
{ "id": "b32fqgm2ejgjkahhetfa", "kind": "set_values", "status": "done", "attempts": 1,
"payload": { "values": [ { "name":"Device.WiFi.AccessPoint.3.Security.KeyPassphrase", "value":"••••••••", … } ] },
"result": { "status": "done", "set_status": 0 },
"created_by": "apikey:erp", "created_at": "2026-09-17T10:31:07-03:00",
"started_at": "2026-09-17T10:31:09-03:00", "finished_at": "2026-09-17T10:31:09-03:00",
"device_key": "40ED00-XX230v-2249B1000123", "serial_number": "2249B1000123" }
Toda tarefa serializada, em qualquer listagem, traz serial_number ao lado de device_key: é o número que o suporte e a etiqueta da ONU usam. set_status: 1 quer dizer que a CPE aplica depois da sessão; o Lumina relê os valores em alguns segundos.
Connection Request sem tarefa
POST …/actions/connection-request só acorda a CPE e responde na hora, sem criar tarefa. Ver Ações.
Listar e buscar CPEs
A lista com filtros, o item da lista, o detalhe de uma CPE, a exportação CSV e os menus de filtro.
GET/api/v1/devicesescopo devices:read
| Parâmetro | Valores |
q | Busca por trecho em serial, login PPPoE, IP WAN, MAC base e MAC WAN, SSIDs das duas bandas, chave, ProductClass e modelo. |
pppoe | Login PPPoE exato, sem diferenciar maiúsculas. É o filtro certo para achar a CPE de um contrato. |
status | Separados por vírgula: online, unstable, offline, unlicensed. |
default_login | 1: só CPEs num login PPPoE padrão do provedor (aguardando instalação, ver assinante). |
quarantined | 1 ou true: só CPEs em quarentena de bootstrap (ver reset e quarentena). |
optical_low | 1: só CPEs com RX óptico abaixo de −27 dBm (o mesmo critério do cartão “Sinal óptico baixo” do painel). |
new_24h | 1: só CPEs vistas pela primeira vez nas últimas 24 h. |
manufacturer | Fabricante contendo o texto (sem diferenciar maiúsculas). |
model · product_class · software · hardware · tag | Filtros exatos. model é o ModelName lido da CPE (ou o ProductClass quando ele vem vazio); software é a versão de firmware; hardware é a versão de hardware. |
sort | last_inform_at (padrão), first_seen_at, serial_number, product_class, status, software. |
order | asc; qualquer outro valor é decrescente (padrão). |
limit · cursor | Página (padrão 50, máximo 500; valor fora disso volta a 50) e o cursor opaco devolvido em next_cursor. |
curl -sG -H "$KEY" "$ACS/api/v1/devices" --data-urlencode "pppoe=cliente@isp"
{ "items": [ {
"key": "40ED00-XX230v-2249B1000123",
"manufacturer": "TP-Link", "product_class": "XX230v", "model_name": "XX230v",
"serial_number": "2249B1000123", "software_version": "0.9.0 0.9.1 v6034.0 Build 240522 Rel.61823n",
"status": "online", "pppoe_username": "cliente@isp", "wan_ip": "100.64.10.20",
"wan_mac": "40:ED:00:11:22:33", "lan_mac": "40:ED:00:11:22:34",
"wifi_ssid_24": "Casa", "optical_rx_dbm": -19.4,
"uptime_seconds": 412233, "hosts_count": 7,
"last_inform_at": "2026-09-17T10:31:07-03:00", "first_seen_at": "2026-08-11T09:02:44-03:00",
"tags": ["pop-centro"],
"hosts_active": 5, "hosts_read_at": "2026-09-17T10:29:41-03:00", "default_login": false } ],
"next_cursor": "eyJrIjoi…", "total": 8342 }
lan_mac é o MAC base da CPE (o MAC que costuma ir para o cadastro do ERP); wan_mac é o da interface voltada para a operadora.
- Hosts conectados são
hosts_active: os ativos da última leitura da tabela de hosts, com a hora dessa leitura em hosts_read_at. Os dois vêm null enquanto a tabela nunca foi lida, e ela só é lida sob demanda (ver Hosts e leituras sob demanda).
hosts_count é o HostNumberOfEntries que a CPE informa a cada sessão. Ele conta toda entrada da tabela, inclusive a concessão DHCP de quem já foi embora, e por isso não serve como “conectados”.
Detalhe de uma CPE
GET/api/v1/devices/{key}escopo devices:read
Acrescenta ao item da lista:
| Grupo | Campos |
| Identificação | hardware_version, data_model (Device ou InternetGatewayDevice), cwmp_version, profile_id, profile_overlays, profile (id, nome, homologação, sobreposições e avisos do modelo), homolog_seal |
| Gerência | remote_ip, connection_request_url, inform_count, last_boot_at, last_refresh_at, periodic_interval, discovered, parameter_key, inform_creds, bootstrap_unverified_at |
| Óptico e sistema | optical_tx_dbm, optical_temp_c, cpu_pct, mem_total_kb, mem_free_kb |
| WAN | wan_link_mbps, wan_duplex, wan_link_status, wan_ipv6_address, wan_ipv6_gateway, wan_ipv6_dns, wan_ipv6_pd_prefix, wan_ipv6_pd_length |
| Wi-Fi | wifi_ssid_5, wifi_clients_24, wifi_clients_5 |
| Estabilidade (24 h) | reboots_24h, drops_24h, inform_gaps_24h |
| Assinante | config_saved, pppoe_password_known, wifi_password_known_24, wifi_password_known_5 |
| Duplicidade | same_unit, quando a mesma unidade (mesmo MAC LAN) aparece sob outro serial |
homolog_seal é {status, approved_at?}, omitido quando o par fabricante/modelo/hardware/firmware não tem selo no catálogo publicado pela Lumina; status é homologated ou community. A homologação de firmware é feita pela Lumina, fora deste ACS: não há rota para disparar ou consultar a bateria. 404 CPE não encontrada para chave desconhecida.
Exportação CSV
GET/api/v1/devices/export.csvescopo devices:read
Os mesmos filtros de GET /devices, todas as páginas, em CSV com separador ; e BOM (abre direto no Excel). Arquivo cpes-AAAA-MM-DD.csv com as colunas:
chave;serial;fabricante;modelo;firmware;status;pppoe;ip_wan;mac;mac_wan;ssid_2g;rx_optico_dbm;uptime_s;hosts_na_tabela_da_cpe;ultimo_inform;primeiro_contato;tags;hosts_ativos;hosts_lidos_em
Confira a última linha. O cabeçalho HTTP 200 sai antes das linhas, então uma exportação cortada não vira erro HTTP. Quando a leitura é interrompida ou o teto de 200 000 linhas é atingido, a última linha é # EXPORTAÇÃO INCOMPLETA: <motivo> — N linhas exportadas. Um importador deve procurar esse prefixo e tratar o arquivo como parcial. Prazo próprio: 10 minutos.
curl -s -H "$KEY" -o cpes.csv "$ACS/api/v1/devices/export.csv?status=online,unstable"
tail -n 1 cpes.csv | grep -q '^# EXPORTAÇÃO INCOMPLETA' && echo "arquivo parcial"
Fabricantes, modelos e firmwares do parque
GET/api/v1/devices/facetsescopo devices:read
Os fabricantes, modelos e firmwares das CPEs do tenant, cada um com o número de CPEs e sem corte: {"manufacturers":[…],"models":[…],"firmwares":[…]}. Serve para montar os menus de filtro de uma tela do ERP com valores que existem de fato.
Perfis de modelo
GET/api/v1/profilesescopo devices:read
Os perfis de modelo carregados e as chaves virtuais que cada um suporta. O perfil é o que traduz “SSID do 5 GHz” nos caminhos TR-069 de cada fabricante; é ele que faz as ações funcionarem sem o ERP conhecer caminhos.
Visões da CPE
Wi-Fi, LAN, NAT, mesh e relógio em formato pronto: cada campo diz o valor, se é gravável, as opções aceitas e o caminho TR-069 de origem. O ERP não precisa conhecer os caminhos do fabricante.
Wi-Fi
GET/api/v1/devices/{key}/wifiescopo devices:read
{ "bands": [ {
"band": "5g", "ssid": "Casa 5G",
"fields": {
"enabled": { "value":true, "writable":true, "path":"Device.WiFi.SSID.3.Enable" },
"channel": { "value":36, "writable":true, "options":["36","40","44","48"], "path":"Device.WiFi.Radio.2.Channel" },
"bandwidth": { "value":"80MHz", "writable":true, "options":[…], "labels":{…}, "path":"…" },
"clients": { "value":4, "writable":false, "path":"…" } },
"missing": ["twt","bss_color"],
"guest": { "ssid":"Casa_Convidados_5G", "enabled":true, "index":4 } } ],
"shared": { "wps": { "value":false, "writable":true, "path":"…" } },
"mixed": [], "optimization": { "auto": {…}, "threshold": {…} },
"scan_supported": true, "updated_at": "…", "profile": "tplink-tr181" }
- Campos por banda:
enabled (rede e rádio da banda juntos), standards, channel, auto_channel, bandwidth, bandwidth_current (só leitura), tx_power, hidden, security_mode, encryption, ofdma, twt, bss_color, mu_mimo, beamforming, wmm, wps, isolation, clients (só leitura).
- O que o fabricante ajusta de uma vez para as duas bandas sai em
shared; mixed lista os que estão divergentes na CPE.
- O que estiver em
missing não existe naquele modelo: esconda na sua tela.
guest é a rede de convidados da banda, quando o fabricante a marca e o nome já foi lido: ssid, enabled (null se ainda não lido) e index (a instância de Device.WiFi.SSID.{i}). Só leitura; ausente quando não há rede de convidados conhecida (e nunca numa TR-098).
optimization só aparece quando a CPE expõe a otimização de canais.
404 CPE sem perfil de modelo compatível quando não há perfil para o modelo.
A CPE não devolve senha por TR-069. KeyPassphrase e a senha do PPPoE voltam vazias. O Lumina guarda as senhas que ele mesmo gravou (e o Wi-Fi cadastrado no ERP) e as entrega por
GET …/secrets, com auditoria. Senha que nunca passou pelo Lumina não existe em lugar nenhum do ACS. Ver
senhas guardadas.
Análise de canais
GET/api/v1/devices/{key}/wifi/analysisescopo devices:read
Lê a última varredura concluída (diagnóstico wifi_scan) e devolve, por banda, current, suggested, congested, networks (vizinhas que interferem no canal atual), score_current, score_suggested e candidates, além de task_id, scan_at e networks_total. Sem varredura, bands vem vazio: dispare actions/diagnostic com {"kind":"wifi_scan"} antes.
LAN e DHCP
GET/api/v1/devices/{key}/lanescopo devices:read
Mesmo formato do Wi-Fi (fields + missing) com ip, mask, dhcp_enabled, dhcp_min, dhcp_max, dhcp_mask, dhcp_gateway, dhcp_dns, dhcp_lease, os campos IPv6 (ipv6_enabled, ipv6_address, ipv6_prefix, ipv6_prefix_origin, ipv6_addr_type, ipv6_prefix_len, ipv6_prefix_type, ipv6_parent_prefix, dhcp6_enabled, dhcp6_iana, dhcp6_dns, ra_enabled, ra_managed, ra_other, ra_rdnss, ra_rdnss_servers) e os derivados mac, ipv6_addr_mode (radvd/dhcpv6) e ipv6_wan. Mais a tabela de reservas:
{ "fields": { "ip":{"value":"192.168.0.1","writable":true,"path":"…"}, "dhcp_lease":{"value":86400,…} },
"missing": [],
"static_leases": [ { "index":1, "path":"…", "mac":"AA:BB:CC:44:55:66", "ip":"192.168.0.31",
"enabled":true, "name":"TV-Sala" } ],
"static_supported": true, "static_max": 16, "updated_at": "…", "profile": "tplink-tr181" }
Valor delegado é só leitura. O endereço e o prefixo IPv6 da LAN vêm delegados pela WAN: gravá-los quebraria o IPv6 do cliente.
NAT
GET/api/v1/devices/{key}/natescopo devices:read
supported, virtual_servers[] (index, enabled, status, name, interface, external_port, external_end, internal_ip, internal_port, internal_end, protocol), vs_writable, dmz e upnp (com as entradas ativas; null quando a CPE não expõe esses objetos da TP-Link), interfaces e updated_at.
Mesh (EasyMesh)
GET/api/v1/devices/{key}/meshescopo devices:read
supported, role (controller, agent ou none), node_count, nodes[] (MAC, modelo, backhaul, rádios, APs, clientes e, quando o nó também é uma CPE registrada, peer), para um agente controller, e o estado da leitura: read_at, pending, read_failed e updated_at (espelha read_at).
- A tabela EasyMesh é lida sob demanda (
actions/refresh-mesh) e, na releitura periódica, só quando o contador de nós que a CPE reporta deixa de bater com a tabela guardada.
role e node_count vêm só dos nós lidos: uma CPE é controller quando a tabela dela tem mais de um nó. Um controlador nunca lido aparece com supported: true, role: "none" e read_at: null.
- Numa CPE agente, Wi-Fi, LAN e NAT são do controlador: as escritas respondem
409 com mesh_agent: true.
Relógio e NTP
GET/api/v1/devices/{key}/timeescopo devices:read
supported, synced, status, current_time, enabled, servers[], timezone, timezone_name, writable, updated_at, tenant (o NTP do provedor) e tenant_applied (a CPE já usa exatamente os servidores do provedor). O NTP do provedor é lido em GET /api/v1/settings/ntp (devices:read).
Próximo degrau de firmware
GET/api/v1/devices/{key}/firmware-nextescopo devices:read
{"next": {"id","source","version","trust"}} ou {"next": null}. É sempre o próximo degrau de um caminho confirmado a partir da versão atual da CPE, nunca um salto nem um downgrade. source é local ou cloud; trust é lumina, community ou confirmed (sucesso confirmado por gente nesta instalação).
Hosts conectados e leituras sob demanda
Hosts, clientes Wi-Fi e a tabela EasyMesh não são relidos na sessão periódica: são lidos quando alguém pede. Como pedir, esperar e ler.
A lista só muda por leitura sob demanda. GET …/hosts devolve a última lista que alguém pediu à CPE (o console ao abrir a aba Hosts, o ERP por refresh-hosts, uma tarefa get_values na raiz da lista), não o estado de agora. Quem precisa de lista fresca dispara refresh-hosts, espera e só então lê.
Lista fresca em três passos
ID=$(curl -s -X POST -H "$KEY" "$ACS/api/v1/devices/$CPE/actions/refresh-hosts" | jq -r .id)
# espere o task.completed desse id (webhook) ou sonde GET …/list-reads até lists.hosts.pending == false
curl -s -H "$KEY" "$ACS/api/v1/devices/$CPE/hosts" | jq '{read_at, read_failed, n: (.items | length)}'
refresh-hosts e refresh-mesh
POST/api/v1/devices/{key}/actions/refresh-hostsescopo devices:safe-ops
POST/api/v1/devices/{key}/actions/refresh-meshescopo devices:safe-ops
refresh-hosts lê hosts, portas, clientes Wi-Fi (e a tabela EasyMesh, onde há); refresh-mesh lê só a tabela EasyMesh. As duas respondem sempre 202 com o id de uma tarefa, para o ERP acompanhar do mesmo jeito em todos os casos. O campo read diz qual foi:
# leitura enfileirada agora (tarefa nova, Connection Request disparado)
{ "id": "c41kq9d0…", "status": "pending", "read": "queued", "connection_request": true, "lists": ["hosts","wifi_clients"] }
# já havia uma leitura em andamento (outro operador, o console): é a tarefa dela
{ "id": "c41kq9d0…", "status": "pending", "read": "pending" }
# tudo lido há menos de 60 s: nada a pedir; a tarefa devolvida é a dessa leitura, já concluída
{ "id": "c41kq9d0…", "status": "done", "read": "fresh", "read_at": "2026-09-18T10:30:55-03:00" }
# numa CPE agente de mesh, mais a leitura pedida ao controlador, à parte
{ "id": "…", "status": "pending", "read": "queued", "connection_request": true, "lists": ["hosts","wifi_clients"],
"controller": { "key": "40ED00-XX530v-2249B1000789", "id": "…", "status": "pending", "read": "queued" } }
- Uma leitura por vez
- Lista lida há menos de 60 s não é pedida de novo (
fresh), e lista com tarefa em aberto não ganha outra (pending). Duas telas, dois operadores ou o console e o ERP abrindo a mesma CPE geram uma leitura só. Em queued, lists diz quais listas entraram na tarefa.
- A tarefa vence em 20 minutos
- Uma CPE sem Connection Request alcançável recebe a leitura no próximo Inform; se ele não vier até lá, a tarefa passa a
expired e as visões deixam de mostrar pending.
- Caminho morto não trava a lista
- Se a CPE recusa o pedido conjunto (9005 num caminho que a build removeu), os caminhos são lidos um a um, a tarefa conclui
done com result.note listando os recusados e a lista recusada fica com read_failed: true. Só quando nenhum caminho responde a tarefa é failed.
- 404
esta CPE não tem lista a ler por TR-069; refresh-mesh numa TR-098 responde esta CPE não expõe EasyMesh por TR-069 (o ramo não existe nesse modelo de dados).
Qualquer get_values concluído cujo pedido inclua a raiz de uma lista (Device.Hosts.Host., Device.WiFi.AccessPoint.{i}.AssociatedDevice., Device.WiFi.MultiAP. e os equivalentes TR-098) também grava a leitura, venha da ação, de uma tarefa crua ou da NBI (refreshObject). A leitura de um item só (Device.Hosts.Host.3.) não conta.
Ler os hosts
GET/api/v1/devices/{key}/hostsescopo devices:read
Junta a tabela de hosts com os clientes associados do Wi-Fi (sinal, taxa negociada) e as portas Ethernet (velocidade, duplex). É a visão que o atendimento usa para explicar lentidão.
{ "items": [
{ "name":"iPhone", "ip":"192.168.0.14", "mac":"AA:BB:CC:11:22:33", "active":true,
"type":"wifi", "band":"5", "signal_dbm":-52, "tx_rate_mbps":780, "client_type":"Phone" },
{ "name":"TV-Sala", "ip":"192.168.0.31", "mac":"AA:BB:CC:44:55:66", "active":true,
"type":"ethernet", "band":"", "port":"LAN2", "speed_mbps":100, "duplex":"Full" } ],
"ports": [ { "name":"LAN1", "status":"Up", "speed_mbps":1000, "duplex":"Full", "mac":"…", "hosts":1, "wan":false } ],
"conn_known": true,
"read_at": "2026-09-18T10:30:55-03:00", "pending": false, "read_failed": false,
"updated_at": "2026-09-18T10:30:55-03:00" }
type é wifi, ethernet ou unknown; band é 2.4, 5 ou vazio. Opcionais: lease_s, address_source, client_type, ipv6 e, no controlador EasyMesh, mesh_node/mesh_node_key.
tx_rate_mbps é a taxa de link negociada, a mesma que a página web da própria CPE mostra.
conn_known: false: a CPE não publica a tabela de clientes Wi-Fi e não dá para separar cabo de Wi-Fi.
read_at é a hora real em que a CPE respondeu à leitura das listas (a mais antiga entre hosts, clientes Wi-Fi e, onde há, MultiAP); null quando nunca foram lidas. pending: há leitura em aberto. read_failed: a última tentativa não leu alguma das listas; a lista anterior continua sendo devolvida. updated_at espelha read_at.
- Numa CPE agente de mesh, os clientes deste nó vêm da tabela do controlador, e a leitura dela aparece separada em
controller: {"key","read_at","pending","read_failed"}.
Sondar sem carregar a lista
GET/api/v1/devices/{key}/list-readsescopo devices:read
O estado das três listas, sem carregar parâmetro nenhum: é a rota para sondar enquanto se espera uma leitura, no lugar de /hosts e /mesh, que montam a visão inteira a cada chamada.
{ "lists": {
"hosts": { "read_at": "2026-09-18T10:30:55-03:00", "pending": false, "read_failed": false },
"wifi_clients": { "read_at": "2026-09-18T10:30:55-03:00", "pending": false, "read_failed": false },
"mesh": { "read_at": null, "pending": true, "read_failed": false } },
"controller": { "key": "40ED00-XX530v-2249B1000789", "read_at": "…", "pending": false, "read_failed": false } }
As três chaves de lists vêm sempre. controller só aparece numa CPE agente de mesh. Uma leitura pedida há mais de 20 minutos que a CPE não atendeu deixa de contar como pendente. Sonde a cada poucos segundos, não em laço fechado. A conclusão chega também por SSE (device.lists) e pelos webhooks task.completed/task.failed.
O que continua periódico
hosts_count, wifi_clients_24 e wifi_clients_5 de /devices seguem sendo relidos a cada sessão. Já hosts_active e hosts_read_at são os ativos da última lista lida e a hora dela.
Peça a lista quando for mostrá-la, não em varredura do parque: cada refresh-hosts é uma sessão TR-069 na CPE.
Histórico, eventos e parâmetros
A série histórica da CPE, a linha do tempo de eventos e o modelo de dados cru.
Histórico
GET/api/v1/devices/{key}/metrics?range=24hescopo devices:read
6h e 24h (padrão) em amostras brutas; 7d e 30d em agregados por hora; 12m por dia.
{ "range": "24h", "resolution": "raw", "from": "2026-09-16T13:31:07Z",
"points": [ { "ts":"…", "n":1, "rx":-19.4, "tx":2.1, "temp":48, "uptime":412233,
"wifi24":3, "wifi5":4, "cpu":12, "mem_used_pct":61.5 } ] }
Nos agregados aparecem também rx_min, rx_max e cpu_max. wired traz os hosts ativos por cabo, lidos da tabela de hosts quando alguém a abre, e só existe para CPE que informa o tipo de conexão de cada host. Campo ausente num ponto = grandeza não medida. Retenção padrão: 30 dias de amostra bruta e 12 meses de agregado. A série do tenant inteiro está em GET /api/v1/dashboard/history?range=24h|7d|30d.
Eventos da CPE
GET/api/v1/devices/{key}/eventsescopo devices:read
Mais recentes primeiro, com ?limit= (padrão 100, máximo 500) e ?cursor=.
{ "items": [ { "id": 918273, "at": "2026-09-17T10:31:07-03:00", "kind": "erp_config_rejected",
"detail": { "erp":"…", "contrato":"1856",
"campos":["wifi_password_24: deve ter entre 8 e 63 caracteres imprimíveis"] } } ],
"total": 1204, "next_cursor": "eyJhdCI6…" }
| kind | Quando | detail |
inform | Cada Inform | events, ip, user; retry só quando maior que zero |
session | Fim de sessão CWMP | rpcs, faults, duration_ms, events; errors só quando houve |
status | Mudança online/instável/offline | de/para |
inform_gap | Inform perdido | |
factory_reset | CPE conhecida voltou de fábrica | signal e afins (ver reset) |
config_applied | Configuração do assinante gravada | reapply quando foi reaplicação |
erp_config_rejected | Campos do contrato recusados pela checagem | erp, contrato, campos (nunca o valor) |
preset | Preset aplicado | |
bootstrap_unverified | CPE entrou em quarentena de bootstrap | |
bootstrap_apply_deferred | A CPE respondeu Status 1 ao bootstrap: aplica no próximo reinício | connreq, inform_credential |
inform_rejected | Inform recusado | reason (tenant_credential ou other_device_credential, com credential_device), ip; mesh quando a exceção do mesh não valeu |
connreq_credencial_recusada | A CPE respondeu 401 à credencial de Connection Request gravada pelo Lumina (no máximo um a cada 5 min) | method, status, udp_enviado, regravar, message |
mesh_cred_accepted | Agente EasyMesh atendido com a credencial do controlador (na primeira vez e a cada troca de controlador) | controller, mac, ip |
auth_tolerated | A CPE falou fora do padrão e foi atendida mesmo assim (uma vez por CPE) | via (digest ou stun), reason, e modelo/firmware/IP |
auth_tolerated não é recusa nem falha de segurança: a senha foi exigida e conferida do mesmo jeito. Serve para o provedor saber quais modelos e versões precisam de correção do fabricante. Não há webhook nem alerta para ele.
Parâmetros crus
GET/api/v1/devices/{key}/params?prefix=escopo devices:read
Escape para o modelo de dados cru quando a visão pronta não basta. Devolve {"items":[{"path","value","type","writable","updated_at"}]}, até 20 000 parâmetros. Use sempre prefix: a árvore de uma ONU passa de 5 mil parâmetros.
- Um caminho que a CPE passou a recusar sai da releitura automática por 24 h e vem com
read_fault_count e read_skip_until. A linha continua no inventário e a escrita continua usando o caminho.
- Três ramos do TR-181 não entram no inventário, em nenhum modelo:
Device.WiFi.DataElements., Device.WiFi.AccessPoint.{i}.AC. e Device.WiFi.SSID.{i}.Stats.. Uma tarefa get_values neles funciona e o valor sai no result da tarefa, só não é guardado.
Ações de alto nível
Ações falam o vocabulário do provedor e resolvem sozinhas os caminhos TR-069 de cada fabricante. É o jeito certo de gravar Wi-Fi, PPPoE, LAN e NAT.
POST/api/v1/devices/{key}/actions/{action}escopo varia por ação
Salvo indicação, a ação responde 202 com a tarefa criada e dispara o Connection Request (ver modelo assíncrono).
# trocar o SSID e a senha do 5 GHz
curl -s -X POST -H "$KEY" -H "Content-Type: application/json" \
-d '{"band":"5g","ssid":"Casa 5G","password":"senha-forte-8"}' \
"$ACS/api/v1/devices/$CPE/actions/wifi"
# 202 {"id":"b32fqgm2ejgjkahhetfa","status":"pending","connection_request":true}
Todas as ações
| action | Corpo e resposta | Escopo |
| Gerência |
reboot | Sem corpo. | safe-ops |
refresh | Sem corpo. Releitura curta, a mesma da sessão periódica: resumo, WAN escolhida (com sondas nas outras), contadores. Não relê hosts, clientes Wi-Fi e mesh. | safe-ops |
connection-request | Sem corpo. Só acorda a CPE e responde na hora 200 {"ok":true} ou 200 {"ok":false,"error":"…"}; 503 quando o processo que atende não faz Connection Request. Se a CPE recusou a credencial gravada pelo Lumina (HTTP 401), a resposta traz "http":"credencial recusada" e "udp" (se a reserva por UDP saiu); a credencial é regravada na próxima sessão. | safe-ops |
confirm-bootstrap | Sem corpo; 200 {"ok":true}. Tira a CPE da quarentena de bootstrap (ver quarentena). | write |
reset-inform-credentials | Sem corpo; 200 {"ok":true}. Com credencial de Inform por CPE ligada, volta a aceitar a credencial do tenant no próximo Inform. | write |
factory-reset | Sempre recusada (403): reset de fábrica não existe pelo ACS. | — |
| Wi-Fi |
wifi | {"band":"2g"|"5g","ssid","password","enabled","channel"} mais campos avançados de GET …/wifi (standards, bandwidth, tx_power, hidden, security_mode, encryption, ofdma, twt, bss_color, mu_mimo, beamforming, wmm, wps, isolation). channel: 0 = automático. "band":"all" só para campos avançados. SSID e senha gravados entram na configuração do assinante. | safe-ops |
wifi-opt | {"auto": true|false, "threshold": 0–1200}: otimização de canais do fabricante, quando a CPE a expõe. | safe-ops |
refresh-wifi | Sem corpo. Releitura só do Wi-Fi. | safe-ops |
| WAN e LAN |
wan | {"pppoe_username","pppoe_password"}, até 128 caracteres imprimíveis. Grava no PPP da operadora (resolvido pela topologia da CPE) e entra na configuração do assinante. | write |
lan | Qualquer campo gravável de GET …/lan, por exemplo {"ip","mask","dhcp_enabled","dhcp_min","dhcp_max","dhcp_dns","dhcp_lease"}; também ipv6_addr_mode (radvd|dhcpv6) e ipv6_wan. | write |
lan-static | {"add":{"mac","ip","name"}}, {"update":{"index","enabled","mac","ip"}} ou {"delete":<index>}. | write |
refresh-lan | Sem corpo. Releitura da LAN. | safe-ops |
| NAT |
nat | {"dmz_enabled","dmz_ip","upnp_enabled"}. Só CPEs TR-181, e só o que a CPE expõe (DMZ e UPnP são objetos da TP-Link): pedir um item que a CPE não tem dá 400 sem enviar nada. {"alg":…} é recusado com 400: o ALG saiu da API. | write |
nat-vs | Servidores virtuais: {"add":{"name","interface","external_port","external_end","internal_ip","internal_port","internal_end","protocol","enabled"}}, {"update":{"index",…}} ou {"delete":<index>}. protocol é TCP ou UDP. Nas TR-181 que não são TP-Link, a faixa interna acompanha a externa (faixa interna própria dá 400). | write |
refresh-nat | Sem corpo. Relê o NAT e os nomes das conexões WAN. 404 quando a CPE não expõe NAT por TR-069. | safe-ops |
| Relógio, listas, diagnóstico e firmware |
time | {"servers":[…],"timezone","timezone_name","enabled"} ou {"tenant":true} para aplicar o NTP do provedor. | write |
refresh-time | Sem corpo. | safe-ops |
refresh-hosts · refresh-mesh | Leitura sob demanda das listas. Ver Hosts e leituras sob demanda. | safe-ops |
diagnostic | {"kind":"ping"|"traceroute"|"download"|"upload"|"wifi_scan","host","url","size"}. Ver Diagnósticos. | safe-ops |
firmware | {"firmware_id"|"cloud_id","confirm"}. Ver Firmware. | firmware:write |
Na coluna Escopo, safe-ops é devices:safe-ops e write é devices:write. Ação desconhecida: 404 ação desconhecida.
Validações e garantias
- SSID de 1 a 32 caracteres imprimíveis; senha Wi-Fi de 8 a 63. Alguns modelos têm réguas próprias declaradas no perfil (SSID sem acento, senha com dígito ou símbolo); a mensagem do
400 diz qual.
- Antes de escrever, o Lumina descarta caminhos que aquela CPE não expõe (o SetParameterValues é atômico: um caminho inexistente derrubaria a escrita inteira).
- Depois de escrever, relê os valores para que a próxima leitura já traga o novo.
- Numa CPE agente EasyMesh,
wifi, wifi-opt, lan, lan-static, nat e nat-vs respondem 409 com mesh_agent: true: a configuração é do controlador.
Diagnósticos
Ping, traceroute, teste de velocidade e varredura de Wi-Fi executados pela própria CPE.
POST/api/v1/devices/{key}/actions/diagnosticescopo devices:safe-ops
Diagnóstico é assíncrono em dois tempos: a tarefa passa por diagnosing, a CPE executa e o resultado chega na sessão seguinte.
| kind | Campos |
ping | host obrigatório; size = repetições. |
traceroute | host obrigatório. |
download · upload | url opcional. Sem ela, usa o servidor de teste embutido no ACS e size (em MB, até 1024) escolhe o tamanho. GET /api/v1/diagnostics/defaults lista as URLs e os tamanhos. |
wifi_scan | Varre as redes vizinhas. Alimenta a análise de canais. |
curl -s -X POST -H "$KEY" -H "Content-Type: application/json" \
-d '{"kind":"ping","host":"8.8.8.8","size":4}' "$ACS/api/v1/devices/$CPE/actions/diagnostic"
# depois, em GET /api/v1/tasks/{id}: result.summary
{ "state":"Complete", "success":4, "failure":0, "min_ms":11, "avg_ms":13, "max_ms":18 }
Resultado por tipo
| kind | Chaves de result.summary |
ping | success, failure, min_ms, avg_ms, max_ms (tempos absurdos que algumas CPEs devolvem ficam de fora) |
traceroute | response_ms, hops, route[] |
download · upload | bytes, seconds, mbps |
wifi_scan | networks[] |
Todos trazem state. Um diagnóstico que o modelo de dados da CPE não tem dá 400 na hora. Ausência do objeto na enumeração da CPE não recusa (há firmware que esconde ramos): o pedido segue e, se a CPE não tem mesmo o objeto, responde 9005 e a tarefa fica failed.
Tarefas: criar, consultar, cancelar, repetir
A tarefa crua (RPC CWMP direto), a lista de tarefas do tenant com filtros e contadores, e as operações de cancelar, repetir e encerrar.
Tarefa crua
POST/api/v1/devices/{key}/tasksescopo conforme o kind
Para quando você sabe exatamente o que quer ler ou escrever. Prefira as ações sempre que houver uma.
curl -s -X POST -H "$KEY" -H "Content-Type: application/json" -d '{
"kind": "set_values",
"values": [ { "name": "Device.DHCPv4.Server.Pool.1.DNSServers", "value": "1.1.1.1,8.8.8.8", "type": "xsd:string" } ],
"connection_request": true,
"read_back": ["Device.DHCPv4.Server.Pool.1."]
}' "$ACS/api/v1/devices/$CPE/tasks"
| kind | Campos | Escopo |
get_values | names[] (um caminho terminado em . lê a subárvore) | devices:safe-ops |
get_names | names[], next_level | devices:safe-ops |
refresh · reboot | — | devices:safe-ops |
diagnostic | diag: {"kind","host","url","size"}; aqui url é obrigatória em download/upload | devices:safe-ops |
set_values | values[] com name, value, type | devices:write |
add_object · delete_object | object_name terminado em . | devices:write |
download | download: {"URL","FileType","FileSize","TargetFileName"} (com essa grafia); URL e FileType obrigatórios | devices:write |
factory_reset é recusado com 400; outros kinds dão 400 kind inválido.
download.URL só é aceita quando é uma URL assinada deste ACS (firmware cadastrado, /fs/…) ou de um host liberado na instalação (LUMINA_DOWNLOAD_HOSTS). Para atualizar firmware, prefira actions/firmware, que aplica as regras de segurança por modelo e hardware.
connection_request vale true quando omitido; false deixa a tarefa esperando o próximo Inform, o modo educado para escrita fora de horário de pico.
read_back relê subárvores na mesma sessão, depois da escrita.
- Tudo que grava ou reinicia conta no teto de 120 tarefas por minuto do principal:
429 com Retry-After. Para muitas CPEs, use um lote.
- Um preset com
set em Device.WiFi.DataElements., AccessPoint.{i}.AC. ou SSID.{i}.Stats. é recusado (400), porque esses ramos não são guardados no inventário. Para gravar ali, use uma tarefa set_values.
Listar as tarefas do tenant
GET/api/v1/tasksescopo tasks:read
| Parâmetro | Valores |
status | Separados por vírgula: pending, running, transferring, rebooting, diagnosing, done, failed, expired. |
kind | Tipo da tarefa (set_values, apply_config, download…). |
q | Trecho da chave, do serial ou do login PPPoE da CPE (até 200 caracteres). |
created_by | Trecho do autor: e-mail do usuário, apikey:<nome>, preset:… (até 200 caracteres). |
from · to | Período de criação em RFC 3339 (from inclusivo, to exclusivo). Formato errado: 400. |
batch | Só as tarefas de um lote. |
limit · cursor | Página (padrão 50, até 500; valor fora disso volta a 50) e cursor de next_cursor. |
A resposta é {"items":[…],"next_cursor","total"}, com total contando as tarefas que casam com o filtro. GET /api/v1/tasks/{id} devolve uma tarefa.
GET/api/v1/tasks/countsescopo tasks:read
Os mesmos filtros (menos status, limit e cursor), devolvendo quantas tarefas há em cada status: {"pending":3,"running":0,…,"failed":12,"expired":4}.
Cancelar e repetir
POST/api/v1/tasks/{id}/canceltasks:read + escopo de criar aquele tipo
Só a tarefa que ainda não terminou (pending, running, rebooting, diagnosing, transferring). Responde 200 {"id","status":"expired"}; a que já terminou responde 409 com o estado. Tarefa de lote é cancelada sozinha, sem mexer no lote. Só muda o registro no ACS: a CPE que já recebeu o pedido pode ainda executá-lo.
POST/api/v1/tasks/{id}/retrytasks:read + escopo de criar aquele tipo
Só a tarefa failed ou expired volta à fila (pending), com resultado zerado; se ela pede Connection Request, ele sai na hora. Responde 200 {"id","status":"pending","connection_request"}. Outro estado: 409. A de escrita passa pelo teto por minuto (429), e a que grava configuração num agente EasyMesh é recusada (409, mesh_agent).
O “escopo de criar aquele tipo” é devices:safe-ops para reboot, refresh, get_values, get_names e diagnostic, e devices:write para os demais. Sem ele: 403 sem permissão para <kind>.
Encerrar um download preso
POST/api/v1/devices/{key}/tasks/{id}/closeescopo devices:write
Encerra o download desta CPE que ficou em transferring porque a CPE não confirmou: a tarefa vira failed com "encerrada por <quem>". Outro tipo ou outro estado: 409; tarefa de outra CPE: 404. Só muda o registro: se a CPE voltar noutra versão, o registro da atualização passa a aplicado.
Configuração do assinante e instalação
O login e a senha PPPoE e o Wi-Fi do cliente, guardados no Lumina para cada CPE. O ACS grava esses valores a pedido e regrava sozinho quando a CPE volta de um reset de fábrica.
Três fontes escrevem no mesmo lugar: o vínculo com o contrato do ERP, o PUT …/config e as ações wan e wifi (SSID e senha). As senhas ficam cifradas no banco.
O que entra pela NBI não vira configuração do assinante. Um setParameterValues da NBI ou uma tarefa set_values solta não é reaplicado depois de um reset. Para provisionar, use PUT …/config ou o vínculo com o ERP.
O fluxo de instalação
- A CPE sai do preset do fabricante com o login PPPoE padrão do provedor e aparece no ACS. Os logins padrão são cadastrados no console (Configurações → Instalação) e lidos por
GET /api/v1/settings/installation (devices:read): {"default_logins":["provedor@provedor"]}.
- O app ou o ERP acha a CPE:
GET /api/v1/devices?default_login=1&status=online, ou ?q= com o MAC ou o serial da etiqueta.
- Grava o cliente: com conector de ERP,
POST …/subscriber (o contrato vira a configuração); sem conector, PUT …/config com os valores do seu ERP.
- Acompanha a tarefa
apply_config pelo task_id da resposta ou pelos webhooks.
Gravar a configuração
PUT/api/v1/devices/{key}/configescopo devices:write
curl -s -X PUT -H "$KEY" -H "Content-Type: application/json" -d '{
"pppoe_username": "cliente@isp", "pppoe_password": "s3nh4",
"wifi_ssid_24": "Casa", "wifi_password_24": "senha2g24",
"wifi_ssid_5": "Casa 5G", "wifi_password_5": "senha5g24",
"apply": true, "link_login": "cliente@isp"
}' "$ACS/api/v1/devices/$CPE/config"
→ 200
{ "saved": true, "default_login": true, "erp": "…", "linked": true, "task_id": "b32fqgm2ejgjkahhetfa",
"config": { "pppoe_username":"cliente@isp", "pppoe_password":"••••••••",
"wifi_ssid_24":"Casa", "wifi_password_24":"••••••••",
"wifi_ssid_5":"Casa 5G", "wifi_password_5":"••••••••",
"source":"manual", "updated_by":"apikey:app-tecnicos", "updated_at":"…",
"applied_at":null, "reapply_count":0, "last_reapply_at":null } }
| Campo | Regra |
pppoe_username · pppoe_password | Até 128 caracteres imprimíveis; senha sem login é recusada. |
wifi_ssid_24 · wifi_ssid_5 | 1 a 32 caracteres imprimíveis. |
wifi_password_24 · wifi_password_5 | 8 a 63 caracteres imprimíveis. |
apply | Padrão true: cria a tarefa apply_config com Connection Request. false só salva. |
link_login | Opcional: grava também o vínculo com o contrato do ERP por esse login. Resposta com linked: true ou, se o vínculo falhou, sgp_error com a categoria do erro (nome histórico do campo, vale para qualquer conector); a gravação acontece de qualquer forma. |
Semântica: campo ausente mantém o que estava salvo; senha igual a •••••••• também mantém; string vazia apaga. Pelo menos um campo precisa ficar preenchido.
Erros: 400 com a mensagem do campo; 409 em CPE agente EasyMesh; 429 quando a configuração foi salva mas o teto de tarefas de escrita estourou (repita com {"apply":true} depois do Retry-After); 500 configuração salva, mas a tarefa de gravação falhou.
Na CPE: a tarefa grava o Wi-Fi primeiro e o PPPoE depois, em dois SetParameterValues, porque a troca do PPPoE derruba a conexão. result.summary traz {"params","reapply"}; depois de done, config.applied_at fica preenchido.
Ler e apagar
GET/api/v1/devices/{key}/configescopo devices:read
{"saved":false,"default_login":…} ou {"saved":true,"config":{…},"default_login":…,"erp":"…"}, com as senhas mascaradas. erp só aparece quando há conector de ERP ativo e traz o identificador dele (o mesmo kind de GET /api/v1/integrations/erp/providers). O mesmo campo aparece nas respostas de PUT …/config e de …/subscriber. Em config.source vem manual (gravada pela API ou pelo console) ou o identificador do conector (veio do contrato).
DELETE/api/v1/devices/{key}/configescopo devices:write
Apaga a configuração salva sem tocar na CPE: 200 {"ok":true} ou 404 sem configuração salva.
Senhas guardadas
GET/api/v1/devices/{key}/secretsescopo devices:safe-ops · auditado
{ "pppoe_password": "s3nh4", "wifi_password_24": "senha2g24", "wifi_password_5": "",
"sources": { "pppoe_password": "lumina", "wifi_password_24": "erp" } }
Devolve em claro as senhas que o Lumina conhece. sources diz de onde veio cada uma: lumina (gravada pelo ACS) ou erp (Wi-Fi cadastrado no contrato quando o Lumina ainda não gravou um: é o cadastro, não necessariamente o que está na CPE). Cada leitura entra na auditoria com os campos devolvidos.
Vincular ao contrato do ERP
POST/api/v1/devices/{key}/subscriberdevices:safe-ops · outro login/serial: devices:write
Com o conector de ERP ativo, uma chamada faz a instalação inteira: o Lumina consulta o contrato, grava o vínculo e guarda como configuração do assinante o login PPPoE, a senha (quando o ERP a expõe) e, com a política “ERP é a fonte”, o SSID e a senha do contrato. Depois decide o que gravar na CPE:
curl -s -X POST -H "$KEY" -H "Content-Type: application/json" \
-d '{"login":"cliente@isp"}' "$ACS/api/v1/devices/$CPE/subscriber"
| Situação | O que acontece |
| CPE fora do login do cliente e senha PPPoE conhecida | Grava PPPoE e Wi-Fi (task_id na resposta). |
| CPE fora do login do cliente e senha desconhecida | needs_pppoe_password: true; só o Wi-Fi que mudou vai para a CPE. Mande a senha com PUT …/config {"pppoe_password":"…"} e o resto é gravado. |
| CPE já no login do cliente | Só o Wi-Fi que mudou no ERP (task_id quando houver). |
| CPE agente EasyMesh | Grava o vínculo e a configuração, sem tarefa. |
{ "linked": true, "saved": true, "default_login": true, "erp": "…",
"task_id": "b32fqgm2ejgjkahhetfa", "needs_pppoe_password": false,
"subscriber": { "erp":"…", "contract_id":"1856", "customer_name":"…", "plan":"100MEGA",
"status":"Ativo", "login":"cliente@isp", "linked_by":"pppoe", … },
"config": { "pppoe_username":"cliente@isp", "pppoe_password":"••••••••", "source":"…", … } }
- Corpo:
{"login":"…"} busca por PPPoE; {"serial":"…"} pelo serial do serviço; {} usa o login e o serial da própria CPE, conforme o “buscar por” do conector.
- Vincular a um login ou serial diferente do que a CPE apresenta grava a credencial de outro contrato nela e por isso exige
devices:write (senão 403).
400 com o conector desativado; 404 com a mensagem do ERP quando não há contrato. Se a tarefa não pôde ser criada, a resposta traz apply_error.
GET/api/v1/devices/{key}/subscriberescopo devices:read
O vínculo e a configuração salva. Formato completo em Conector de ERP. Sem vínculo, linked é false e subscriber não vem.
DELETE/api/v1/devices/{key}/subscriberescopo devices:write
Desfaz o vínculo e apaga a configuração salva: {"ok":true,"config_deleted":true}. A CPE não muda.
Consultar contratos sem gravar
POST/api/v1/devices/{key}/config/lookupescopo devices:safe-ops
Para um app montar o próprio formulário. Corpo {"login":"…"} ou {"document":"CPF ou CNPJ"}. Resposta {"items":[{"contract_id","customer_name","document","plan","status","login","has_password","wifi":{"ssid_24","channel_24","ssid_5","channel_5"},"address"}]}. Não devolve senha: has_password só diz se o ERP a expõe.
Toda consulta que chega ao ERP vai para a auditoria (subscriber.lookup) com o campo, o valor parcialmente mascarado e o resultado. Limite por principal: 20 consultas por minuto e 300 por hora; acima disso, 429 com Retry-After.
Reset de fábrica, reaplicação e quarentena
O que o Lumina faz sozinho quando uma CPE volta de fábrica, e por que às vezes ele segura a CPE até alguém confirmar.
Reaplicação automática
Quando uma CPE conhecida volta de fábrica, o Lumina registra o evento factory_reset e, se há configuração do assinante salva, regrava PPPoE e Wi-Fi, só o que difere do salvo (evento config_applied com reapply: true; reapply_count e last_reapply_at sobem). Nada a fazer no ERP, que pode acompanhar pelos webhooks device.factory_reset e device.config_applied ou por uma regra de alerta factory_reset.
detail.signal diz o que afirmou o reset:
| signal | Significa |
connreq_username · parameter_key | A CPE perdeu a gerência que o ACS gravou. |
bootstrap_confirmed | O operador confirmou um bootstrap em quarentena. |
config_regressed | A gerência ficou intacta (como no preset de provedor da TP-Link), mas a configuração do assinante voltou à de fábrica: login PPPoE igual a um login padrão ou vazio, ou os dois SSIDs diferentes dos salvos. Traz pppoe_read, default_login e ssids_differ. |
vendor_counter | O contador de resets do fabricante, declarado no perfil do modelo, subiu. Traz fr_count, fr_count_prev e fr_date. |
Quarentena de bootstrap
Quando uma CPE que já tinha recebido credencial de Inform própria volta ao ACS com a senha genérica do provedor e o evento 0 BOOTSTRAP, o Lumina não sabe se foi um reset real ou alguém se passando por ela. A CPE fica em quarentena: segue informando, mas fica sem PPPoE e Wi-Fi do assinante, sem presets e sem tarefas até um operador confirmar.
| Como o ERP enxerga | Onde |
| Campo | bootstrap_unverified_at em GET /devices/{key} |
| Lista | GET /api/v1/devices?quarantined=1 (também no CSV e no filtro de lote) |
| Evento da CPE | bootstrap_unverified |
| Alerta | Regra bootstrap_unverified, ligada em todo tenant, que sai pelos webhooks alert.opened/alert.resolved |
POST/api/v1/devices/{key}/actions/confirm-bootstrapescopo devices:write
A quarentena também termina sozinha se a CPE voltar a usar a credencial própria ou concluir um download de firmware enviado pelo ACS.
Não confirme às cegas. Confirmar entrega a configuração do assinante (inclusive a senha PPPoE) a quem apresentou a CPE. Automatize o aviso, não a confirmação.
Etiquetas da CPE
Marcas livres do provedor (POP, bairro, cliente empresarial) que servem de filtro na lista, nos lotes e nas regras de alerta.
PUT/api/v1/devices/{key}/tagsescopo devices:write
curl -s -X PUT -H "$KEY" -H "Content-Type: application/json" \
-d '{"tags":["pop-centro","empresarial"]}' "$ACS/api/v1/devices/$CPE/tags"
# 200 {"ok":true,"tags":["pop-centro","empresarial"]}
- Substitui o conjunto inteiro. Para acrescentar uma, leia
tags de GET /devices/{key}, acrescente e mande tudo. {"tags":[]} limpa.
- Cada etiqueta: 1 a 32 caracteres, só letras minúsculas
a-z, dígitos, hífen e sublinhado. No máximo 20 por CPE; repetidas contam uma vez. Fora disso, 400 com o motivo.
- A troca entra na auditoria (
device.tags) com o conjunto anterior.
- Filtrar:
GET /devices?tag=pop-centro; no lote, "Tag"; na regra de alerta, filter.tag.
Pela NBI
POST/DELETE /nbi/devices/{id}/tags/{tag} acrescentam ou removem uma etiqueta (escopo devices:write ou devices:safe-ops).
Réguas diferentes. A NBI aceita qualquer etiqueta de 1 a 64 caracteres, como o GenieACS. A API REST só aceita a régua acima. Uma etiqueta criada pela NBI com maiúscula, espaço ou acento faz o PUT …/tags seguinte responder 400 se for reenviada como veio. Se o ERP usa as duas vias, use só minúsculas, dígitos, hífen e sublinhado.
Remoção de CPE e licença
A licença conta CPEs que informaram nos últimos 30 dias. Por isso remover tem três modos, e a escolha muda o que acontece com a vaga de licença.
| mode | No console | Efeito | Quando usar no ERP |
remove (padrão) | Remover | Apaga só o registro. Se a CPE informar de novo, volta como CPE nova e reconsome a licença. | Limpeza de cadastro duplicado. |
release | Remover e liberar licença | Apaga e o ACS passa a ignorar os Informs dela: a vaga fica livre de verdade. Ela é readmitida sozinha quando mandar 0 BOOTSTRAP (reset de fábrica, reinstalação). | ONU recolhida, volta ao estoque para outro cliente. |
block | Remover e bloquear | Recusada sempre; nem o BOOTSTRAP traz de volta. Só readmissão manual. | Equipamento de terceiro, furto, fraude. |
DELETE/api/v1/devices/{key}?mode=releaseescopo devices:write
POST/api/v1/devices/removeescopo devices:write · até 1000
curl -s -X POST -H "$KEY" -H "Content-Type: application/json" \
-d '{"keys":["40ED00-XX230v-2249B1000123","40ED00-XX530v-2249B1000456"],"mode":"release"}' \
"$ACS/api/v1/devices/remove"
# 200 {"removed":2,"keys":[…],"mode":"released"} (404 se nenhuma existir)
Ver e desfazer recusas
GET/api/v1/device-removalsescopo devices:read
Aceita ?mode=release|block, ?q= (serial, chave, PPPoE, modelo) e ?limit= (até 500). Devolve {"items":[…],"counts":{…},"total":N}, cada item com removed_by, removed_at, last_seen_at e ignored_count (Informs ignorados desde a remoção).
POST/api/v1/device-removals/{key}/readmitescopo devices:write
Na resposta e nos registros o modo aparece como removed, released ou blocked. Toda remoção pela API REST ou pelo console emite o webhook device.removed com mode, serial, modelo, PPPoE e quem removeu: bom gancho para o ERP registrar a devolução do equipamento. A remoção pela NBI só apaga o registro e não emite esse webhook.
Ações em massa (lotes)
Um lote aplica a mesma tarefa a todas as CPEs que casam com um filtro, com taxa controlada e salvaguardas obrigatórias no servidor.
As salvaguardas
- Quantidade reconferida
- O corpo precisa trazer
confirm_count igual ao número de CPEs que o filtro alcança agora. Sem ele, ou com número diferente: 409 com {"error":"confirme digitando a quantidade de CPEs do filtro: N","count":N}. Mostre N a quem pediu e só então reenvie.
- Janela de arrependimento
- O lote nasce
pending e só começa depois de start_at: 5 minutos para o que grava ou reinicia, 1 minuto para leituras. Dá para cancelar nesse intervalo.
- Primeira onda
- Lotes que gravam ou reiniciam com mais de 10 CPEs rodam primeiro em 10 e param em
canary até alguém chamar POST /api/v1/batches/{id}/continue (204).
- Tetos
- Lotes que gravam ou reiniciam aceitam no máximo 500 CPEs (
422 acima disso: restrinja o filtro) e 300 tarefas por minuto (valor maior é reduzido).
5 min, 10 CPEs e 500 CPEs são os padrões (LUMINA_BULK_GRACE, LUMINA_BULK_CANARY, LUMINA_BULK_MAX_WRITE) e podem ter sido ajustados na instalação. Os valores desta instalação estão em limits de GET /api/v1/firmwares/options e em bulk de GET /api/v1/guides/context.
Criar um lote
POST/api/v1/batchesbulk:write + escopo do kind
# 1ª chamada: sem confirm_count, só para saber quantas CPEs o filtro alcança
curl -s -X POST -H "$KEY" -H "Content-Type: application/json" -d '{
"name": "DNS novo — POP Centro",
"filter": { "Tag": "pop-centro", "Status": ["online"] },
"task": { "kind": "set_values",
"values": [ { "name": "Device.DHCPv4.Server.Pool.1.DNSServers", "value": "1.1.1.1,8.8.8.8", "type": "xsd:string" } ] },
"rate_per_minute": 120,
"connection_request": false
}' "$ACS/api/v1/batches"
# 409 {"error":"confirme digitando a quantidade de CPEs do filtro: 214","count":214}
# 2ª chamada: o mesmo corpo com "confirm_count": 214
# 202 {"id":"…","status":"pending","start_at":"…","canary":10,"confirm_count":214,"rate_per_minute":120,…}
Chaves do filtro
| Chave | Equivale a |
Q | q da listagem (busca por trecho) |
Status | Lista: ["online","unstable"] |
Manufacturer · Model · ProductClass · Software · Hardware · Tag | Os filtros de mesmo nome (Hardware é a versão de hardware) |
PPPoE | Login PPPoE exato |
DefaultLogin | true: só CPEs no login padrão |
Quarantined | true: só CPEs em quarentena de bootstrap |
OpticalLow | true: só CPEs com RX óptico abaixo de −27 dBm |
NewLast24h | true: só CPEs vistas pela primeira vez nas últimas 24 h |
O filtro do lote não usa os nomes da query string. Chave desconhecida, como product_class ou default_login, e valor de tipo errado, como "Status":"online" em vez de lista, são ignorados sem erro, e o lote alcançaria mais CPEs do que você pretendia. É exatamente para isso que existe o count do 409: confira-o antes de confirmar.
Tarefas aceitas no lote: get_values, get_names, refresh, set_values, add_object, delete_object, reboot e download (mesma restrição de URL da tarefa crua). rate_per_minute padrão 60. 400 nenhuma CPE no filtro quando a contagem é zero.
Acompanhar e controlar
| Rota | Escopo | Faz |
GET /api/v1/batches | tasks:read | Lotes, mais recentes primeiro, com contagens. |
GET /api/v1/batches/{id} | tasks:read | status (pending, expanding, running, canary, done, cancelled), counts por status de tarefa, total. |
GET /api/v1/tasks?batch={id} | tasks:read | Tarefa a tarefa. |
POST /api/v1/batches/{id}/continue | bulk:write | Libera o restante depois da primeira onda (204). |
POST /api/v1/batches/{id}/cancel | bulk:write | Cancela o que ainda não saiu (204). |
Escreva com connection_request: false em manutenção de massa. As tarefas pegam carona no Inform periódico e não geram pico.
Firmware e campanhas
O repositório de firmware, a atualização de uma CPE com o portão de segurança e as campanhas para o parque.
Só o próximo passo. O Lumina nunca oferece um salto de versão nem a versão em que a CPE já está: cada atualização é o próximo degrau de um caminho confirmado. O ACS é uma ferramenta: não recomenda nem desaconselha firmware, e mostra o changelog do fabricante.
Repositório
POST/api/v1/firmwaresescopo firmware:write · multipart
curl -s -X POST -H "$KEY" \
-F file=@XX530v_v2_0.9.1.bin -F manufacturer=TP-Link -F model=XX530v -F hardware_version="XX530v v2.0" \
-F version="0.9.1" -F from_versions="0.8.0,0.9.0" -F changelog="Correções de Wi-Fi" \
"$ACS/api/v1/firmwares"
Campos: file (até 512 MB), manufacturer e model (obrigatórios), hardware_version, version (destino), from_versions (obrigatório: as versões que as CPEs têm hoje, separadas por vírgula, ponto e vírgula ou linha), changelog, notes e blocks_downgrade (true = depois desta versão não se volta atrás). 201 com o firmware; 200 quando o mesmo arquivo (mesmo SHA-256) já existia, com as versões de origem novas acrescentadas. O arquivo é servido às CPEs por URL assinada com prazo de 7 dias (/fs/…), com retomada.
| Rota | Escopo | Faz |
GET /api/v1/firmwares | devices:read | Repositório do tenant, com o selo de homologação de cada item. |
PATCH /api/v1/firmwares/{id} | firmware:write | Edita blocks_downgrade, changelog, notes. |
DELETE /api/v1/firmwares/{id} | firmware:write | Apaga (409 se usado por campanha). |
GET /api/v1/firmwares/options | devices:read | Modelos e versões do parque para formulários; com ?firmware_id= ou ?cloud_id=, a contagem de CPEs (devices) de cada origem e os limits de lote. |
GET /api/v1/firmwares/cloud | devices:read | Catálogo de firmware homologado pela Lumina. |
POST /api/v1/firmwares/cloud/{id}/fetch | firmware:write | Baixa um item do catálogo para o repositório local. |
GET /api/v1/firmwares/updates | devices:read | Histórico de atualizações: ?device=&limit=&q= (série, fabricante, modelo, hardware, versão ou arquivo, sem caixa e sem acento); traz total. |
POST /api/v1/firmwares/updates/{id}/feedback | firmware:write | Registra se a atualização funcionou: {"ok":true,"note":"…"} (409 enquanto a CPE não voltou). |
Atualizar uma CPE
POST/api/v1/devices/{key}/actions/firmwareescopo firmware:write
Corpo {"firmware_id":"…"} (repositório local) ou {"cloud_id":"…"} (catálogo da Lumina, baixado na hora se preciso). Antes de criar a tarefa o Lumina confere fabricante, modelo, hardware, versão atual, caminho confirmado e travas de downgrade:
| Resposta | Significa |
202 | {"id","status":"pending","connection_request":true,"update_id","from","to"}: tarefa criada. |
409 com confirm_required: true | O caminho não tem confirmação de sucesso, o arquivo não declara hardware, ou é downgrade (downgrade: true). Passa se reenviar com "confirm" igual ao modelo da CPE, assumindo o risco. |
422 com blocked: true | Recusado de vez: outro modelo ou hardware, CPE já na versão, versão atual proíbe voltar. |
503 com fetching: true | O item do catálogo ainda está sendo baixado; tente de novo em instantes. |
Para saber o que aplicar, use GET …/firmware-next. O resultado fica em GET /api/v1/firmwares/updates?device={key}. Download preso em transferring pode ser encerrado (ver Tarefas).
Campanhas
POST/api/v1/campaignsescopo firmware:write
{ "firmware_id": "…", "from_version": "0.9.0", "name": "XX530v → 0.9.1",
"rate_per_minute": 30, "window_start": "01:00", "window_end": "05:00", "max_failure_pct": 20,
"confirm_count": 37 }
- A campanha cria um lote de Download cujo filtro é sempre fabricante + modelo + hardware do firmware +
from_version: ela nunca alcança uma CPE que a regra de segurança recusaria uma a uma. Por isso o firmware precisa declarar fabricante, modelo e hardware (400 senão).
- Aceita também
cloud_id, confirm (mesma regra da atualização unitária) e allow_downgrade (exigido quando a origem é mais nova que o arquivo; sem ele, 409).
- Padrões: 30 por minuto, até 20 % de falhas. Com pelo menos 5 atualizações concluídas e falhas acima do limite, a campanha pausa sozinha. Encerrada ou pausada, emite o webhook
campaign.done.
- Salvaguardas de lote em toda campanha:
confirm_count obrigatório (400 sem ele; 409 com count se não bate; 422 acima do teto), janela de arrependimento, primeira onda com /batches/{id}/continue e taxa até 300/min. A janela de horário conta a partir do fim da janela de arrependimento.
- Ordem das recusas: firmware, portão (409 de confirm ou de allow_downgrade, 422), campos, e só então a contagem.
- Pela lista de CPEs: com
filter (o mesmo de POST /batches), a contagem é a do filtro restrito ao par da campanha. O filtro só restringe (status, busca…): fabricante, modelo, hardware e versão são sempre os do par. Com filter, exige também bulk:write.
GET /api/v1/campaigns (devices:read) lista; POST /api/v1/campaigns/{id}/cancel (204) cancela a campanha e o lote dela.
Prévia da atualização em massa
POST/api/v1/batches/firmware-previewescopo bulk:write · só leitura
{ "filter": { "Model": "EG8145V5", "Hardware": "1.0", "Software": "V5R020C10S100", "Status": ["online"] } }
Responde total, groups e combinations (até 50 combinações modelo · hardware · versão, as maiores primeiro) e limits. Só quando há exatamente uma combinação vêm targets: cada firmware cadastrado cuja campanha teria esse par como origem, com o veredito do portão (verdict, reason, downgrade, trust), o selo do destino e o par de origem a mandar de volta em POST /campaigns; e upgrade_unsupported_devices, as CPEs do par cujo perfil não aceita atualização por TR-069.
Alertas, regras e canais
Regras que observam o parque e abrem alertas; para o ERP, o caminho natural é receber alert.opened por webhook e abrir a OS.
Tipos de regra
| kind | Dispara quando | params (padrão) |
offline | CPE offline há mais de N minutos. | {"minutes": 30} |
optical_low | RX óptico abaixo de X dBm. | {"dbm": -27} |
unstable | CPE marcada como instável (Informs perdidos). | — |
frequent_reboots | N reboots não pedidos pelo ACS em 24 h. | {"count": 3} |
firmware_not_approved | Firmware fora da lista aprovada. | {"versions": [...]} (obrigatório) |
factory_reset | CPE voltou de reset de fábrica nas últimas N horas. | {"hours": 24} |
bootstrap_unverified | CPE em quarentena de bootstrap; resolve sozinho quando a quarentena termina. Vem criada e ligada em todo tenant (severidade critical); pode ser editada, desligada ou excluída (excluída, não volta). detail traz ip e quarantined_at. | — |
Criar uma regra
POST/api/v1/alert-rulesescopo alerts:write
curl -s -X POST -H "$KEY" -H "Content-Type: application/json" -d '{
"name": "Sinal óptico baixo", "kind": "optical_low", "params": {"dbm": -26},
"filter": {"tag": "pop-centro"}, "severity": "warning", "channel_ids": [], "cooldown_minutes": 60
}' "$ACS/api/v1/alert-rules"
Campos: name, kind, params, filter (manufacturer, product_class, tag), severity (info, warning padrão, critical), channel_ids (só canais do próprio tenant; id inexistente dá 400), enabled, cooldown_minutes (padrão 60, até 10 080) e notify_resolve. PATCH /alert-rules/{id} altera só o que vier; DELETE apaga a regra e os alertas dela. GET /api/v1/alert-rules (devices:read) lista regras, tipos e severidades aceitos.
Listar e resolver
GET/api/v1/alertsescopo devices:read
| Parâmetro | Valores |
state | open (padrão), resolved ou all |
device | Chave da CPE |
rule_id | Regra que abriu o alerta |
severity | info, warning ou critical (outro valor dá 400) |
q | Cada palavra tem de aparecer na chave, no serial ou no login PPPoE, sem diferença de caixa nem de acento (até 200 bytes) |
limit · cursor | Padrão 100, máximo 500 |
Resposta {"items","total","next_cursor"}, abertos primeiro, do mais recente ao mais antigo. Cada alerta traz id, rule_id, rule_name, kind, device_key, serial_number, product_class, manufacturer, severity, title, message, detail, opened_at, resolved_at e resolved_by.
| Rota | Escopo | Faz |
GET /api/v1/alerts/groups | devices:read | Os mesmos filtros (sem limit/cursor), uma linha por regra: rule_id, rule_name, kind, severity, count, newest_at, oldest_at; da pior severidade para a mais branda. |
POST /api/v1/alerts/{id}/resolve | alerts:write | Resolve um alerta aberto. |
POST /api/v1/alerts/resolve | alerts:write | Resolve de uma vez os abertos de uma regra: {"rule_id","severity","q","device"}, rule_id obrigatório; devolve {"resolved": n}. |
A resolução é automática quando a condição cessa, ou manual. As duas emitem o webhook alert.resolved com o mesmo payload (um por alerta, também na resolução em grupo).
Canais de notificação
E-mail, Telegram e webhook simples, ligados às regras por channel_ids. GET /api/v1/channels (devices:read, segredos mascarados); POST, PATCH /{id}, DELETE /{id} (tira o canal das regras) e POST /{id}/test com alerts:write.
Canal de alerta ou webhook de integração? O canal avisa pessoas. Para o ERP, use os
webhooks de integração, que são assinados, têm fila, retentativa e histórico de entregas.
Webhooks
O Lumina avisa o ERP quando algo acontece: cada evento vira um POST JSON assinado, entregue por uma fila com retentativa.
Configurados em Integrações → Webhooks por um administrador (ou por POST /api/v1/webhooks com essa sessão; nenhuma chave de API cria webhook) com nome, URL, segredo e a lista de eventos.
Envelope
POST https://erp.provedor.com.br/hooks/lumina
Content-Type: application/json
User-Agent: LuminaACS-Webhook/1
X-Lumina-Event: device.offline
X-Lumina-Delivery: 84213
X-Lumina-Timestamp: 1789651267
X-Lumina-Signature: sha256=6b1f…
{ "id": 84213, "event": "device.offline", "tenant": "default",
"timestamp": "2026-09-17T13:41:07.52Z",
"data": { "key":"40ED00-XX230v-2249B1000123", "from":"online", "to":"offline" } }
timestamp é quando o evento aconteceu (UTC); X-Lumina-Timestamp é quando esta tentativa saiu (segundos desde a época). X-Lumina-Signature só vem quando o webhook tem segredo: configure sempre um.
Eventos
| event | Quando | Campos de data |
device.discovered | Primeiro Inform de uma CPE nova | key, manufacturer, product_class, serial_number, remote_ip |
device.online | Voltou a ficar online: offline → online (no próprio Inform) ou unstable → online | key, from, to · quarantined |
device.offline | Sem informar além do prazo (1,5× o intervalo de Inform mais uma folga) | key, from, to |
device.unstable | Informs perdidos repetidos (padrão: 3 em 24 h, um na última hora) | key, from, to |
device.unlicensed | CPE nova barrada pelo limite de licença | key |
device.removed | CPE removida pela API REST ou pelo console | key, mode, serial_number, product_class, manufacturer, pppoe_username, removed_by |
device.factory_reset | CPE conhecida voltou de fábrica | key, events, reapply, pppoe_username |
device.config_applied | Configuração do assinante gravada (a pedido ou reaplicada após reset) | key, task, params, reapply, pppoe_username, ssid_24, ssid_5, error |
task.completed | Tarefa concluída com sucesso | task_id, kind, key, status, error, summary |
task.failed | Tarefa falhou | task_id, kind, key, status, error, summary |
alert.opened | Regra de alerta disparou | id, rule, kind, severity, key, serial_number, title, message, detail |
alert.resolved | Alerta resolvido, automática ou manualmente | id, rule, kind |
campaign.done | Campanha de firmware encerrada ou pausada por falhas | id, name, firmware, status, note |
- Fechar a OS de
device.offline: device.online sai uma vez por volta. Não emitem device.online a CPE nova (o primeiro Inform é device.discovered) e a unlicensed. Com retentativa, um device.online pode chegar antes do device.offline que veio antes dele: compare o timestamp dos dois antes de fechar a OS.
- CPE em quarentena que volta de offline emite
device.online com "quarantined": true: quem mandou o Inform pode não ser a CPE. Não trate essa volta como serviço restabelecido sem conferir. Fora da quarentena o campo não vem.
device.removed traz mode como removed, released ou blocked.
device.config_applied só traz task quando houve tarefa (a reaplicação após reset não tem) e só traz error quando algo falhou.
task.* cobre todos os tipos, inclusive apply_config (summary = {"params","reapply"}) e diagnósticos (summary = resultado resumido).
Verificar a assinatura
X-Lumina-Signature é sha256= + HMAC-SHA256 (hex) do segredo sobre X-Lumina-Timestamp + "." + corpo bruto. Compare em tempo constante e rejeite timestamps muito antigos.
# Python
import hmac, hashlib
ts = request.headers["X-Lumina-Timestamp"]
corpo = request.get_data() # bytes exatamente como chegaram
mac = hmac.new(segredo.encode(), ts.encode() + b"." + corpo, hashlib.sha256)
valido = hmac.compare_digest("sha256=" + mac.hexdigest(), request.headers["X-Lumina-Signature"])
// PHP
$ts = $_SERVER['HTTP_X_LUMINA_TIMESTAMP'];
$corpo = file_get_contents('php://input');
$mac = 'sha256=' . hash_hmac('sha256', $ts . '.' . $corpo, $segredo);
$valido = hash_equals($mac, $_SERVER['HTTP_X_LUMINA_SIGNATURE'] ?? '');
// Node.js
const crypto = require('crypto');
const ts = req.get('X-Lumina-Timestamp');
const mac = 'sha256=' + crypto.createHmac('sha256', segredo).update(ts + '.').update(corpoBruto).digest('hex');
const recebido = req.get('X-Lumina-Signature') || '';
const valido = mac.length === recebido.length && crypto.timingSafeEqual(Buffer.from(mac), Buffer.from(recebido));
Entrega e retentativa
- A fila é varrida a cada 3 s. Responda 2xx rápido (o timeout é de 15 s) e processe de forma assíncrona no seu lado.
- Falhas são retentadas com backoff 30 s → 2 min → 10 min → 30 min → 1 h → 3 h → 6 h → 12 h → 24 h, até 10 tentativas.
- 4xx desiste na hora (exceto 408 e 429): um erro de validação seu vira evento perdido. Devolva 5xx se quiser retentativa.
- A URL passa pela política de saída do ACS (destinos de rede interna podem ser recusados ao salvar) e seguem-se no máximo 3 redirecionamentos.
- Entregas podem repetir: use
id (o mesmo de X-Lumina-Delivery) como chave de idempotência.
- Um evento só é guardado quando, no momento em que acontece, existe webhook ligado e inscrito nele: webhook criado ou inscrito depois não recebe eventos anteriores. Eventos e entregas são apagados depois de 7 dias.
Configurar pela API
POST/api/v1/webhooksescopo integrations:write · sessão de administrador
{ "name": "ERP — eventos de CPE", "url": "https://erp.provedor.com.br/hooks/lumina",
"secret": "troque-por-um-segredo-longo", "enabled": true,
"events": ["device.offline","device.online","device.factory_reset","task.completed","task.failed","alert.opened"] }
PATCH /api/v1/webhooks/{id} altera só o que vier; secret volta mascarado nas leituras e reenviar •••••••• mantém o atual. Evento fora da lista dá 400 evento desconhecido; GET /api/v1/webhooks devolve {"items":[…],"events":[…]} com a lista válida. GET /api/v1/webhooks/{id}/deliveries mostra o histórico (status pending/sent/failed, attempts, response_code, last_error, next_attempt_at). POST /api/v1/webhooks/{id}/test dispara na hora um webhook.test com id 0: não trate o 0 como chave de idempotência.
Tempo real (SSE)
Um fluxo Server-Sent Events com os acontecimentos do tenant, para telas de atendimento abertas. Para integração servidor a servidor, use webhooks.
GET/api/v1/events/streamescopo devices:read
# todos os eventos do tenant
curl -sN -H "$KEY" "$ACS/api/v1/events/stream"
# Informs e sessões só desta CPE (o resto continua vindo do tenant inteiro)
curl -sN -H "$KEY" "$ACS/api/v1/events/stream?keys=$CPE"
event: hello
data: {"tenant":"default"}
event: task.update
data: {"type":"task.update","at":"2026-09-17T10:31:09.2-03:00","data":{"id":"b32fqgm2ejgjkahhetfa","status":"done","error":""}}
event: ping
data: {"at":"2026-09-17T10:31:34-03:00","data":{}}
| event | Quando | Campos de data |
device.inform | Cada Inform (alto volume; filtrável por keys) | key, events, status, first_seen, at |
device.session | Fim de sessão CWMP (filtrável por keys) | key, rpcs, faults, errors |
device.status | Transição calculada (→ offline, → instável, instável → online) ou marcação de licença; a volta de offline aparece como device.inform com status online | key, from, to · ou key, status |
device.connreq | Resultado de um Connection Request | key, ok, error, http?, udp? |
device.factory_reset | CPE voltou de fábrica | key, events, reapply, pppoe_username, signal, signals? e os campos do sinal |
device.config_applied | Configuração do assinante gravada | key, task, params, reapply, pppoe_username, ssid_24, ssid_5, error |
device.lists | Uma leitura sob demanda de lista chegou e já está gravada (do tenant inteiro, não passa por keys) | key, lists |
task.update | Tarefa criada ou mudou de status | id, status, key, kind, error |
batch.update · campaign.update | Lote ou campanha mudou | id, status, total, note, error |
firmware.update | Atualização de firmware avançou | id, key, status, to |
alert.opened · alert.resolved | Alerta abriu / foi resolvido | id, rule, kind, severity, key, title · id, rule, by |
?keys=a,b (até 50 chaves) limita device.inform e device.session a essas CPEs; ?keys= vazio não recebe nenhum desses dois; sem o parâmetro, recebe tudo. Os demais eventos vêm sempre do tenant inteiro.
- O corpo de cada evento é
{"type","at","data"}. ping chega a cada 25 s; depois de 30 minutos o servidor envia bye e fecha: reconecte (com backoff se cair antes).
- Cliente lento perde eventos (o servidor não enfileira sem limite): ao reconectar, recarregue o estado pela API.
- O fluxo entrega o que é publicado no processo que atende a conexão. Com CWMP, API e worker em processos separados, parte dos eventos não chega a esse processo: mais um motivo para integração servidor a servidor usar webhooks.
NBI compatível com o GenieACS
O Lumina publica em /nbi uma API que imita a NBI do GenieACS 1.2. Um ERP que já tem conector GenieACS funciona apontando para cá, sem desenvolvimento no ERP.
Mesma porta e mesmo domínio do console. Nenhuma linha de código do GenieACS é usada; é uma reimplementação da interface.
Estado: a homologar. O modo NBI está implementado e coberto por testes, mas ainda não foi homologado com o módulo de ACS de um ERP em produção. Na primeira sincronização, acompanhe o log do Lumina: um campo de consulta fora da lista suportada aparece como 400 campo … não suportado.
Autenticação e escopos
HTTP Basic com usuário livre e a chave lmk_… como senha (a chave no lugar do usuário também é aceita), ou Authorization: Bearer lmk_…. Na NBI, qualquer recusa de credencial (chave inválida, IP fora da lista da chave, limite por minuto excedido) responde 401 com WWW-Authenticate: Basic, e não 403/429 como na API REST. Para o Gerenciador de CPE completo, a chave precisa de devices:read, devices:safe-ops, devices:write e tasks:read, mais firmware:write se o ERP for atualizar firmware.
Rotas
| Método | Rota | Escopo | Observações |
| GET | /nbi/devices | devices:read | ?query= (JSON estilo Mongo), ?projection= (caminhos separados por vírgula), ?limit= (padrão 100, máximo 1000), ?skip=. Último Inform primeiro. O cabeçalho total é a quantidade desta resposta, não do parque: pagine por skip até vir menos que limit. |
| POST | /nbi/devices/{id}/tasks | por tarefa | ?connection_request e ?timeout= ms (padrão 3000, máximo 30000). |
| POST | /nbi/devices/{id}/tags/{tag} | write ou safe-ops | Acrescenta etiqueta (1 a 64 caracteres; ver a diferença de régua). |
| DELETE | /nbi/devices/{id}/tags/{tag} | write ou safe-ops | Remove etiqueta. |
| DELETE | /nbi/devices/{id} | devices:write | Apaga o registro, como mode=remove, mas sem o webhook device.removed. |
| GET | /nbi/tasks | tasks:read ou devices:read | ?query={"device":"…","status":"pending"}; até 200 tarefas. |
| DELETE | /nbi/tasks/{id} | write ou safe-ops | Cancela tarefa ainda não concluída (fica expired). |
| POST | /nbi/tasks/{id}/retry | devices:safe-ops | Devolve à fila uma tarefa falha, expirada ou presa. |
| GET | /nbi/faults | devices:read | Até 200 tarefas que falharam, no formato de fault do GenieACS. |
| DELETE | /nbi/faults/{id} | autenticado | Responde 200 e não faz nada (existe para o ERP não quebrar). |
| GET | /nbi/files | devices:read | Repositório de firmware no formato de arquivos do GenieACS (_id = versão). |
| GET | /nbi/presets · /nbi/provisions | autenticado | Lista vazia: o provisionamento do Lumina é declarativo e fica no console. |
Erros da NBI têm corpo em string JSON (ex.: "sem permissão devices:read"), como no GenieACS.
Consultas suportadas
Subconjunto do dialeto Mongo do GenieACS, com $and e $or em qualquer nível:
| Campo | Operadores |
_id · _deviceId._SerialNumber · _deviceId._OUI · _deviceId._ProductClass · _deviceId._Manufacturer | igualdade, $ne, $in, $regex (sem diferenciar maiúsculas, até 256 caracteres) |
_tags | igualdade, $ne, $in |
_lastInform · _registered · _lastBoot | igualdade, $gt, $gte, $lt, $lte; valor em RFC 3339 ou milissegundos desde a época |
qualquer caminho TR-098/TR-181 (com ou sem ._value) | igualdade, $ne, $in, $regex, $exists, $gt/$gte/$lt/$lte (comparação numérica) |
Outro campo começado por _ dá 400 campo … não suportado. Os caminhos de serial, versões, fabricante, ProductClass, usuário PPPoE (…WANPPPConnection.1.Username e Device.PPP.Interface.1.Username), IP WAN do TR-098 e SSID 2,4 GHz consultam as colunas do inventário (os mesmos valores de GET /api/v1/devices); os demais consultam os parâmetros lidos da CPE.
# pelo serial da ONU
curl -sG -u "erp:lmk_…" "$ACS/nbi/devices" --data-urlencode 'query={"_deviceId._SerialNumber":"2249B1000123"}'
# pelo login PPPoE (TR-098)
curl -sG -u "erp:lmk_…" "$ACS/nbi/devices" \
--data-urlencode 'query={"InternetGatewayDevice.WANDevice.1.WANConnectionDevice.1.WANPPPConnection.1.Username":"cliente@isp"}'
# quem não informa desde um instante (ms desde a época), só identificação
curl -sG -u "erp:lmk_…" "$ACS/nbi/devices" \
--data-urlencode 'query={"_lastInform":{"$lt":1789650000000}}' --data-urlencode 'projection=_id,_lastInform'
Documento devolvido
Cada CPE vira o documento do GenieACS, com a árvore de parâmetros conhecida e folhas {_object,_value,_type,_timestamp,_writable}; objetos têm _object:true. O Lumina acrescenta _status (online/unstable/offline/unlicensed) como extensão. Com projection só de campos _…, a árvore de parâmetros nem é lida.
{ "_id": "40ED00-XX230v-2249B1000123",
"_deviceId": { "_Manufacturer":"TP-Link", "_OUI":"40ED00",
"_ProductClass":"XX230v", "_SerialNumber":"2249B1000123" },
"_lastInform": "2026-09-17T13:31:07.412Z", "_registered": "2026-08-11T12:02:44Z", "_lastBoot": "…",
"_tags": ["pop-centro"], "_status": "online",
"Device": { "_object":true, "WiFi": { "_object":true, "SSID": { "_object":true, "1": { "_object":true,
"SSID": { "_object":false, "_value":"Casa", "_type":"xsd:string",
"_writable":true, "_timestamp":"2026-09-17T13:31:07Z" } } } } } }
Tarefas aceitas
| name | Campos | Vira, no Lumina | Escopo |
getParameterValues | parameterNames[] | get_values | devices:read |
refreshObject | objectName (vazio = InternetGatewayDevice.) | get_values na subárvore | devices:read |
reboot | — | reboot | devices:safe-ops |
setParameterValues | parameterValues: [[nome, valor, tipo?]] | set_values | devices:write |
addObject · deleteObject | objectName | add_object · delete_object | devices:write |
download | file ou fileName (id, versão ou nome do firmware no repositório), fileType, targetFileName | download com URL assinada, depois do portão de segurança | firmware:write |
factoryReset | — | recusado com 400 | — |
Os tipos são inferidos do JSON quando o terceiro elemento não vem: booleano vira xsd:boolean com 1/0, número inteiro vira xsd:int, o resto vira xsd:string. Outro name dá 400 tarefa … não suportada. Toda tarefa criada pela NBI entra na auditoria com a chave usada.
download passa pelo mesmo portão de segurança da ação de firmware. A diferença é que a NBI não tem como assumir risco: o que a ação pediria confirmação (409) e o que ela recusaria (422) viram 400 com o motivo (firmware recusado pelo portão de segurança: …), e nenhuma tarefa é criada. Um caminho que ainda precisa de confirmação tem de sair pelo console ou pela ação REST com confirm.
Semântica do connection_request
Igual à do GenieACS, e é o ponto que o ERP precisa tratar direito. Basta a presença do parâmetro ?connection_request; sem ele, a tarefa entra na fila e a resposta é 202 na hora.
| Resposta | Significa |
200 com status:"done" e result | A CPE acordou e executou dentro do timeout. |
202 com status:"pending" | A CPE não respondeu a tempo (CGNAT, porta bloqueada, desligada). A tarefa continua válida e executa no próximo Inform. Não repita a chamada em laço: consulte /nbi/tasks. |
202 com status:"failed" e fault | A CPE executou e recusou (parâmetro inexistente, valor inválido). |
404 | CPE não encontrada, ou o teto de tarefas de escrita por minuto da chave foi atingido (muitas tarefas de escrita por minuto para esta chave; aguarde). |
A resposta é {"_id","name","device","timestamp","status"} mais result ou fault.
O que entra pela NBI não vira configuração do assinante e não é reaplicado depois de um reset. Ver
Configuração do assinante.
Módulo de ACS do ERP
Muitos ERPs de provedor já trazem um módulo que fala com um ACS do tipo GenieACS (às vezes chamado de “Gerenciador de CPE” ou “Integração TR-069”). Apontá-lo para o Lumina é uma configuração, não um desenvolvimento.
Configurar no ERP
O nome do menu varia de um ERP para outro (os caminhos conhecidos estão em ERPs: APIs e conectores); os campos são sempre estes:
| Campo no ERP | Valor |
| Tipo de ACS / integração | GenieACS |
| URL da API (NBI) | https://acs.provedor.com.br/nbi |
| Usuário | qualquer um, por exemplo erp |
| Senha | a chave lmk_… criada no Lumina, com os escopos da NBI |
A URL pronta para colar aparece em nbi_url de GET /api/v1/integrations/erp e na tela Integrações → ERP do console. Se o ERP só aceita usuário e senha separados, informe qualquer usuário e a chave como senha; se aceita token, a chave vai como Bearer.
O que cada função costuma disparar
| Função típica no ERP | Chamada | Resultado no Lumina |
| Sincronizar / buscar CPE | GET /nbi/devices?query=… | Documento com identificação, último Inform, etiquetas e a árvore de parâmetros. |
| Importar Wi-Fi | getParameterValues | Lê SSID e configurações; a senha volta vazia (a CPE não a devolve por TR-069). |
| Definir Wi-Fi | setParameterValues | Grava SSID e senha nos caminhos que o ERP conhece. Não vira configuração do assinante. |
| Configurar WAN | setParameterValues | Grava login e senha PPPoE nos caminhos que o ERP conhece. |
| Reiniciar | reboot | Reboot com Connection Request. |
| Atualizar firmware | download | Resolve a versão no repositório do Lumina, confere o portão de segurança e entrega URL assinada; recusa com 400 o que o console pediria confirmação. Exige firmware:write. |
Caminhos de gravação
O módulo do ERP escreve nos caminhos TR-069 que ele conhece, que nem sempre são os que a CPE usa. Em várias CPEs TR-181, o PPP da operadora não é o
Device.PPP.Interface.1. Para Wi-Fi e PPPoE que precisam sobreviver a um reset de fábrica, prefira o
conector de ERP ou o
PUT …/config, que resolvem os caminhos pelo perfil do modelo.
Conferir a integração
- No ERP, use a função de sincronizar ou buscar CPE com um serial conhecido.
- No Lumina, a chamada aparece na auditoria com o nome da chave (
apikey:<nome>).
- Um
400 campo … não suportado no log quer dizer que o ERP consulta por um campo fora da lista suportada: mande o trecho do log para a equipe do Lumina.
Conector de ERP
O caminho inverso: o Lumina consulta o ERP e guarda, para cada CPE, o contrato do assinante e a configuração do assinante que vem dele.
Serve para o atendimento ver cliente, plano, endereço e POP na tela da CPE, e para gravar na CPE o PPPoE e o Wi-Fi do contrato. O conector é somente leitura no ERP: o Lumina nunca escreve no ERP. Cancelamento, troca de plano e reserva de equipamento continuam sendo feitos no ERP.
Conectores disponíveis
O Lumina traz conectores prontos para os ERPs mais usados por provedores. A lista, e o que cada conector sabe fazer, vem da própria instalação:
GET/api/v1/integrations/erp/providersescopo integrations:write
{ "default": "…", "items": [
{ "kind": "…", "name": "…", "validated": true, "wifi": true,
"lookups": ["login","serial","mac","document"],
"fields": [ { "key":"base_url", "label":"URL do ERP", "type":"url", "required":true, "placeholder":"https://erp.provedor.com.br" },
{ "key":"token", "label":"Token", "type":"password", "required":true }, … ],
"notes": "Onde criar a credencial no ERP…" },
… ] }
| Campo | Significa |
kind | Identificador do conector, usado em provider e devolvido em erp nas respostas da CPE. |
validated | true: validado com uma instalação real do ERP. false: escrito a partir da documentação pública da API do ERP e ainda não validado (ver abaixo). |
lookups | Por quais campos a API do ERP deixa buscar o contrato: login (PPPoE), serial, mac, document (CPF/CNPJ). Uma busca que o conector não tem responde “busca por serial não disponível neste ERP”. |
wifi | Se o contrato do ERP traz SSID e senha do Wi-Fi (necessário para “ERP é a fonte”). |
fields | O que o grupo Conexão do console desenha: chave, rótulo, tipo (text, password, url), obrigatório, ajuda, exemplo. password volta mascarado nas leituras. |
notes | Onde criar a credencial dentro daquele ERP. |
O que cada ERP pede e o que o conector chama na API dele, sistema por sistema: ERPs: APIs e conectores.
Conector não validado. Antes de ativar, use Testar conexão e, na primeira sincronização, confira no cartão Assinante de uma CPE conhecida se cliente, contrato, plano e login vieram certos. Se algo vier errado, mande à equipe do Lumina o “Último erro” e o horário: nomes de rota e de campo ainda não confirmados contra o ERP de verdade ficam isolados no conector, para ajuste rápido.
ERP sem conector se integra pelos outros caminhos: lê o contrato no próprio ERP e manda os valores por
PUT /api/v1/devices/{key}/config, e, se tiver módulo de ACS do tipo GenieACS, pela
NBI.
No ERP, antes
- Crie uma credencial só para o Lumina (token, aplicação ou usuário de API, conforme o ERP;
notes diz onde). Só permissão de consulta de clientes, contratos e logins: o Lumina não escreve no ERP.
- Para o Lumina gravar o PPPoE do contrato, a credencial precisa enxergar a senha do serviço. Sem ela, o vínculo grava o login e pede a senha (
needs_pppoe_password).
- Se a credencial restringe origem, libere o IP de saída do ACS (não o IP de quem acessa o console).
No console
Em Integrações → ERP, o grupo Sistema escolhe o ERP. Um tenant tem um ERP: trocar o sistema com configuração salva de outro pede confirmação e, ao salvar, substitui a configuração anterior (as CPEs já vinculadas continuam vinculadas e passam a ser reconciliadas pelo novo sistema). Abaixo vêm:
- Conexão (os
fields do conector): URL e credenciais. Senhas e tokens voltam mascarados.
- Sincronização (igual para todos): Buscar por login PPPoE (padrão), serial ou ambos; Reconciliar a cada N horas; Wi-Fi: “CPE é a fonte” ou “ERP é a fonte”. O que o conector não oferece aparece anotado abaixo do campo.
Testar conexão confere as credenciais que estão na tela, sem salvar. Sincronizar agora faz uma passada na hora (só com a integração ligada e para o sistema salvo).
Pela API
As rotas exigem integrations:write, que nenhuma chave recebe: são para a sessão de um administrador (o próprio console as usa). O conector vai no campo provider (o kind de /providers); sem o campo vale o ERP do tenant; valor fora da lista responde 400 com os aceitos.
| Rota | Faz |
GET /api/v1/integrations/erp | provider, config, enabled, last_error, last_sync_at, updated_at, linked (quantas CPEs têm vínculo) e nbi_url. |
PUT /api/v1/integrations/erp | Salva e liga/desliga. config, quando enviado, substitui a configuração inteira: mande sempre todas as chaves. Reenviar •••••••• preserva o segredo. Com enabled: true e credenciais faltando: 400. |
POST /api/v1/integrations/erp/test | Valida as credenciais com uma consulta inócua (um login que não existe). Aceita a mesma forma do PUT, para testar antes de salvar. {"provider","ok":true} ou {"provider","ok":false,"error"}; “contrato não encontrado” conta como sucesso. |
POST /api/v1/integrations/erp/sync | Passada imediata de até 200 CPEs candidatas, num lote que caiba no tempo de uma requisição. {"provider","ok":true,"synced":137}; num parque grande, cada chamada segue de onde a anterior parou. |
{ "provider": "<kind do conector>", "enabled": true,
"config": { "base_url":"https://erp.provedor.com.br",
"token":"8f3c…",
"lookup_by":"both", "resync_hours":"12",
"rate_per_second":"3", "wifi_policy":"report" } }
| Chave comum | Valor | Padrão |
lookup_by | pppoe · serial · both | pppoe |
resync_hours | Intervalo de reconciliação de um vínculo existente | 12 |
rate_per_second | Teto de chamadas ao ERP | 3 |
wifi_policy | report · erp | report |
Todas as chaves vão como texto no JSON. As demais são as fields do conector escolhido. Gravar gera a auditoria erp.save com o conector no detalhe.
Mensagens de erro
Quando algo falha (Testar, Sincronizar agora, vincular ou consultar contrato), o console e a API mostram a categoria, a mesma frase que fica em “Último erro”, sempre com o nome do ERP configurado:
- contrato não encontrado no <ERP>
- <ERP> recusou as credenciais
- conector do ERP desativado…
- o <ERP> respondeu sem contrato e com uma mensagem de erro (caso típico: credencial sem permissão para a consulta; o texto do ERP fica no log)
- busca por serial não disponível neste ERP
- o motivo da conexão: nome não resolve, conexão recusada, tempo esgotado, TLS, resposta inesperada
O detalhe técnico (URL, status HTTP, corpo da resposta) vai só para o log do servidor: a base_url é escolhida por um administrador do tenant, e devolver ao navegador o que responde nesse endereço transformaria a tela num leitor de serviços internos. A base_url também passa pela política de saída do ACS: destinos que a instalação bloqueia são recusados.
Vínculo com o contrato
Como o Lumina acha o contrato de cada CPE no ERP, o que ele guarda e quando grava alguma coisa na CPE.
Por qual campo o Lumina procura
pppoe (padrão): o login PPPoE que a própria CPE reportou.
serial: o serial cadastrado no serviço do ERP, tentando primeiro a chave completa OUI-Modelo-Serial (o id que o ERP guarda quando o serviço foi cadastrado por um ACS do tipo GenieACS) e depois só o serial.
both: PPPoE e, se não achar, o serial.
ONUs em modo bridge, sem PPPoE na própria CPE, precisam de serial e do serial cadastrado no serviço. Pela API, o vínculo manual busca por login ou serial, e a consulta sem gravar por login ou document (ver consultar contratos).
Escolha do contrato
Quando o ERP devolve vários contratos para a mesma busca, o Lumina escolhe, nesta ordem: (1) o contrato cujo login do serviço bate com o PPPoE (sem diferenciar maiúsculas), (2) o primeiro ativo, (3) o primeiro da lista. É o que evita colar a CPE no contrato cancelado de um cliente que trocou de plano.
Ciclo de sincronização
- Um sincronizador faz uma passada por minuto em cada tenant com o conector ativo, até 50 CPEs por passada, respeitando
rate_per_second.
- São candidatas as CPEs sem vínculo e as com vínculo vencido (
resync_hours). É o que detecta troca de plano, mudança de login e cancelamento.
- Contrato não encontrado fica registrado como “não encontrado” e só é consultado de novo depois de
resync_hours, para não martelar o ERP com CPEs sem cadastro.
- Credencial recusada interrompe a passada inteira e grava o erro em
last_error.
O que o vínculo grava
- O vínculo: cliente, contrato, plano, status, endereço, POP, telefones. Senhas do contrato nunca ficam junto: são guardadas cifradas, à parte.
- A configuração do assinante: sempre o login PPPoE do contrato e a senha quando o ERP a expõe; o Wi-Fi só com “ERP é a fonte”. Campo vazio no ERP não apaga o que já estava salvo.
- Mesma checagem do formulário. Cada campo passa pelas regras do
PUT …/config (PPPoE até 128, SSID 1–32, senha Wi-Fi 8–63, só imprimíveis). O que não passa é ignorado e o resto segue. PPPoE e cada banda do Wi-Fi andam em par: se o login ou a senha é recusado, os dois ficam de fora.
- Recusas visíveis. Campos recusados geram o evento
erp_config_rejected na CPE, com {"erp","contrato","campos":["campo: motivo"]}, nunca o valor.
- Em segundo plano, só o Wi-Fi que mudou é gravado na CPE (tarefa
apply_config restrita ao Wi-Fi, sem Connection Request: aplica no próximo Inform). O PPPoE nunca é regravado sem alguém pedir.
Vincular uma CPE que ainda está no login padrão do provedor grava tudo nela (instalação), e a configuração é reaplicada sozinha após um reset de fábrica.
Política de Wi-Fi
wifi_policy | No console | Comportamento |
report (padrão) | CPE é a fonte | O Wi-Fi cadastrado no ERP fica registrado junto ao vínculo (wifi_desired), para conferência, e não vai para a configuração do assinante. |
erp | ERP é a fonte | SSID e senha do contrato entram na configuração do assinante; quando mudam no ERP, a sincronização cria a tarefa apply_config do Wi-Fi, e o mesmo Wi-Fi volta sozinho depois de um reset de fábrica. Só para conectores com wifi: true. |
Cuidado ao ligar “ERP é a fonte” num parque já instalado. O Wi-Fi do contrato substitui o que o cliente configurou pelo aplicativo do roteador sempre que muda no ERP ou que a CPE volta de fábrica. Ligue depois de confirmar que o ERP tem o SSID e a senha certos de cada contrato.
Como o vínculo aparece na API
GET/api/v1/devices/{key}/subscriberescopo devices:read
{ "linked": true, "saved": true, "default_login": false, "erp": "…",
"subscriber": {
"device_key": "40ED00-XX230v-2249B1000123",
"erp": "…", "customer_id": "10422", "contract_id": "20981",
"customer_name": "…", "document": "123.456.789-00",
"plan": "Fibra 600 Mega", "status": "Ativo", "login": "cliente@isp",
"address": "Rua das Palmeiras, 120, Centro · CEP 88000-000",
"city": "Florianópolis/SC", "pop": "POP Centro", "latlng": "-27.59,-48.55",
"phones": ["(48) 99999-0000"],
"wifi_desired": { "ssid_24":"Casa", "channel_24":"6", "ssid_5":"Casa 5G" },
"linked_by": "pppoe", "synced_at": "2026-09-17T09:00:11-03:00" },
"config": { "pppoe_username": "cliente@isp", "pppoe_password": "••••••••", "source": "…", … } }
| Campo | Conteúdo |
contract_id · customer_id | Identificadores do contrato e do cliente no ERP. |
customer_name · document | Nome ou razão social; CPF/CNPJ. |
plan · status | Plano e situação do contrato, como o ERP os exibe. |
login | Login PPPoE do serviço, conferido contra o que a CPE reporta. |
address · city · latlng | Endereço de instalação, Cidade/UF e coordenadas, quando o ERP os tem. |
pop | POP ou ponto de presença, útil para correlacionar quedas. |
phones | Telefones de contato do cliente. |
wifi_desired | SSID e canal cadastrados no contrato (sem senha), quando o conector traz Wi-Fi. |
linked_by · synced_at | Por qual campo o vínculo foi feito e quando foi reconciliado pela última vez. |
Campos que o ERP não fornece vêm vazios. Sem vínculo, linked é false e subscriber não vem, inclusive quando a última consulta não achou contrato: a tela pode mostrar “não encontrado no ERP” sem confundir com erro. As senhas do contrato só saem por GET …/secrets.
ERPs: APIs e conectores
O único artigo da wiki que trata cada ERP pelo nome: o que cadastrar em cada sistema, o que o conector do Lumina chama na API dele, por quais campos busca, o que traz e como apontar o módulo de ACS do ERP para o Lumina.
SGP validadoIXC Soft, Hubsoft, MK-Auth e Voalle a validarDocumentação das APIs de outubro de 2026
Resumo
| ERP | provider | Estado | Busca por | Senha PPPoE | Wi-Fi do contrato | Módulo de ACS (NBI) |
| SGP | sgp | validado | login, serial, MAC, CPF/CNPJ | quando o token expõe | sim | Gerenciador de CPE |
| IXC Soft | ixc | não validado | login, MAC, CPF/CNPJ | sim | não | — |
| Hubsoft | hubsoft | não validado | login, CPF/CNPJ | sim | não | Gerenciador de CPE |
| MK-Auth | mkauth | não validado | login, MAC, CPF/CNPJ | sim | não | — |
| Voalle | voalle | não validado | login, CPF/CNPJ | sim | não | — |
Validado quer dizer que o conector roda contra uma instalação real do ERP com clientes de verdade. Não validado quer dizer que foi escrito a partir da documentação pública da API do ERP: rotas e nomes de campo ainda não confirmados ficam isolados num bloco “a confirmar” do conector, para ajuste rápido na primeira instalação real. Antes de ativar um deles, use Testar conexão e confira o cartão Assinante de uma CPE conhecida depois da primeira sincronização.
“—” na coluna do módulo de ACS quer dizer que esta wiki ainda não documenta o caminho naquele ERP; se ele tiver integração com ACS do tipo GenieACS, a receita genérica de Módulo de ACS do ERP vale.
O que é igual para todos (busca, escolha do contrato, política de Wi-Fi, ciclo de sincronização, mensagens de erro) está em Conector de ERP e Vínculo com o contrato.
SGP validado
No SGP, antes
- Em Administração › Integrações › Tokens, crie um token para o Lumina.
- Marque a rota
/api/ura/consultacliente/ entre as permitidas. Para o Lumina gravar o PPPoE do contrato, o token precisa expor servico_senha.
- Se o token usar “Hosts permitidos”, libere o IP de saída do ACS.
Alternativa ao token: usuário e senha (HTTP Basic). O SGP não aceita Bearer.
Configuração no Lumina
| Chave | Valor |
base_url | https://provedor.sgp.net.br, obrigatório |
app · token | Nome do app e token (recomendado; vão no corpo do POST) |
basic_user · basic_pass | Alternativa por HTTP Basic |
O que o Lumina chama
POST https://provedor.sgp.net.br/api/ura/consultacliente/
Content-Type: application/x-www-form-urlencoded
Accept: application/json
User-Agent: LuminaACS
login=cliente%40isp&app=lumina&token=8f3c…
- Só POST: um 405 vira a mensagem “a API do SGP só aceita POST”. Timeout de 20 s por chamada; resposta lida até 4 MB.
- Campos de busca:
login (PPPoE), servico_serial (primeiro a chave completa OUI-Modelo-Serial, que o SGP guarda quando o serviço foi cadastrado por um GenieACS, e depois só o serial), MAC e cpfcnpj.
- Escolha entre vários contratos: o que tem
servico_login igual ao PPPoE; senão o primeiro ativo (contratoStatus 1 ou “Ativo”); senão o primeiro.
Mapa de campos SGP → Lumina
| Campo no SGP | Campo no Lumina | Observação |
contratoId · clienteId | contract_id · customer_id | |
razaoSocial | customer_name | Cai para nome quando vazio. |
cpfCnpj | document | |
contratoStatusDisplay | status | Texto exibível (“Ativo”, “Cancelado”…). |
contratoStatus | — | Numérico; 1 = ativo, usado só na escolha do contrato. |
servico_plano | plan | Cai para planointernet quando vazio. |
servico_login | login · config.pppoe_username | Conferido contra o PPPoE lido da CPE. |
servico_senha | config.pppoe_password | Só quando o token expõe; cifrada, sai apenas por /secrets. |
endereco_logradouro, _numero, _complemento, _bairro, _cep | address | Concatenados com vírgulas; o CEP entra como · CEP …. |
endereco_cidade + endereco_uf | city | Cidade/UF. |
popNome | pop | |
endereco_ll | latlng | |
telefones[].contato | phones[] | |
servico_wifi_ssid, _channel, _password | wifi_desired.ssid_24 · channel_24 · senha cifrada | Com “ERP é a fonte”, vão para config.wifi_ssid_24 e config.wifi_password_24. |
servico_wifi_ssid_5, _channel_5, _password_5 | wifi_desired.ssid_5 · channel_5 · senha cifrada | 5 GHz, mesma regra. |
servico_tipo_conexao, servico_mac e servico_vlan são lidos mas não aparecem na resposta do vínculo. O documento do SGP é guardado sem senhas.
Gerenciador de CPE do SGP (NBI)
Em Sistema › Gerenciador de CPE: tipo GenieACS, URL https://acs.provedor.com.br/nbi, usuário qualquer (por exemplo sgp) e a chave lmk_… como senha, com os escopos da NBI. Sincronizar, Importar Wi-Fi, Definir Wi-Fi, Configurar WAN, Reiniciar e Atualizar firmware viram as tarefas descritas em Módulo de ACS do ERP.
Estado: a homologar com a TSMX. O modo NBI está coberto por testes, mas ainda não foi homologado com uma instância real do SGP. Nas TP-Link TR-181, o PPP da operadora não é o PPP.Interface.1 que o Gerenciador de CPE grava: para PPPoE e Wi-Fi que sobrevivem a reset, use o conector.
IXC Soft não validado
No IXC, antes
Em Configurações › Tokens de API, um token com permissão de listagem nas tabelas radusuarios, cliente e cliente_contrato. Libere o IP de saída do ACS, se o token restringir origem.
Configuração no Lumina
| Chave | Valor |
base_url | https://ixc.provedor.com.br |
token | Token no formato id:chave, enviado em Authorization: Basic |
O que o Lumina chama
POST https://ixc.provedor.com.br/webservice/v1/radusuarios
Authorization: Basic base64(id:chave)
ixcsoft: listar
Content-Type: application/json
{ "qtype": "radusuarios.login", "query": "cliente@isp", "oper": "=",
"page": "1", "rp": "20", "sortname": "radusuarios.id", "sortorder": "asc" }
- A resposta é
{"page","total","registros":[…]} ou {"type":"error","message":"…"}.
- Por login PPPoE ou MAC:
radusuarios (campos login ou mac). Por CPF/CNPJ: o cliente em cliente e depois os logins dele em radusuarios.
- Completa com o cadastro do cliente (
cliente) e o contrato (cliente_contrato: status e plano).
- Traz login, senha, MAC, cliente, documento, endereço, telefones, status e plano. Não busca por serial nem traz Wi-Fi.
Hubsoft não validado
No Hubsoft, antes
Uma aplicação OAuth2 (client_id e client_secret) e um usuário de API com permissão de consulta de clientes.
Configuração no Lumina
| Chave | Valor |
base_url | https://api.provedor.hubsoft.com.br |
client_id · client_secret | Credencial OAuth2 da aplicação |
username · password | Usuário de API |
O que o Lumina chama
POST https://api.provedor.hubsoft.com.br/oauth/token
{ "grant_type": "password", "client_id": "…", "client_secret": "…", "username": "…", "password": "…" }
GET https://api.provedor.hubsoft.com.br/api/v1/integracao/cliente?busca=login&termo_busca=cliente@isp&limit=…
Authorization: Bearer <token>
- Busca por login do serviço (
busca=login) ou CPF/CNPJ (busca=cpf_cnpj), com o valor em termo_busca. O token vai como Bearer.
- Traz, por serviço do cliente, contrato, plano, status, login, senha, endereço de instalação e telefones. Não busca por serial nem MAC; não traz Wi-Fi.
Gerenciador de CPE do Hubsoft (NBI)
Em Configurações › Integrações › Gerenciador de CPE: escolha GenieACS, URL https://acs.provedor.com.br/nbi, um usuário qualquer e a chave lmk_… como senha.
MK-Auth não validado
No MK-Auth, antes
Em Sistema › API, cadastre uma API (id e segredo) com permissão de consulta de clientes.
Configuração no Lumina
| Chave | Valor |
base_url | https://mkauth.provedor.com.br |
client_id · client_secret | Id e segredo da API, enviados por HTTP Basic |
O que o Lumina chama
GET https://mkauth.provedor.com.br/api/cliente/show/login/cliente@isp # por login PPPoE
GET https://mkauth.provedor.com.br/api/cliente/show/mac/40:ED:00:11:22:33 # por MAC
GET https://mkauth.provedor.com.br/api/cliente/show/12345678900 # por CPF/CNPJ
Authorization: Basic base64(id:segredo)
- Busca por login PPPoE, MAC ou CPF/CNPJ.
- Traz login, senha, nome, documento, plano, situação (ativo, bloqueado, inativo), endereço, MAC e telefones. Não busca por serial; não traz Wi-Fi.
Voalle não validado
No Voalle, antes
Uma integração de terceiros com client_id, client_secret e syndata (pedida à Voalle ou cadastrada no ERP), com permissão de consulta de pessoas, contratos e autenticações de contrato.
Configuração no Lumina
| Chave | Valor |
base_url | Gateway da API externa, https://erp.provedor.com.br:45715 no padrão |
auth_url | Servidor de identidade, https://erp.provedor.com.br:45700 no padrão |
client_id · client_secret · syndata | Credenciais da integração; syndata vai junto do pedido de token |
O que o Lumina chama
POST https://erp.provedor.com.br:45700/connect/token
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials&scope=syngw&client_id=…&client_secret=…&syndata=…
- Com o token (como
Bearer), consulta a API externa de terceiros no gateway, sob /external/integrations/thirdparty/:
| Busca | Rotas, em ordem |
| Login PPPoE | authenticationcontracts/user/{login} → contracts/{id} |
| CPF/CNPJ | people/txid/{documento} → contracts/people/{id} → authenticationcontracts/contract/{id} de cada contrato |
- Traz contrato, plano, status, cliente, documento, login, senha, MAC e endereço. Não busca por serial nem MAC; não traz Wi-Fi.
Nomes históricos na API
O conector nasceu só para o SGP, e alguns identificadores da API ficaram com esse nome. Todos valem para qualquer ERP:
| Identificador | Onde | Significa hoje |
sgp (booleano) | Respostas de …/config e …/subscriber | true quando há qualquer conector ativo; use erp, que diz qual. |
sgp_error | Resposta de PUT …/config com link_login | Categoria do erro quando o vínculo falhou (a configuração foi salva). |
wifi_policy: "sgp" | Configuração do conector | Sinônimo de "erp" (“ERP é a fonte”). |
/api/v1/integrations/sgp | Rotas do conector | Nome antigo de /api/v1/integrations/erp; responde igual, para o ERP do tenant. |
erp.sgp.save | Auditoria | Nome antigo de erp.save, presente só em registros anteriores a 09/10/2026. |
Receitas de integração
Os fluxos que os ERPs mais pedem, prontos para adaptar.
Achar a CPE do cliente a partir do contrato
O caminho barato é o login PPPoE, que o ERP conhece. pppoe é igualdade exata; q busca por trecho também em MAC, serial e SSID.
curl -sG -H "$KEY" "$ACS/api/v1/devices" --data-urlencode "pppoe=cliente@isp" | jq -r '.items[0].key // empty'
curl -sG -H "$KEY" "$ACS/api/v1/devices" --data-urlencode "q=40:ED:00:11:22:34" --data-urlencode "limit=5"
Instalação pelo app do técnico
# CPEs online que subiram com o login padrão do provedor
curl -s -H "$KEY" "$ACS/api/v1/devices?default_login=1&status=online"
# grava o cliente (sem conector: valores do seu ERP) e cria a tarefa apply_config
curl -s -X PUT -H "$KEY" -H "Content-Type: application/json" \
-d '{"pppoe_username":"cliente@isp","pppoe_password":"s3nh4","wifi_ssid_24":"Casa","wifi_password_24":"senha2g24"}' \
"$ACS/api/v1/devices/$CPE/config" | jq -r .task_id
# acompanha (a cada 3 a 5 s por até um minuto; se seguir pending, a CPE grava no próximo Inform)
curl -s -H "$KEY" "$ACS/api/v1/tasks/b32fqgm2ejgjkahhetfa" | jq -r .status
Com o conector de ERP ativo, troque o PUT por POST …/subscriber {"login":"cliente@isp"} e trate needs_pppoe_password (ver vincular). GET /api/v1/tasks/{id} exige tasks:read; com só devices:read, procure o id em GET /api/v1/devices/{key}/tasks.
Tela de atendimento, uma chamada por aba
| Aba | Chamada |
| Resumo | GET /devices/{key} |
| Contrato | GET /devices/{key}/subscriber |
| Conectados | GET /devices/{key}/hosts (depois de refresh-hosts) |
| Wi-Fi | GET /devices/{key}/wifi |
| Sinal | GET /devices/{key}/metrics?range=24h |
| Alertas | GET /alerts?device={key} |
Ao abrir a tela, dispare actions/refresh-hosts. Mostre a última lista com o read_at dela enquanto o atendente lê o resumo e troque-a quando chegar o task.completed do id devolvido, ou quando GET …/list-reads mostrar pending: false e um read_at maior. Se a resposta vier com read: "fresh", a lista já é de menos de um minuto atrás. Mantenha um SSE com ?keys={key} para ver o Inform chegar.
Cancelamento com devolução de equipamento
curl -s -X DELETE -H "$KEY" "$ACS/api/v1/devices/$CPE/subscriber" # desfaz vínculo e configuração salva
curl -s -X DELETE -H "$KEY" "$ACS/api/v1/devices/$CPE?mode=release" # libera a vaga de licença
A ONU volta a ser aceita sozinha quando for resetada de fábrica na próxima instalação. Se o equipamento não voltar, use mode=block.
Inventário completo para o ERP
Pagine /devices com limit=500 (ver paginação) ou baixe GET /devices/export.csv com os mesmos filtros e confira a última linha. Depois da carga inicial, mantenha atualizado por webhooks (device.discovered, device.removed, device.online/offline) em vez de varreduras periódicas.
Abrir OS automática por sinal óptico ou reset de fábrica
Crie a regra optical_low (ou factory_reset) com o limiar do seu padrão de rede e assine alert.opened. O payload traz a CPE, o serial e o detalhe da medição; com o vínculo do ERP ativo, o ERP já sabe qual contrato e qual POP. Para reset, também dá para assinar direto device.factory_reset. Feche a OS no alert.resolved.
Contratos com cadastro inválido no ERP
Quando o ERP tem, por exemplo, uma senha Wi-Fi de 5 caracteres, o campo é ignorado e a CPE registra erp_config_rejected. Um relatório para o time de cadastro:
curl -s -H "$KEY" "$ACS/api/v1/devices/$CPE/events?limit=500" \
| jq '.items[] | select(.kind=="erp_config_rejected") | {at, contrato: .detail.contrato, campos: .detail.campos}'
Erros, limites e boas práticas
Os códigos HTTP, os tetos do servidor e o que fazer em cada caso.
Códigos
| Código | Significa | O que fazer |
200 | Leitura ou operação síncrona concluída | — |
201 | Recurso criado (chave de API, firmware novo) | Guarde o segredo da chave, que não se repete. |
202 | Tarefa, lote ou campanha aceitos, ainda não executados | Acompanhe por /tasks/{id}, /batches/{id} ou webhook. Não é erro. |
204 | Lote cancelado ou continuado; campanha cancelada | — |
400 | Corpo inválido, valor fora de faixa, kind desconhecido, provedor de ERP fora da lista, conector desligado, diagnóstico que o modelo de dados não tem, etiqueta fora da régua | Leia error; a mensagem diz o campo. |
401 | Chave inválida, revogada ou vencida (na NBI, também IP fora da lista e limite excedido) | Não retente com a mesma chave. |
403 | Escopo faltando, IP fora da lista da chave, reset de fábrica pedido, vínculo com outro login sem devices:write | Ajuste os escopos ou a lista de IPs. |
404 | CPE, tarefa ou recurso inexistente; ação desconhecida; contrato não encontrado no ERP; recurso que a CPE não expõe por TR-069 | Confira a chave da CPE exatamente como a API a devolve. |
409 | Confirmação exigida (confirm_count, confirm_required, allow_downgrade); CPE agente EasyMesh (mesh_agent); firmware em uso; tarefa em estado que não permite cancelar ou repetir; feedback antes de a CPE voltar | Leia os campos extras do corpo e decida; não reenvie às cegas. |
422 | Lote acima do teto de CPEs; firmware recusado de vez (blocked) | Restrinja o filtro; escolha outro arquivo. |
429 | Limite da chave, teto de tarefas de escrita ou de consultas de contrato | Respeite o Retry-After (segundos). |
500 | Erro interno | Retente com backoff; cite o X-Request-Id no chamado. |
503 | Recurso indisponível neste processo ou momento (Connection Request, armazenamento, licença, item do catálogo ainda baixando) | Tente de novo em instantes. |
504 | A requisição passou de 60 s | Restrinja a consulta (filtro, prefix, limit). |
Limites
| Assunto | Limite |
| Tamanho | Corpo JSON 1 MB · tarefa NBI 256 KB · firmware 512 MB · remoção em lote 1000 chaves |
| Tempo | 60 s por requisição (fora SSE e CSV, que tem 10 min) · Connection Request até 12 s · espera da NBI até 30 s |
| Por chave | O limite configurado (padrão 600/min, janela deslizante) |
| Por principal | 120 tarefas que gravam ou reiniciam por minuto, fora dos lotes · consulta de contrato 20/min e 300/h |
| Páginas | /devices, /tasks, /alerts, /devices/{key}/events até 500 · /params até 20 000 · CSV até 200 000 linhas · NBI até 1000 CPEs e 200 tarefas ou faults |
| Lotes que gravam | Até 500 CPEs, 300 tarefas/min, 5 min antes de começar, primeira onda de 10 (padrões da instalação) |
| Etiquetas | 20 por CPE, 32 caracteres (64 pela NBI) |
| Conector de ERP | 3 chamadas/s por padrão · 50 CPEs por passada · uma passada por minuto · sincronização manual de até 200 |
| Webhooks e SSE | 15 s de timeout · 10 tentativas · eventos guardados 7 dias · SSE 30 min por conexão, até 50 chaves |
Boas práticas
- Não faça polling de CPE. Assine webhooks.
- Peça a lista de hosts quando for mostrá-la, não em varredura do parque.
- Uma chave por integração, com o mínimo de escopos e lista de IPs.
- Provisione pela configuração do assinante, não por
set_values solto: só ela volta sozinha depois de um reset.
- Escreva com
connection_request: false em manutenção de massa.
- Trate
null: ausência não é zero.
- Nunca gere a chave da CPE no ERP; leia-a de
/devices.
- Idempotência nos webhooks pelo
id da entrega.
- Exportação CSV: confira a última linha antes de importar.
Login de pessoas por SSO
Não afeta integrações por chave. Quando o login OIDC do console falha, o ACS redireciona para /login?sso_error=<código>. Os códigos são contrato (só se acrescentam): generic, disabled, issuer, state, expired, provider, exchange, id_token, email_missing, email_unverified, domain, role, no_user, user_disabled, mgmt_ip, internal.
Índice de rotas
Todas as rotas da API, uma por linha. autenticado é qualquer chave ou sessão válida; público dispensa credencial.
| Método | Rota | Escopo | Faz |
| Saúde (fora de /api/v1) |
| GET | /healthz | público | Processo no ar: {"status":"ok","version"}. |
| GET | /readyz | público | Banco e dependências prontos; 503 quando não. |
| Painel e referência |
| GET | /api/v1/dashboard/summary | devices:read | Contadores do parque (cache de 5 s). |
| GET | /api/v1/dashboard/history | devices:read | Série do tenant: ?range=24h|7d|30d. |
| GET | /api/v1/events/stream | devices:read | SSE do tenant. |
| GET | /api/v1/profiles | devices:read | Perfis de modelo. |
| GET | /api/v1/diagnostics/defaults | devices:read | Servidor de teste de velocidade embutido. |
| GET | /api/v1/settings/installation | devices:read | Logins PPPoE padrão. |
| GET | /api/v1/settings/ntp | devices:read | NTP e fuso do provedor. |
| GET | /api/v1/guides/context | devices:read | URLs do CWMP do tenant, usuário do Inform, intervalo periódico e salvaguardas de lote desta instalação. |
| GET | /api/v1/license | autenticado | Estado da licença e contagem de CPEs. |
| GET | /api/v1/auth/me | autenticado | Quem é o principal (para chave: role api). |
| GET | /api/v1/news · POST /news/seen | autenticado | Mural de novidades do console. |
| CPEs: leitura |
| GET | /api/v1/devices | devices:read | Lista e filtra. |
| GET | /api/v1/devices/export.csv | devices:read | CSV. |
| GET | /api/v1/devices/facets | devices:read | Fabricantes, modelos e firmwares com contagem. |
| GET | /api/v1/devices/{key} | devices:read | Detalhe. |
| GET | /api/v1/devices/{key}/params | devices:read | Parâmetros crus. |
| GET | /api/v1/devices/{key}/events | devices:read | Eventos. |
| GET | /api/v1/devices/{key}/tasks | devices:read | Tarefas da CPE. |
| GET | /api/v1/devices/{key}/metrics | devices:read | Histórico. |
| GET | /api/v1/devices/{key}/hosts | devices:read | Hosts. |
| GET | /api/v1/devices/{key}/list-reads | devices:read | Estado das leituras. |
| GET | /api/v1/devices/{key}/wifi · /wifi/analysis | devices:read | Wi-Fi. |
| GET | /api/v1/devices/{key}/lan | devices:read | LAN. |
| GET | /api/v1/devices/{key}/nat | devices:read | NAT. |
| GET | /api/v1/devices/{key}/mesh | devices:read | Mesh. |
| GET | /api/v1/devices/{key}/time | devices:read | Relógio. |
| GET | /api/v1/devices/{key}/firmware-next | devices:read | Próximo degrau. |
| CPEs: ações, tarefas, etiquetas e remoção |
| POST | /api/v1/devices/{key}/actions/{action} | varia | Ações. |
| POST | /api/v1/devices/{key}/tasks | conforme kind | Tarefa crua. |
| POST | /api/v1/devices/{key}/tasks/{id}/close | devices:write | Encerra download preso. |
| PUT | /api/v1/devices/{key}/tags | devices:write | Etiquetas. |
| DELETE | /api/v1/devices/{key} | devices:write | Remove; ?mode=remove|release|block. |
| POST | /api/v1/devices/remove | devices:write | Remove até 1000. |
| GET | /api/v1/device-removals | devices:read | CPEs recusadas. |
| POST | /api/v1/device-removals/{key}/readmit | devices:write | Volta a aceitar. |
| Assinante e configuração do assinante |
| GET | /api/v1/devices/{key}/subscriber | devices:read | Vínculo e configuração salva. |
| POST | /api/v1/devices/{key}/subscriber | safe-ops (outro login: write) | Vincula. |
| DELETE | /api/v1/devices/{key}/subscriber | devices:write | Desvincula. |
| GET | /api/v1/devices/{key}/config | devices:read | Configuração do assinante. |
| PUT | /api/v1/devices/{key}/config | devices:write | Salva e grava. |
| DELETE | /api/v1/devices/{key}/config | devices:write | Apaga a salva. |
| POST | /api/v1/devices/{key}/config/lookup | devices:safe-ops | Consulta contratos. |
| GET | /api/v1/devices/{key}/secrets | devices:safe-ops | Senhas guardadas (auditado). |
| Tarefas, lotes e presets |
| GET | /api/v1/tasks | tasks:read | Lista com filtros. |
| GET | /api/v1/tasks/counts | tasks:read | Contagem por status. |
| GET | /api/v1/tasks/{id} | tasks:read | Uma tarefa. |
| POST | /api/v1/tasks/{id}/cancel · /retry | tasks:read + kind | Cancela ou repete. |
| GET | /api/v1/batches · /batches/{id} | tasks:read | Lotes. |
| POST | /api/v1/batches | bulk:write | Cria lote. |
| POST | /api/v1/batches/firmware-preview | bulk:write | Prévia de firmware em massa. |
| POST | /api/v1/batches/{id}/cancel · /continue | bulk:write | Cancela; libera depois da primeira onda. |
| GET | /api/v1/presets | devices:read | Presets (operações mascaradas sem presets:write). |
| POST | /api/v1/presets · /presets/validate | presets:write | Cria; valida sem salvar. |
| PATCH DELETE | /api/v1/presets/{id} | presets:write | Edita; apaga. |
| Firmware e campanhas |
| GET POST | /api/v1/firmwares | read · firmware:write | Repositório; envio multipart. |
| PATCH DELETE | /api/v1/firmwares/{id} | firmware:write | Edita; apaga. |
| GET | /api/v1/firmwares/options · /cloud · /updates | devices:read | Opções do parque; catálogo; histórico. |
| POST | /api/v1/firmwares/cloud/{id}/fetch | firmware:write | Baixa do catálogo. |
| POST | /api/v1/firmwares/updates/{id}/feedback | firmware:write | Registra se funcionou. |
| GET POST | /api/v1/campaigns | read · firmware:write | Campanhas. |
| POST | /api/v1/campaigns/{id}/cancel | firmware:write | Cancela. |
| Alertas |
| GET | /api/v1/alerts · /alerts/groups | devices:read | Lista; agrupa por regra. |
| POST | /api/v1/alerts/{id}/resolve · /alerts/resolve | alerts:write | Resolve um; resolve os de uma regra. |
| GET | /api/v1/alert-rules · /channels | devices:read | Regras; canais. |
| POST PATCH DELETE | /api/v1/alert-rules · /channels (e /{id}) | alerts:write | Cria, edita, apaga; POST /channels/{id}/test testa. |
| Só pessoas no console (nenhuma chave recebe estes escopos) |
/api/v1/auth/* (login, primeiro acesso, SSO, logout, senha, 2FA, sessões) | público · sessão | Login e conta de pessoas. |
/api/v1/users e subrotas | users:write | Usuários do tenant. |
/api/v1/settings/sso · /mgmt-ips · /cwmp-ips · PUT /ntp · PUT /installation | settings:write | Configurações do tenant (cwmp-ips só no hospedado). |
/api/v1/license/activate · /heartbeat · /deactivate · /invoices | settings:write | Licença e faturas. |
POST /api/v1/guides/cwmp-password · POST /api/v1/news | settings:write | Revela a senha de bootstrap (auditado); publica novidade. |
/api/v1/integrations/erp · /api-keys · /webhooks | integrations:write | Conector, chaves e webhooks. |
Fora do /api/v1 existem ainda as rotas que as próprias CPEs usam (/cwmp/…, /fs/… para firmware por URL assinada e /tr143/… para o teste de velocidade) e a NBI. Nenhuma delas é para o ERP chamar, exceto a NBI.
Vocabulário
Os termos que aparecem em toda a wiki.
- Inform
- Sessão que a CPE abre com o ACS, periodicamente ou por evento (boot, mudança de valor, Connection Request). É o único momento em que o ACS pode falar com ela.
- Connection Request
- Cutucão do ACS para a CPE abrir uma sessão agora. Por HTTP na porta da CPE ou, atrás de NAT e com o STUN do Lumina ligado, por UDP.
- Chave da CPE
OUI-ProductClass-SerialNumber. Mesmo formato de id do GenieACS, o que preserva vínculos já cadastrados no ERP.
- Perfil de modelo
- Mapa que traduz “SSID do 5 GHz” nos caminhos TR-069 reais de cada fabricante e firmware, com as particularidades conhecidas. É o que faz as ações funcionarem sem o ERP saber de caminhos.
- Status da CPE
online; unstable (Informs perdidos repetidos: padrão 3 em 24 h com um na última hora; volta a online depois de 1 h sem novos); offline (sem informar além de 1,5× o intervalo); unlicensed (CPE nova além do limite da licença).
- CPE licenciada
- CPE que informou nos últimos 30 dias. Acima do limite contratado há tolerância; só CPEs novas são barradas, as já licenciadas continuam gerenciadas. Um agente EasyMesh cujo controlador é CPE licenciada não consome licença.
- Configuração do assinante
- PPPoE e Wi-Fi do cliente guardados por CPE. O ACS grava a pedido e regrava sozinho depois de um reset de fábrica.
- Login padrão
- Login PPPoE com que a CPE sai do preset do fabricante para o provedor. CPE nesse login está aguardando instalação (
default_login: true).
- Quarentena de bootstrap
- Estado de uma CPE que voltou com a senha genérica depois de já ter credencial própria. Fica sem configuração do assinante até um operador confirmar.
- Agente e controlador EasyMesh
- Na rede mesh, o controlador é a CPE principal; o agente é o nó satélite. Configuração de Wi-Fi, LAN e NAT é do controlador.
- Tenant
- O provedor dentro do ACS. Cada chave de API pertence a um tenant e só vê as CPEs dele.
- Lote
- A mesma tarefa aplicada a todas as CPEs de um filtro, com salvaguardas.
- Campanha
- Lote de atualização de firmware com janela de horário, taxa e limite de falhas.
O que mudou
Para quem integrou por uma versão anterior da documentação. quebra marca o que muda o que uma integração antiga recebe ou precisa enviar.
14/10/2026 — esta wiki
A documentação passou de uma página única para esta wiki. Conteúdo conferido com o código e acrescido de rotas que já existiam e não estavam documentadas:
| Assunto | Agora |
GET /tasks | Traz total e aceita q, created_by, from/to além de status, kind e batch. A página anterior dizia que /tasks não trazia total. |
| Tarefas | GET /tasks/counts, POST /tasks/{id}/cancel, POST /tasks/{id}/retry e POST /devices/{key}/tasks/{id}/close. |
| Etiquetas | PUT /devices/{key}/tags, com régua própria (diferente da NBI). |
| Lista de CPEs | Filtros hardware, optical_low e new_24h; no lote, OpticalLow e NewLast24h. Rota /devices/facets. |
| Wi-Fi | guest por banda em GET …/wifi, só leitura. |
| Assinante | Campo erp com o identificador do conector ativo na configuração do assinante e no vínculo. |
| Guias | GET /guides/context (URLs do CWMP e salvaguardas desta instalação). |
Desde a revisão de 02/09/2026
| Assunto | Antes | Agora |
| Reset de fábrica quebra | Ação, tarefa e NBI aceitavam. | Sempre recusado (403 na ação, 400 na tarefa e na NBI). |
| Lotes quebra | POST /batches criava e respondia 202 running. | confirm_count obrigatório (409 com count), janela de arrependimento, primeira onda com /continue, teto de 500 CPEs e 300/min. Filtro com as chaves Q, Status (lista), Model… |
| Alertas e eventos quebra | Só items, cortados no limite. | items, total e next_cursor; /alerts aceita state=all. |
| Firmware quebra | Upload com product_classes/hardware_versions; ação só com firmware_id. | Upload com model, hardware_version e from_versions obrigatório; ação com cloud_id, confirm e respostas 409/422; campanha com from_version e filtro fixo. |
| Campanhas quebra | Criadas na hora, sem salvaguardas. | confirm_count obrigatório em toda campanha, com as salvaguardas de lote. |
| Tarefa download quebra | Qualquer URL. | Só URL assinada do ACS ou host liberado; campos URL, FileType, FileSize, TargetFileName. |
| Wi-Fi quebra | radio_enabled, channels_in_use, guard_interval. | Removidos. Novos: wps, isolation, clients, shared, mixed, optimization, scan_supported; band: "all"; senha de 8 a 63. |
| SSE quebra | device.update; ping como comentário. | Eventos da tabela do SSE; ping nomeado; ?keys=; bye a cada 30 min. |
| NBI quebra | download entregava o arquivo como pedido. | Exige firmware:write e passa pelo portão de segurança; getParameterValues exige só devices:read. |
| Hosts, clientes Wi-Fi e mesh quebra | Relidos em toda releitura periódica. | Lidos só sob demanda, com read_at, pending, read_failed. Quem lia /hosts contando com a releitura periódica passa a ver a lista parada: dispare refresh-hosts antes. |
| Hosts conectados quebra | “Conectados” era o hosts_count. | hosts_active e hosts_read_at; no CSV, coluna hosts_na_tabela_da_cpe e, no fim, hosts_ativos e hosts_lidos_em; /metrics deixa de amostrar hosts. |
| Ramos não guardados quebra | DataElements, AC, Stats no inventário. | Fora de /params e da NBI; preset com set neles é recusado. |
| ALG e disparo de portas quebra | alg, alg_order e triggering_supported em /nat. | Removidos; {"alg":…} na ação nat é recusado com 400. |
| Conector de ERP | Um único sistema. | Escolha do sistema no campo provider; /integrations/erp/providers descreve cada conector; auditoria erp.save. |
| Configuração do assinante | Não existia. | …/config, …/config/lookup, …/secrets, settings/installation, default_login, apply_config, reaplicação após reset. |
| Chaves de API | Janela fixa, 0 = sem limite. | Janela deslizante, Retry-After calculado, 0 = 600; teto de 120 tarefas de escrita por minuto; X-Request-Id. |
Webhook device.online | Só na saída de instável. | Também na volta de offline; quarantined: true na CPE em quarentena. |
| Agentes de mesh TP-Link | Recusados com a credencial do controlador. | Aceitos quando constam na tabela EasyMesh do controlador; evento mesh_cred_accepted. |
| Rotas novas | — | /nat, /mesh, /time, /wifi/analysis, /firmware-next, /list-reads; ações wifi-opt, nat, nat-vs, time, confirm-bootstrap, reset-inform-credentials; SSE device.lists. |