API Online · Produção

Documentação de Integração

API de Licenciamento EngiCheck — Guia completo para desenvolvedores

Endpoint base

https://api.base44.app/api/apps/68120a83c0d84e8b4d62c82b/functions/licenseGateway

Visão Geral

A API de Licenciamento EngiCheck é o único ponto de contato entre o seu aplicativo e o sistema de controle de acesso da plataforma. Ela valida usuários, gerencia sessões simultâneas e aplica os limites de cada plano contratado.

Autenticação segura

Toda requisição precisa do x-api-key no header.

Seats simultâneos

A API controla quantos usuários podem estar ativos ao mesmo tempo.

Stateful

Sessões ficam ativas por 2h sem ping. Após isso são expiradas.

Autenticação

Todas as requisições precisam incluir a chave de API no header x-api-key. Sem essa chave, a API retorna 401 Unauthorized.

bash
curl -X POST https://api.base44.app/api/apps/68120a83c0d84e8b4d62c82b/functions/licenseGateway \
  -H "x-api-key: SUA_LICENSE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"action": "ping", "session_token": "abc..."}'

🔐 Segurança

Nunca exponha a LICENSE_API_KEY no código do cliente. Armazene em variáveis de ambiente ou no keychain do dispositivo. Em caso de comprometimento, solicite uma nova chave via suporte.

Fluxo de Integração

1
validateValidar acesso ao abrir o app

Verifique se o usuário já tem acesso aprovado e a licença está ativa. Se válido → pule para o passo 3.

2
request_accessSolicitar acesso (se não aprovado)

Cria solicitação pendente. Mostre tela de 'aguardando aprovação' e tente validar novamente depois.

3
loginCriar sessão (login)

Cria uma sessão ativa e retorna o session_token. Armazene-o localmente.

4
pingHeartbeat a cada 15 minutos

Renova a sessão. Sem ping por 2h a sessão expira e o usuário é deslogado automaticamente.

5
logoutLogout ao fechar o app

Libera o seat imediatamente para outro usuário poder entrar.

Endpoints

Todos os endpoints usam POST no mesmo URL base. O campo action no body define a operação.

Cria uma solicitação de acesso para um usuário. O admin do portal EngiCheck precisa aprovar antes que o usuário possa fazer login. Se já existe solicitação pendente ou aprovada, retorna o status correspondente sem criar duplicata.

Parâmetros do Body (JSON)

CampoTipoObrigatórioDescrição
actionstringSim"request_access"
emailstringSimE-mail do usuário que quer acesso
tenant_emailstringSimE-mail da empresa (tenant) — usado para vincular à licença
user_namestringNãoNome do usuário (recomendado para o admin identificar)
company_namestringNãoNome da empresa
appstringNãoIdentificador do app. Default: "engcheck"

Exemplo de requisição (cURL)

bash
curl -X POST https://api.base44.app/api/apps/68120a83c0d84e8b4d62c82b/functions/licenseGateway \
  -H "x-api-key: SUA_LICENSE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "action": "request_access",
    "email": "joao@construtora.com",
    "tenant_email": "admin@construtora.com",
    "user_name": "João Silva",
    "company_name": "Construtora ABC",
    "app": "engcheck"
  }'

Resposta de sucesso (200)

json
{
  "success": true,
  "status": "pending",
  "message": "Solicitação enviada. Aguarde aprovação."
}

Possíveis erros

company_no_license— Empresa não possui licença ativa

→ Mostrar tela de contato para contratar plano

license_expired— Licença vencida

→ Avisar para renovar

request_pending— Solicitação já enviada e aguardando aprovação
already_approved— Usuário já tem acesso aprovado → ir para login

Verifica se o usuário tem acesso aprovado e se a licença da empresa está ativa e dentro da validade. Não cria sessão. Use ao abrir o app para decidir o próximo passo.

Parâmetros do Body (JSON)

CampoTipoObrigatórioDescrição
actionstringSim"validate"
emailstringSimE-mail do usuário
tenant_emailstringSimE-mail da empresa (tenant)
appstringNãoIdentificador do app. Default: "engcheck"

Exemplo de requisição (cURL)

bash
curl -X POST https://api.base44.app/api/apps/68120a83c0d84e8b4d62c82b/functions/licenseGateway \
  -H "x-api-key: SUA_LICENSE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "action": "validate",
    "email": "joao@construtora.com",
    "tenant_email": "admin@construtora.com",
    "app": "engcheck"
  }'

Resposta de sucesso (200)

json
{
  "valid": true,
  "license": {
    "id": "lic_abc123",
    "plan": "profissional",
    "expiry_date": "2026-12-31",
    "seat_limit": 15
  }
}

Possíveis erros

access_not_approved— Admin ainda não aprovou o acesso

→ Mostrar tela de aguardando aprovação

company_no_license— Empresa sem licença ativa
license_expired— Licença vencida — valid retorna false

Cria uma sessão ativa para o usuário e retorna o session_token. Respeita o limite de seats simultâneos da licença. Se o usuário já tem uma sessão ativa, a reutiliza. Sessões expiradas de outros usuários são automaticamente limpas.

Parâmetros do Body (JSON)

CampoTipoObrigatórioDescrição
actionstringSim"login"
emailstringSimE-mail do usuário
tenant_emailstringSimE-mail da empresa (tenant)
device_infostringNãoEx: 'iPhone 14 / iOS 17.2' — para rastreabilidade
ip_addressstringNãoIP do dispositivo
appstringNãoIdentificador do app. Default: "engcheck"

Exemplo de requisição (cURL)

bash
curl -X POST https://api.base44.app/api/apps/68120a83c0d84e8b4d62c82b/functions/licenseGateway \
  -H "x-api-key: SUA_LICENSE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "action": "login",
    "email": "joao@construtora.com",
    "tenant_email": "admin@construtora.com",
    "device_info": "Samsung Galaxy S23 / Android 14",
    "ip_address": "189.40.12.55",
    "app": "engcheck"
  }'

Resposta de sucesso (200)

json
{
  "success": true,
  "session_token": "a1b2c3d4e5f6...",
  "license": {
    "plan": "profissional",
    "expiry_date": "2026-12-31",
    "seat_limit": 15,
    "app": "engcheck"
  },
  "active_seats": 3,
  "seat_limit": 15
}

Possíveis erros

access_not_approved— Usuário não tem acesso aprovado
company_no_license— Empresa sem licença ativa
license_expired— Licença vencida
seat_limit_reached— Todos os seats simultâneos em uso

→ Avisar para aguardar ou fazer upgrade de plano

⚠ Observações

  • Armazene o session_token localmente (SharedPreferences, SecureStorage, etc.).
  • Se o usuário já tem sessão ativa, a API a reutiliza e retorna reused_session: true.
  • Sessões expiradas de outros usuários são limpas automaticamente no login.

Renova a sessão ativa (heartbeat). Deve ser chamado a cada 15 minutos enquanto o app estiver em uso. Sem ping por 2 horas, a sessão é automaticamente marcada como expirada e o seat liberado.

Parâmetros do Body (JSON)

CampoTipoObrigatórioDescrição
actionstringSim"ping"
session_tokenstringSimToken retornado no login

Exemplo de requisição (cURL)

bash
curl -X POST https://api.base44.app/api/apps/68120a83c0d84e8b4d62c82b/functions/licenseGateway \
  -H "x-api-key: SUA_LICENSE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "action": "ping",
    "session_token": "a1b2c3d4e5f6..."
  }'

Resposta de sucesso (200)

json
{
  "valid": true
}

Possíveis erros

session_expired— Sessão expirada (sem ping por 2h)

→ Redirecionar para tela de login

⚠ Observações

  • Recomendamos um timer de 15 minutos para chamar o ping em background.
  • Se valid retornar false, o usuário deve fazer login novamente.

Encerra a sessão do usuário e libera imediatamente o seat para outro usuário. Chame sempre ao fechar o app ou ao fazer logout manual.

Parâmetros do Body (JSON)

CampoTipoObrigatórioDescrição
actionstringSim"logout"
session_tokenstringSimToken da sessão ativa

Exemplo de requisição (cURL)

bash
curl -X POST https://api.base44.app/api/apps/68120a83c0d84e8b4d62c82b/functions/licenseGateway \
  -H "x-api-key: SUA_LICENSE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "action": "logout",
    "session_token": "a1b2c3d4e5f6..."
  }'

Resposta de sucesso (200)

json
{
  "success": true
}

⚠ Observações

  • Mesmo que o token já esteja inválido, a API retorna success: true.
  • Chame no onDispose ou lifecycleObserver do app para garantir a liberação do seat.

Retorna quantos usuários simultâneos a licença permite, quantos estão ativos agora e quantos slots ainda estão disponíveis. Útil para exibir no painel administrativo do cliente.

Parâmetros do Body (JSON)

CampoTipoObrigatórioDescrição
actionstringSim"check_seats"
tenant_emailstringSimE-mail da empresa (tenant)

Exemplo de requisição (cURL)

bash
curl -X POST https://api.base44.app/api/apps/68120a83c0d84e8b4d62c82b/functions/licenseGateway \
  -H "x-api-key: SUA_LICENSE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "action": "check_seats",
    "tenant_email": "admin@construtora.com"
  }'

Resposta de sucesso (200)

json
{
  "seat_limit": 5,
  "active_seats": 2,
  "available_seats": 3,
  "plan": "essencial",
  "status": "ativa"
}

Possíveis erros

company_no_license— Empresa sem licença ativa

Retorna o número máximo de obras ativas permitidas pelo plano da licença. Use para bloquear a criação de novas obras quando o limite for atingido.

Parâmetros do Body (JSON)

CampoTipoObrigatórioDescrição
actionstringSim"check_obras"
tenant_emailstringSimE-mail da empresa (tenant)

Exemplo de requisição (cURL)

bash
curl -X POST https://api.base44.app/api/apps/68120a83c0d84e8b4d62c82b/functions/licenseGateway \
  -H "x-api-key: SUA_LICENSE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "action": "check_obras",
    "tenant_email": "admin@construtora.com"
  }'

Resposta de sucesso (200)

json
{
  "obras_limit": 3,
  "plan": "essencial",
  "status": "ativa",
  "expiry_date": "2026-12-31"
}

Possíveis erros

company_no_license— Empresa sem licença ativa

⚠ Observações

  • 9999 significa ilimitado (plano Consultor).
  • Esta API não rastreia obras criadas — apenas informa o limite do plano. O controle de quantas obras existem é responsabilidade do seu app.

Lista todas as sessões ativas de uma empresa. Use para painel administrativo — não expor para usuários finais.

Parâmetros do Body (JSON)

CampoTipoObrigatórioDescrição
actionstringSim"sessions"
tenant_emailstringSimE-mail da empresa (tenant)

Exemplo de requisição (cURL)

bash
curl -X POST https://api.base44.app/api/apps/68120a83c0d84e8b4d62c82b/functions/licenseGateway \
  -H "x-api-key: SUA_LICENSE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "action": "sessions",
    "tenant_email": "admin@construtora.com"
  }'

Resposta de sucesso (200)

json
{
  "sessions": [
    {
      "user_email": "joao@construtora.com",
      "device_info": "Samsung Galaxy S23",
      "last_ping": "2026-04-08T14:30:00Z",
      "status": "active"
    }
  ],
  "count": 1,
  "seat_limit": 5
}

Limites por Plano

PlanoUsuários simultâneosObras ativas
Start21
Essencial53
Profissional155
ConsultorIlimitadoIlimitado
Nota: O campo seat_limit retornado no login e no check_seats reflete o plano contratado. O admin pode ter um limite customizado configurado diretamente na licença — neste caso prevalece sobre o padrão do plano.

Tabela de Códigos de Erro

Código (reason)DescriçãoAção recomendada no app
access_not_approvedAdmin não aprovou aindaMostrar tela 'Aguardando aprovação'
company_no_licenseEmpresa sem licença ativaRedirecionar para contratar plano
license_expiredLicença vencidaMostrar aviso de renovação
seat_limit_reachedTodos os seats em usoAvisar para aguardar ou fazer upgrade
request_pendingSolicitação já enviada e pendenteMostrar 'Aguardando aprovação'
already_approvedAcesso já aprovadoRedirecionar para login
session_expiredSessão expirou (2h sem ping)Redirecionar para login

Exemplo em Flutter/Dart

Exemplo completo de como integrar o fluxo de licenciamento em um app Flutter.

dart
import 'dart:convert';
import 'package:http/http.dart' as http;

const String API_URL =
    "https://api.base44.app/api/apps/68120a83c0d84e8b4d62c82b/functions/licenseGateway";
const String API_KEY = "SUA_LICENSE_API_KEY"; // Guarde no .env ou no keychain

class LicenseService {
  static final _headers = {
    'Content-Type': 'application/json',
    'x-api-key': API_KEY,
  };

  static Future<Map<String, dynamic>> _post(Map body) async {
    final res = await http.post(
      Uri.parse(API_URL),
      headers: _headers,
      body: jsonEncode(body),
    );
    return jsonDecode(res.body);
  }

  /// 1. Validar acesso ao abrir o app
  static Future<Map<String, dynamic>> validate(String email, String tenantEmail) {
    return _post({
      'action': 'validate',
      'email': email,
      'tenant_email': tenantEmail,
      'app': 'engcheck',
    });
  }

  /// 2. Solicitar acesso (quando validate retorna access_not_approved)
  static Future<Map<String, dynamic>> requestAccess(
      String email, String tenantEmail, String name, String company) {
    return _post({
      'action': 'request_access',
      'email': email,
      'tenant_email': tenantEmail,
      'user_name': name,
      'company_name': company,
      'app': 'engcheck',
    });
  }

  /// 3. Login — cria sessão e retorna session_token
  static Future<Map<String, dynamic>> login(String email, String tenantEmail) {
    return _post({
      'action': 'login',
      'email': email,
      'tenant_email': tenantEmail,
      'device_info': 'Flutter App',
      'app': 'engcheck',
    });
  }

  /// 4. Ping — heartbeat a cada 15min
  static Future<void> ping(String sessionToken) async {
    await _post({'action': 'ping', 'session_token': sessionToken});
  }

  /// 5. Logout ao fechar o app
  static Future<void> logout(String sessionToken) async {
    await _post({'action': 'logout', 'session_token': sessionToken});
  }

  /// Checar limite de obras
  static Future<Map<String, dynamic>> checkObras(String tenantEmail) {
    return _post({'action': 'check_obras', 'tenant_email': tenantEmail});
  }
}

// ─── Uso no app ───────────────────────────────────────────────────────────────

void main() async {
  const email = 'joao@construtora.com';
  const tenant = 'admin@construtora.com';

  // 1. Validar
  final v = await LicenseService.validate(email, tenant);
  if (v['valid'] == true) {
    // 3. Login direto
    final login = await LicenseService.login(email, tenant);
    if (login['success'] == true) {
      final token = login['session_token'];
      print('Logado! Token: $token');

      // 4. Ping periódico (a cada 15min)
      // Timer.periodic(Duration(minutes: 15), (_) => LicenseService.ping(token));

      // 5. Logout ao fechar
      // await LicenseService.logout(token);
    }
  } else if (v['reason'] == 'access_not_approved') {
    // 2. Solicitar acesso
    await LicenseService.requestAccess(email, tenant, 'João', 'Construtora ABC');
    print('Solicitação enviada!');
  }
}

EngiCheck API · v1.0 · Qualquer dúvida entre em contato com o suporte técnico via WhatsApp