Lugotech Sync API

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

Versão 2.0 | Atualizado em Março 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 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

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)✅ Permitido✅ Permitido
PUT (atualizar)❌ Bloqueado✅ Permitido
DELETE (eliminar)❌ Bloqueado✅ Permitido
POST (criar/importar)❌ Bloqueado✅ Permitido

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 novo

O endpoint /api/opportunities devolve negócios/propostas sincronizados do Salesflare.

GET /api/opportunities

Query Parameters

ParâmetroTipoDescrição
searchstringPesquisa por nome
tipoProdutostringProduto: eletricidade ou gas
naturezaVendastringNatureza da venda
donebooleantrue = fechadas; false = abertas
pipelineIdintegerFiltrar por pipeline
stageIdintegerFiltrar por etapa
accountSalesflareIdintegerFiltrar por empresa (Salesflare ID)
pageintegerNúmero da página
pageSizeintegerRegistos por página

Principais Campos da Oportunidade

CampoTipoDescrição
idGUIDIdentificador único
namestringNome do negócio
valuedecimalValor estimado (€)
probabilityintegerProbabilidade de fecho (%)
donebooleanSe está fechado (ganho ou perdido)
tipoProdutostringProduto: eletricidade ou gas
volumeConsumoAnualKwhintegerVolume de consumo anual (kWh)
duracaoContratoMesesintegerDuração do contrato (meses)
closeDatedatetimeData de fecho prevista
stageobjectEtapa: { stageId, stageName, stageOrder, stageProbability }
accountobjectEmpresa: { accountId, accountName, accountSalesflareId }
pipelineobjectPipeline: { pipelineId, pipelineName }
ownerobjectResponsável: { ownerId, ownerName, ownerEmail }

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

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=328c217e-4b9a-4fa1-86f1-f1faf8ac4753' \ -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'

Python

import requests API_KEY = 'sua_api_key_aqui' USER_ID = '328c217e-4b9a-4fa1-86f1-f1faf8ac4753' 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.