store.loog.ai — API v1

API HTTP+JSON para integração programática com os serviços da Loog. Esta v1 cobre o produto vpo-sem-parar (Vale-Pedágio Obrigatório via Sem Parar). Próximos produtos: ciot-ailog, cte-*, mdfe-* — todos seguirão a mesma taxonomia.

Base URL

https://store.loog.ai/api/v1

Não há subdomain por ambiente — o ambiente (HMLG ou PROD) é definido pelo token.

Autenticação

Todas as requests precisam de um API token no header:

Authorization: Bearer loog_prod_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Ou, opcionalmente:

X-Loog-Token: loog_prod_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Como obter um token

  1. Acesse https://store.loog.ai/configuracoes/api-tokens com um usuário gestor/admin do seu tenant
  2. Clique "Novo token", dê um nome descritivo (ex: "ERP SAP", "Querocarga", "Mobile App") e escolha o ambiente
  3. Copie o token imediatamente — ele só aparece uma vez. Depois fica só o hash no banco.

Estrutura do token

loog_{env}_{base64url(32 bytes aleatórios)}
  • loog_hmlg_* — só atua em HMLG (homologação, dados fake, sem cobrança)
  • loog_prod_* — atua em PROD (emite VPO real, debita saldo)

Um token PROD nunca consegue acessar dados/viagens de HMLG e vice-versa — isolamento estrito.

Códigos de erro de autenticação

Status Significado
401 Token ausente Header não veio ou está malformatado
401 Token inválido Token não bate com nenhum hash ativo
401 Token revogado Token foi revogado pelo admin do tenant
401 Token expirado Passou da data expires_at
403 Token sem permissão pro serviço "X" Token foi criado sem scope para esse produto

Taxonomia de endpoints

/api/v1/{produto}/{ação}[/{id}]
  • {produto} é o serviço Loog: vpo-sem-parar, ciot-ailog (futuro), etc.
  • {ação} é o verbo: route-preview, emit, status, cancel, etc.
  • {id} opcional dependendo da ação (status e cancel precisam, emit e preview não)

Endpoints disponíveis na v1

Método Endpoint Descrição
POST /v1/vpo/sem-parar/route-preview Calcula praças+custo de uma rota (sem emitir)
POST /v1/vpo/sem-parar/verificar-placa Verifica se a placa está apta (TAG Sem Parar ativa)
POST /v1/vpo/sem-parar/emit Emite um VPO completo
GET /v1/vpo/sem-parar/list Lista VPOs do tenant (paginado, filtros)
GET /v1/vpo/sem-parar/status/{viagemId} Consulta status + recibo de uma viagem emitida
POST /v1/vpo/sem-parar/cancel/{viagemId} Cancela uma viagem (estorna créditos em prod)
GET /v1/vpo/sem-parar/saldo Saldo de créditos pré-pagos Loog do tenant
POST /v1/maplink/distancia KM/duração da rota (Maplink Trip, sem pedágio) — scope maplink
POST /v1/maplink/trip Rota completa + praças/pedágio por eixos — scope maplink
GET /v1/maplink/geocode Autocomplete de cidades (Geocode Maplink) — scope maplink

1. Preview de rota — calcular custo antes de emitir

POST /api/v1/vpo/sem-parar/route-preview

Útil pra mostrar ao usuário do seu sistema (ERP, marketplace) quanto vai custar o VPO antes de pagar. Não emite nada, não debita saldo.

Request

{
  "origem":  { "lat": -18.9186, "lng": -48.2772 },
  "destino": { "lat": -23.5505, "lng": -46.6333 },
  "paradas": [
    { "lat": -19.7472, "lng": -47.9381 }
  ],
  "eixos": 6
}

Campos:

  • origem (obrigatório): { lat, lng } numéricos
  • destino (obrigatório): idem
  • paradas (opcional): array de { lat, lng } — paradas intermediárias na ordem da viagem
  • eixos (opcional, default 6): número de eixos do veículo (2 a 10)

Response 200

{
  "ok": true,
  "env": "prod",
  "totalKm": 870,
  "custoTotal": 235.67,
  "eixos": 6,
  "pracas": [
    {
      "id": 572,
      "praca": "UBERABA KM 104+900 SUL",
      "rodovia": "BR 050",
      "km": 104.9,
      "concessionaria": "MGO RODOVIAS",
      "tarifa": 15.80,
      "lat": -19.754353,
      "lng": -47.933698
    },
    { "id": 574, "praca": "DELTA KM 198+060 SUL", "rodovia": "BR 050", "tarifa": 11.20, /* ... */ }
  ],
  "segmentos": [
    { "from": "-18.9186,-48.2772", "to": "-19.7472,-47.9381", "custo": 27.00, "pracas": 2, "km": 110 },
    { "from": "-19.7472,-47.9381", "to": "-23.5505,-46.6333", "custo": 208.67, "pracas": 10, "km": 760 }
  ]
}

Erros

Status Mensagem
400 origem e destino devem ter { lat, lng } numéricos
502 Falha roteirizar segmento N — Sem Parar fora do ar ou rota sem pedágio

2. Emitir VPO

POST /api/v1/vpo/sem-parar/emit

Cria (ou atualiza) motorista, veículo e rota; emite o VPO no Sem Parar; debita o saldo. Operação atômica — ou tudo dá certo, ou tudo falha.

Request

{
  "motorista": {
    "cpfCnpj": "32984580151",
    "nome": "Adenilson Ferreira da Silva",
    "rntrc": "004398915",
    "telefone": "+5511999999999"
  },
  "veiculo": {
    "placa": "ABC1D23",
    "eixos": 6,
    "tipo": "cavalo"
  },
  "rota": {
    "origem":  { "lat": -18.9186, "lng": -48.2772, "label": "Uberlândia/MG" },
    "destino": { "lat": -23.5505, "lng": -46.6333, "label": "São Paulo/SP" },
    "paradas": []
  },
  "periodo": {
    "inicio": "2026-06-01",
    "fim": "2026-06-05"
  },
  "referencia_externa": "PEDIDO-12345"
}

Campos:

  • motorista.cpfCnpj (obrigatório): só números, 11 ou 14 dígitos, DV válido
  • motorista.nome (obrigatório): razão social ou nome completo
  • motorista.rntrc (opcional): se não passar, é resolvido pelo Sem Parar via lookup do CPF
  • veiculo.placa (obrigatório): 7 caracteres alfanuméricos (formato Mercosul ou antigo)
  • veiculo.eixos (obrigatório): 2 a 10
  • rota.{origem,destino}.lat,lng (obrigatório): coordenadas
  • rota.{origem,destino}.label (opcional): rótulo legível pra aparecer no recibo
  • rota.paradas (opcional): paradas intermediárias
  • rota.pracas (opcional): IDs Sem Parar das praças da rota escolhida pelo seu sistema (ex.: pracas[].id_sem_parar de POST /v1/maplink/rota). Quando informado, o roteirizador Sem Parar não roda: a rota é cadastrada exatamente com essas praças e o VPO cobre só elas. Use quando o usuário escolheu o trajeto num mapa (alternativas Maplink). rota.km e rota.custo_previsto (opcionais) só alimentam o registro da viagem.
  • periodo.inicio/fim (obrigatório): YYYY-MM-DD, máximo 15 dias de janela
  • referencia_externa (opcional): seu ID interno (contrato, pedido). Aparece em /status pra correlação.

Response 201

{
  "ok": true,
  "viagemId": "75e64d34-4667-41d3-9220-09f32ebdf3cd",
  "env": "prod",
  "sem_parar_codigo": 103243030,
  "nsu": 1780016186869,
  "custoTotal": 235.67,
  "confirmada": true,
  "status": "emitida"
}

sem_parar_codigo é o número que precisa entrar no campo VPO do MDF-e do cliente.

Erros

Status Mensagem Causa
400 motorista.cpfCnpj inválido (DV ou tamanho) DV não bate ou não é 11/14 dígitos
400 veiculo.placa deve ter 7 caracteres Placa malformada
400 Rota sem praças de pedágio A rota não passa por pedágios
402 Saldo insuficiente Créditos pré-pagos zerados
502 Falha ao vincular placa ao embarcador RNTRC inativo ou inconsistente
502 Falha na compra (com timeout: true) Timeout do Sem Parar — consulte status antes de retentar

2b. Emitir VPO pelas praças da Maplink

POST /api/v1/vpo/sem-parar/emit-por-pracas

Emite o VPO a partir das praças que a Maplink já traçou, sem passar de novo pelo roteirizador do Sem Parar. Para quem usa o hub de rotas (seção 6), reroteirizar é trabalho repetido — e pode devolver um caminho diferente do planejado, o que faz o vale cobrir uma estrada que o motorista não vai fazer.

Cotação, verificação de saldo, compra e débito continuam os mesmos da seção 2: o valor debitado é o que o Sem Parar cobra, e a tarifa da Maplink serve só para conferência.

Request — as praças, de um dos dois jeitos:

{
  "motorista": { "cpfCnpj": "...", "nome": "..." },
  "veiculo":   { "placa": "ABC1D23", "eixos": 6 },
  "periodo":   { "inicio": "2026-09-22", "fim": "2026-09-25" },

  "pracas": [ { "id_sem_parar": 572, "nome": "Uberlândia", "tarifa": 12.50 } ],

  "rota": { "origem": {"lat": -18.64, "lng": -48.18},
            "destino": {"lat": -18.97, "lng": -49.46} },

  "politica_cobertura": "exigir_todas",
  "teto_divergencia_pct": 20,
  "referencia_externa": "OC-8891"
}
  • pracas — as que a Maplink devolveu em /api/v1/maplink/rota. Basta o id_sem_parar de cada uma; tarifa melhora a conferência de custo.
  • rota — alternativa a pracas: informando os pontos, o serviço consulta a Maplink internamente (o token precisa ter também o escopo maplink).
  • politica_cobertura — exigir_todas (default) ou permitir_parcial.
  • teto_divergencia_pct — default 20.

⚠️ Praça sem identificador do Sem Parar. O identificador vem da operadora Via Fácil na resposta da Maplink, e nem toda praça tem. Emitir ignorando uma praça compra VPO a menos: o motorista chega na cabine sem vale. Por isso o default recusa com 422, dizendo quais praças faltam; liberar exige politica_cobertura: "permitir_parcial", e aí a resposta traz o aviso do que ficou descoberto.

Response 201

{
  "ok": true,
  "viagemId": "…",
  "sem_parar_codigo": 123456,
  "custoTotal": 187.40,
  "antt_id_vpo": "…",
  "status": "emitida",
  "pracas": { "fonte": "maplink", "usadas": 6, "total": 6, "sem_id": 0, "cobertura_pct": 100 },
  "custo_conferencia": { "estimado": 181.10, "cotado": 187.40,
                         "diferenca": 6.30, "diferenca_pct": 3.5, "dentro_do_teto": true }
}

Response 422 — cobertura insuficiente

{
  "error": "cobertura de praças insuficiente para emitir",
  "motivo": "1 de 6 praças do trajeto não têm identificador do Sem Parar (…). Emitir assim compra VPO a menos…",
  "pracas_total": 6, "pracas_com_id": 5, "cobertura_pct": 83.3
}

Divergência de custo acima do teto não desfaz a emissão — o vale já foi comprado e debitado pelo valor do Sem Parar. Ela volta em avisos, porque quase sempre significa que as praças não são as mesmas dos dois lados.

Contabilização: cada chamada entra em api_request_log como vpo_emit_pracas, com o desfecho, a origem das praças e a divergência apurada.

3. Consultar status + recibo

GET /api/v1/vpo/sem-parar/status/{viagemId}

Consulta o estado atual no Sem Parar + recibo oficial. Idempotente, sem efeitos colaterais (exceto sincronizar status local se mudou).

Response 200

{
  "ok": true,
  "viagemId": "75e64d34-4667-41d3-9220-09f32ebdf3cd",
  "env": "prod",
  "referencia_externa": "PEDIDO-12345",
  "status_local": "emitida",
  "sem_parar_codigo": 103243030,
  "nsu": 1780016186869,
  "status": {
    "codigo": 2,
    "label": "Confirmada",
    "success": true
  },
  "recibo": {
    "success": true,
    "cnpjEmissor": "07.604.556/0001-36",
    "nomeEmissor": "EMPRESA BRASILEIRA DE BEBIDAS E ALIMENTOS S/A",
    "cnpjTransp": "039.350.207-40",
    "nomeTransp": "JOAQUIM JOSE FERNANDES",
    "nomeRota": "ASTOLFO DUTRA/MG → RIO DE JANEIRO/RJ",
    "catVeiculo": "02 EIXOS ROD DUPLA",
    "dataCompra": "2026-05-28T21:56:29-03:00",
    "dataExp": "2026-05-31",
    "dataViagem": "2026-05-28",
    "total": "65.93",
    "tipo": "ROTA PLANEJADA",
    "pracas": [
      {
        "nomePraca": "BR116, KM789+900, SUL, LEOPOLDINA",
        "nomeRodovia": "BR116",
        "nomeConcessionaria": "ECORIOMINAS",
        "placa": "DVT1B37",
        "tag": "0740177920",
        "tarifa": "27.55"
      }
    ]
  }
}

Códigos de status Sem Parar

Código Label Significado
1 Comprada VPO criado, ainda não confirmado pela cabine
2 Confirmada VPO ativo e válido
8 Cancelada Foi cancelada (pela embarcadora ou pelo Sem Parar)
9 Encerrada Motorista passou em todas as praças ou venceu prazo

Observação importante sobre o cnpjTransp no recibo

O CPF/CNPJ que aparece em cnpjTransp NÃO é necessariamente o mesmo que você enviou em motorista.cpfCnpj na emissão. O Sem Parar resolve o RNTRC vinculado à placa na base da ANTT e devolve o CPF do titular real do RNTRC. Esse é o CPF que vai aparecer no MDF-e e em fiscalizações.


3b. Listar VPOs

GET /api/v1/vpo/sem-parar/list

Lista os VPOs do seu tenant no ambiente do token (um token hmlg só vê viagens de homologação; prod só vê produção). Ideal para a tela de listagem.

Query params (todos opcionais)

Param Default Observação
limit 50 itens por página (máx 100)
offset 0 deslocamento para paginação
status — filtra por status (emitida, cancelada, erro, rascunho…)
referencia_externa — filtra pelo seu ID externo (igualdade)

Response 200

{
  "ok": true,
  "total": 137,
  "limit": 50,
  "offset": 0,
  "vpos": [
    {
      "viagemId": "d8ad700f-8f6f-4ef4-9bc7-f82fae00e6ce",
      "status": "emitida",
      "env": "prod",
      "referencia_externa": "PEDIDO-4471",
      "placa": "ABC1D23",
      "eixos": 5,
      "transportador": "TRANSPORTES EXEMPLO LTDA",
      "origem": "Uberlândia/MG",
      "destino": "Araguari/MG",
      "custo_total": 11,
      "antt_id_vpo": "89616552778700127661",
      "sem_parar_codigo": "104620273",
      "data_inicio": "2026-06-22",
      "data_fim": "2026-07-06",
      "created_at": "2026-06-19T14:08:27Z"
    }
  ]
}

Para o detalhe completo (recibo, praças, status atual no Sem Parar), use GET /v1/vpo/sem-parar/status/{viagemId} com o viagemId retornado aqui. Para buscar pelo seu identificador, passe ?referencia_externa=....


Verificar aptidão da placa

POST /api/v1/vpo/sem-parar/verificar-placa

Antes de emitir, verifica se a placa tem TAG Sem Parar ativa (apta a receber Vale-Pedágio). Evita tentar emitir para um veículo que vai falhar.

Request

{ "placa": "ABC1D23" }

Response 200

{
  "ok": true,
  "placa": "ABC1D23",
  "apto": true,
  "temTag": true,
  "tag": "0740177920",
  "proprietario": "JOAQUIM JOSE FERNANDES",
  "eixos": 5,
  "statusCodigo": 0,
  "motivo": "Apto: a placa possui TAG Sem Parar ativa e pode receber Vale-Pedágio."
}
  • apto: true → pode emitir. apto: false → não emita (oriente o titular a regularizar a TAG). statusCodigo: 0 = apto, 4 = sem TAG/não encontrada.

4. Cancelar VPO

POST /api/v1/vpo/sem-parar/cancel/{viagemId}

Cancela uma viagem emitida ou em uso. Em ambiente PROD, estorna automaticamente o valor para o saldo de créditos da Loog.

Response 200

{
  "ok": true,
  "viagemId": "75e64d34-4667-41d3-9220-09f32ebdf3cd",
  "status": "cancelada"
}

Erros

Status Mensagem Causa
400 Status "encerrada" — só cancela emitida/em_uso A viagem já foi consumida em praça
403 Token de ambiente "prod" não pode cancelar viagem do ambiente "hmlg" Mismatch de env

5. Saldo de créditos

GET /api/v1/vpo/sem-parar/saldo

Retorna o saldo de créditos pré-pagos Loog do seu tenant. Útil pra exibir o saldo disponível e bloquear a emissão antes mesmo de chamar /emit (que também valida saldo e devolve 402 Saldo insuficiente). O saldo é por tenant, não por ambiente — em HMLG não há débito real, mas o número reflete o saldo que valeria em PROD. Adicionar saldo é feito na store (https://store.loog.ai/creditos).

Response 200

{
  "ok": true,
  "env": "hmlg",
  "saldo_centavos": 1500000,
  "saldo": 15000,
  "saldo_formatado": "R$ 15.000,00"
}

6. Maplink — rotas e distâncias (hub de APIs)

A store centraliza o acesso à Maplink Platform para os sistemas do ecossistema (scope maplink no token — a mesma autenticação da v1). Toda requisição é registrada por cliente (token) e aparece no módulo "Maplink → Uso por cliente" do dashboard.

6.1 Distância da rota (a API de KM)

POST /api/v1/maplink/distancia

Só a quilometragem/duração da rota, sem pedágio (~300ms). O critério default é THE_SHORTEST (mais curta) — a modalidade usada na emissão de VPO — e vem explícito na resposta (mode).

Request (pontos aceitam coordenada OU cidade+UF, como no route-preview):

{
  "origem":  { "cidade": "Araguari", "uf": "MG" },
  "destino": { "lat": -29.8425, "lng": -51.1408 },
  "paradas": [ { "cidade": "Santa Cruz do Sul", "uf": "RS" } ],
  "calculationMode": "THE_SHORTEST"
}
  • origem / destino (obrigatórios): { lat, lng } ou { cidade, uf }
  • paradas (opcional): máx. 10, mesma forma, na ordem da viagem
  • calculationMode (opcional): THE_SHORTEST (default) | THE_FASTEST

Response 200 (cache permanente por origem/destino/paradas/critério — km de rota não varia; a 1ª consulta de cada rota vai à Maplink, as demais saem do banco):

{
  "ok": true,
  "km": 1715.4,
  "metros": 1715400,
  "duracaoMin": 1654,
  "mode": "THE_SHORTEST",
  "cached": false
}

Erros: 400 (ponto inválido, geocode falhou, >10 paradas), 401/403 (token/scope), 429 (rate limit 120/min por token, header Retry-After), 502 (Maplink Trip: HTTP n + detail).

6.2 Rota completa + pedágio

POST /api/v1/maplink/trip

Proxy fiel da Trip v2 síncrona: polyline por trecho, praças com tarifa por eixo e serviceTypes (o serviceId da operadora "Via Facil" é o ID da praça no Sem Parar). Body: points[{siteId?, latitude, longitude}] (2–200), vehicleType? (enum Maplink de eixos), calculationMode? (default THE_FASTEST, igual à Maplink), tagDiscount?, calculationDate? (ms), avoidanceTypes?. Resposta = JSON cru da Maplink. Rate limit 60/min.

6.2b Rota completa (com paradas) + pedágio por eixo

POST /api/v1/maplink/rota

Calcula a rota real origem → paradas (na ordem) → destino numa chamada e devolve km + pedágio total para o veículo (por nº de eixos) + praças com ID Sem Parar. É a fonte do cache de rota do QC (piso ANTT que respeita as paradas).

Request:

{
  "pontos": [
    { "cidade": "Araguari", "uf": "MG" },
    { "cidade": "Santa Cruz do Sul", "uf": "RS" },
    { "lat": -29.8425, "lng": -51.1408 }
  ],
  "eixos": 5
}
  • pontos (obrigatório): ≥ 2, na ordem (origem, paradas…, destino); cada um {lat,lng} ou {cidade,uf}; até 200.
  • eixos (opcional, default 5): 2–10, vira o vehicleType da Toll.
  • calculationMode (opcional): THE_SHORTEST (default) | THE_FASTEST.

Response 200 (cache de 7 dias — km é estável, o pedágio é uma prévia de 1 semana):

{
  "ok": true,
  "km": 1715.8,
  "pedagio_total": 636.60,
  "eixos": 5,
  "mode": "THE_SHORTEST",
  "pracas": [
    { "nome": "Pedágio - Araguari 2", "rodovia": "BR-050 ...", "tarifa": 31.50, "id_sem_parar": "570" }
  ],
  "cached": false
}

Erros: 400 (ponto inválido / geocode / limites), 401/403, 429 (120/min por token), 502.

6.3 Autocomplete de cidades

GET /api/v1/maplink/geocode?q=uberl

Devolve { results: [{ label, type, city, state, lat, lon }] } — só cidades (CITY_CENTROID). q com menos de 3 caracteres devolve lista vazia (200). Rate limit 240/min.



7. CIOT — emissão na ANTT (hub de APIs)

Serviço de emissão de CIOT (Treeal/ANTT) exposto como API do hub: scope ciot no token, uma linha em api_request_log por requisição, limite por cliente. É independente do CIOT de tela da store (/api/ciot/treeal/*) — não compartilha tabela nem cobrança; as credenciais do canal são as mesmas.

O contrato de entrada completo, com o modelo de perfil por cliente e os casos de aceite, está em docs/ciot-contrato-v1.md.

⚠️ ambiente é obrigatório em toda chamada (prod | hmg). Não existe default em lugar nenhum deste serviço: esquecer o ambiente nunca pode mandar uma declaração de teste para a ANTT de verdade.

7.1 Emitir

POST /api/v1/ciot/emissoes

Faz o ciclo inteiro: reserva o número na ANTT (gerar), confere se uma tentativa anterior já ficou registrada e declara.

{
  "ambiente": "prod",
  "referencia_externa": "161147",
  "operacao": {
    "contratado":   { "cpf_cnpj": "...", "rntrc": "..." },
    "destinatario": { "cpf_cnpj": "..." },
    "veiculos": [ { "placa": "ABC1D23", "eixos": 3 } ],
    "viagem": {
      "inicio": "2026-09-14", "fim": "2026-09-16", "km_total": 654,
      "trechos": [
        { "origem": {"ibge": 2301109}, "destino": {"ibge": 2607901}, "km": 639 },
        { "origem": {"ibge": 2607901}, "destino": {"ibge": 2611606}, "km": 15 }
      ]
    },
    "carga": { "natureza_ncm4": 2202, "peso_kg": 12276 },
    "frete": {
      "valor": 4584.00,
      "creditado": { "cpf_cnpj": "...", "conta": { "banco": "237", "agencia": "...", "conta": "..." } }
    },
    "contexto": { "filial": "ARACATI" }
  }
}

O que não precisa ir no request quando o perfil do cliente resolve: contratante, parcelas (frações, desconto, prazo), tipo de operação e tipo de carga. O que o serviço deriva sozinho: composição veicular a partir dos eixos do conjunto, km inteiro por trecho fechando com o total, junção de trechos curtos e o próprio CIOT 12.

Três modos, no mesmo endpoint:

modo quando usar
operacao o serviço monta o payload com o perfil do cliente
payload_pronto você já monta o payload da ANTT e quer só o canal
dry_run: true monta e devolve sem tocar na ANTT; com explain: true, diz de onde veio cada campo

Response 200

{
  "ok": true,
  "operacao_id": "…",
  "ciot_12": "520030583215",
  "ciot_16": "5600005466380452",
  "protocolo": "…",
  "codigo_antt": "110",
  "perfil": "britvic_v1",
  "perfil_versao": 3
}

Idempotência. A mesma referencia_externa, do mesmo cliente, no mesmo ambiente é sempre a MESMA operação: já declarada devolve 200 com ja_declarada: true e o número que existe, sem falar com a ANTT; em andamento devolve 409. Declarar duas vezes gera documento fiscal duplicado que, na modalidade 1, não se retifica — só se resolve por cancelamento em 24 h.

Quando falha. 422 com faltando: [{campo, motivo, esperado_de}] (dados insuficientes) ou com o código da ANTT (recusa — o número reservado é reaproveitado na próxima tentativa da mesma referência). 502 quando o canal não responde: repita com a mesma referência, que o serviço consulta a ANTT antes de declarar de novo.

7.2 Estado da operação

GET /api/v1/ciot/emissoes/{referencia_externa}?ambiente=prod

Lê o registro do serviço (não chama a ANTT): status, CIOT 12/16, protocolo, código, mensagem e cancelamento.

7.3 Cancelar

POST /api/v1/ciot/emissoes/{referencia_externa}/cancelar
{ "ambiente": "prod", "motivo_cancelamento": "carga cancelada pelo embarcador" }

Janela de 24 h após a declaração; o motivo é obrigatório e fica no registro.

7.4 Consultar um CIOT na ANTT

POST /api/v1/ciot/consultar
{ "ambiente": "prod", "ciot_12": "520030583215", "ano": 2026 }

Pergunta direta ao canal — útil para saber se uma declaração que ficou sem resposta chegou a ser registrada.

7.5 Pré-check do transportador

POST /api/v1/ciot/transportadores/situacao
{ "ambiente": "prod", "cpf_cnpj": "...", "rntrc": "..." }

POST /api/v1/ciot/transportadores/frota
{ "ambiente": "prod", "cpf_cnpj": "...", "rntrc": "...", "placas": ["ABC1D23"] }

Transportador irregular e placa sem vínculo são as recusas mais comuns, e descobrir isso na declaração desperdiça um número reservado. A resposta separa "a ANTT disse que não serve" de "não deu para perguntar" (indisponivel: true) — a primeira é problema de cadastro, a segunda é problema de canal.

7.6 Descobrir o que mandar

GET /api/v1/ciot/schema

Devolve o contrato menos o que o perfil deste token já resolve, as dimensões que ele espera em operacao.contexto (ex.: filial) e o que o serviço deriva sozinho. Dois clientes recebem respostas diferentes.

7.7 Limites

endpoint por minuto, por cliente
ciot_emitir, ciot_cancelar 30
ciot_consultar, ciot_status, ciot_precheck 120
ciot_schema 60

Boas práticas

1. Use HMLG primeiro

Antes de ir a PROD, teste todo o fluxo em HMLG (token loog_hmlg_*). Não há cobrança, não há débito de saldo. AVISO: o Sem Parar tem instabilidade conhecida no HMLG — se cair na call, o nosso endpoint indica que você pode usar PROD com placas de teste pra validar.

2. Use referencia_externa

Sempre passe seu ID interno (pedido, contrato, frete) em referencia_externa no /emit. Ele aparece no /status — fica fácil correlacionar com seu sistema sem precisar guardar o mapeamento.

3. Idempotência

Se /emit der timeout (502 com timeout: true), NÃO retente cegamente — primeiro consulte por referencia_externa (use o endpoint /status quando disponível) ou via dashboard. O Sem Parar pode ter emitido com sucesso mesmo retornando timeout pra nós.

4. Polling de status

Pra acompanhar consumo (motorista passando pelas praças), faça polling a cada 5-15 minutos no /status. Não é Webhook (ainda) — em roadmap.

5. Rate limit

Não publicamos limite estrito ainda — esperamos uso razoável de até 50 req/min por token. Avise se precisar de mais.


Exemplos práticos

cURL — preview

curl -X POST https://store.loog.ai/api/v1/vpo/sem-parar/route-preview \
  -H "Authorization: Bearer loog_prod_xxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "origem":  { "lat": -18.9186, "lng": -48.2772 },
    "destino": { "lat": -23.5505, "lng": -46.6333 },
    "eixos": 6
  }'

Node.js (fetch nativo)

const TOKEN = process.env.LOOG_API_TOKEN; // loog_prod_...

async function emitirVpo(payload) {
  const r = await fetch("https://store.loog.ai/api/v1/vpo/sem-parar/emit", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${TOKEN}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify(payload),
  });
  const j = await r.json();
  if (!r.ok) throw new Error(`${r.status}: ${j.error}`);
  return j;
}

// Uso:
const resp = await emitirVpo({
  motorista: { cpfCnpj: "32984580151", nome: "Adenilson Ferreira" },
  veiculo:   { placa: "ABC1D23", eixos: 6 },
  rota: {
    origem:  { lat: -18.9186, lng: -48.2772, label: "Uberlândia/MG" },
    destino: { lat: -23.5505, lng: -46.6333, label: "São Paulo/SP" },
  },
  periodo: { inicio: "2026-06-01", fim: "2026-06-05" },
  referencia_externa: "PEDIDO-12345",
});
console.log("VPO emitido:", resp.sem_parar_codigo);

Python (requests)

import os, requests

TOKEN = os.environ["LOOG_API_TOKEN"]
BASE = "https://store.loog.ai/api/v1"

def emitir_vpo(payload):
    r = requests.post(
        f"{BASE}/vpo-sem-parar/emit",
        json=payload,
        headers={"Authorization": f"Bearer {TOKEN}"},
    )
    r.raise_for_status()
    return r.json()

Roadmap

  • Webhooks para eventos vpo.consumed / vpo.cancelled (eliminar polling)
  • Listagem de VPOs + busca por referencia_externa (GET /vpo/sem-parar/list)
  • Endpoint de relatório (extrato, totalizadores) por período
  • Emissão de CIOT na ANTT (/api/v1/ciot/*, seção 7)

Sugestões e bugs: comercial@loog.ai