Referência da API
Guia do TMS: enviar o manifesto e receber entregas, comprovantes, baixas e km
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 que | Onde | Para que serve |
|---|---|---|
| Swagger (mapa interativo) | https://api.veltrasolution.com/swagger-ui.html | Ver cada rota e cada campo e fazer chamadas de teste. Abre sem chave. |
| Contrato em JSON | https://api.veltrasolution.com/v3/api-docs/integracao | Gerar um cliente automaticamente. Abre sem chave. |
| Este guia | esta página | Explicar 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#
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#
| Etapa | Rota | Chave |
|---|---|---|
| Testar a chave | GET /v1/api-keys/atual | TMS |
| Filiais | GET e POST /integracao/v1/filiais, GET e PUT /integracao/v1/filiais/{id} | TMS |
| Enviar | POST /integracao/v1/manifestos | TMS |
| Consultar | GET /integracao/v1/manifestos/{numeroExterno} | TMS |
| Alterar | PUT /integracao/v1/manifestos/{numeroExterno} | TMS |
| Cancelar | POST /integracao/v1/manifestos/{numeroExterno}/cancelar | TMS |
| Eventos | GET /v1/saida/eventos | TMS |
| Manifestos alterados | GET /v1/saida/manifestos e GET /v1/saida/manifestos/{id} | TMS |
| Situação de um documento | GET /v1/saida/documentos/{id} | TMS |
| PDF do comprovante | GET /v1/saida/comprovantes/{id}/pdf | TMS |
| Pacote de evidências | GET /v1/saida/comprovantes/{id}/pacote | TMS |
| XML do evento 110180 | GET /v1/saida/eventos-cte/{id}/xml | TMS |
| Prazo | GET e PUT /v1/saida/configuracao-prazo | TMS |
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.
BASE="https://api.veltrasolution.com"
curl -s "$BASE/v1/api-keys/atual" \
-H "X-Veltra-Api-Key: SUA_CHAVE_AQUI"Resposta esperada, 200:
{
"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 -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:
{
"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 -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,
ENTREGAouCOLETA. 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#
tipo | Para que serve |
|---|---|
ROTA | O caminhão que sai com entregas e coletas juntas. É o que recomendamos para tudo que não é transferência. |
TRANSFERENCIA | Entre filiais. Exige filialOrigemSigla e filialDestinoSigla. |
ENTREGA, COLETA | Continuam 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#
| Campo | Obrigatório | Regra |
|---|---|---|
tipo | sim | Veja a tabela acima. |
numeroExterno | sim | Número do manifesto no seu sistema (até 60 caracteres). É a chave natural: único por transportadora e ambiente. |
motorista | sim | cpf (11 dígitos, com ou sem máscara, com dígitos verificadores válidos) e nome (até 200 caracteres). |
veiculo | sim | placa, do padrão antigo ou Mercosul, com ou sem hífen. |
ajudantes | não | Até 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, filialDestinoSigla | só em TRANSFERENCIA | Siglas das filiais cadastradas. |
chaveMdfe | não | Chave do MDF-e do manifesto (modelo 58, 44 posições), só para rastreabilidade. O dígito verificador é conferido. Não substitui o numeroExterno. |
quantidadePaletes | não | Número inteiro, informado e guardado com o manifesto. |
documentos | sim | De 1 a 1000 documentos. |
Campos de cada documento#
| Campo | Obrigatório | Regra |
|---|---|---|
modelo | sim | Nú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. |
chave | CT-e: sim | 44 posições. O Veltra confere o modelo e o dígito verificador. Para NF-e é opcional. |
numero | sim | Até 20 caracteres. |
serie | não | Até 10 caracteres. |
cnpjTransportadora | não | Só CT-e. Ausente, o Veltra usa o CNPJ que está dentro da chave (posições 7 a 20). |
cnpjEmbarcador | NF-e: sim | CNPJ de quem emitiu a NF. No CT-e não é usado. |
tomadorCnpj | não | Só CT-e: CNPJ de quem paga o frete. Ausente, nenhum terceiro recebe PDF nem canhoto desse documento. |
tarefa | não | ENTREGA ou COLETA. |
embarcador | não | cnpjCpf e nome de quem embarca a mercadoria. |
destinatario | sim | Veja a tabela abaixo. |
nfVinculadas | recomendado no CT-e | As NF-e que o CT-e transporta. Só vale para CT-e: em NF-e, o Veltra ignora e avisa. |
volumes, peso | não | volumes inteiro, peso em kg. Não podem ser negativos. |
valor | não | Valor do documento em reais, só para exibição. Não entra em cálculo. |
prazoEntrega | não | Data AAAA-MM-DD. Veja o prazo de entrega. |
cteEmitidoEm | não | Data e hora de emissão do CT-e (formato ISO, com fuso). Serve de data-base do prazo. |
latitude, longitude | não | Devem vir juntas. Com elas, a parada usa a posição direto; sem elas, o endereço é localizado em segundo plano. |
protocoloAutorizacao | não | Só CT-e: nProt, exatamente 15 dígitos. Sem ele, o evento 110180 desse CT-e fica pendente de dados. |
tipoServico | não | Só CT-e: NORMAL, SUBCONTRATACAO, REDESPACHO, REDESPACHO_INTERMEDIARIO ou SERVICO_VINCULADO_MULTIMODAL. |
globalizado | não | Só CT-e: verdadeiro ou falso. |
O destinatario:
| Campo | Obrigatório | Regra |
|---|---|---|
cnpjCpf | sim | CNPJ (numérico ou alfanumérico) ou CPF, com ou sem máscara, com dígitos verificadores válidos. Agrupa as paradas. |
nome | sim | Até 200 caracteres. |
endereco | sim | logradouro, municipio e uf são obrigatórios. numero, complemento, bairro e cep são opcionais. |
email | não | Para 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, nuncacnpj_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:
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 -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" }
}
}
]
}
JSONO 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 -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
}
]
}
JSONEm 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.
{
"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.
| Campo | O que é |
|---|---|
id, numeroExterno, tipo | Identificação do manifesto. |
ambiente | TESTE ou PRODUCAO, conforme a chave. |
versaoAtual | 1 na criação; sobe 1 a cada alteração. |
quantidadeParadas, quantidadeDocumentos | Totais. |
documentos | Cada documento com seu status e as NF dele. O status é por documento, nunca por manifesto. |
erros | Documentos 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):
tipoausente ou fora da lista;numeroExternovazio;motoristaouveiculoausente; CPF do motorista inválido;placainválida;- mais de 2
ajudantes, ou CPF ou nome de ajudante inválido; chaveMdfeinválida;documentosvazio ou com mais de 1000 itens;TRANSFERENCIAsem as duas siglas, ou com sigla de filial que não existe;- cabeçalho
Idempotency-Keyausente 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_INVALIDA | Chave com tamanho, caractere ou dígito verificador errado. |
CHAVE_CTE_OBRIGATORIA | CT-e (modelo 57) sem chave. |
MODELO_INCOMPATIVEL | O modelo informado não bate com o modelo que a chave indica. |
CNPJ_INVALIDO | CNPJ 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_INVALIDO | Destinatá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_BAIXADO | Só no PUT: você tirou da lista um documento que já teve baixa ou ocorrência. |
OUTRO | Qualquer 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:
{
"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:
{
"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ção | Resultado |
|---|---|
Mesma Idempotency-Key e mesmo corpo | 200 com a resposta original; nada novo é gravado. |
Mesma Idempotency-Key com corpo diferente | 409. |
numeroExterno que já existe, com outra Idempotency-Key, num POST | 409. 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 -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" }
}
}
]
}
JSONO que vale saber:
- Resposta
200, comversaoAtualsomada 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_BAIXADOemerros[], comdocumentoIndexnulo. 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. 404se 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 -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 -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#
- 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.
- 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. - 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_CONFERENCIAe, quando a filial confere o recebimento, aNA_FILIAL. - 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. - 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_OCORRENCIAe continua aberto para uma nova tentativa. - 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).
- 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:
| Status | Quer dizer |
|---|---|
PENDENTE | Recebido, esperando o trabalho começar. |
EM_TRANSPORTE | Em 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. |
ENTREGUE | Entregue por completo. |
ENTREGUE_PARCIAL | CT-e com parte das NF entregues e parte com ocorrência ou devolução. |
DEVOLVIDO | Devolvido ao remetente. Faz parte do contrato, mas hoje o Veltra não grava este status. |
CANCELADO | Cancelado antes da baixa. |
COLETADO | Coleta confirmada no campo. |
COM_OCORRENCIA | Tentativa com ocorrência; segue aberto para nova tentativa. |
AGUARDANDO_CONFERENCIA | Transferência baixada pelo motorista, esperando o conferente da filial. |
NA_FILIAL | Transferê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:
# 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"{
"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:
limitevai de 1 a 200 (padrão 50). Continue chamando enquanto vierem itens; guarde oproximoCursorde 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
iddo evento: processar duas vezes deve dar o mesmo resultado. - O
iddo evento é estável e, nos eventos de baixa, ocorrência e comprovante, é o mesmo valor do id que vem dentro dedados(baixaId,ocorrenciaId,comprovanteId). - Sugestão de rotina: uma leitura a cada 30 a 60 segundos é suficiente.
Onde está cada informação#
| Eu quero saber | Onde o TMS encontra |
|---|---|
| Que o motorista chegou ou saiu da parada | Eventos parada.chegada e parada.saida. |
| Que um documento foi entregue ou coletado | Evento baixa.registrada e o status do documento. |
| Que houve ocorrência e qual código | Evento ocorrencia.registrada (campo codigo). |
| Que o comprovante ficou pronto | Evento comprovante.emitido (campo comprovanteId). |
| O PDF do comprovante | GET /v1/saida/comprovantes/{comprovanteId}/pdf. |
| Quem recebeu, a hora, o local e as fotos | Dentro do pacote de evidências (manifesto.json e fotos/), e no PDF. |
| A situação do evento 110180 do CT-e | Evento evento_cte.estado_alterado. |
| Os pontos de GPS do veículo | Evento posicao.lote. |
| A quilometragem do veículo | kmSaida, 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.
| Tipo | Enviado hoje? | Quando nasce |
|---|---|---|
parada.chegada | sim | O motorista toca em Cheguei e o aviso chega ao Veltra. |
parada.saida | sim | O motorista registra a saída da parada. |
baixa.registrada | sim | Uma baixa de entrega ou coleta chega ao Veltra. |
ocorrencia.registrada | sim | Uma ocorrência chega ao Veltra. |
comprovante.emitido | sim | O 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_alterado | sim | Muda o estado do evento 110180 do CT-e. Também entra pela varredura de 60 segundos. |
posicao.lote | sim, só para o TMS, só em Produção | A cada 5 minutos, por veículo. |
veiculo.km_registrado | sim, só para o TMS | O 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):
{
"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:
{
"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):
{
"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:
{
"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:
{
"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):
{
"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:
estado | Quer dizer |
|---|---|
PENDENTE_DE_DADOS | Faltam dados do CT-e (por exemplo, o protocoloAutorizacao). Envie pelo PUT. |
AGUARDANDO_CTE_COMPLETO | Esperando as demais NF-e do CT-e serem entregues. |
AGUARDANDO_REVISAO | Esperando aprovação da transportadora para enviar. |
PENDENTE_DE_ENVIO | Na fila para enviar à SEFAZ. |
EM_TRANSMISSAO | Sendo enviado à SEFAZ. |
REGISTRADO | Registrado na SEFAZ. |
REGISTRADO_COM_ALERTA | Registrado com alerta; a SEFAZ pediu atenção. |
REJEITADO | Rejeitado pela SEFAZ. |
CANCELADO | Comprovante 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):
{
"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}devolveid,numero,status,podeVerPdfECanhoto(para o TMS é sempre verdadeiro) eatualizadoEm.
curl -s "$BASE/v1/saida/documentos/0192f5aa-1c00-7a10-8b00-0000000000a1" \
-H "X-Veltra-Api-Key: SUA_CHAVE_AQUI"{
"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.jsondentro 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:
{
"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
motoristaque 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 -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 -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 -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/manifestoseGET /v1/saida/manifestos/{id}trazemkmSaida,kmChegadaekmRodados. Sãonullquando o motorista não informou (ekmRodadostambém énullse 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 papelTMSrecebe, e ele aparece na leitura incrementalGET /v1/saida/eventossó 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 -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:
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 -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
}
]
}
JSONResposta: 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:
| # | Evento | O que aconteceu | Quando o TMS vê |
|---|---|---|---|
| 1 | parada.chegada (parada 1) | O motorista chegou ao mercado. | Segundos depois de o celular enviar. |
| 2 | baixa.registrada (documentoId ...a1) | O mercado recebeu. Status do documento: ENTREGUE. | Idem. |
| 3 | parada.saida (parada 1) | O motorista foi embora. | Idem. |
| 4 | comprovante.emitido (documentoId ...a1) | O comprovante da entrega no mercado foi selado. | Até cerca de 1 minuto depois de selado. |
| 5 | parada.chegada (parada 2) | O motorista chegou à farmácia. | Segundos. |
| 6 | ocorrencia.registrada (documentoId ...a2, codigo 01) | Ninguém para receber. Status: COM_OCORRENCIA. | Segundos. |
| 7 | comprovante.emitido (documentoId ...a2) | Comprovante da ocorrência. | Até cerca de 1 minuto depois de selado. |
| 8 | parada.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 -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 -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 -s "$BASE/integracao/v1/manifestos/REL-2026-004600" \
-H "X-Veltra-Api-Key: SUA_CHAVE_AQUI"{
"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ódigo | Quando |
|---|---|
200 | Consulta, alteração, cancelamento, ou repetição idempotente (mesma chave, mesmo corpo). |
201 | Manifesto criado. Olhe sempre erros[]. |
400 | Dado 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. |
401 | Chave ausente, errada ou revogada. |
403 | Chave 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. |
404 | Manifesto, documento ou comprovante que não existe no ambiente da chave. |
409 | Idempotency-Key repetida com outro corpo; numeroExterno já existente num POST; sigla de filial repetida; TESTE_ENCERRADO; COMPROVANTE_AINDA_NAO_PUBLICADO. |
429 | Limite 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
statusdo 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.