Guia de integração com ERP e outros sistemas
Delimitar uma integração antes de a construir: o que a API alcança, o que não alcança e onde estão as fronteiras
Este guia destina-se ao programador que está a delimitar uma integração entre o FreeMaint e outro sistema: um ERP, um historiador SCADA, um software de armazém ou de contabilidade. Os restantes artigos desta secção explicam como chamar a API; este fala deliberadamente de fronteiras, porque o erro caro nunca é um cabeçalho errado: é descobrir a meio do desenvolvimento que uma capacidade que dava como garantida não existe. Cada limite indicado abaixo está aqui de propósito.
Uma chave, três portas
A chave de API (começa por fmk_) é criada em Definições da Empresa > API (/company-settings/api) por um administrador, ou por uma pessoa para si própria em Perfil > Segurança > Chaves API, e enviada como Authorization: Bearer fmk_... . Autentica exatamente três coisas: o endpoint GraphQL em POST /api/v1/graphql, o endpoint de ingestão de energia, e /public-api/whoami e /public-api/ping. NÃO autentica as rotas REST em /api/v1/{resource} — essas são a API interna da aplicação e exigem uma sessão de utilizador (JWT). Construa a sua integração sobre GraphQL: é essa a superfície que uma chave alcança.
O que uma chave de API lhe dá
A chave em claro é mostrada uma única vez, na criação, e depois disso nunca mais é recuperável: guarda-se apenas um hash SHA-256, juntamente com os 6 caracteres que se seguem a fmk_ para que a lista consiga identificar a chave no ecrã.
Nome
Um rótulo legível: «Ponte ERP Oracle», «Quiosque da linha 2». Emita uma chave por cada sistema consumidor, para poder revogar uma sem perturbar as outras.
Nível de acesso
READ, READ_STOCK ou FULL, uma hierarquia ordenada que se soma às permissões do proprietário. READ executa apenas consultas e é a predefinição. READ_STOCK acrescenta a mutação adjustStock. FULL permite todas as operações que a função do proprietário permite, incluindo criar, alterar e eliminar — e o ponto de ingestão de energia quando o proprietário pode criar leituras de energia.
Validade
Um expiresAt facultativo. Passado esse momento, a chave deixa de autenticar. Deixe-o vazio para uma integração de serviço permanente; preencha-o para um prestador ou um piloto.
Revogação
isActive é um interruptor por chave que produz efeito em segundos. O registo é conservado para auditoria, por isso uma chave revogada continua rastreável em vez de desaparecer.
Última utilização
lastUsedAt regista a chamada autenticada mais recente: o suficiente para detetar uma chave que já ninguém usa.
Proprietário
A pessoa em nome de quem a chave atua. Cada chamada transporta as permissões dela e a visibilidade de dados dela, e o nome dela assina o que a chave escreve. Para um ERP, crie um utilizador dedicado — «Integração ERP» — com uma função que cubra exatamente aquilo de que a ponte precisa, e atribua-lhe a chave.
O que uma chave de API não lhe dá
São limites reais da implementação atual, não omissões desta página. Se a sua revisão de segurança depender de algum deles, desenhe a solução em conformidade agora e não depois de construir.
- Sem lista de módulos por chave. Uma chave lê e escreve o que a função do seu proprietário permite — para a restringir, atribua-a a um proprietário cuja função seja mais restrita. Para além disso, só se aplicam os três níveis de acesso: não pode enumerar, na própria chave, os módulos que ela pode usar.
- Sem lista branca de endereços IP. Uma chave é válida a partir de qualquer ponto da Internet. Trate-a como um segredo: um cofre ou o gestor de segredos da sua plataforma, nunca um repositório, uma pasta partilhada ou um pedido de suporte.
- Sem registo de utilização por chave. Só o lastUsedAt é conservado. Não existe histórico de chamadas por chave, nem quota por chave, nem qualquer registo de que chave executou que operação. O contador de quota diária é por EMPRESA e é partilhado por todas as chaves, pelo que uma integração descontrolada pode esgotar a quota de todas as outras.
- Sem aprovações em nome de outra pessoa. Uma chave nunca aprova uma etapa do fluxo de trabalho, mesmo quando o proprietário o poderia fazer: as aprovações continuam a ser um ato humano na aplicação.
- Uma empresa por chave. Uma chave fica ligada à empresa onde foi criada e não pode aceder aos dados de outra empresa: um registo de outra empresa simplesmente não é encontrado.
O que pode ler
O GraphQL expõe 16 consultas de leitura: whoami, workOrders, workOrder(id), assets, asset(id), assetDowntimes, meters(assetId), parts, part (por id ou código de barras), stockMovements, locations, vendors, customers, purchaseOrders, purchaseOrder(id | reference | erpReference) e floorPlans(locationId). Todas as listas podem ser percorridas até ao fim: workOrders, assets, assetDowntimes, meters, parts, purchaseOrders, locations, vendors e customers aceitam todas limit (de 1 a 200) mais offset, e workOrders, assets, parts, purchaseOrders, locations, vendors e customers aceitam ainda updatedSince para sincronizações incrementais. A página por omissão tem 50 linhas, exceto em locations, vendors e customers, que mantêm o anterior valor por omissão de 200. Todas as listas são ordenadas com um critério de desempate pelo id, pelo que uma fronteira de página nunca salta nem repete uma linha. stockMovements é o registo de stock a partir do qual se faz a reconciliação: lê-se do mais antigo para o mais recente e pagina-se com afterId — indique o id do último movimento recebido —, o que se mantém exato enquanto são escritos novos movimentos, e since permite iniciar uma recuperação do histórico a partir de uma data. As ordens de trabalho que estão na Reciclagem nunca são devolvidas. purchaseOrders filtra-se também por status e vendorId; os montantes vêm na moeda da empresa, que cada encomenda traz no campo currency.
Verificar a chave e ler ordens de trabalho numa só chamada: curl -X POST https://freemaint.com/api/v1/graphql -H "Authorization: Bearer $FMK" -H "Content-Type: application/json" -d '{"query":"{ whoami { companyId tier } workOrders(limit: 5, status: \"OPEN\") { id reference title status priority dueDate } }"}'
O que pode escrever
Doze mutações: criar, atualizar e eliminar ordens de trabalho, ativos e peças, mais adjustStock para um movimento de stock com sinal. Correm pelos mesmos serviços que a própria aplicação utiliza, pelo que as referências (WO-…, ASS-…, PART-…), os registos de auditoria e as notificações se comportam exatamente como na interface. Para o stock, prefira adjustStock: escreve um registo de auditoria StockMovement na mesma transação e recusa-se a colocar uma quantity abaixo de zero, ao passo que updatePart(quantity) define um valor absoluto e regista a diferença como um ajuste. Numa peça gerida por localização, indique locationId a adjustStock; aí, updatePart(quantity) é recusado. A décima primeira, addMeterReading, regista uma leitura de contador pelo mesmo serviço que o ecrã de contadores, pelo que os acionadores e a manutenção preventiva por contador reagem exatamente como para uma leitura introduzida na aplicação. A décima segunda, setPurchaseOrderErpReference, guarda o número de documento do seu ERP numa encomenda de compra, para que os dois registos possam ser emparelhados mais tarde com purchaseOrder(erpReference: …). Exige uma chave de acesso FULL e, de propósito, não dispara nenhum webhook: o seu ERP nunca recebe a sua própria escrita como evento.
O que a API não cobre de todo
O esquema GraphQL contém ordens de trabalho, ativos, paragens de máquina, peças e os seus movimentos de stock, contadores, encomendas de compra, localizações, fornecedores, clientes e plantas. Nada mais. O que se segue são partes plenamente funcionais do FreeMaint que hoje NÃO têm superfície de API: não podem ser lidas, escritas nem subscritas a partir de uma integração, e nenhuma combinação das consultas existentes as alcança.
- As inspeções: nem os modelos, nem as execuções agendadas, nem os resultados registados, nem os códigos de ação. Não existe consulta de inspeção, mutação de inspeção nem evento de webhook de inspeção.
- Utilizadores, funções e equipas: não pode listar, criar nem desativar um utilizador através desta API. O aprovisionamento de utilizadores faz-se na aplicação; no plano Enterprise pode ser automatizado com SCIM, que é um mecanismo separado e não faz parte desta API.
- Projetos: nenhuma consulta e nenhuma mutação.
- Requisições de compra: nenhuma consulta e nenhuma mutação. As encomendas de compra podem ser lidas (purchaseOrders, purchaseOrder), o seu ERP pode escrever nelas a sua referência (setPurchaseOrderErpReference) e os webhooks purchase_order.* anunciam-nas, mas uma encomenda não pode ser criada, alterada nem rececionada pela API: isso continua na aplicação.
- Ficheiros, fotografias e anexos: não pode carregar um documento nem uma fotografia, e também não os pode ler de volta. As fotografias das ordens de trabalho, as imagens das peças e as evidências de inspeção ficam todas fora da API. Um URL que apareça numa resposta, como o imageUrl de uma peça, não é um caminho de carregamento.
- Os planos de manutenção preventiva, os registos de tempo, as tarefas e as listas de verificação: não estão expostos.
Importante
Webhooks — o catálogo de eventos
Os webhooks são a metade «push» de uma integração: em vez de consultar repetidamente, é o FreeMaint que faz um POST para o seu URL no momento em que algo acontece. Configura-os em Definições da empresa > Webhooks (/company-settings/webhooks) e subscreve cada webhook aos eventos de que precisa. O catálogo é uma lista fixa.
- work_order.created — foi criada uma ordem de trabalho
- work_order.completed — uma ordem de trabalho passou a Concluída
- work_order.status_changed — uma ordem de trabalho mudou para qualquer estado; o payload traz new_status. Dispara além de work_order.completed, que se mantém inalterado
- request.created — foi submetido um pedido de manutenção
- request.status_changed — um pedido foi aprovado, recusado ou cancelado
- part.low_stock — uma peça desceu ao seu mínimo ou abaixo dele
- part.out_of_stock — uma peça chegou a zero; é um evento distinto, não uma variante de low_stock
- part.low_stock_summary — um resumo diário por empresa com as peças no ponto de encomenda ou abaixo, incluindo as que nunca se movimentaram
- part.out_of_stock_summary — o mesmo resumo diário, para as peças a zero
- part.consumed — uma peça foi consumida numa ordem de trabalho, ou dela devolvida; quantity tem sinal
- compliance.chain_divergence — a cadeia de hash de auditoria quebrou (Enterprise)
- asset.downtime_started — foi aberta uma paragem de máquina
- asset.downtime_ended — foi fechada uma paragem de máquina; a duração já pode ser imputada
- report.daily_summary — os trabalhos concluídos do dia, uma vez por dia à hora que escolher
- purchase_order.created — foi criada uma encomenda de compra; a carga útil contém a encomenda (reference, status, total_cost, currency, expected_date, erp_reference) e o fornecedor
- purchase_order.status_changed — uma encomenda de compra mudou de estado (aprovada, encomendada, cancelada…); a carga útil contém previous_status e new_status
- purchase_order.received — foi recebida uma entrega de uma nota de encomenda (enviado em cada entrega, incluindo as parciais); a carga útil lista cada linha com quantity_ordered, quantity_received (esta entrega), quantity_received_total e quantity_remaining, além de receipt_id, fully_received e closed_short; new_status é PARTIALLY_RECEIVED enquanto se espera mercadoria e RECEIVED quando a encomenda está completa ou fechada
Aviso
O envelope de entrega
Cada entrega GENERIC é um POST cujo corpo segue o mesmo envelope de três chaves: event, timestamp e data, em que data é indexado por entidade. Acompanham-no três cabeçalhos. X-FreeMaint-Signature tem a forma t=<segundos unix>,v1=<hex>, em que v1 é o HMAC-SHA256 da cadeia exata «<t>.<corpo em bruto>» assinada com o segredo desse webhook; X-FreeMaint-Event repete o nome do evento; X-FreeMaint-Delivery-Id identifica a entrega. Verifique a assinatura sobre o corpo EM BRUTO, antes de o interpretar: voltar a serializar o JSON altera os bytes e a assinatura deixa de coincidir.
{ "event": "work_order.created", "timestamp": "2026-09-05T08:14:02.371Z", "data": { "work_order": { "id": 5521, "title": "Fuga na bomba n.º 3", "status": "OPEN", "priority": "HIGH", "dueDate": "2026-09-08T00:00:00.000Z" } } }
Fiabilidade e monitorização
Uma entrega é tentada 3 vezes com recuo exponencial sobre uma base de 2 segundos — cerca de 2 s, depois 4 s, depois 8 s — e cada tentativa expira ao fim de 10 segundos. Um 2xx marca-a como entregue. As respostas 5xx e os erros de rede são repetidos. Um 4xx é tratado como recusa definitiva e NÃO é repetido de todo: se o seu endpoint responder 400 ou 401 por causa de um erro do seu lado, essa entrega pára ali e só um reenvio manual a recupera.
- Cada entrega é guardada: o evento, a carga útil exata enviada, o estado, o código HTTP que devolveu, os primeiros ~500 caracteres do corpo da sua resposta, o número de tentativas e a mensagem de erro.
- Os estados são PENDING, DELIVERED, FAILED, RATE_LIMITED (foi atingido o limite Starter de 100 entregas por dia) e SKIPPED_TIER (o plano já não dá direito a esse destino).
- Uma entrega guardada pode ser reenviada a partir do histórico do webhook. Reenvia a carga útil guardada sem alterações, pelo que as chaves de negócio que ela contém permanecem idênticas e um recetor que deduplique corretamente não a lançará duas vezes.
- Cada webhook traz lastTriggeredAt, lastSuccessAt, lastFailureAt e um failureCount, o que basta para uma verificação de saúde sem abrir o histórico completo.
- A lista de webhooks e o respetivo registo de entregas são consultáveis em todos os planos, pelo que uma integração parada por uma descida de plano fica visível em vez de morrer em silêncio.
- Nada o alerta quando as entregas começam a falhar: nem e-mail, nem notificação. Se uma falha silenciosa fosse problemática, consulte o registo de entregas a partir da sua própria monitorização.
Segurança
Os URL são validados quando guarda o webhook, e as regras são propositadamente estritas: um webhook é um pedido de saída feito pelos nossos servidores, pelo que uma regra permissiva seria um vetor de SSRF.
- As chaves são guardadas como hashes SHA-256: o valor em claro não pode ser recuperado do FreeMaint depois da criação.
- Os URL de webhook têm de usar https. O http simples e todos os outros esquemas são recusados.
- Apenas as portas 443 e 8443 são aceites.
- Os anfitriões de loopback, de ligação local e de gamas privadas (127.x, 10.x, 192.168.x, 172.16–31.x, ::1 e afins) são recusados: o recetor tem de ser alcançável a partir da Internet pública. Um sistema que só existe dentro da sua fábrica precisa de um relé público ou de um túnel.
- As credenciais no URL (https://user:pass@host) são recusadas.
- Os redirecionamentos não são seguidos: o endpoint que regista é o endpoint que recebe o POST.
- Cada webhook tem o seu próprio segredo de assinatura e existe uma ação de rotação do segredo. A rotação produz efeito imediato, por isso implante o novo segredo do seu lado na mesma janela.
- Os destinos SLACK e TEAMS recebem uma mensagem formatada para a plataforma e NENHUM cabeçalho HMAC do FreeMaint: para eles, o sigilo do URL é a única autenticação. Use GENERIC para tudo o que precise de verificar criptograficamente.
Padrões que funcionam bem hoje
Estas são as integrações que a superfície atual suporta de forma limpa, e o pormenor que torna cada uma delas segura em produção.
Espelhar o consumo num ERP
Subscreva part.consumed. quantity tem sinal — negativo quando a peça sai para o trabalho, positivo quando é devolvida — e a carga útil transporta um stock_movement_id que se mantém estável entre repetições e reenvios: use-o como chave de idempotência. A mutação adjustStock deliberadamente NÃO gera este evento, e é isso que impede que as suas próprias escritas lhe voltem em eco, em ciclo.
Enviar correções de stock
Chame adjustStock com um delta com sinal a partir do seu lado. Uma chave READ_STOCK consegue fazê-lo sem poder criar, alterar ou eliminar seja o que for, o que a torna uma escolha ajustada para uma ponte de stock de sentido único.
Criar ordens de trabalho a partir de outro sistema
createWorkOrder a partir de um alarme SCADA, de um pedido de helpdesk ou de um aviso de avaria do ERP. A ordem de trabalho recebe a sua referência normal e gera work_order.created como qualquer outra.
Manter um catálogo de peças sincronizado
parts(updatedSince:) devolve apenas o que mudou desde o momento que escolher. createPart e updatePart aceitam customReference — o seu próprio código para a peça — e part(barcode:) passa então a fazer a correspondência por ele, pelo que pode procurar uma peça pelo identificador que o seu ERP já usa. A unicidade não é imposta; garanti-la é consigo.
Fechar o ciclo na conclusão
work_order.completed transporta a ordem de trabalho e o seu estado anterior, o que basta para fechar um aviso de manutenção do outro lado.
O que precisa de ter
Duas condições independentes: uma para a API, outra para os webhooks. Uma empresa no Starter pode receber webhooks sem ter a API.
API GraphQL e chaves de API
Business e superior. O Core e o Starter não podem criar nenhuma chave.
Quota diária
5.000 chamadas por dia no Business, 100.000 no Enterprise. Contadas por empresa e por operação, reiniciando às 00:00 UTC — agrupar operações num único pedido HTTP poupa idas e voltas, nunca quota.
Webhooks
Starter e superior, com um limite de 100 entregas por dia; ilimitadas a partir do Business.
Destinos Slack e Teams
Enterprise. Um webhook genérico está disponível a partir do Starter.
Dica
Artigos relacionados
Esta página foi útil?