Lugotech Sync API

Documentação Completa - Integração com Salesflare

Versão 3.0 | Atualizado em Junho 2026

Bem-vindo à Lugotech Sync API

A Lugotech Sync API permite acesso e gestão completa de empresas (Accounts), contactos (Contacts), oportunidades e eventos de timeline sincronizados com o Salesflare.

Dados Disponíveis:
549.998 Accounts (empresas/clientes)
15.714 Contacts (pessoas/contactos)

Atualizado 01-02-2026

Novidades v3.0 (Junho 2026)

  • Comissões — módulo completo: /api/commissions/opportunity (receber, resultado, consulta, CPEs, reconciliação manual)
  • Oportunidades por comercial — novo filtro ownerEmail (owner, assignee, coldcaller, discovery, closer)
  • Ordenação dinâmica em oportunidades — parâmetros orderBy e orderDir
  • Limit em oportunidades — parâmetro limit (atalho para pageSize=N&page=1)
  • Auditoria — logs completos por entidade, utilizador e ação (/api/audit)
  • Campo salesforceProposalId nas Oportunidades (referência da proposta externa)
  • Índices de performance adicionados (trigram search, FK indexes)

Novidades v2.0 (Março 2026)

  • Novos filtros em Accounts: segment, tag, country, minElectricityKwh, minGasKwh
  • Novos filtros em Contacts: position, noAccount
  • CRUD completo para Accounts e Contacts (PUT/DELETE)
  • Importação CSV para Accounts e Contacts
  • Endpoint de Oportunidades (/api/opportunities)
  • Endpoint de Timeline (/api/timeline)
  • Gestão de Duplicados (/api/duplicates)
  • Estado da Sincronização (/api/sync/status)
  • Campo accountName devolvido nos Contacts

Casos de Uso

Exportação de Dados
Exporte todos os accounts e contacts para análise externa, relatórios ou backup.
Integração com CRM
Sincronize dados entre o Salesflare e outros sistemas internos da empresa.
Importação CSV
Importe ficheiros Excel/CSV de empresas e contactos diretamente para o sistema.
Gestão de Qualidade
Detete e resolva duplicados, gerencie contactos sem empresa associada.

Visão Geral dos Endpoints

GET/api/accounts Listar e filtrar empresas
PUT/api/accounts/{id} Atualizar empresa
DELETE/api/accounts/{id} Eliminar empresa
POST/api/accounts/import-csv Importar empresas via CSV/Excel
GET/api/contacts Listar e filtrar contactos
PUT/api/contacts/{id} Atualizar contacto
DELETE/api/contacts/{id} Eliminar contacto
POST/api/contacts/import-csv Importar contactos via CSV/Excel
GET/api/opportunities Listar e filtrar oportunidades
GET/api/timeline Eventos de timeline por empresa
GET/api/sync/status Estado da sincronização com Salesflare
GET/api/duplicates Listar registos duplicados
POST/api/commissions/opportunity Receber proposta de comissão (integração motor de comissões externo)
PUT/api/commissions/opportunity/{id}/result Registar resultado de comissão calculada
GET/api/commissions/opportunities Listar comissões com filtros
GET/api/audit Logs de auditoria por entidade/utilizador/ação

Autenticação

A API suporta dois métodos de autenticação consoante o tipo de integração.

1. JWT Bearer Token (Aplicação Web)

Utilizado pela aplicação frontend. Obtenha um token através do endpoint de login e inclua-o no header Authorization.

## 1. Login para obter token curl -X POST https://api.lugo.tech/api/auth/login \ -H 'Content-Type: application/json' \ -d '{"email":"utilizador@empresa.pt","password":"password123"}' ## Resposta: { "token": "eyJ...", "user": { "id": "...", ... } } ## 2. Usar o token nas chamadas seguintes curl -H 'Authorization: Bearer eyJ...' \ https://api.lugo.tech/api/accounts?page=1&pageSize=50

2. API Key (Integrações Externas — apenas leitura)

Para integrações externas (Python, C#, scripts, etc.), envie a API Key no header X-API-Key.

API Keys são read-only: apenas métodos GET são permitidos.
Para criar, atualizar ou eliminar registos é necessário autenticação JWT via login.
curl -H 'X-API-Key: sua_api_key_aqui' \ https://api.lugo.tech/api/accounts?page=1&pageSize=50
Segurança Importante:
• Nunca compartilhe ou exponha a sua API Key publicamente
• Não faça commit da chave em repositórios de código
• Use variáveis de ambiente para armazenar a chave
• Tokens JWT expiram — implemente renovação automática

Comparação de Métodos de Autenticação

OperaçãoAPI KeyJWT (Login)
GET (leitura)PermitidoPermitido
PUT (atualizar)BloqueadoPermitido
DELETE (eliminar)BloqueadoPermitido
POST (criar/importar)BloqueadoPermitido

Gestão de Utilizadores

Endpoints de gestão de utilizadores — requerem JWT com role admin, exceto /me e /change-password.

GET/api/auth/me Utilizador autenticado atual
POST/api/auth/logout Limpa o cookie JWT
POST/api/auth/register Criar novo utilizador (admin)
GET/api/auth/users Listar utilizadores (admin)
PUT/api/auth/users/{id} Atualizar utilizador
PUT/api/auth/users/{id}/toggle-active Ativar/desativar utilizador
PUT/api/auth/users/{id}/role Alterar role (admin/user)
PUT/api/auth/users/{id}/reset-password Reset password (admin)
DELETE/api/auth/users/{id} Eliminar utilizador
PUT/api/auth/change-password Alterar password própria

Health Check

GET/health Estado da API, ambiente (Production/Staging) e versão
{ "status": "ok", "environment": "Production", "stagingMode": false, "timestamp": "2026-06-02T10:00:00Z" }

Accounts - Empresas

O endpoint /api/accounts suporta listagem, filtragem avançada, atualização e eliminação de empresas sincronizadas com o Salesflare.

Listar / Filtrar Empresas

GET /api/accounts

Query Parameters

Parâmetro Tipo Obrigatório Descrição
page integer Não Número da página (padrão: 1)
pageSize integer Não Registos por página (padrão: 50, máx: 1000)
search string Não Pesquisa por nome, NIF, domínio, cidade, segmento ou tag
city string Não Filtrar por cidade
country novo string Não Filtrar por país (ex: PT, Portugal)
segment novo string Não Filtrar por segmento (ex: SILVER, GOLD)
tag novo string Não Filtrar por tag (pesquisa parcial no tagsJson)
minElectricityKwh novo integer Não Consumo mínimo de eletricidade em kWh/ano
minGasKwh novo integer Não Consumo mínimo de gás em kWh/ano

Exemplo de resposta

{ "total": 549998, "page": 1, "pageSize": 50, "items": [ { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "salesflareId": 12345, "name": "501262270 - Empresa Exemplo Lda", "nif": "501262270", "email": "geral@empresa.pt", "city": "Lisboa", "country": "PT", "electricityConsumptionYearKwh": 85000, "gasConsumptionYearKwh": 12000, "tnw": 45000.00, "segmentName": "GOLD", "tagsJson": "[\"Leads_Solar_18032026\",\"Cliente_Ativo\"]", "syncedAt": "2026-02-01T10:00:00Z", "createdAt": "2025-10-15T08:30:00Z" } ] }

Atualizar Empresa novo

PUT /api/accounts/{id}

Atualiza os dados de uma empresa. Apenas os campos enviados são atualizados. A alteração é sincronizada com o Salesflare.

curl -X PUT https://api.lugo.tech/api/accounts/3fa85f64-... \ -H 'Authorization: Bearer eyJ...' \ -H 'Content-Type: application/json' \ -d '{"name":"Nova Designação Lda","city":"Porto","electricityConsumptionYearKwh":95000}'

Verificar antes de Eliminar novo

GET /api/accounts/{id}/delete-check Verifica quantos contactos/oportunidades/eventos serão afetados
{ "canDelete": true, "contacts": 3, "opportunities": 1, "timelineEvents": 12 }

Eliminar Empresa novo

DELETE /api/accounts/{id}?force=false

Se tiver registos associados, use force=true para forçar a eliminação em cascata.

Atenção: A eliminação com force=true remove também os contactos, oportunidades e eventos de timeline associados. Esta ação não é reversível.

Empresas Sem Oportunidades novo

GET /api/accounts/cleanup/empty Lista empresas sem oportunidades nem contactos associados

Fundir Empresas Duplicadas novo

POST /api/accounts/merge Funde uma empresa duplicada na empresa principal
curl -X POST https://api.lugo.tech/api/accounts/merge \ -H 'Authorization: Bearer eyJ...' \ -H 'Content-Type: application/json' \ -d '{"masterId":"id-principal","duplicateId":"id-duplicado","deleteFromSalesflare":false}'

Campos da Empresa

CampoTipoDescrição
idGUIDIdentificador único interno
salesflareIdintegerID no Salesflare
namestringNome/razão social
nifstringNIF/NIPC da empresa (chave única global)
emailstringEmail principal
phonestringTelefone
websitestringWebsite
descriptionstringDescrição
citystringCidade
countrystringPaís
tnwdecimalTotal Network Worth — valor do cliente (€)
electricityConsumptionYearKwhintegerConsumo anual de eletricidade (kWh)
gasConsumptionYearKwhintegerConsumo anual de gás (kWh)
segmentNamestringSegmento (ex: SILVER, GOLD)
tagsJsonJSON stringArray JSON com tags — usar JSON.parse() no cliente
syncedAtdatetimeData da última sincronização com Salesflare
createdAtdatetimeData de criação do registo
Sobre o campo tagsJson: vem como string JSON. Exemplos:
"[]" — sem tags
"[\"Leads_Solar\",\"Cliente_Ativo\"]" — array de strings
Use sempre JSON.parse() para processar.

Contacts - Pessoas/Contactos

O endpoint /api/contacts suporta listagem com filtros avançados, atualização e eliminação de contactos.

Listar / Filtrar Contactos

GET /api/contacts

Query Parameters

ParâmetroTipoObrigatórioDescrição
page integer Não Número da página (padrão: 1)
pageSize integer Não Registos por página (padrão: 50, máx: 1000)
search string Não Pesquisa por nome, email ou telefone
accountId GUID Não Filtrar contactos de uma empresa específica
position novo string Não Filtrar por cargo/função (pesquisa parcial, ex: Gerente)
noAccount novo boolean Não Se true, devolve apenas contactos sem empresa associada

Exemplo de resposta

{ "total": 15714, "page": 1, "pageSize": 50, "items": [ { "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "salesflareId": 67890, "accountId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "accountName": "501262270 - Empresa Exemplo Lda", "name": "João Silva", "firstname": "João", "lastname": "Silva", "email": "joao.silva@empresa.pt", "phone": "+351 91 000 0000", "position": "Sócio-Gerente", "syncedAt": "2026-02-01T10:00:00Z" } ] }

Atualizar Contacto novo

PUT /api/contacts/{id}

Atualiza os dados de um contacto, incluindo a empresa associada (accountId). A alteração é sincronizada com o Salesflare.

curl -X PUT https://api.lugo.tech/api/contacts/a1b2c3d4-... \ -H 'Authorization: Bearer eyJ...' \ -H 'Content-Type: application/json' \ -d '{"email":"novo@empresa.pt","position":"Diretor","accountId":"guid-da-empresa"}'

Eliminar Contacto novo

DELETE /api/contacts/{id}

Campos do Contacto

CampoTipoDescrição
idGUIDIdentificador único interno
salesflareIdintegerID no Salesflare
accountIdGUIDID da empresa associada
accountNamestringNome da empresa associada (campo calculado) novo
namestringNome completo
firstnamestringPrimeiro nome
lastnamestringApelido
emailstringEmail principal
phonestringTelefone principal
mobilePhonestringTelemóvel
positionstringCargo/Função
rolestringPapel na empresa (ex: Sócio-Gerente)
positionsJsonJSONArray JSON com posições históricas
optOutbooleanOpt-out de comunicações
bouncedbooleanSe o email fez bounce
archivedbooleanSe está arquivado
syncedAtdatetimeData da última sincronização
createdAtdatetimeData de criação

Oportunidades

O endpoint /api/opportunities devolve negócios/propostas sincronizados do Salesflare com suporte a filtragem por comercial, ordenação dinâmica e limit.

GET /api/opportunities
GET /api/opportunities/{id} Detalhe completo por GUID interno (inclui StageUpdatesJson)
GET /api/opportunities/sf/{salesflareId} Detalhe por Salesflare ID
GET /api/opportunities/account/{accountSalesflareId} Todas as oportunidades de uma empresa
GET /api/opportunities/stages Valores distintos de stage existentes na BD
GET /api/opportunities/pipelines Valores distintos de pipeline existentes na BD

Query Parameters — GET /api/opportunities

ParâmetroTipoDescrição
pageintegerNúmero da página (padrão: 1)
pageSizeintegerRegistos por página (padrão: 50, máx: 500)
limit novointegerAtalho para pageSize=N&page=1. Ex: limit=1 para top 1. Ignora page/pageSize.
searchstringPesquisa por nome, empresa, assignee ou owner
ownerEmail novostringFiltra oportunidades onde o email aparece como owner, assignee, coldcaller, discovery caller ou closer
tipoProdutostringProduto: eletricidade ou gas (pesquisa parcial)
naturezaVendastringNatureza da venda (pesquisa parcial)
donebooleantrue = fechadas; false = abertas
pipelineIdintegerFiltrar por pipeline
stageIdintegerFiltrar por etapa
assigneeIdintegerFiltrar por assignee (Salesflare ID)
ownerIdintegerFiltrar por owner (Salesflare ID)
accountSalesflareIdintegerFiltrar por empresa (Salesflare ID)
orderBy novostringCampo para ordenar (ver tabela abaixo). Padrão: salesflareModificationDate
orderDir novostringasc ou desc (padrão: desc)

Valores aceites em orderBy

probability
orderByCampo ordenado
valueValor da oportunidade (€)
calculatedValueValor calculado (€)
Probabilidade de fecho (%)
volumeConsumoAnualKwhConsumo anual (kWh)
consumoContratadoKwhConsumo contratado (kWh)
margemUnitariaMargem unitária
duracaoContratoMesesDuração do contrato (meses)
inicioFornecimentoData início de fornecimento
fimFornecimentoData fim de fornecimento
closeDate / startDateData de fecho / início previsto
contractStartDate / contractEndDateDatas do contrato
lastInteractionDateData da última interação
lastStageChangeDateData da última mudança de etapa
salesflareCreationDateData de criação no Salesflare
name / accountNameOrdenação alfabética
(outro ou omitido)salesflareModificationDate desc (comportamento padrão)

Exemplos de uso

## Todas as oportunidades de um comercial (em qualquer papel) GET /api/opportunities?ownerEmail=joao@lugotech.pt ## Top 1 com maior consumo (objetivo de vendas) GET /api/opportunities?ownerEmail=joao@lugotech.pt&orderBy=volumeConsumoAnualKwh&orderDir=desc&limit=1 ## Contratos ordenados por duração crescente (gestão de prazos) GET /api/opportunities?ownerEmail=joao@lugotech.pt&orderBy=duracaoContratoMeses&orderDir=asc ## Contratos a terminar primeiro (renovações) GET /api/opportunities?ownerEmail=joao@lugotech.pt&orderBy=fimFornecimento&orderDir=asc&done=false

Principais Campos da Oportunidade

CampoTipoDescrição
idGUIDIdentificador único interno
salesflareIdintegerID no Salesflare
namestringNome do negócio
valuedecimalValor estimado (€)
probabilityintegerProbabilidade de fecho (%)
donebooleanSe está fechado (ganho ou perdido)
tipoProdutostringProduto: eletricidade ou gas
naturezaVendastringNatureza da venda
volumeConsumoAnualKwhnumberVolume de consumo anual (kWh)
margemUnitariadecimalMargem unitária (€/MWh)
duracaoContratoMesesintegerDuração do contrato (meses)
inicioFornecimentodatetimeInício do fornecimento
fimFornecimentodatetimeFim do fornecimento
salesforceProposalId novostringReferência da proposta externa
closeDatedatetimeData de fecho prevista
stageobject{ stageId, stageName, stageOrder, stageProbability }
accountobject{ accountId, accountName, accountSalesflareId }
pipelineobject{ pipelineId, pipelineName }
ownerobject{ ownerId, ownerName, ownerEmail, ownerPicture }
assigneeobject{ assigneeId, assigneeName, assigneeEmail }
lastInteractionobject{ lastInteractionDate, lastInteractionType, lastInteractionPersonName }
Pipeline de Vendas: Use GET /api/opportunities/stages e GET /api/opportunities/pipelines para obter os IDs e nomes de etapas e pipelines disponíveis.

Comissões novo

Módulo de receção, cálculo e consulta de comissões. Recebe propostas da empresa parceira (motor de comissões externo), faz match com oportunidades do Salesflare e mantém o histórico completo.

Receber Proposta de Comissão

POST /api/commissions/opportunity

Recebe os dados de uma proposta adjudicada. Faz match automático com oportunidade local via SalesforceProposalId (fallback: AccountName + data). Persiste em CommissionOpportunities + CommissionProducts + CommissionCpes.

curl -X POST https://api.lugo.tech/api/commissions/opportunity \ -H 'X-API-Key: sua_api_key_aqui' \ -H 'Content-Type: application/json' \ -d '{ "id": "REF-2026-001", "account_name": "Empresa Exemplo Lda", "distribution_mode": "SPLIT", "coldcaller": "12345", "closer": "67890", "energy": { "sale_type": "NEW", "tariff_type": "BTN", "unit_margin": 2.5, "annual_volume": 85000, "start_date": "2026-07-01", "end_date": "2028-06-30" }, "cpes": ["PT0001234567890123AB", "PT0009876543210987CD"] }'
{ "commission_opportunity_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "status": "PENDING" }

Registar Resultado de Comissão

PUT /api/commissions/opportunity/{salesforceProposalId}/result

Recebe o valor calculado e o breakdown. Atualiza status para CALCULATED e depois faz push ao Salesflare (SYNCED).

curl -X PUT https://api.lugo.tech/api/commissions/opportunity/REF-2026-001/result \ -H 'X-API-Key: sua_api_key_aqui' \ -H 'Content-Type: application/json' \ -d '{ "commission_value": 1250.00, "breakdown": { "coldcaller": 375.00, "closer": 875.00 } }'

Consultar Comissão

GET /api/commissions/opportunity/{salesforceProposalId}
GET /api/commissions/opportunity/{salesforceProposalId}/cpes Lista de CPEs/CUIs da proposta

Listar Comissões

GET /api/commissions/opportunities?status=&page=&pageSize=

Filtros: status (PENDING, CALCULATED, SYNCED, ERROR), page, pageSize.

Resumo por Comercial

GET /api/commissions/users/{salesflareUserId}/summary

Reconciliação Manual

POST /api/commissions/opportunity/{id}/link Liga manualmente uma proposta a uma oportunidade (quando match automático falhou)
curl -X POST https://api.lugo.tech/api/commissions/opportunity/REF-2026-001/link \ -H 'Authorization: Bearer eyJ...' \ -H 'Content-Type: application/json' \ -d '{"opportunity_id": "uuid-da-oportunidade-local"}'

Estados da Comissão (CommissionStatus)

StatusSignificado
PENDINGProposta recebida, aguarda cálculo ou match manual
CALCULATEDValor calculado recebido da motor de comissões externo
SYNCEDValor sincronizado para o Salesflare
ERRORErro no processamento

Modos de Distribuição (DistributionMode)

ValorDescrição
INDIVIDUALComissão para um único comercial
SPLITComissão dividida entre coldcaller e closer
EXTERNAL_PARTNERParceiro externo
Match automático proposta ↔ oportunidade:
1. Por SalesforceProposalId (campo da oportunidade)
2. Fallback automático por AccountName + data aproximada
3. Se não encontrar: status fica PENDING sem OpportunityId — aparece no frontend para resolução manual via /link

Timeline novo

Eventos de timeline (chamadas, emails, reuniões, tarefas) associados a uma empresa.

GET /api/timeline?accountId={guid}&page=1&pageSize=30

Resumo da Timeline

GET /api/timeline/summary?accountSalesflareId={id} Totais por tipo e tarefas pendentes
{ "total": 47, "byType": [ { "type": "email", "count": 23 }, { "type": "call", "count": 12 }, { "type": "meeting", "count": 8 }, { "type": "task", "count": 4 } ], "pendingTasks": 2 }

Campos do Evento de Timeline

CampoTipoDescrição
idGUIDIdentificador único
typestringTipo: email, call, meeting, task, etc.
subjectstringAssunto
descriptionstringDescrição/corpo
eventDatedatetimeData do evento
createdByobjectCriado por: { createdByUserId, createdByName }
taskobjectDados de tarefa: { taskDone, taskDueDate, taskAssigneeName }

Sincronização novo

Endpoints para monitorizar e gerir a sincronização com o Salesflare.

Estado da Sincronização

GET /api/sync/status
{ "timestamp": "2026-03-22T10:30:00Z", "accounts": { "total": 549998, "synced": 549998, "syncPercentage": 100 }, "contacts": { "total": 15714, "synced": 15714, "syncPercentage": 100 }, "queue": { "pendingChanges": 0, "conflicts": 0, "duplicates": 3 }, "estimatedTimeRemaining": "0 minutos" }

Disparar Sincronização de Oportunidades

POST /api/sync/trigger-opportunities

Conflitos de Sincronização

GET /api/sync/conflicts?page=1&pageSize=50
POST /api/sync/resolve-conflict/{id}

Duplicados novo

Detete e resolva registos duplicados de empresas e contactos.

Listar Duplicados

GET /api/duplicates?entityType=Account entityType: Account ou Contact
[ { "entityType": "Account", "uniqueValue": "501262270", "masterId": "id-da-empresa-principal", "duplicates": ["id-duplicado-1", "id-duplicado-2"] } ]

Resolver Duplicado

POST /api/duplicates/resolve
curl -X POST https://api.lugo.tech/api/duplicates/resolve \ -H 'Authorization: Bearer eyJ...' \ -H 'Content-Type: application/json' \ -d '{"entityType":"Account","keepId":"id-a-manter","removeIds":["id-a-remover"]}'

Duplicados por NIF (Empresas)

GET /api/accounts/cleanup/duplicates Grupos de empresas com mesmo NIF

Auditoria novo

Registo automático de todas as criações, atualizações e eliminações de Accounts, Contacts e Oportunidades. Cada entrada inclui o diff campo a campo.

Listar Logs

GET /api/audit

Query Parameters

ParâmetroTipoDescrição
entityTypestringAccount, Contact ou Opportunity
entityIdGUIDID da entidade específica
userIdGUIDFiltrar por utilizador
sourcestringAPI, Sync ou Bulk
actionstringCreate, Update ou Delete
from / todatetimeIntervalo de datas
page / pageSizeintegerPaginação
GET /api/audit/account/{id} Todos os logs de uma empresa específica
GET /api/audit/contact/{id} Todos os logs de um contacto específico

Exemplo de resposta

{ "total": 3, "items": [ { "id": "...", "entityType": "Account", "entityId": "...", "entityName": "Empresa Exemplo Lda", "action": "Update", "source": "API", "userId": "...", "userName": "João Silva", "fieldsChangedJson": "{\"city\":{\"from\":\"Lisboa\",\"to\":\"Porto\"}}", "createdAt": "2026-06-01T14:30:00Z" } ] }
Campos ignorados no diff: NeedsSyncToSalesflare, SyncedAt, UpdatedAt, JsonRaw — campos internos de controlo não são auditados.

Ambientes

A API está disponível em dois ambientes distintos. Cada um tem a sua base de dados separada e comportamentos diferentes nos workers de sincronização.

Produção
https://api.lugo.tech

• Base de dados real — dados de clientes reais
• Sincronização com Salesflare ativa (a cada 24h)
• Push de alterações ao Salesflare ativo (a cada 30s)
• Qualquer escrita (PUT/POST/DELETE) afeta dados reais
Usar apenas em produção
Staging
https://staging.lugo.tech

• Base de dados de teste — separada da produção
• Sincronização com Salesflare desligada (StagingMode: true)
• Push ao Salesflare desligado — alterações não propagam
• Seguro para testar imports, endpoints e integrações
Usar para desenvolvimento e testes

Como confirmar em que ambiente estás

O endpoint /health devolve o ambiente atual e o estado do modo staging:

curl https://api.lugo.tech/health curl https://staging.lugo.tech/health

Resposta — Produção

{ "status": "ok", "environment": "Production", "stagingMode": false }

Resposta — Staging

{ "status": "ok", "environment": "Staging", "stagingMode": true }

Diferenças de comportamento

FuncionalidadeProduçãoStaging
Base de dadosBD de produção (real)BD de staging (teste)
Sync Salesflare → BD (24h)AtivoDesligado
Push BD → Salesflare (30s)AtivoDesligado
Leitura de dados (GET)Dados reaisDados de teste
Escrita (PUT/POST/DELETE)Afeta dados reaisSeguro para testar
Import CSVImporta para produção✅ Seguro para validar mapeamentos
Comissões (POST /commissions)Regista em produçãoSeguro para testar integração

Autenticação por ambiente

As API Keys e credenciais JWT são independentes por ambiente — uma key de staging não funciona em produção e vice-versa. Gere chaves separadas para cada ambiente.

## Staging — obter token curl -X POST https://staging.lugo.tech/api/auth/login \ -H 'Content-Type: application/json' \ -d '{"email":"utilizador@empresa.pt","password":"password"}' ## Staging — testar import CSV sem afetar produção curl -X POST https://staging.lugo.tech/api/accounts/import-csv \ -H 'Authorization: Bearer eyJ...' \ -F 'file=@teste.xlsx' ## Staging — testar endpoint de comissões curl -X POST https://staging.lugo.tech/api/commissions/opportunity \ -H 'X-API-Key: key_de_staging' \ -H 'Content-Type: application/json' \ -d '{"id":"TEST-001", ...}'
Migrações de base de dados: Ambos os ambientes correm migrações EF Core automaticamente no arranque. Nunca é necessário correr migrações manualmente — e nunca apagam dados existentes (apenas adicionam).

Importação CSV novo

Importe empresas e contactos em massa a partir de ficheiros CSV ou Excel (.xlsx).

Importar Empresas

POST /api/accounts/import-csv
curl -X POST https://api.lugo.tech/api/accounts/import-csv \ -H 'Authorization: Bearer eyJ...' \ -F 'file=@empresas.xlsx' \ -F 'userId=00000000-0000-0000-0000-000000000000' \ -F 'syncToSalesflare=true'

Importar Contactos

POST /api/contacts/import-csv

Resposta da Importação

{ "success": true, "stats": { "totalProcessed": 1250, "created": 980, "updated": 265, "errors": 5, "syncQueued": true }, "errors": [ "Linha 45: NIF inválido '12345'", "Linha 102: Email duplicado" ] }

Mapeamento de Colunas — Empresas

Campo internoColunas aceites no CSV/Excel
Nifnif, nipc, nif_nipc, numero_fiscal, tax_number
Namename, nome, company, empresa, account, nome cliente
Citycity, cidade, localidade, municipio, distrito, distrito_da_instalacao, concelho_da_instalacao
ElectricityConsumptionYearKwhelectricity_kwh, consumo_ee, consumo_eletricidade, consumo anual, consumo_anual_kwh
TagsJsontags, tag, etiquetas, etiqueta, label, labels
SegmentNamesegment, segmento, segment_name, tier
Emailemail, e-mail, email_address
Phonephone, telefone, telephone, tel
Upsert por NIF: Se já existir uma empresa com o mesmo NIF, os dados são atualizados (não criado duplicado). O NIF é a chave única global de empresas.

Mapeamento de Colunas — Contactos

Campo internoColunas aceites no CSV/Excel
Firstnamefirstname, primeiro_nome, first_name, nome_proprio, given_name
Lastnamelastname, apelido, last_name, surname, sobrenome
Emailemail, e-mail, email_address, correio
Phonephone, telefone, tel, telephone, mobile, telemovel, celular
Positionposition, cargo, funcao, role, titulo, job_title

Exemplos de Código

cURL

Obter accounts com filtros

curl -X GET 'https://api.lugo.tech/api/accounts?page=1&pageSize=100&segment=GOLD&minElectricityKwh=50000' \ -H 'X-API-Key: sua_api_key_aqui'

Obter contactos sem empresa

curl -X GET 'https://api.lugo.tech/api/contacts?noAccount=true&pageSize=100' \ -H 'X-API-Key: sua_api_key_aqui'

Obter contacts de uma empresa

curl -X GET 'https://api.lugo.tech/api/contacts?accountId=empresa_id' \ -H 'X-API-Key: sua_api_key_aqui'

Oportunidades abertas de eletricidade

curl -X GET 'https://api.lugo.tech/api/opportunities?tipoProduto=eletricidade&done=false' \ -H 'X-API-Key: sua_api_key_aqui'

Oportunidades de um comercial (todas as roles)

curl -X GET 'https://api.lugo.tech/api/opportunities?ownerEmail=joao@lugotech.pt&done=false' \ -H 'X-API-Key: sua_api_key_aqui'

Top 1 maior consumo de um comercial (objetivo de vendas)

curl -X GET 'https://api.lugo.tech/api/opportunities?ownerEmail=joao@lugotech.pt&orderBy=volumeConsumoAnualKwh&orderDir=desc&limit=1' \ -H 'X-API-Key: sua_api_key_aqui'

Comissões pendentes de match manual

curl -X GET 'https://api.lugo.tech/api/commissions/opportunities?status=PENDING' \ -H 'X-API-Key: sua_api_key_aqui'

Logs de auditoria de hoje

curl -X GET 'https://api.lugo.tech/api/audit?from=2026-06-02T00:00:00Z&source=API' \ -H 'X-API-Key: sua_api_key_aqui'

Python

import requests API_KEY = 'sua_api_key_aqui' USER_ID = '00000000-0000-0000-0000-000000000000' BASE_URL = 'https://api.lugo.tech' headers = {'X-API-Key': API_KEY} # Obter accounts com filtros response = requests.get( f'{BASE_URL}/api/accounts', headers=headers, params={'page': 1, 'pageSize': 100, 'segment': 'GOLD', 'minElectricityKwh': 50000} ) data = response.json() print(f'Total: {data["total"]}') # Exportar TODOS os accounts all_accounts = [] page = 1 while True: resp = requests.get( f'{BASE_URL}/api/accounts', headers=headers, params={'page': page, 'pageSize': 1000} ) data = resp.json() all_accounts.extend(data['items']) if len(all_accounts) >= data['total']: break page += 1 print(f'Total exportado: {len(all_accounts)}') # Contactos sem empresa orphans = requests.get( f'{BASE_URL}/api/contacts', headers=headers, params={'noAccount': 'true', 'pageSize': 1000} ).json() print(f'Contactos sem empresa: {orphans["total"]}')

JavaScript

const API_KEY = 'sua_api_key_aqui'; const BASE = 'https://api.lugo.tech'; async function apiGet(path, params = {}) { const q = new URLSearchParams(params); const res = await fetch(`${BASE}/api/${path}?${q}`, { headers: { 'X-API-Key': API_KEY } }); return res.json(); } // Empresas GOLD com consumo > 50 000 kWh const result = await apiGet('accounts', { segment: 'GOLD', minElectricityKwh: 50000, pageSize: 100 }); console.log(`Empresas GOLD: ${result.total}`); // Tags (parsear tagsJson) result.items.forEach(a => { const tags = JSON.parse(a.tagsJson || '[]'); console.log(`${a.name}: ${tags.join(', ')}`); }); // Estado da sincronização const status = await apiGet('sync/status'); console.log(`Sync accounts: ${status.accounts.syncPercentage}%`);

C#

using System.Net.Http; using System.Text.Json; var client = new HttpClient(); client.DefaultRequestHeaders.Add("X-API-Key", "sua_api_key_aqui"); // Obter accounts var response = await client.GetAsync( "https://api.lugo.tech/api/accounts?page=1&pageSize=100&segment=GOLD" ); var json = await response.Content.ReadAsStringAsync(); var data = JsonSerializer.Deserialize<AccountsResponse>(json); Console.WriteLine($"Total: {data.Total}"); // Atualizar um account var update = JsonSerializer.Serialize(new { city = "Porto", electricityConsumptionYearKwh = 95000 }); var content = new StringContent(update, System.Text.Encoding.UTF8, "application/json"); await client.PutAsync($"https://api.lugo.tech/api/accounts/{accountId}", content);

Suporte Técnico

Email de Suporte: marcionicolau@lugo.tech
Swagger (Dev Admin): api.lugo.tech/swagger
Health Check: api.lugo.tech/health

Melhores Práticas

• Use pageSize=1000 para exports massivos
• Implemente cache local e atualize periodicamente
• Use retry logic para erros 429 e 5xx
• Campos JSON (tagsJson, etc) vêm como string — use JSON.parse()
• Use os filtros segment, tag, city para reduzir volume de dados
• Verifique /accounts/{id}/delete-check antes de eliminar empresas

Performance

Tempos médios de resposta: • pageSize=50 → ~200-400ms • pageSize=1000 → ~800-1500ms Exportação massiva: • 549.998 accounts → ~15-25 minutos • 15.714 contacts → ~1-2 minutos Importação CSV: • 1.000 linhas → ~5-15 segundos • 10.000 linhas → ~1-2 minutos

FAQ

Os dados são atualizados em tempo real?

A sincronização com o Salesflare ocorre automaticamente a cada 24 horas. Alterações feitas via PUT são sincronizadas imediatamente para o Salesflare.

Posso modificar dados através da API?

Sim. A API suporta operações completas: GET, PUT e DELETE para Accounts e Contacts. As alterações são sincronizadas automaticamente com o Salesflare.

Como funciona o upsert no import CSV?

O sistema usa o NIF como chave única para empresas. Se já existir uma empresa com o mesmo NIF, os dados são atualizados sem criar duplicados.

Qual o limite de requisições?

Não há limite fixo, mas recomendamos não exceder 60 requisições por minuto.

Como filtrar empresas com uma tag específica?

Use o parâmetro tag: /api/accounts?tag=Leads_Solar_18032026. A pesquisa é parcial — tag=Solar encontra todas as empresas com tags que contêm Solar.

Como obter contactos sem empresa?

Use noAccount=true: /api/contacts?noAccount=true.