Skip to content

API de Integração

Referência de endpoints para integração entre sistema parceiro e TOMODAT3.

Convenções de autenticação

Endpoints de parceiro (server-to-server)

Use:

  • Header X-Integration-API-Key: <api_key>
  • Header X-Tenant: <tenant_id> quando o endpoint operar no banco do tenant

Endpoints do cliente final dentro do TOMODAT

Use JWT de usuário logado (na interface do TOMODAT), não API key do parceiro.

O que é tenant na API

tenant é o identificador da conta/ambiente do cliente final no TOMODAT.

Em outras plataformas, isso pode equivaler a:

  • conta
  • organização
  • workspace
  • cliente

Quando o endpoint opera no banco do cliente final, envie X-Tenant: <tenant_id>.

Tipos integráveis atualmente suportados

Um tipo só é sincronizável quando tem is_integrable = true (flag global do ItemType) e está configurado para o tenant no vínculo do parceiro (integrated_types, ver "Endpoints de vínculo de tenant"). Use esses slugs em item_type_slug:

Vertical Hídrica

Nome do tipo Slug técnico (item_type_slug) Geometria esperada (geometry.type) Integrável
Cliente Hídrico cliente-hidrico Point Sim
Reservatório reservatorio Point Sim
Poço poco Point Sim

Os demais tipos hídricos (tubulacao, hidrometro) e todos os tipos FTTH não são integráveis por padrão. A integrabilidade é curada por seeder — ver database/seeders/Tenant/*/ItemTypesSeeder.php.

Regras importantes:

  • O slug deve ser enviado exatamente como documentado.
  • geometry.type deve respeitar a geometria esperada do tipo.
  • Se o item for criado sem geometria, o usuário final poderá posicioná-lo depois no mapa.

Regra de manutenção desta tabela

Sempre que a lista de tipos integráveis mudar no produto, esta documentação deve ser atualizada no mesmo ciclo de entrega.

Sobre properties:

  • properties é JSON livre.
  • Atualmente não existe schema obrigatório por tipo nessa integração.
  • Recomendação: manter estrutura estável no sistema parceiro para facilitar suporte.

Endpoints de itens via integração

POST /api/integration/items

Cria item integrado.

Headers:

  • X-Integration-API-Key
  • X-Tenant

Body exemplo:

{
  "external_id": "cli-123",
  "item_type_slug": "cliente-hidrico",
  "item_model": "casa",
  "name": "Joao da Silva",
  "geometry": {
    "type": "Point",
    "coordinates": [-49.1234, -26.4567]
  },
  "properties": {
    "status": "ativo"
  },
  "external_data": {
    "telefone": "47999990000",
    "plano": "Residencial"
  },
  "external_actions": [
    {
      "label": "Ver cliente no ERP",
      "url": "https://erp.exemplo.com/clientes/cli-123",
      "icon": "eye"
    }
  ]
}

Resposta sucesso: 201.

Campo item_model (opcional):

  • Identifica o modelo do item, casado contra item_models.code, escopado ao tipo do item.
  • Define o ícone exibido no mapa (COALESCE(item_models.icon, item_types.map_icon)) — útil para distinguir subcategorias dentro de um tipo (ex.: "Casa" vs. "Produtor" sob cliente-hidrico).
  • Envie o code do modelo (ex.: "casa"), não o nome de exibição. Ver ADR-0013.
  • Omitido ou nulo → item fica sem modelo. Code inexistente sob o tipo → 422.

Erros comuns:

  • 401: API key ausente/inválida.
  • 403: tenant não pertence ao parceiro.
  • 404: tipo integrável não encontrado.
  • 409: external_id já existe para esse parceiro no tenant.
  • 422: validação de payload, coordenadas não-numéricas, ou item_model (code) não encontrado sob o tipo.

PUT /api/integration/items/{external_id}

Atualiza item integrado por external_id.

Body permitido:

  • name
  • item_model (code do modelo; ver POST)
  • properties
  • external_data
  • external_actions
  • geometry

Regra de item_model (ver ADR-0013): só altera o modelo quando um code não-vazio é enviado; item_model nulo/omitido preserva o modelo existente (nunca zera via integração).

Regra de geometria (ver ADR-0014):

  • geometry é aceita, mas aplicada apenas quando o item ainda não tem geometria.
  • Se o item já tem geometria (posicionamento manual), a coordenada enviada é ignorada silenciosamente — sem erro. Os demais campos são atualizados normalmente.
  • Geometria existente nunca é sobrescrita pela integração.

Erros comuns:

  • 404: item não encontrado.
  • 422: payload inválido, geometry.type incompatível com o tipo, coordenadas não-numéricas, ou item_model inexistente.

DELETE /api/integration/items/{external_id}

Soft delete (item fica inativo).

Resposta sucesso: 200.

Resposta exemplo:

{
  "message": "Item desativado",
  "deleted_at": "2026-03-12T10:00:00+00:00"
}

POST /api/integration/items/{external_id}/restore

Restaura item soft-deleted.

Resposta sucesso: 200.

POST /api/integration/items/sync

Sincronização em lote por tipo (full sync).

Body exemplo:

{
  "item_type_slug": "cliente-hidrico",
  "items": [
    {
      "external_id": "cli-123",
      "name": "Joao da Silva",
      "item_model": "casa",
      "properties": { "status": "ativo" },
      "external_data": { "telefone": "47999990000" },
      "external_actions": [
        { "label": "Ver cliente", "url": "https://erp.exemplo.com/clientes/cli-123" }
      ]
    },
    {
      "external_id": "cli-456",
      "name": "Maria Souza"
    }
  ]
}

Comportamento:

  1. Cria itens novos.
  2. Atualiza itens existentes (inclui mudança de item_model quando um code não-vazio é enviado; item_model nulo/omitido preserva o modelo existente — ver ADR-0013).
  3. Restaura itens soft-deleted presentes no payload.
  4. Não desativa itens ausentes do payload. A desativação é explícita, via DELETE /items/{external_id} (ver ADR-0015). Um lote parcial nunca apaga itens.
  5. Geometria: aplicada apenas a itens sem geometria; itens já posicionados têm a coordenada enviada ignorada silenciosamente (nunca sobrescreve — ver ADR-0014).
  6. Item inválido (geometry.type incompatível, coordenadas não-numéricas, item_model inexistente) é pulado e contado em skipped, sem derrubar o lote. Detalhes ficam em integration_item_sync_logs.meta.

Cada item do items[] aceita os mesmos campos do POST (item_model, geometry, properties, external_data, external_actions).

Resposta sucesso: 200.

Resposta exemplo:

{
  "created": 5,
  "updated": 12,
  "restored": 1,
  "deactivated": 0,
  "unchanged": 81,
  "skipped": 1,
  "total_received": 100,
  "total_in_tomodat": 99
}

deactivated é sempre 0 (mantido por compatibilidade; o sync não desativa — ver ADR-0015).

Endpoints de vínculo de tenant

Fluxo A - cliente final gera código, parceiro vincula

Passo 1 (cliente final, dentro do TOMODAT):

  • POST /api/integration/tenant-link-codes (JWT do usuário admin)

Passo 2 (sistema parceiro):

  • POST /api/integration/tenants/link com X-Integration-API-Key

Body exemplo:

{
  "code": "A1B2C3D4",
  "external_id": "erp_client_42",
  "integrated_types": ["cliente-hidrico", "reservatorio", "poco"]
}

Resposta sucesso:

{
  "tenant_id": "tenant_abc",
  "external_id": "erp_client_42",
  "linked": true,
  "integrated_types_configured": [
    {
      "slug": "cliente-hidrico",
      "external_base_url": null
    },
    {
      "slug": "reservatorio",
      "external_base_url": null
    },
    {
      "slug": "poco",
      "external_base_url": null
    }
  ]
}

Erros comuns:

  • 422: código inválido, expirado, já usado, ou tenant vinculado a outro parceiro.
  • 401: API key ausente/inválida.

Fluxo B - tenant já é do parceiro, só configurar tipos

Endpoint:

  • POST /api/integration/tenants/{tenant_id}/configure-types

Body exemplo:

{
  "integrated_types": [
    {
      "slug": "cliente-hidrico",
      "external_base_url": "https://erp.exemplo.com/api/clientes"
    },
    {
      "slug": "poco",
      "external_base_url": "https://erp.exemplo.com/api/pocos"
    }
  ]
}

Dados externos e ações

external_data

Objeto JSON livre com dados que o TOMODAT exibe para o usuário final.

Exemplo:

{
  "telefone": "47999990000",
  "status_financeiro": "em_dia",
  "plano": "Residencial"
}

external_actions

Lista de ações clicáveis para abrir páginas do sistema parceiro.

Formato recomendado:

[
  { "label": "Ver Detalhes", "url": "https://...", "icon": "eye" },
  { "label": "Nova OS", "url": "https://...", "icon": "plus" }
]

Observações:

  • icon é opcional.
  • url deve ser absoluta.
  • Links são abertos em nova aba no frontend.

Sobre endpoint de detalhes externos (proxy)

Na versão atual, o fluxo oficial usa dados cacheados (external_data e external_actions).

Um endpoint proxy para buscar detalhes online no sistema parceiro pode ser adicionado em versão futura, mas não faz parte do contrato obrigatório atual.

Export KML

GET /api/export/kml

Exporta todos os itens com geometria do tenant atual como arquivo KML (Google Earth).

Autenticação: JWT do usuário logado do tenant (não usa API key de parceiro). Acessível também pelo iframe embed.

Headers:

  • Authorization: Bearer <jwt>
  • X-Tenant: <tenant_id>

Comportamento:

  • Itens são agrupados por tipo em <Folder>.
  • Um <Style> por modelo (ou por tipo, quando o item não tem modelo) carrega o ícone do mapa; linhas usam line_color/line_width das specs do modelo, quando definidos.
  • Coordenadas em ordem KML lon,lat (WGS84).
  • Itens inativos (soft-deleted) são omitidos.

Resposta: 200, Content-Type: application/vnd.google-earth.kml+xml, com Content-Disposition: attachment; filename="tomodat-export.kml".

Fluxo do entregável IAT: exportar o KML → abrir no Google Earth Pro → montar o PDF a partir do mapa.

Embed token e iframe

Endpoint para gerar token:

  • POST /api/integration/embed-token

Body:

{
  "tenant_id": "tenant_abc"
}

Resposta:

{
  "embed_token": "<jwt>",
  "expires_in": 3600
}

Para renovação automática via postMessage, veja Renovação de Embed Token.

Troubleshooting rápido

401 Unauthorized

  • API key ausente ou inválida.
  • Token JWT expirado no fluxo de iframe.

403 Forbidden

  • Tenant não pertence ao parceiro da API key usada.

422 Unprocessable Entity

  • Payload inválido.
  • Tipo integrável inexistente.
  • Código de vínculo expirado/já usado.

429 Too Many Requests

  • Rate limit em geração de código de vínculo ou endpoints públicos.