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ção | API Key | JWT (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
| Campo | Tipo | Descrição |
id | GUID | Identificador único interno |
salesflareId | integer | ID no Salesflare |
name | string | Nome/razão social |
nif | string | NIF/NIPC da empresa (chave única global) |
email | string | Email principal |
phone | string | Telefone |
website | string | Website |
description | string | Descrição |
city | string | Cidade |
country | string | País |
tnw | decimal | Total Network Worth — valor do cliente (€) |
electricityConsumptionYearKwh | integer | Consumo anual de eletricidade (kWh) |
gasConsumptionYearKwh | integer | Consumo anual de gás (kWh) |
segmentName | string | Segmento (ex: SILVER, GOLD) |
tagsJson | JSON string | Array JSON com tags — usar JSON.parse() no cliente |
syncedAt | datetime | Data da última sincronização com Salesflare |
createdAt | datetime | Data 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.
Oportunidades novo
O endpoint /api/opportunities devolve negócios/propostas sincronizados do Salesflare.
GET
/api/opportunities
Query Parameters
| Parâmetro | Tipo | Descrição |
search | string | Pesquisa por nome |
tipoProduto | string | Produto: eletricidade ou gas |
naturezaVenda | string | Natureza da venda |
done | boolean | true = fechadas; false = abertas |
pipelineId | integer | Filtrar por pipeline |
stageId | integer | Filtrar por etapa |
accountSalesflareId | integer | Filtrar por empresa (Salesflare ID) |
page | integer | Número da página |
pageSize | integer | Registos por página |
Principais Campos da Oportunidade
| Campo | Tipo | Descrição |
id | GUID | Identificador único |
name | string | Nome do negócio |
value | decimal | Valor estimado (€) |
probability | integer | Probabilidade de fecho (%) |
done | boolean | Se está fechado (ganho ou perdido) |
tipoProduto | string | Produto: eletricidade ou gas |
volumeConsumoAnualKwh | integer | Volume de consumo anual (kWh) |
duracaoContratoMeses | integer | Duração do contrato (meses) |
closeDate | datetime | Data de fecho prevista |
stage | object | Etapa: { stageId, stageName, stageOrder, stageProbability } |
account | object | Empresa: { accountId, accountName, accountSalesflareId } |
pipeline | object | Pipeline: { pipelineId, pipelineName } |
owner | object | Responsá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
| Campo | Tipo | Descrição |
id | GUID | Identificador único |
type | string | Tipo: email, call, meeting, task, etc. |
subject | string | Assunto |
description | string | Descrição/corpo |
eventDate | datetime | Data do evento |
createdBy | object | Criado por: { createdByUserId, createdByName } |
task | object | Dados 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 interno | Colunas aceites no CSV/Excel |
Nif | nif, nipc, nif_nipc, numero_fiscal, tax_number |
Name | name, nome, company, empresa, account, nome cliente |
City | city, cidade, localidade, municipio, distrito, distrito_da_instalacao, concelho_da_instalacao |
ElectricityConsumptionYearKwh | electricity_kwh, consumo_ee, consumo_eletricidade, consumo anual, consumo_anual_kwh |
TagsJson | tags, tag, etiquetas, etiqueta, label, labels |
SegmentName | segment, segmento, segment_name, tier |
Email | email, e-mail, email_address |
Phone | phone, 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 interno | Colunas aceites no CSV/Excel |
Firstname | firstname, primeiro_nome, first_name, nome_proprio, given_name |
Lastname | lastname, apelido, last_name, surname, sobrenome |
Email | email, e-mail, email_address, correio |
Phone | phone, telefone, tel, telephone, mobile, telemovel, celular |
Position | position, 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
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.