Pular para o conteúdo
Veltra Ajuda
Manual da APIPlaybookEntrar

Referência da API

Guia do TMS: enviar o manifesto e receber entregas, comprovantes, baixas e km

Abrir o Swagger

Guia do TMS: enviar o manifesto e receber entregas, comprovantes, baixas e km

Este guia vai do primeiro teste até o comprovante na mão do seu sistema. Ele é para duas pessoas: quem programa a integração do TMS da transportadora e quem opera a torre e precisa entender o que o TMS recebe de volta. Todos os valores dos exemplos são fictícios e os exemplos são completos, para você copiar e colar.

Swagger e este guia: qual usar#

A API do Veltra tem um endereço único para todas as transportadoras: https://api.veltrasolution.com.

O queOndePara que serve
Swagger (mapa interativo)https://api.veltrasolution.com/swagger-ui.htmlVer cada rota e cada campo e fazer chamadas de teste. Abre sem chave.
Contrato em JSONhttps://api.veltrasolution.com/v3/api-docs/integracaoGerar um cliente automaticamente. Abre sem chave.
Este guiaesta páginaExplicar o porquê, a ordem das chamadas e o que esperar de volta.

O Swagger é o mapa interativo e este guia é o roteiro explicado. Dois cuidados:

  • O Swagger público cobre as rotas /integracao/**: filiais e manifestos. As rotas de leitura (/v1/saida) têm o próprio OpenAPI, que você baixa com a sua chave: GET /v1/saida/openapi/INTEGRACAO_TMS.
  • No Swagger, o botão de autorizar pede a chave no cabeçalho X-Veltra-Api-Key. Use a chave de Teste para experimentar. Uma chamada feita com a chave de Produção grava dado de verdade.

Nos exemplos deste guia, a chave é sempre SUA_CHAVE_AQUI, texto de mentira. Troque pela sua chave real, guardada em cofre de segredos e nunca em código versionado.

A história inteira em uma figura#

Texto
TMS                         Veltra                     Motorista (app)
 |                            |                              |
 |-- 1. POST manifesto ------>|                              |
 |<-- 201 (ids dos docs) -----|                              |
 |                            |-- manifesto chega no app --->|
 |                            |                              |
 |                            |<-- Cheguei (parada.chegada) -|
 |                            |<-- Baixa com foto/assinatura-|
 |                            |    (parada.saida, baixa.registrada)
 |                            |                              |
 |                            |-- sela o comprovante         |
 |<-- 2. evento/webhook ------|   (comprovante.emitido)      |
 |-- 3. GET eventos (cursor) >|                              |
 |-- 4. GET pdf / pacote ---->|                              |
 |-- 5. GET manifesto (confere tudo)                         |

Em palavras: o TMS envia o manifesto, o Veltra entrega a carga ao motorista, o motorista faz o trabalho no campo, e o Veltra devolve ao TMS cada fato (chegada, baixa, ocorrência, comprovante). Se o TMS perder um aviso, ele consegue recuperar tudo perguntando de novo (seção 6).

O mapa das rotas#

EtapaRotaChave
Testar a chaveGET /v1/api-keys/atualTMS
FiliaisGET e POST /integracao/v1/filiais, GET e PUT /integracao/v1/filiais/{id}TMS
EnviarPOST /integracao/v1/manifestosTMS
ConsultarGET /integracao/v1/manifestos/{numeroExterno}TMS
AlterarPUT /integracao/v1/manifestos/{numeroExterno}TMS
CancelarPOST /integracao/v1/manifestos/{numeroExterno}/cancelarTMS
EventosGET /v1/saida/eventosTMS
Manifestos alteradosGET /v1/saida/manifestos e GET /v1/saida/manifestos/{id}TMS
Situação de um documentoGET /v1/saida/documentos/{id}TMS
PDF do comprovanteGET /v1/saida/comprovantes/{id}/pdfTMS
Pacote de evidênciasGET /v1/saida/comprovantes/{id}/pacoteTMS
XML do evento 110180GET /v1/saida/eventos-cte/{id}/xmlTMS
PrazoGET e PUT /v1/saida/configuracao-prazoTMS

1. Criar a chave de API e testar#

Gere a chave no portal

Quem tem perfil de administrador abre Integrações > Chaves de consulta (/integracoes/chaves), desce até a seção Chaves do TMS, toca em Gerar chave do TMS, escolhe um Nome (por exemplo, "TMS teste") e o Ambiente: Teste ou Produção, e confirma em Gerar chave. O valor completo aparece uma única vez: copie na hora e guarde em cofre. A chave de Produção só é emitida para transportadora ativa.

Entenda os dois ambientes

A chave de Teste grava manifestos de teste (aba "Teste" em Manifestos), que nunca chegam a motoristas reais, nunca disparam webhook de produção e são apagados depois de cerca de 30 dias. A chave de Produção grava manifestos de verdade. O mesmo numeroExterno pode existir uma vez em cada ambiente, e cada chave só enxerga os dados do seu ambiente.

Teste a chave com 'quem sou eu'

Esta chamada não grava nada. Ela devolve a transportadora e o ambiente da chave que a fez.

curl
BASE="https://api.veltrasolution.com"

curl -s "$BASE/v1/api-keys/atual" \
  -H "X-Veltra-Api-Key: SUA_CHAVE_AQUI"

Resposta esperada, 200:

JSON
{
  "transportadoraId": "0192f3a4-0000-7000-8000-000000000001",
  "apiKeyId": "0192f3a4-0000-7000-8000-000000000002",
  "apiKeyPrefixo": "vlt_test_ab12cd34",
  "ambiente": "TESTE"
}

Chave errada, apagada ou revogada devolve 401. Chave de Produção que o teste grátis não cobre mais ainda lê normalmente; o que para é o envio de manifesto novo (veja TESTE_ENCERRADO na seção 8).

2. Cadastrar e sincronizar as filiais#

O manifesto de transferência cita as filiais pela sigla externa (siglaExterna), a mesma que você cadastra no Veltra. Sigla desconhecida recusa o manifesto inteiro com 400. Então as filiais vêm antes do primeiro manifesto de transferência.

O jeito mais simples é cadastrar no portal. Para sincronizar pelo TMS, use a chave de Produção (a de Teste só lê filiais; gravar com ela devolve 403).

Crie a filial

nome e siglaExterna são obrigatórios. A sigla não pode se repetir na transportadora (repetida devolve 409). raioGeofenceM (de 10 a 50000) é o raio, em metros, usado para conferir se o motorista está perto; se você não enviar, o padrão é 200.

curl
curl -X POST "$BASE/integracao/v1/filiais" \
  -H "X-Veltra-Api-Key: SUA_CHAVE_AQUI" \
  -H "Content-Type: application/json" \
  -d '{
    "nome": "Filial Campinas",
    "siglaExterna": "CPS",
    "endereco": "Rodovia Exemplo km 10, Campinas - SP",
    "latitude": -22.9056,
    "longitude": -47.0608,
    "raioGeofenceM": 300
  }'

Resposta, 201:

JSON
{
  "id": "0192f3a4-1111-7000-8000-000000000010",
  "nome": "Filial Campinas",
  "siglaExterna": "CPS",
  "endereco": "Rodovia Exemplo km 10, Campinas - SP",
  "latitude": -22.9056,
  "longitude": -47.0608,
  "raioGeofenceM": 300,
  "ativa": true
}

Pela API, o endereço não é localizado no mapa sozinho: sem latitude e longitude, a filial fica sem pino até alguém ajustar no portal.

Liste e atualize

curl
curl -s "$BASE/integracao/v1/filiais" \
  -H "X-Veltra-Api-Key: SUA_CHAVE_AQUI"

curl -X PUT "$BASE/integracao/v1/filiais/0192f3a4-1111-7000-8000-000000000010" \
  -H "X-Veltra-Api-Key: SUA_CHAVE_AQUI" \
  -H "Content-Type: application/json" \
  -d '{ "nome": "Filial Campinas", "siglaExterna": "CPS", "raioGeofenceM": 250, "ativa": true }'

No PUT, envie nome e siglaExterna de novo (são obrigatórios). ativa omitido mantém o valor atual.

3. Enviar o manifesto#

O modelo, em linguagem simples#

  • Manifesto: uma viagem ou romaneio. Tem um motorista, um veículo e uma lista de documentos. Vai inteiro, numa chamada, em JSON. O Veltra não recebe XML: recebe as chaves e os dados essenciais.
  • Documento: um CT-e (modelo 57) ou uma NF-e (modelo 55). O normal é mandar o CT-e com as NF-e que ele transporta. Em coleta, onde o CT-e ainda não existe, o documento é a própria NF-e.
  • Parada: o Veltra monta sozinho. Documentos do mesmo destinatário (CNPJ ou CPF) no mesmo endereço viram uma parada só. O mesmo CNPJ em outro endereço (as lojas de uma rede) é outra parada.
  • Tarefa: o que o motorista faz com o documento na parada, ENTREGA ou COLETA. Coleta e entrega no mesmo cliente e no mesmo manifesto ficam na mesma parada, com as duas tarefas.
  • Ordem: a ordem da lista documentos é a ordem sugerida das paradas.

Tipo do manifesto#

tipoPara que serve
ROTAO caminhão que sai com entregas e coletas juntas. É o que recomendamos para tudo que não é transferência.
TRANSFERENCIAEntre filiais. Exige filialOrigemSigla e filialDestinoSigla.
ENTREGA, COLETAContinuam aceitos por compatibilidade e funcionam como ROTA. Em COLETA, um documento sem tarefa é coleta. Nos demais tipos, sem tarefa é entrega.

O tipo não muda depois de criado: trocar o tipo num PUT devolve 400.

Campos do manifesto#

CampoObrigatórioRegra
tiposimVeja a tabela acima.
numeroExternosimNúmero do manifesto no seu sistema (até 60 caracteres). É a chave natural: único por transportadora e ambiente.
motoristasimcpf (11 dígitos, com ou sem máscara, com dígitos verificadores válidos) e nome (até 200 caracteres).
veiculosimplaca, do padrão antigo ou Mercosul, com ou sem hífen.
ajudantesnãoAté 2, cada um com cpf e nome. Não são usuários do app: são só dados do manifesto. Mais de 2 recusa o manifesto.
filialOrigemSigla, filialDestinoSiglasó em TRANSFERENCIASiglas das filiais cadastradas.
chaveMdfenãoChave do MDF-e do manifesto (modelo 58, 44 posições), só para rastreabilidade. O dígito verificador é conferido. Não substitui o numeroExterno.
quantidadePaletesnãoNúmero inteiro, informado e guardado com o manifesto.
documentossimDe 1 a 1000 documentos.

Campos de cada documento#

CampoObrigatórioRegra
modelosimNúmero: 57 (CT-e) ou 55 (NF-e). O 58 (MDF-e) é aceito pela validação, mas o uso normal do MDF-e é o chaveMdfe do manifesto. Envie como número, não como texto.
chaveCT-e: sim44 posições. O Veltra confere o modelo e o dígito verificador. Para NF-e é opcional.
numerosimAté 20 caracteres.
serienãoAté 10 caracteres.
cnpjTransportadoranãoSó CT-e. Ausente, o Veltra usa o CNPJ que está dentro da chave (posições 7 a 20).
cnpjEmbarcadorNF-e: simCNPJ de quem emitiu a NF. No CT-e não é usado.
tomadorCnpjnãoSó CT-e: CNPJ de quem paga o frete. Ausente, nenhum terceiro recebe PDF nem canhoto desse documento.
tarefanãoENTREGA ou COLETA.
embarcadornãocnpjCpf e nome de quem embarca a mercadoria.
destinatariosimVeja a tabela abaixo.
nfVinculadasrecomendado no CT-eAs NF-e que o CT-e transporta. Só vale para CT-e: em NF-e, o Veltra ignora e avisa.
volumes, pesonãovolumes inteiro, peso em kg. Não podem ser negativos.
valornãoValor do documento em reais, só para exibição. Não entra em cálculo.
prazoEntreganãoData AAAA-MM-DD. Veja o prazo de entrega.
cteEmitidoEmnãoData e hora de emissão do CT-e (formato ISO, com fuso). Serve de data-base do prazo.
latitude, longitudenãoDevem vir juntas. Com elas, a parada usa a posição direto; sem elas, o endereço é localizado em segundo plano.
protocoloAutorizacaonãoSó CT-e: nProt, exatamente 15 dígitos. Sem ele, o evento 110180 desse CT-e fica pendente de dados.
tipoServiconãoSó CT-e: NORMAL, SUBCONTRATACAO, REDESPACHO, REDESPACHO_INTERMEDIARIO ou SERVICO_VINCULADO_MULTIMODAL.
globalizadonãoSó CT-e: verdadeiro ou falso.

O destinatario:

CampoObrigatórioRegra
cnpjCpfsimCNPJ (numérico ou alfanumérico) ou CPF, com ou sem máscara, com dígitos verificadores válidos. Agrupa as paradas.
nomesimAté 200 caracteres.
enderecosimlogradouro, municipio e uf são obrigatórios. numero, complemento, bairro e cep são opcionais.
emailnãoPara o aviso da entrega ao destinatário. É dado pessoal: não registre em log.

Cada item de nfVinculadas: numero e cnpjEmbarcador obrigatórios; serie, chave (da NF-e), valor e nfeEmitidoEm opcionais. A mesma NF pode estar em mais de um CT-e.

Dois avisos que poupam horas de depuração:

  • Os nomes diferenciam maiúsculas de minúsculas. Um campo obrigatório com o nome errado é tratado como ausente e gera erro (por exemplo, cnpjEmbarcador, nunca cnpj_embarcador). Um campo que não existe no contrato é simplesmente ignorado.
  • A chave de acesso tem 44 posições. Normalmente só dígitos; os 12 caracteres do CNPJ de quem emitiu podem ter letras maiúsculas.

As chaves dos exemplos#

Os exemplos usam variáveis do shell para as chaves, cada uma partida em duas metades só para caber na tela. O resultado é a chave inteira, com o dígito verificador correto. Rode este bloco uma vez antes dos exemplos desta seção:

Shell
BASE="https://api.veltrasolution.com"

CTE1="3526111122233300018157""0010000051011187654329"
CTE2="3526111122233300018157""0010000051021287654336"
CTE3="3526111122233300018157""0010000051031387654343"
NFE1="3526111234567800019555""0010000880011111111112"
NFE2="3526111234567800019555""0010000880021222222220"
NFE3="3526111234567800019555""0010000880031333333337"
NFE_COLETA="3526111234567800019555""0010000880041444444444"
MDFE="3526111122233300018158""0010000007001555555558"
CTE_MIN="3526111122233300018157""0010000053011131313136"

Exemplo mínimo#

Uma entrega, um CT-e, só o que é obrigatório:

curl
curl -X POST "$BASE/integracao/v1/manifestos" \
  -H "X-Veltra-Api-Key: SUA_CHAVE_AQUI" \
  -H "Idempotency-Key: 0192f5aa-0000-7000-8000-000000000a01" \
  -H "Content-Type: application/json" \
  -d @- <<JSON
{
  "tipo": "ROTA",
  "numeroExterno": "MIN-0001",
  "motorista": { "cpf": "529.982.247-25", "nome": "Marcos Exemplo da Silva" },
  "veiculo": { "placa": "ABC1D23" },
  "documentos": [
    {
      "modelo": 57,
      "chave": "$CTE_MIN",
      "numero": "5301",
      "destinatario": {
        "cnpjCpf": "11.444.777/0001-61",
        "nome": "Mercado Exemplo Ltda",
        "endereco": { "logradouro": "Rua das Flores 120", "municipio": "Campinas", "uf": "SP" }
      }
    }
  ]
}
JSON

O cabeçalho Idempotency-Key é obrigatório (até 255 caracteres). Use um UUID novo a cada versão do manifesto.

Exemplo completo#

Um manifesto com tudo o que o Veltra aceita: ajudante, paletes, MDF-e, dois CT-e para o mesmo cliente (uma parada com dois documentos), um CT-e para outro cliente e uma coleta (NF-e) na indústria. No total, 4 documentos e 3 paradas.

curl
curl -X POST "$BASE/integracao/v1/manifestos" \
  -H "X-Veltra-Api-Key: SUA_CHAVE_AQUI" \
  -H "Idempotency-Key: 0192f5aa-0000-7000-8000-000000000b01" \
  -H "Content-Type: application/json" \
  -d @- <<JSON
{
  "tipo": "ROTA",
  "numeroExterno": "REL-2026-004518",
  "motorista": { "cpf": "529.982.247-25", "nome": "Marcos Exemplo da Silva" },
  "veiculo": { "placa": "ABC1D23" },
  "ajudantes": [
    { "cpf": "111.444.777-35", "nome": "Ajudante Exemplo Um" }
  ],
  "chaveMdfe": "$MDFE",
  "quantidadePaletes": 4,
  "documentos": [
    {
      "modelo": 57,
      "chave": "$CTE1",
      "numero": "5101",
      "serie": "1",
      "cnpjTransportadora": "11.222.333/0001-81",
      "tomadorCnpj": "12.345.678/0001-95",
      "tarefa": "ENTREGA",
      "embarcador": { "cnpjCpf": "12.345.678/0001-95", "nome": "Indústria Exemplo S.A." },
      "destinatario": {
        "cnpjCpf": "11.444.777/0001-61",
        "nome": "Mercado Exemplo Ltda",
        "endereco": {
          "logradouro": "Rua das Flores", "numero": "120", "complemento": "Galpão 2",
          "bairro": "Centro", "municipio": "Campinas", "uf": "SP", "cep": "13010-000"
        },
        "email": "recebimento@mercado.exemplo"
      },
      "nfVinculadas": [
        { "chave": "$NFE1", "numero": "88001", "serie": "1", "cnpjEmbarcador": "12.345.678/0001-95",
          "valor": 1520.75, "nfeEmitidoEm": "2026-11-04T14:30:00-03:00" }
      ],
      "volumes": 12,
      "peso": 350.5,
      "valor": 1520.75,
      "prazoEntrega": "2026-11-09",
      "cteEmitidoEm": "2026-11-04T15:10:00-03:00",
      "latitude": -22.9056,
      "longitude": -47.0608,
      "protocoloAutorizacao": "135260012345678",
      "tipoServico": "NORMAL",
      "globalizado": false
    },
    {
      "modelo": 57,
      "chave": "$CTE2",
      "numero": "5102",
      "serie": "1",
      "tarefa": "ENTREGA",
      "destinatario": {
        "cnpjCpf": "11.444.777/0001-61",
        "nome": "Mercado Exemplo Ltda",
        "endereco": {
          "logradouro": "Rua das Flores", "numero": "120", "complemento": "Galpão 2",
          "bairro": "Centro", "municipio": "Campinas", "uf": "SP", "cep": "13010-000"
        }
      },
      "nfVinculadas": [
        { "chave": "$NFE2", "numero": "88002", "serie": "1", "cnpjEmbarcador": "12.345.678/0001-95" }
      ],
      "volumes": 5,
      "peso": 90
    },
    {
      "modelo": 57,
      "chave": "$CTE3",
      "numero": "5103",
      "serie": "1",
      "tarefa": "ENTREGA",
      "destinatario": {
        "cnpjCpf": "44.455.566/0001-83",
        "nome": "Farmácia Exemplo Ltda",
        "endereco": {
          "logradouro": "Avenida Brasil", "numero": "800", "bairro": "Jardim Exemplo",
          "municipio": "Valinhos", "uf": "SP", "cep": "13270-000"
        }
      },
      "nfVinculadas": [
        { "chave": "$NFE3", "numero": "88003", "serie": "1", "cnpjEmbarcador": "12.345.678/0001-95" }
      ],
      "volumes": 8,
      "peso": 140
    },
    {
      "modelo": 55,
      "chave": "$NFE_COLETA",
      "numero": "88004",
      "serie": "1",
      "cnpjEmbarcador": "12.345.678/0001-95",
      "tarefa": "COLETA",
      "destinatario": {
        "cnpjCpf": "12.345.678/0001-95",
        "nome": "Indústria Exemplo S.A.",
        "endereco": { "logradouro": "Avenida Industrial 1000", "municipio": "Sumaré", "uf": "SP" }
      },
      "volumes": 20,
      "peso": 800
    }
  ]
}
JSON

Em TRANSFERENCIA, o formato é o mesmo, com mais dois campos no manifesto ("filialOrigemSigla": "CPS" e "filialDestinoSigla": "SJC") e o destinatario de cada documento é a filial de destino, com o CNPJ e o endereço dela.

Ler a resposta#

Resposta do exemplo completo, 201. As chaves aparecem abreviadas aqui com reticências; a resposta traz a chave inteira.

JSON
{
  "id": "0192f5aa-1c00-7a10-8b00-000000000001",
  "numeroExterno": "REL-2026-004518",
  "tipo": "ROTA",
  "ambiente": "PRODUCAO",
  "versaoAtual": 1,
  "quantidadeParadas": 3,
  "quantidadeDocumentos": 4,
  "documentos": [
    {
      "id": "0192f5aa-1c00-7a10-8b00-0000000000a1",
      "modelo": 57,
      "chave": "3526111122233300018157…",
      "numero": "5101",
      "serie": "1",
      "status": "PENDENTE",
      "nfVinculadas": [
        { "id": "0192f5aa-1c00-7a10-8b00-0000000000b1", "chave": "3526111234567800019555…",
          "numero": "88001", "serie": "1", "status": "PENDENTE" }
      ]
    }
  ],
  "erros": []
}

(A lista documentos real tem os 4 documentos; o exemplo mostra o primeiro.) Guarde o id de cada documento: é com ele que os eventos e as consultas falam. O id do manifesto é o manifestoId dos eventos de parada.

CampoO que é
id, numeroExterno, tipoIdentificação do manifesto.
ambienteTESTE ou PRODUCAO, conforme a chave.
versaoAtual1 na criação; sobe 1 a cada alteração.
quantidadeParadas, quantidadeDocumentosTotais.
documentosCada documento com seu status e as NF dele. O status é por documento, nunca por manifesto.
errosDocumentos recusados nesta chamada. Vazio quando tudo foi aceito.

O que o Veltra valida e o que rejeita#

O Veltra confere em duas camadas.

Camada 1: o cabeçalho do manifesto. Erro aqui recusa a chamada inteira com 400 (application/problem+json, tipo validacao):

  • tipo ausente ou fora da lista;
  • numeroExterno vazio;
  • motorista ou veiculo ausente; CPF do motorista inválido; placa inválida;
  • mais de 2 ajudantes, ou CPF ou nome de ajudante inválido;
  • chaveMdfe inválida;
  • documentos vazio ou com mais de 1000 itens;
  • TRANSFERENCIA sem as duas siglas, ou com sigla de filial que não existe;
  • cabeçalho Idempotency-Key ausente ou com mais de 255 caracteres;
  • JSON ilegível.

Camada 2: cada documento. Erro num documento recusa só aquele documento. O resto é gravado e a resposta lista o motivo em erros[], com documentoIndex (a posição do documento, começando em 0, na sua lista documentos). Se todos os documentos forem recusados, a chamada inteira vira 400.

Código em erros[]Quando
CHAVE_INVALIDAChave com tamanho, caractere ou dígito verificador errado.
CHAVE_CTE_OBRIGATORIACT-e (modelo 57) sem chave.
MODELO_INCOMPATIVELO modelo informado não bate com o modelo que a chave indica.
CNPJ_INVALIDOCNPJ do emitente (o cnpjTransportadora do CT-e, ou o cnpjEmbarcador da NF-e), do tomador ou de uma NF vinculada com dígito verificador errado.
DESTINATARIO_INVALIDODestinatário sem CNPJ ou CPF, com CNPJ ou CPF inválido, com nome acima de 200 caracteres, com e-mail inválido ou sem endereço completo (logradouro, município e UF).
DOCUMENTO_JA_BAIXADOSó no PUT: você tirou da lista um documento que já teve baixa ou ocorrência.
OUTROQualquer outro motivo; leia o texto em motivo.

Casos que caem em OUTRO: modelo fora de 55, 57 e 58; numero vazio ou longo demais; tarefa diferente de ENTREGA e COLETA; latitude sem longitude (ou o contrário) ou fora do intervalo; volumes, peso ou valor negativos; protocoloAutorizacao que não tem 15 dígitos; tipoServico fora da lista; o mesmo documento repetido no manifesto.

OUTRO também serve de aviso: se você mandar nfVinculadas, protocoloAutorizacao, tipoServico ou globalizado num documento que não é CT-e, o documento é aceito, esses campos são ignorados e aparece um item em erros[] dizendo isso.

Exemplo de um documento recusado entre dois aceitos (uma chave com o último dígito trocado). A resposta é 201 e o manifesto está gravado sem esse documento:

JSON
{
  "id": "0192f5aa-1c00-7a10-8b00-000000000002",
  "numeroExterno": "REL-2026-004519",
  "tipo": "ROTA",
  "ambiente": "PRODUCAO",
  "versaoAtual": 1,
  "quantidadeParadas": 1,
  "quantidadeDocumentos": 1,
  "documentos": [ "…só o documento aceito…" ],
  "erros": [
    {
      "documentoIndex": 1,
      "codigo": "CHAVE_INVALIDA",
      "motivo": "chave de acesso: dígito verificador inválido",
      "chave": "3526111122233300018157…",
      "numero": "5102"
    }
  ]
}

E quando nenhum documento serve, 400:

JSON
{
  "type": "https://veltra.com.br/problemas/documentos-invalidos",
  "title": "Nenhum documento válido",
  "status": 400,
  "detail": "Todos os documentos do manifesto foram recusados.",
  "erros": [
    { "documentoIndex": 0, "codigo": "DESTINATARIO_INVALIDO", "motivo": "destinatario.cnpjCpf inválido",
      "chave": "3526111122233300018157…", "numero": "5101" }
  ]
}

Regra de ouro para o seu código: depois de um 201 ou 200, sempre olhe erros[]. HTTP de sucesso não quer dizer que todos os documentos entraram.

Reenviar, atualizar e deduplicar#

Repetir o mesmo envio sem duplicar. Se a rede cair e você não souber se o envio chegou, repita a mesma chamada com a mesma Idempotency-Key. O Veltra guarda a chave por pelo menos 72 horas, separada por transportadora e ambiente.

SituaçãoResultado
Mesma Idempotency-Key e mesmo corpo200 com a resposta original; nada novo é gravado.
Mesma Idempotency-Key com corpo diferente409.
numeroExterno que já existe, com outra Idempotency-Key, num POST409. Para alterar, use PUT.

Alterar é reenviar o manifesto completo com PUT /integracao/v1/manifestos/{numeroExterno}, com Idempotency-Key nova. O numeroExterno do corpo precisa ser igual ao do caminho.

curl
curl -X PUT "$BASE/integracao/v1/manifestos/MIN-0001" \
  -H "X-Veltra-Api-Key: SUA_CHAVE_AQUI" \
  -H "Idempotency-Key: 0192f5aa-0000-7000-8000-000000000a02" \
  -H "Content-Type: application/json" \
  -d @- <<JSON
{
  "tipo": "ROTA",
  "numeroExterno": "MIN-0001",
  "motorista": { "cpf": "529.982.247-25", "nome": "Marcos Exemplo da Silva" },
  "veiculo": { "placa": "XYZ9A87" },
  "documentos": [
    {
      "modelo": 57,
      "chave": "$CTE_MIN",
      "numero": "5301",
      "destinatario": {
        "cnpjCpf": "11.444.777/0001-61",
        "nome": "Mercado Exemplo Ltda",
        "endereco": { "logradouro": "Rua das Flores 120", "municipio": "Campinas", "uf": "SP" }
      }
    }
  ]
}
JSON

O que vale saber:

  • Resposta 200, com versaoAtual somada de 1. Reenviar exatamente o conteúdo da versão atual não cria versão nova.
  • Documentos novos são acrescentados. Documentos que saíram da lista são marcados como removidos e saem do app do motorista. O Veltra guarda o histórico por versão.
  • Um documento que já teve baixa ou ocorrência não é removido: o resto da alteração é aplicado e a resposta traz DOCUMENTO_JA_BAIXADO em erros[], com documentoIndex nulo. Um comprovante nunca some.
  • Trocar motorista, veículo ou ajudantes é permitido. Trocar o tipo não (400).
  • Documento cancelado ou devolvido que volta num manifesto novo vira uma nova tentativa e volta a ficar PENDENTE. Documento já entregue continua entregue.
  • 404 se o manifesto não existe no ambiente da chave.

Cancelar#

Cancela só o que ainda não foi entregue ou coletado, documento por documento. O que já foi feito fica, com o comprovante. Os documentos cancelados saem do app. O corpo é opcional; o motivo (até 500 caracteres) fica na linha do tempo de cada documento cancelado.

curl
curl -X POST "$BASE/integracao/v1/manifestos/REL-2026-004518/cancelar" \
  -H "X-Veltra-Api-Key: SUA_CHAVE_AQUI" \
  -H "Content-Type: application/json" \
  -d '{ "motivo": "Veículo quebrado; carga redistribuída em outro manifesto" }'

Cancelar de novo devolve 200 com a mesma situação. Não existe o status "manifesto cancelado": confira o status CANCELADO de cada documento na resposta.

Consultar#

curl
curl -s "$BASE/integracao/v1/manifestos/REL-2026-004518" \
  -H "X-Veltra-Api-Key: SUA_CHAVE_AQUI"

Devolve o manifesto na versão atual, com o status de cada documento e de cada NF. É a consulta que você usa para conferir (seção 6).

4. O que acontece depois, em linguagem simples#

  1. O manifesto chega ao app. O motorista abre o app e vê uma rota com paradas na ordem. Se você alterar ou cancelar, o app mostra o aviso Alterado ou Cancelado.
  2. Coleta. Na parada de coleta, o motorista toca em Cheguei, marca as notas coletadas, tira as fotos que a transportadora exige e conclui a baixa. O documento passa a COLETADO.
  3. Transferência entre filiais. O motorista não dá baixa nota por nota. Ele registra a saída da base (com a placa, os manifestos e a quilometragem de saída) e, ao chegar na filial de destino, registra a chegada (com a quilometragem e as fotos pedidas). O documento passa a AGUARDANDO_CONFERENCIA e, quando a filial confere o recebimento, a NA_FILIAL.
  4. Entrega. Em cada parada: Cheguei, Dar baixa (notas entregues, foto do canhoto e assinatura de quem recebeu, quando a transportadora exige) e, ao ir embora, Saí. O documento passa a ENTREGUE.
  5. Não deu para entregar. O motorista registra uma ocorrência com o motivo (da lista que a transportadora cadastrou). O documento passa a COM_OCORRENCIA e continua aberto para uma nova tentativa.
  6. Comprovante. A cada baixa, o Veltra monta um comprovante com a hora, o local, as fotos, o recebedor e o código de verificação de cada arquivo. Se a transportadora ativou a assinatura digital opcional, o PDF também sai assinado (módulo 6).
  7. Sem sinal. Se o celular estiver sem internet, o app guarda tudo e envia quando o sinal volta. Por isso o seu TMS pode receber um fato minutos depois de ele ter acontecido na rua. A hora do fato vem no evento (ocorridoEm).

Os status que um documento pode ter:

StatusQuer dizer
PENDENTERecebido, esperando o trabalho começar.
EM_TRANSPORTEEm trânsito. Faz parte do contrato, mas hoje o Veltra não grava este status: o documento sai de PENDENTE direto para o resultado do campo.
ENTREGUEEntregue por completo.
ENTREGUE_PARCIALCT-e com parte das NF entregues e parte com ocorrência ou devolução.
DEVOLVIDODevolvido ao remetente. Faz parte do contrato, mas hoje o Veltra não grava este status.
CANCELADOCancelado antes da baixa.
COLETADOColeta confirmada no campo.
COM_OCORRENCIATentativa com ocorrência; segue aberto para nova tentativa.
AGUARDANDO_CONFERENCIATransferência baixada pelo motorista, esperando o conferente da filial.
NA_FILIALTransferência recebida e conferida.

5. Como o TMS recebe o que aconteceu#

Duas formas, e o melhor é usar as duas#

  • Cursor (GET /v1/saida/eventos): o seu sistema pergunta "o que mudou desde o ponto onde parei?". Simples, e não perde nada.
  • Webhook: o Veltra chama o seu endereço a cada fato novo. É mais rápido. Como a rede falha, trate o webhook como o aviso rápido e o cursor como a rede de proteção. A assinatura, as tentativas e o reenvio estão na referência de webhooks e no módulo 5.

Ler pelo cursor. Todo evento chega no mesmo envelope, e é o mesmo envelope dos webhooks:

curl
# primeira leitura: sem alteradoDesde
curl -s "$BASE/v1/saida/eventos?limite=50" \
  -H "X-Veltra-Api-Key: SUA_CHAVE_AQUI"

# as próximas: com o cursor guardado
curl -s "$BASE/v1/saida/eventos?limite=50&alteradoDesde=PROXIMO_CURSOR_AQUI" \
  -H "X-Veltra-Api-Key: SUA_CHAVE_AQUI"
JSON
{
  "itens": [
    {
      "id": "0192f5b0-3a00-7000-8000-000000000101",
      "tipo": "parada.chegada",
      "versao": 1,
      "ocorridoEm": "2026-11-06T11:02:14Z",
      "ambiente": "PRODUCAO",
      "dados": {
        "paradaId": "0192f5aa-1c00-7a10-8b00-0000000000c1",
        "manifestoId": "0192f5aa-1c00-7a10-8b00-000000000001"
      }
    }
  ],
  "proximoCursor": "PROXIMO_CURSOR_AQUI"
}

Regras que evitam surpresa:

  • limite vai de 1 a 200 (padrão 50). Continue chamando enquanto vierem itens; guarde o proximoCursor de cada resposta.
  • O cursor é opaco: não monte nem edite. Você pode guardar os últimos cursores e voltar a um deles se precisar reler.
  • O Veltra só devolve eventos com pelo menos 5 segundos de idade. Um evento recém-registrado pode demorar alguns segundos para aparecer.
  • Repetição é normal. O mesmo evento pode vir em duas respostas seguidas. Trate pelo id do evento: processar duas vezes deve dar o mesmo resultado.
  • O id do evento é estável e, nos eventos de baixa, ocorrência e comprovante, é o mesmo valor do id que vem dentro de dados (baixaId, ocorrenciaId, comprovanteId).
  • Sugestão de rotina: uma leitura a cada 30 a 60 segundos é suficiente.

Onde está cada informação#

Eu quero saberOnde o TMS encontra
Que o motorista chegou ou saiu da paradaEventos parada.chegada e parada.saida.
Que um documento foi entregue ou coletadoEvento baixa.registrada e o status do documento.
Que houve ocorrência e qual códigoEvento ocorrencia.registrada (campo codigo).
Que o comprovante ficou prontoEvento comprovante.emitido (campo comprovanteId).
O PDF do comprovanteGET /v1/saida/comprovantes/{comprovanteId}/pdf.
Quem recebeu, a hora, o local e as fotosDentro do pacote de evidências (manifesto.json e fotos/), e no PDF.
A situação do evento 110180 do CT-eEvento evento_cte.estado_alterado.
Os pontos de GPS do veículoEvento posicao.lote.
A quilometragem do veículokmSaida, kmChegada e kmRodados em GET /v1/saida/manifestos e o evento veiculo.km_registrado. Veja a seção "Km" abaixo.

Todos os tipos de evento#

O catálogo tem 8 tipos, e o Veltra envia todos. Esta seção diz quando cada um nasce, com um payload de cada um. O envelope é sempre id, tipo, versao (hoje 1), ocorridoEm, ambiente e dados. Mudança futura só acrescenta campo, sem subir a versao.

TipoEnviado hoje?Quando nasce
parada.chegadasimO motorista toca em Cheguei e o aviso chega ao Veltra.
parada.saidasimO motorista registra a saída da parada.
baixa.registradasimUma baixa de entrega ou coleta chega ao Veltra.
ocorrencia.registradasimUma ocorrência chega ao Veltra.
comprovante.emitidosimO comprovante é selado. O evento aparece até cerca de 1 minuto depois, porque o Veltra o registra numa varredura a cada 60 segundos.
evento_cte.estado_alteradosimMuda o estado do evento 110180 do CT-e. Também entra pela varredura de 60 segundos.
posicao.lotesim, só para o TMS, só em ProduçãoA cada 5 minutos, por veículo.
veiculo.km_registradosim, só para o TMSO motorista informou o km do veículo na saída da base ou na chegada.

Por isso, não dependa de evento para saber se o seu manifesto foi aceito, alterado ou cancelado: o POST, o PUT e o POST .../cancelar já respondem na hora, e a consulta do manifesto confirma.

O horário ocorridoEm: nos eventos de parada, baixa e ocorrência é a hora informada pelo celular (pode ter o relógio ajustado). No comprovante.emitido é a hora em que o Veltra selou o comprovante.

parada.chegada e parada.saida (mesmos campos):

JSON
{
  "id": "0192f5b0-3a00-7000-8000-000000000101",
  "tipo": "parada.chegada",
  "versao": 1,
  "ocorridoEm": "2026-11-06T11:02:14Z",
  "ambiente": "PRODUCAO",
  "dados": {
    "paradaId": "0192f5aa-1c00-7a10-8b00-0000000000c1",
    "manifestoId": "0192f5aa-1c00-7a10-8b00-000000000001"
  }
}

baixa.registrada:

JSON
{
  "id": "0192f5b0-3a00-7000-8000-000000000102",
  "tipo": "baixa.registrada",
  "versao": 1,
  "ocorridoEm": "2026-11-06T11:09:40Z",
  "ambiente": "PRODUCAO",
  "dados": {
    "baixaId": "0192f5b0-3a00-7000-8000-000000000102",
    "documentoId": "0192f5aa-1c00-7a10-8b00-0000000000a1",
    "paradaId": "0192f5aa-1c00-7a10-8b00-0000000000c1",
    "capturadoEm": "2026-11-06T11:09:40Z",
    "recebidoEm": "2026-11-06T11:09:47Z",
    "recebedor": { "nome": "Joana Exemplo" },
    "coordenada": { "latitude": -23.5505, "longitude": -46.6333, "precisaoM": 12.5 },
    "quantidadeDeFotos": 2
  }
}

O evento já traz o mínimo para você não precisar abrir o pacote: capturadoEm (a hora do celular, igual a ocorridoEm), recebidoEm (quando o Veltra recebeu), o nome de quem recebeu em recebedor.nome, a coordenada do celular na hora da baixa e quantidadeDeFotos. recebedor e coordenada só vêm quando o aparelho enviou o dado. O documento do recebedor (CPF ou RG) nunca vai no evento (LGPD): ele fica só no pacote e no PDF. Assinante TERCEIRO que não é o tomador do documento não recebe recebedor nem coordenada, só a quantidade de fotos. A hora do celular não é o horário legal: a prova está no comprovante (comprovante.emitido).

Um ponto de atenção: uma baixa pode cobrir vários documentos da mesma parada, mas o evento traz um documentoId (o de menor identificador). Para saber quais documentos foram baixados, confira o status de cada documento do manifesto (seção 6).

veiculo.km_registrado (só o assinante com papel TMS recebe; TERCEIRO não pode assinar este tipo):

JSON
{
  "id": "0192f5b0-3a00-7000-8000-000000000110",
  "tipo": "veiculo.km_registrado",
  "versao": 1,
  "ocorridoEm": "2026-11-06T18:30:00Z",
  "ambiente": "PRODUCAO",
  "dados": {
    "saidaId": "0192f5b0-3a00-7000-8000-000000000108",
    "momento": "CHEGADA",
    "placa": "ABC1D23",
    "manifestoIds": ["0192f5aa-1c00-7a10-8b00-000000000001"],
    "km": 120450,
    "kmSaida": 120000,
    "kmChegada": 120450,
    "kmRodados": 450
  }
}

Nasce um evento para a saída da base (momento SAIDA, com kmSaida) e outro para a chegada (momento CHEGADA, com kmChegada e, quando o km de saída é conhecido e a chegada não é menor, kmSaida e kmRodados). saidaId identifica a viagem; km é o número informado naquele momento. Os eventos de km só valem para viagens a partir desta versão: o Veltra não reenvia o histórico antigo, mas a rota GET /v1/saida/manifestos mostra o km de todas as viagens.

ocorrencia.registrada:

JSON
{
  "id": "0192f5b0-3a00-7000-8000-000000000105",
  "tipo": "ocorrencia.registrada",
  "versao": 1,
  "ocorridoEm": "2026-11-06T12:21:03Z",
  "ambiente": "PRODUCAO",
  "dados": {
    "ocorrenciaId": "0192f5b0-3a00-7000-8000-000000000105",
    "documentoId": "0192f5aa-1c00-7a10-8b00-0000000000a2",
    "codigo": "01"
  }
}

O codigo (até 20 caracteres) é o código que a transportadora cadastrou no portal. O texto livre e as fotos da ocorrência não vão no evento.

comprovante.emitido:

JSON
{
  "id": "0192f5b0-3a00-7000-8000-000000000104",
  "tipo": "comprovante.emitido",
  "versao": 1,
  "ocorridoEm": "2026-11-06T11:10:31Z",
  "ambiente": "PRODUCAO",
  "dados": {
    "comprovanteId": "0192f5b0-3a00-7000-8000-000000000104",
    "documentoId": "0192f5aa-1c00-7a10-8b00-0000000000a1"
  }
}

O comprovanteId é o que entra nas rotas de PDF e pacote. Também há comprovante de ocorrência e de transferência: o tipo do comprovante está dentro do pacote.

evento_cte.estado_alterado (só existe se a transportadora usa o perfil de comprovante digital homologado, que gera o evento 110180 do CT-e):

JSON
{
  "id": "0192f5b0-3a00-7000-8000-000000000106",
  "tipo": "evento_cte.estado_alterado",
  "versao": 1,
  "ocorridoEm": "2026-11-06T11:14:02Z",
  "ambiente": "PRODUCAO",
  "dados": {
    "documentoId": "0192f5aa-1c00-7a10-8b00-0000000000a1",
    "codigoEvento": 110180,
    "estado": "REGISTRADO",
    "cStat": 135,
    "protocolo": "135260012345678"
  }
}

cStat e protocolo só aparecem quando já existem. Os estados possíveis:

estadoQuer dizer
PENDENTE_DE_DADOSFaltam dados do CT-e (por exemplo, o protocoloAutorizacao). Envie pelo PUT.
AGUARDANDO_CTE_COMPLETOEsperando as demais NF-e do CT-e serem entregues.
AGUARDANDO_REVISAOEsperando aprovação da transportadora para enviar.
PENDENTE_DE_ENVIONa fila para enviar à SEFAZ.
EM_TRANSMISSAOSendo enviado à SEFAZ.
REGISTRADORegistrado na SEFAZ.
REGISTRADO_COM_ALERTARegistrado com alerta; a SEFAZ pediu atenção.
REJEITADORejeitado pela SEFAZ.
CANCELADOComprovante cancelado.

O XML assinado do evento está em GET /v1/saida/eventos-cte/{id}/xml (404 quando o perfil não gerou o 110180). Atenção: o {id} dessa rota é o identificador do pedido do evento no Veltra, e ele não vem no payload de evento_cte.estado_alterado (que traz documentoId). Veja "Lacunas conhecidas" na seção 8. O Veltra não transmite o evento 110190 de insucesso; ele é do TMS.

posicao.lote (só o TMS recebe; no máximo 60 pontos por lote; a cada 5 minutos, por veículo):

JSON
{
  "id": "0192f5b0-3a00-7000-8000-000000000107",
  "tipo": "posicao.lote",
  "versao": 1,
  "ocorridoEm": "2026-11-06T11:15:00Z",
  "ambiente": "PRODUCAO",
  "dados": {
    "placa": "ABC1D23",
    "janelaInicio": "2026-11-06T11:10:00Z",
    "janelaFim": "2026-11-06T11:15:00Z",
    "pontos": [
      { "lat": -22.9056, "lon": -47.0608, "precisaoM": 8, "registradoEm": "2026-11-06T11:10:20Z" },
      { "lat": -22.9101, "lon": -47.0577, "precisaoM": 11, "registradoEm": "2026-11-06T11:14:40Z" }
    ]
  }
}

Entregas e baixas: quem, quando, onde, recebedor#

O evento baixa.registrada é um aviso curto: diz que a baixa aconteceu, de qual documento, a hora, o nome de quem recebeu, a coordenada e quantas fotos. O detalhe completo (documento do recebedor, fotos, hashes) está no comprovante:

  • Pelo documento: GET /v1/saida/documentos/{documentoId} devolve id, numero, status, podeVerPdfECanhoto (para o TMS é sempre verdadeiro) e atualizadoEm.
curl
curl -s "$BASE/v1/saida/documentos/0192f5aa-1c00-7a10-8b00-0000000000a1" \
  -H "X-Veltra-Api-Key: SUA_CHAVE_AQUI"
JSON
{
  "id": "0192f5aa-1c00-7a10-8b00-0000000000a1",
  "numero": "5101",
  "status": "ENTREGUE",
  "podeVerPdfECanhoto": true,
  "atualizadoEm": "2026-11-06T11:09:41Z"
}
  • Pelo pacote de evidências: o arquivo manifesto.json dentro do ZIP traz, em texto, quem (o identificador do motorista e do aparelho), quando (a hora do celular e a hora em que o servidor recebeu), onde (latitude, longitude, precisão em metros e se a posição era simulada), quem recebeu (nome, documento e tipo do documento: CPF, RG ou OUTRO) e o resultado por documento. Um trecho, com campos abreviados:
JSON
{
  "versao": "veltra.comprovante.v1",
  "comprovanteId": "0192f5b0-3a00-7000-8000-000000000104",
  "tipo": "ENTREGA",
  "ambiente": "PRODUCAO",
  "emissor": { "id": "0192f3a4-0000-7000-8000-000000000001", "razaoSocial": "Transportadora Exemplo Ltda", "cnpj": "11222333000181" },
  "fato": {
    "id": "0192f5b0-3a00-7000-8000-000000000102",
    "tipo": "BAIXA_DOCUMENTOS",
    "capturadoEm": "2026-11-06T11:09:40.000Z",
    "recebidoEm": "2026-11-06T11:09:41.000Z",
    "motoristaId": "0192f4c0-0000-7000-8000-000000000031",
    "dispositivoId": "0192f4c0-0000-7000-8000-000000000041"
  },
  "posicao": { "latitude": "-22.9056000", "longitude": "-47.0608000", "precisaoM": 8, "mock": false },
  "recebedor": { "nome": "Joana Exemplo", "documento": "12345678909", "tipo": "CPF" },
  "documentos": [
    { "id": "0192f5aa-1c00-7a10-8b00-0000000000a1", "modelo": 57, "chave": "3526111122233300018157…", "numero": "5101", "resultado": "ENTREGUE" }
  ],
  "fotos": [
    { "id": "0192f5b0-3a00-7000-8000-000000000201", "tipo": "CANHOTO", "contentType": "image/jpeg" }
  ]
}

O manifesto.json também tem o código de verificação (SHA-256) de cada foto, a assinatura do aparelho e o elo do livro-razão. Os arquivos das fotos estão na pasta fotos/ do ZIP, sem recompressão.

Duas observações honestas:

  • O identificador do motorista no pacote é o do cadastro do Veltra, não o nome. O nome e o CPF você já tem: são os do motorista que o seu TMS enviou no manifesto.
  • Não existe hoje uma rota em JSON só com "quem recebeu". Esse dado está no PDF e no pacote.

Fotos e comprovantes: baixar o PDF e o pacote#

Pegue o comprovanteId

Ele vem no evento comprovante.emitido, no campo dados.comprovanteId.

Baixe o PDF

curl
curl -s -o "comprovante-5101.pdf" \
  "$BASE/v1/saida/comprovantes/0192f5b0-3a00-7000-8000-000000000104/pdf" \
  -H "X-Veltra-Api-Key: SUA_CHAVE_AQUI"

O PDF sai assinado digitalmente quando a transportadora ativou a assinatura opcional com o próprio e-CNPJ A1; sem ela, sai o PDF do comprovante sem assinatura. Se o comprovante ainda não foi publicado, a resposta é 409 (COMPROVANTE_AINDA_NAO_PUBLICADO): espere um pouco e tente de novo. Comprovante que não existe, ou de outro ambiente, devolve 404.

Baixe o pacote de evidências

curl
curl -s -o "evidencias-5101.zip" \
  "$BASE/v1/saida/comprovantes/0192f5b0-3a00-7000-8000-000000000104/pacote" \
  -H "X-Veltra-Api-Key: SUA_CHAVE_AQUI"

O ZIP traz: LEIA-ME.txt, comprovante.pdf, manifesto.json, manifesto.json.p7s (a assinatura do manifesto, só se o comprovante foi assinado), as fotos originais em fotos/, hashes.sha256 para conferir os arquivos (sha256sum -c hashes.sha256) e os arquivos de validação, quando existirem. O LEIA-ME.txt dentro do ZIP explica como conferir cada parte. A assinatura é do tipo AD-RB, sem carimbo do tempo (ADR 0011): a referência de tempo é a hora do servidor do Veltra. Se o comprovante será aceito por um embarcador, uma seguradora ou um juiz, isso depende de parecer jurídico; veja o módulo 6.

Baixe o XML do evento 110180, se o seu perfil o gera

curl
curl -s -o "evento-110180.xml" \
  "$BASE/v1/saida/eventos-cte/ID_DO_PEDIDO_AQUI/xml" \
  -H "X-Veltra-Api-Key: SUA_CHAVE_AQUI"

O XML vem assinado e com o protocolo da SEFAZ. Sem o evento, 404. Veja o aviso sobre o {id} no tipo evento_cte.estado_alterado.

Quem baixa PDF, pacote e XML é só a chave do TMS. Chaves de consulta (embarcador e destinatário) não têm essas rotas.

Ocorrências#

O evento ocorrencia.registrada traz o codigo e o documentoId. A ocorrência também gera um comprovante (do tipo ocorrência), com o mesmo comprovante.emitido, PDF e pacote. O documento passa a COM_OCORRENCIA e continua aberto para uma nova tentativa.

Km: o que existe e o que o TMS recebe#

O km vem do hodômetro informado pelo motorista. Na transferência entre filiais, o app pede a quilometragem de saída da base e de chegada na base, quando a transportadora liga a opção de km do veículo nas configurações do portal. O Veltra guarda os dois números por saída (com a placa e a hora) e avisa a torre quando o km de chegada é menor que o de saída ou quando o km de saída difere da última leitura conhecida do veículo.

O TMS recebe o km de duas formas:

  • Pela consulta de manifestos. GET /v1/saida/manifestos e GET /v1/saida/manifestos/{id} trazem kmSaida, kmChegada e kmRodados. São null quando o motorista não informou (e kmRodados também é null se a chegada for menor que a saída). Vale a viagem mais recente que levou o manifesto.

    JSON
    { "id": "0192f3a4-5b6c-7d8e-9f01-000000000001", "numero": "REL-2026-000123", "tipoManifesto": "TRANSFERENCIA",
      "atualizadoEm": "2026-11-06T18:30:00Z", "kmSaida": 120000, "kmChegada": 120450, "kmRodados": 450 }
  • Pelo evento veiculo.km_registrado, na hora em que o motorista informa (saída e chegada). Veja o payload na lista de eventos. Só o assinante com papel TMS recebe, e ele aparece na leitura incremental GET /v1/saida/eventos só para a chave de TMS.

Atenção ao cursor de manifestos. Informar o km não muda o atualizadoEm do manifesto. Se você usa GET /v1/saida/manifestos?alteradoDesde=... para descobrir mudanças, não vai ver um manifesto reaparecer só porque o km chegou: use o evento veiculo.km_registrado (ou consulte o manifesto pelo id quando o evento chegar).

O relatório de produtividade do portal (km percorrido por motorista no período) continua existindo, com o login do portal. O evento posicao.lote entrega os pontos de GPS do veículo, mas o Veltra não calcula nem entrega distância de rota por GPS: o km é o do hodômetro informado.

6. Conciliar: o TMS conferindo que recebeu tudo#

A regra: o manifesto é a lista do que deve acontecer; os eventos são o que aconteceu. Para fechar a conta, compare os dois.

Liste o que esperar

Para cada manifesto enviado, guarde o id de cada documento que veio na resposta do POST. Eles são a sua lista de espera.

Confira o status por manifesto

curl
curl -s "$BASE/integracao/v1/manifestos/REL-2026-004518" \
  -H "X-Veltra-Api-Key: SUA_CHAVE_AQUI"

Cada documento traz o status. PENDENTE e EM_TRANSPORTE ainda estão em aberto. ENTREGUE, ENTREGUE_PARCIAL, COLETADO, DEVOLVIDO, NA_FILIAL e CANCELADO terminaram. COM_OCORRENCIA terminou a tentativa, mas segue aberto.

Cruze com os comprovantes

Para todo documento que terminou, deve existir um evento comprovante.emitido com o mesmo documentoId. Monte no seu banco o mapa documentoId para comprovanteId a partir dos eventos. Documento terminado sem comprovante depois de alguns minutos: o comprovante pode estar esperando as fotos do aparelho ou sendo selado. Consulte o comprovante no portal; se ainda estiver sendo emitido, o PDF responde 409.

Releia quando faltar alguma coisa

Para reprocessar, volte o cursor: leia de novo a partir de um cursor antigo que você guardou, ou omita alteradoDesde para reler tudo desde o começo. Como cada evento tem id estável, reler não duplica nada se o seu sistema trata pelo id.

Duplicatas. Trate toda entrada pelo id do evento: se já está no seu banco, ignore. Para os fatos de campo, a chave natural é também o id interno (baixaId, ocorrenciaId, comprovanteId). Webhooks reenviados mantêm o mesmo webhook-id e o mesmo corpo.

Webhook que falhou. Se o seu endereço ficou fora do ar, o Veltra tenta de novo por até 72 horas (detalhes na referência de webhooks) e você pode pedir o reenvio no portal. Como segurança extra, rode o cursor pelo menos uma vez por hora.

Manifestos que mudaram. GET /v1/saida/manifestos?alteradoDesde=... lista os manifestos por data de atualização (id, numero, tipoManifesto, atualizadoEm e o km: kmSaida, kmChegada, kmRodados) e GET /v1/saida/manifestos/{id} devolve um. Use para descobrir manifestos mexidos, mas não como único sinal de baixa: para baixa, use o status dos documentos e os eventos.

Baixa com vários documentos. Como o evento traz só um documentoId por baixa, a conferência por status (passo 2) é a que não deixa documento para trás.

7. Fluxo completo de exemplo: 3 paradas, do envio ao comprovante#

Um manifesto de entrega com três paradas (um mercado, uma farmácia e uma pessoa física). Rode primeiro:

Shell
BASE="https://api.veltrasolution.com"
CTEa="3526111122233300018157""0010000052011212121215"
CTEb="3526111122233300018157""0010000052021222222232"
CTEc="3526111122233300018157""0010000052031232323233"
NFEa="3526111234567800019555""0010000881011313131314"
NFEb="3526111234567800019555""0010000881021323232323"
NFEc="3526111234567800019555""0010000881031333333340"

Passo A. O TMS envia o manifesto.

curl
curl -X POST "$BASE/integracao/v1/manifestos" \
  -H "X-Veltra-Api-Key: SUA_CHAVE_AQUI" \
  -H "Idempotency-Key: 0192f5aa-0000-7000-8000-000000000c01" \
  -H "Content-Type: application/json" \
  -d @- <<JSON
{
  "tipo": "ROTA",
  "numeroExterno": "REL-2026-004600",
  "motorista": { "cpf": "529.982.247-25", "nome": "Marcos Exemplo da Silva" },
  "veiculo": { "placa": "ABC1D23" },
  "documentos": [
    {
      "modelo": 57, "chave": "$CTEa", "numero": "5201", "serie": "1",
      "destinatario": {
        "cnpjCpf": "11.444.777/0001-61", "nome": "Mercado Exemplo Ltda",
        "endereco": { "logradouro": "Rua das Flores 120", "bairro": "Centro", "municipio": "Campinas", "uf": "SP" }
      },
      "nfVinculadas": [ { "chave": "$NFEa", "numero": "88101", "serie": "1", "cnpjEmbarcador": "12.345.678/0001-95" } ],
      "volumes": 10, "peso": 200
    },
    {
      "modelo": 57, "chave": "$CTEb", "numero": "5202", "serie": "1",
      "destinatario": {
        "cnpjCpf": "44.455.566/0001-83", "nome": "Farmácia Exemplo Ltda",
        "endereco": { "logradouro": "Avenida Brasil 800", "bairro": "Jardim Exemplo", "municipio": "Valinhos", "uf": "SP" }
      },
      "nfVinculadas": [ { "chave": "$NFEb", "numero": "88102", "serie": "1", "cnpjEmbarcador": "12.345.678/0001-95" } ],
      "volumes": 4, "peso": 60
    },
    {
      "modelo": 57, "chave": "$CTEc", "numero": "5203", "serie": "1",
      "destinatario": {
        "cnpjCpf": "123.456.789-09", "nome": "Maria Exemplo",
        "endereco": { "logradouro": "Rua das Acácias 45", "bairro": "Vila Exemplo", "municipio": "Vinhedo", "uf": "SP" }
      },
      "nfVinculadas": [ { "chave": "$NFEc", "numero": "88103", "serie": "1", "cnpjEmbarcador": "12.345.678/0001-95" } ],
      "volumes": 1, "peso": 12
    }
  ]
}
JSON

Resposta: 201, versaoAtual 1, quantidadeParadas 3, quantidadeDocumentos 3, e os três documentos PENDENTE. O TMS guarda os id: ...a1, ...a2 e ...a3. Se o envio der erro de rede, o TMS repete a mesma chamada com a mesma Idempotency-Key e recebe 200 com a resposta original.

Passo B. O motorista trabalha, o TMS é avisado. A sequência de eventos que o TMS encontra em GET /v1/saida/eventos, na ordem em que o Veltra os recebeu:

#EventoO que aconteceuQuando o TMS vê
1parada.chegada (parada 1)O motorista chegou ao mercado.Segundos depois de o celular enviar.
2baixa.registrada (documentoId ...a1)O mercado recebeu. Status do documento: ENTREGUE.Idem.
3parada.saida (parada 1)O motorista foi embora.Idem.
4comprovante.emitido (documentoId ...a1)O comprovante da entrega no mercado foi selado.Até cerca de 1 minuto depois de selado.
5parada.chegada (parada 2)O motorista chegou à farmácia.Segundos.
6ocorrencia.registrada (documentoId ...a2, codigo 01)Ninguém para receber. Status: COM_OCORRENCIA.Segundos.
7comprovante.emitido (documentoId ...a2)Comprovante da ocorrência.Até cerca de 1 minuto depois de selado.
8parada.chegada, baixa.registrada, parada.saida e comprovante.emitido (documentoId ...a3)Entrega para a Maria. Status: ENTREGUE.Idem.

Se o celular ficou sem sinal na estrada, os eventos 1 a 4 podem chegar todos juntos e depois. Cada um tem o ocorridoEm da hora real em que aconteceu.

Passo C. O TMS lê os eventos e baixa os comprovantes.

curl
curl -s "$BASE/v1/saida/eventos?limite=50&alteradoDesde=PROXIMO_CURSOR_AQUI" \
  -H "X-Veltra-Api-Key: SUA_CHAVE_AQUI"

Para cada item comprovante.emitido:

curl
curl -s -o "comprovante.pdf" \
  "$BASE/v1/saida/comprovantes/ID_DO_COMPROVANTE_AQUI/pdf" \
  -H "X-Veltra-Api-Key: SUA_CHAVE_AQUI"

Passo D. O TMS confere o fechamento.

curl
curl -s "$BASE/integracao/v1/manifestos/REL-2026-004600" \
  -H "X-Veltra-Api-Key: SUA_CHAVE_AQUI"
JSON
{
  "id": "0192f5aa-1c00-7a10-8b00-000000000010",
  "numeroExterno": "REL-2026-004600",
  "tipo": "ROTA",
  "ambiente": "PRODUCAO",
  "versaoAtual": 1,
  "quantidadeParadas": 3,
  "quantidadeDocumentos": 3,
  "documentos": [
    { "id": "0192f5aa-1c00-7a10-8b00-0000000000a1", "modelo": 57, "numero": "5201", "serie": "1", "status": "ENTREGUE" },
    { "id": "0192f5aa-1c00-7a10-8b00-0000000000a2", "modelo": 57, "numero": "5202", "serie": "1", "status": "COM_OCORRENCIA" },
    { "id": "0192f5aa-1c00-7a10-8b00-0000000000a3", "modelo": 57, "numero": "5203", "serie": "1", "status": "ENTREGUE" }
  ],
  "erros": []
}

(Os campos chave e nfVinculadas de cada documento foram omitidos aqui para encurtar.) A conta fecha: 2 entregues, 1 com ocorrência, 3 comprovantes. O documento da farmácia continua aberto para uma nova tentativa: para tentar de novo, o TMS inclui o CT-e 5202 num manifesto novo, com a mesma chave.

8. Erros e perguntas frequentes#

Códigos HTTP#

CódigoQuando
200Consulta, alteração, cancelamento, ou repetição idempotente (mesma chave, mesmo corpo).
201Manifesto criado. Olhe sempre erros[].
400Dado inválido no cabeçalho do manifesto, nenhum documento aceito, Idempotency-Key ausente, JSON ilegível, cursor ou limite inválido, troca de tipo no PUT.
401Chave ausente, errada ou revogada.
403Chave de Teste tentando gravar filial; chave de consulta numa rota só do TMS; chave de consulta suspensa (CHAVE_SUSPENSA), o que não acontece com a chave do TMS.
404Manifesto, documento ou comprovante que não existe no ambiente da chave.
409Idempotency-Key repetida com outro corpo; numeroExterno já existente num POST; sigla de filial repetida; TESTE_ENCERRADO; COMPROVANTE_AINDA_NAO_PUBLICADO.
429Limite de chamadas por minuto da chave, quando a chave tem limite. Espere o tempo do cabeçalho Retry-After.

Os erros vêm no formato application/problem+json, com type, title, status e detail.

Perguntas frequentes#

Preciso mandar o XML da nota? Não. O Veltra recebe as chaves e os dados essenciais, em JSON.

Posso mandar uma coleta sem CT-e? Pode. O documento é a NF-e (modelo 55), com numero, serie e cnpjEmbarcador. A chave da NF é opcional. Quando o CT-e for emitido, envie-o em outro manifesto com as mesmas NF e o Veltra liga os dois pelas chaves.

O que é TESTE_ENCERRADO? O teste grátis acabou. A chave de Produção não grava manifesto novo e responde 409 com esse código. A chave de Teste não é afetada, e repetir um envio que já foi gravado continua devolvendo o resultado gravado. O módulo 1 explica o período de teste.

Mandei um manifesto de teste e nada chegou ao celular. É assim mesmo: manifestos de Teste não vão a motoristas reais nem disparam webhook de Produção. Para o fluxo de campo completo, use um manifesto de Produção, no período de teste grátis.

O mesmo CT-e pode estar em dois manifestos? Pode, por exemplo numa reentrega. Cancelado ou devolvido volta a PENDENTE; entregue continua entregue. O mesmo documento duas vezes no mesmo manifesto é recusado.

Qual a diferença entre numero e chave? A chave identifica o documento fiscal (44 posições). O numero é o número que aparece na nota. Os dois são enviados.

O evento de baixa traz o recebedor? Traz só o nome (recebedor.nome), a hora, a coordenada e a quantidadeDeFotos. O documento do recebedor (CPF ou RG), as fotos e a prova completa estão no pacote de evidências e no PDF, depois do comprovante.emitido.

A hora do evento é a hora legal? Não. ocorridoEm de baixa e ocorrência é a hora declarada pelo celular, que pode ter o relógio ajustado. O pacote traz também a hora em que o servidor do Veltra recebeu (recebidoEm), que é a referência do comprovante.

Quanto tempo o evento demora? Eventos de parada, baixa e ocorrência aparecem poucos segundos depois de o celular enviar. O comprovante.emitido e o evento_cte.estado_alterado aparecem até cerca de 1 minuto depois do fato, porque são registrados por uma varredura a cada 60 segundos; o comprovante em si pode demorar mais, se estiver esperando as fotos do aparelho. A leitura por cursor ainda espera 5 segundos de segurança.

Como sei o prazo de entrega? O prazo é calculado pelo Veltra com a configuração da transportadora. Veja o guia de prazo e a configuração em GET e PUT /v1/saida/configuracao-prazo.

Onde fica o OpenAPI das rotas de leitura? Em GET /v1/saida/openapi/INTEGRACAO_TMS, com a sua chave. Ele traz só as rotas que a sua chave pode chamar.

Lacunas conhecidas#

São coisas que este guia descreve com franqueza porque o Veltra ainda não faz:

  • Transferência: a saída da base, a chegada e a conferência na filial não geram evento. O TMS acompanha pelo status do documento (AGUARDANDO_CONFERENCIA, NA_FILIAL).
  • Documento do recebedor e fotos em JSON: só dentro do pacote e do PDF. O evento de baixa traz o nome do recebedor, a hora, a coordenada e a quantidade de fotos, nunca o CPF ou RG.
  • XML do 110180: a rota pede o identificador do pedido do evento, que não vem nos eventos de saída. Combine com o suporte do Veltra como obter esse identificador para o seu CT-e.
  • Baixa com vários documentos: o evento traz um documentoId; os outros se conferem pelo status.

Para o contrato completo, o Swagger em https://api.veltrasolution.com/swagger-ui.html e a referência da API de entrada são os mapas; as regras de redação e os módulos de aprendizado estão no módulo 4.