Logistivo CLI: opere sua logística pelo terminal e pelo seu ERP

Um único catálogo de comandos alimenta três superfícies: o cliente de terminal lg, o assistente de IA dentro do painel e o canal ao qual assistentes de IA externos se conectam. Uma capacidade é escrita uma vez e aparece nas três — e toda ação de escrita do painel agora tem um comando, então seus scripts e seus próprios agentes de IA podem fazer o trabalho que sua equipe faz na tela. Pagamentos, consentimento legal e encerramento de conta ficam no navegador de propósito. Esta página é um contrato. A tabela de comandos abaixo não é escrita à mão; ela é gerada a partir do registro de comandos ao vivo a cada requisição.

O que é a Logistivo CLI?

A Logistivo CLI (lg) é um cliente de linha de comando que executa o trabalho da sua conta Logistivo — cargas e viagens, demandas, propostas e concorrências, frota e motoristas, documentos, contatos e contas correntes, faturas e contabilidade, armazém e estoque, membros da equipe e tarifas aduaneiras — a partir de um terminal, de um script ou do seu próprio agente de IA. Os comandos não vêm embutidos no cliente: o cliente baixa o catálogo de comandos do servidor e se monta a partir dele. Os mesmos comandos podem ser chamados via HTTP sem instalar nada — é o caminho que as integrações de ERP e CRM usam — e pelo canal MCP ao qual assistentes de IA externos se conectam.

O que está no ar hoje e o que está a caminho

Esta seção fica no topo da página de propósito. O erro mais caro que um documento de integração pode cometer é descrever um endpoint que ainda não está aberto como se estivesse: o integrador escreve o código, o endpoint retorna 404 e a confiança não volta.

Como o contrato está congelado, o código de integração que você escreve hoje continua funcionando à medida que as peças restantes são abertas. Campos que você não conhece devem ser ignorados; é essa regra que nos permite adicionar sem quebrar.

O que torna isso diferente: o cliente não conhece nenhum comando, ele os desenha

Esta é a única decisão que dá sentido ao resto da página. Em uma CLI comum, os comandos ficam dentro do cliente; na Logistivo, eles ficam no servidor.

No arranjo clássico, quando o servidor ganha uma capacidade, o cliente precisa de uma nova versão e o usuário precisa instalá-la. Nesse meio-tempo, os dois lados sabem coisas diferentes — e esse é o modo de falha mais silencioso das integrações: o script roda, o código de saída é 0 e o comando simplesmente não descreve o que o servidor consegue fazer.

Na Logistivo, a definição de um comando é um único registro: seu nome, o que ele faz, seus parâmetros em JSON Schema, quais perfis podem vê-lo, se ele precisa de confirmação e se o seu efeito é reversível. Quando o lg inicia, ele baixa esse catálogo e gera a partir dele o texto de ajuda, as flags e a validação de entrada. No momento em que um comando é adicionado no servidor, o lg passa a conhecê-lo — sem nenhuma atualização do cliente.

O mesmo catálogo alimenta o assistente de IA do painel e o canal ao qual assistentes de IA externos se conectam. Uma capacidade é escrita uma vez e aparece em três lugares — e os três passam pela mesma barreira de perfil, pela mesma barreira de confirmação e pela mesma trilha de auditoria. Não existe um inventário separado de comandos da CLI; se existisse, as três superfícies divergiriam silenciosamente em seis meses.

A regra de nomenclatura — como os nomes de comandos crescem

O catálogo vai crescer. Novos comandos não são nomeados de improviso; seis regras são aplicadas. Nós as publicamos aqui porque esses são os nomes que a sua integração vai escrever durante anos, e a previsibilidade deles tem valor para você.

Cobertura: tudo o que há no painel, a partir de um comando

Muitas equipes conectam a Logistivo ao seu próprio agente de IA ou harness de automação, e um agente só é útil se consegue terminar o trabalho. Por isso, a regra é simples: tudo o que um usuário de escritório pode alterar no painel, um comando também pode — atrás da mesma barreira de perfil, da mesma barreira de confirmação e da mesma trilha de auditoria.

Isso é medido, não afirmado. Todo endpoint de escrita por trás do painel é verificado contra o registro de comandos, e um endpoint só conta como coberto quando um comando o declara como seu espelho HTTP ou chama o mesmo serviço compartilhado. Dos endpoints de escrita dentro do escopo, 100% estão cobertos e nenhum está sem comprovação. Todo endpoint deixado de fora tem um motivo escrito — etapas de identidade e de pagamento, o plano de controle da integração, os endpoints próprios do aplicativo do motorista, telemetria do cliente, cópias mortas sem nenhum chamador — e os que importam para um integrador estão listados abaixo.

Comandos que recebem um arquivo seguem a mesma regra: 33 comandos da tabela recebem um documento ou uma foto, e todos funcionam pelo lg, via HTTP e via MCP (veja Comandos que recebem arquivos).

O trabalho de gestão também é coberto, atrás de uma chave mais forte: 62 comandos da tabela — membros da equipe e perfis, configurações da empresa, contas bancárias, cartões salvos, chaves de integração — precisam de uma chave de integração de nível manager. Uma chave de nível employee, o padrão, executa as operações do dia a dia e é recusada nesses comandos com INSUFFICIENT_KEY_LEVEL.

Contas de afiliado (parceiro de indicação) têm um catálogo próprio: 55 comandos em 20 domínios — indicações, leads, notas e tarefas de CRM, apresentações de vendas, ganhos e repasses, além dos comandos de conta e de equipe que todo perfil tem. Os comandos exclusivos de afiliados não estão na tabela pública abaixo, que lista os três perfis do painel; uma conta de afiliado os vê em GET /api/common/commands e em lg commands. Uma empresa que é ao mesmo tempo afiliada e embarcadora, transportadora ou despachante aduaneira vê os dois conjuntos.

Um agente deve começar com lg users get (nome de API get_current_user): ele retorna o perfil, o nível de acesso da chave em uso, as permissões efetivas e se uma aprovação legal pendente ou um número de telefone não verificado está bloqueando as escritas neste momento — assim, o agente pode prever uma recusa em vez de colecioná-la.

A cobertura é medida por um relatório que compara o registro de comandos com os endpoints de escrita do painel; um endpoint do painel adicionado sem comando aparece nele como sem comprovação. Se você encontrar uma ação do painel que não consegue alcançar por um comando, informe o suporte.

Instalação e identidade

lg é um script Node de arquivo único e sem dependências (Node 18+), publicado no npm como @lgstv/lg. O pacote instala dois comandos idênticos, lg e logistivo — use logistivo se lg já for um alias na sua máquina. Como é um único arquivo, copiar lg.mjs para o servidor de ERP de um cliente também funciona. Você não precisa do cliente para construir uma integração de ERP/CRM — os mesmos comandos estão disponíveis via HTTP.

1. Instale — Node 18 ou mais recente é tudo de que você precisa; não há outras dependências. Instale globalmente ou execute sem instalar via npx.

2. Faça login com um código de aprovação — lg login mostra um código de aprovação curto e abre logistivo.com/connect no seu navegador; via SSH, em CI ou com --no-browser, ele apenas imprime o endereço, que você pode abrir em qualquer dispositivo, inclusive no seu celular. Entre ali como de costume, confira se o código na página é o mesmo do seu terminal, escolha o acesso somente leitura ou de leitura e ação e aprove. Nada secreto é digitado no terminal: nem senha, nem chave, nem código de uso único. A sessão então continua conectada naquele computador — fechar o terminal não desconecta você, e o lg renova o access token sozinho antes que ele expire. lg logout também encerra a sessão no servidor, e você pode removê-la no painel em Settings › Integration keys › AI and CLI connections. lg login --password (e-mail e senha) continua disponível como alternativa. Requer lg 0.11 ou posterior.

3. Para servidores e CI: uma chave, não um código de aprovação — Um servidor ou um job de CI não tem ninguém para aprovar um código no navegador. Emita uma chave no painel (Settings › Integration keys) ou com lg keys create — opcionalmente em nome de um membro da equipe dedicado e com escopo restrito — e coloque-a em LOGISTIVO_TOKEN, ou salve-a com lg login --api-key. Enquanto a variável estiver definida, o arquivo de configuração nunca é lido. Detalhes: a seção “Conectando com uma chave de API”.

4. Verifique e explore o catálogo — lg commands lista todos os comandos que o seu perfil pode ver, agrupados por domínio; lg help imprime os verbos desse domínio, e lg help imprime a lista completa de parâmetros de um comando, com os tipos e quais deles são obrigatórios. Nenhum desses textos fica dentro do cliente — tudo vem do catálogo. Lembre-se de que a lista depende do seu perfil: os verbos de um domínio não são os mesmos para uma transportadora e para um embarcador (loads set-status, por exemplo, está apenas no catálogo da transportadora). Se faltar um verbo, verifique o seu perfil antes de verificar a sua instalação.

Nunca coloque a chave em controle de versão, em um log de CI ou em uma janela de chat. Se você entrou com um código de aprovação ou com uma chave de integração, lg logout também encerra esse acesso no servidor; em uma sessão com senha (lg login --password), ele apaga apenas a cópia LOCAL e diz isso explicitamente. A trilha de auditoria armazena seus argumentos, mas mascara como *** qualquer campo cujo nome contenha token, password, IBAN, card ou OTP, além de todo parâmetro que um comando declara como secreto.

Ergonomia do cliente

Estes recursos facilitam o uso diário do cliente `lg`. Nenhum deles altera o contrato HTTP — pule esta seção se você integra diretamente via HTTP.

Conectando com uma chave de API

Um ERP, CRM, script ou agente de IA se conecta à Logistivo com uma chave de integração emitida no painel ou com lg keys create. A chave age em nome de um membro real da equipe: o perfil dessa pessoa decide a lista de comandos, o nível de acesso da chave decide o quão pesado pode ser o trabalho e, quando essa pessoa sai da equipe, a chave morre junto. Não existe uma “conta de serviço” sintética, e não vai existir — o isolamento multi-tenant está vinculado ao usuário, e uma ponte sem dono é do tipo que continua gravando em silêncio durante anos.

1. Emita uma chave no painel — Settings › Integration keys › New key. Dê um nome a ela (“ERP nightly sync”), escolha o escopo e o nível de acesso e, se quiser, encurte a validade. A chave é exibida uma única vez, apenas naquela tela; o servidor não a armazena. Se você a perder, Rotate emite uma nova — a antiga se torna inválida imediatamente.

2. Conecte-se a partir do terminal — lg login --api-key valida a chave, imprime a identidade e o escopo e baixa o catálogo de comandos. lg whoami pergunta ao servidor com qual usuário, empresa, perfil e escopo você está executando. lg logout também revoga a chave no servidor, assim como encerra ali uma sessão aberta com código de aprovação (só o lg logout de uma sessão com senha não pode fazer isso).

3. Ou HTTP puro — A chave é um access token enviado no header Authorization: Bearer e funciona diretamente nos três endpoints de comandos. Em um servidor ou em CI, coloque-a em LOGISTIVO_TOKEN; enquanto essa variável estiver definida, o arquivo de configuração nunca é lido.

4. Ou gerencie as chaves a partir do terminal: lg keys — lg keys é o gêmeo de terminal da tela do painel: mesmos endpoints, mesma barreira (a permissão de gestão da equipe e, quando você o chama com uma chave, uma chave de nível manager). create, rotate e transfer devolvem um novo token uma única vez: com --save-profile=NAME, ele vai direto para um profile local e nunca é impresso; caso contrário, o lg o imprime uma vez. No modo --json, o token só é incluído se você adicionar --show-secret, e uma chamada --json sem nenhuma das duas flags é recusada antes de a requisição ser enviada, para que o token não possa cair silenciosamente na saída de um script. rotate, transfer e revoke pedem confirmação (--yes); uma chave é identificada pelo seu nome exato ou pelo seu uuid, nunca por um palpite. O nível de acesso é mantido sem alterações em uma chave rotacionada ou transferida.

O escopo é aplicado em toda a superfície da API, não apenas no catálogo de comandos: uma chave somente leitura passa nas requisições de leitura (GET/HEAD/OPTIONS), e todas as outras requisições de escrita respondem 403 forbidden. Há duas exceções deliberadas, e ambas são protegidas por uma barreira mais fina: o endpoint de execução de comandos (a decisão vem da própria flag read_only do comando, porque comandos que não gravam também são chamados com POST) e uma chave revogando a si mesma (lg logout, permitido também em uma chave somente leitura). Escopo, concessões por domínio e nível de acesso são três eixos independentes, e todos precisam passar: uma chave read não pode gravar nem no nível manager, uma chave write de nível employee não pode gerenciar a equipe, e uma concessão de domínio loads:write não abre o trabalho de gestão. Emita a chave em nome do membro com menos privilégios que cubra as suas necessidades: é o perfil que decide quais registros você consegue ver, para começo de conversa.

Os primeiros dez minutos: do painel à sua primeira chamada

Os minutos mais caros de uma integração são os dez primeiros. Os quatro passos abaixo apresentam esse caminho em ordem; a mesma ordem existe no painel como um assistente de configuração (Settings › Integration › Setup wizard), e o assistente de configuração chama exatamente os endpoints descritos aqui. Um desenvolvedor sem acesso ao assistente de configuração chega ao mesmo lugar lendo esta seção.

Nenhum dos quatro passos termina com “eu enviei”. Se a chave realmente funciona, quem responde é o registro de requisições; se o webhook realmente chega, quem responde é o registro de entregas. Ambos estão abertos no painel e no lg. Uma configuração que nunca é medida é uma configuração que apenas se acredita que funciona.

Sandbox: pratique escritas sem tocar a produção

O preflight (dry_run) diz o que um comando FARIA; mas se a idempotência realmente repete o resultado, se o webhook realmente é entregue, como são os ramos de erro e de onde vem o crédito só se vê em uma escrita REAL. É para isso que serve o sandbox: um tenant separado que espelha a sua empresa — mesmo código, mesmo catálogo de comandos — e nunca toca o seu livro-razão de produção.

O sandbox não é uma flag de requisição, é um TENANT. Uma empresa Logistivo separada, aberta para a sua: seu próprio usuário, seu próprio pacote, seu próprio registro de créditos, suas próprias inscrições de webhook. O ambiente é DERIVADO da empresa da chave e não pode ser informado por quem chama, então, mesmo que você escolha o profile errado, a requisição para no servidor com 403 ENVIRONMENT_MISMATCH. “Achei que estava gravando no sandbox e gravei na produção” é estruturalmente impossível.

A superfície é idêntica — e isso foi medido: o catálogo que uma chave de sandbox monta é o mesmo que a sua chave de produção monta (em uma chave de embarcador, 448 comandos dos dois lados). Uma ponte que funciona no sandbox funciona na produção; nada do que você aprende precisa ser aprendido duas vezes.

O sandbox não abre vazio: ele contém dados iniciais gerados (NÃO copiados) — contatos, cargas nas duas direções e demandas. Os nomes são abertamente inventados e os endereços de e-mail ficam no domínio @sandbox.invalid; nenhuma linha de um tenant real é jamais lida, clonada ou usada como modelo.

De propósito, NÃO existe botão de reset, e nenhum registro, arquivo ou documento é jamais apagado. Se você quiser recomeçar do zero, a renovação abre uma nova geração e a antiga permanece, aposentada. Algumas centenas de linhas de ensaio acumuladas não custam nada perto de uma rotina de exclusão ligada ao tenant errado.

O modelo de comandos

Existe uma única gramática, e ela não tem exceções. Para saber como chamar um comando, a única coisa de que você precisa é o que o catálogo diz sobre ele.

lg loads list --status=in_transport --limit=20 --json

Os nomes das flags são os nomes dos parâmetros do schema, e o lg aceita as duas grafias: --load_code e --load-code chegam ao mesmo campo (lg help imprime a forma kebab-case). Ver uma flag, portanto, já diz em qual campo JSON ela vai parar, e passar da CLI para HTTP não exige tradução de nomes. Um parâmetro booleano escrito sem valor é true (--only_open); o oposto é --no-only_open. Escreva um argumento cujo valor começa com hífen como --flag=value; caso contrário, uma flag esquecida engole silenciosamente a seguinte como seu valor. Em um comando que recebe arquivo, coloque @ na frente de um caminho local (--attachment_uuid=@./cmr.pdf) — veja Comandos que recebem arquivos.

Exemplos reais

Todos os comandos abaixo existem hoje no catálogo; nenhum nome ou parâmetro aqui é inventado. As saídas estão abreviadas. As saídas mostram o nível `data.result`; via HTTP, o mesmo objeto chega dentro do envelope `{success, data:{…}}` (veja a API HTTP de comandos).

Sem o cliente: a API HTTP de comandos

O lg é uma conveniência, não a porta. O catálogo e a execução passam por três endpoints HTTP (arquivos têm um quarto, o endpoint de upload — veja Comandos que recebem arquivos); uma integração de ERP ou CRM se conecta diretamente aqui. A identidade é um token de acesso pessoal enviado em Authorization: Bearer. TODA resposta volta no mesmo envelope: `{ "success": bool, "data": { … }, "code": "…", "message": { … } }`. Todo o contrato fica dentro de `data` — `contract_version`, `command`, `status` e, como saída do comando, `result`; em caso de falha, `error_code`, `error_key` e `error`. Os exemplos de resposta desta página mostram o objeto `data`, e não o envelope, para economizar espaço; não se esqueça de desembrulhar `data` no seu próprio código. `lg --json` também imprime o envelope INTEIRO, e é por isso que os caminhos jq começam com `.data.result…`. A única exceção é `/api/public/status`: esse endpoint não tem envelope, para que watchdogs possam disparar alertas sem precisar interpretá-lo.

Pedir confirmação com 200 + ok:false é proibido e nunca vai acontecer. O motivo é simples: os clientes tratam 200 como sucesso, então um pedido de confirmação devolvido como 200 mataria o fluxo silenciosamente e ninguém perceberia. Confirmação é sempre 409.

Comandos que recebem arquivos

33 comandos da tabela recebem um documento ou uma foto: anexar um CMR a uma carga, ler códigos HS de uma fatura, criar um veículo a partir do seu documento de registro, estimar um palete a partir de fotos, enviar um documento da empresa, conferir uma fatura com um packing list. No navegador, você solta o arquivo em um formulário; em todos os outros lugares, são dois passos — fazer o upload do arquivo e depois passar o id dele ao comando.

Um parâmetro recebe um arquivo quando o seu nome termina em attachment_uuid ou attachment_uuids — photos[].attachment_uuid dentro de uma lista, front_attachment_uuid em um cheque. Você não precisa memorizar isso: toda linha do catálogo carrega file_params com os caminhos exatos, o schema marca esses campos (format: uuid, x-logistivo-file: true), e a resposta do catálogo carrega um bloco attachments com o endereço de upload, os tipos aceitos, os limites de tamanho e o tempo de vida. lg help também os mostra.

Fazer upload é uma escrita, então uma chave com escopo de leitura não pode fazer upload; qualquer chave com escopo de escrita pode, em qualquer um dos níveis de acesso. Os uploads pertencem ao usuário, não à chave: duas chaves do mesmo usuário veem os mesmos uploads, e ninguém pode usar os de outra pessoa. Um id desconhecido, expirado ou que não é seu simplesmente não é encontrado, e o motivo não é revelado.

Os anexos do chat do painel e os uploads de comandos são espaços separados: um arquivo anexado no chat do assistente não pode ser passado a um comando pelo id, e um upload de comando nunca aparece em uma conversa do chat.

Um upload não é um comando e não tem efeito de negócio por si só: nada acontece com os seus dados até que um comando use o id. É também por isso que o upload nunca pede confirmação — quem carrega a barreira é o comando que consome o arquivo.

A barreira de confirmação e os comandos irreversíveis

Todo comando do catálogo carrega duas flags: confirmation_needed (se exige aprovação explícita antes de rodar) e irreversible (se o seu efeito pode ser desfeito). São perguntas separadas, e ambas são aplicadas no servidor, em vez de ficarem a cargo da boa educação do cliente.

A barreira trata de responsabilidade, não de regras de negócio. Enviar uma proposta é o primeiro passo rumo a um contrato de frete; emitir uma fatura cria um lançamento contábil; uma movimentação de estoque altera um livro-razão. Nada disso deveria jamais ser algo que você possa executar duas vezes por acidente.

A barreira é lida do comando EFETIVO. Quando um comando envolve outro — como faz o executor genérico do chat —, a barreira vem das flags do comando que vai realmente rodar, não do invólucro. Com uma flag fixa, uma de duas falhas seria inevitável: ou o comando de fatura rodaria sem confirmação, ou toda consulta de país abriria um cartão de confirmação.

read_only e sem barreira não são a mesma coisa. Existem três estados distintos: invoices preview não tem barreira e não grava nada; loads create tem barreira e grava; invoices create não tem barreira, mas grava — um rascunho passa a existir. Um único teste resolve a questão: quando o comando retorna, alguma linha do banco de dados mudou?

Algumas ações não ficam atrás desta barreira porque nem sequer são comandos: comprar um pacote, adicionar um cartão com 3D Secure, aprovar textos legais e encerrar uma conta acontecem no próprio navegador da pessoa. Para essas, lg handoff-links generate retorna um link de logistivo.com — gratuito, sem gravar nada, sem carregar nenhum segredo — e a pessoa conclui a etapa lá. Nenhuma flag, como quer que esteja definida, transforma um pagamento ou um consentimento em uma chamada de API.

É tecnicamente possível que uma integração passe pela barreira automaticamente em toda chamada, mas isso não é recomendável. O padrão certo é automatizar a barreira só para os comandos que você sabe que são reversíveis e encaminhar os irreversíveis para a fila de uma pessoa.

Preflight: veja o que um comando de escrita faria, sem executá-lo

A etapa mais cara ao montar uma ponte com o ERP é descobrir se o seu corpo de requisição está certo executando o comando DE VERDADE: um rascunho de fatura no livro-razão de produção, um crédito queimado, limpeza manual. dry_run acaba com isso — você adiciona um campo à requisição, o comando não roda e você lê o que aconteceria.

Hoje o registro tem 519 comandos; 233 deles apenas leem e não gravam nada. Dos 286 comandos de escrita restantes, 251 têm barreira: chamados sem confirm, eles já respondem 409 e descrevem o que fariam. A lacuna eram os outros 35 — rascunhos de fatura, rascunhos de documentos de exportação, itens de linha: exatamente a cadeia que uma ponte conecta primeiro. Para esses, chamar era fazer.

Havia uma segunda lacuna mesmo nos comandos com barreira: a validação do schema roda DEPOIS da barreira. Assim, uma chamada com um argumento faltando recebia primeiro o cartão de confirmação, confirmava e só então ouvia “este campo está faltando”. Um preflight responde às duas coisas em uma única ida e volta.

E o bug de ponte mais comum — e o descoberto mais tarde — é este: você envia um campo que não está no schema (customer_id onde se espera recipient_contact_id), o servidor o descarta silenciosamente, o comando retorna 200 e o registro vai parar no lugar errado. A resposta do preflight lista esses campos pelo nome em arguments.ignored; nenhuma mensagem de erro jamais fará isso.

Um preflight não grava nada, e isso é estrutural antes de ser uma medição: o caminho do preflight nunca entra no handler do comando, então isso vale para todo comando, inclusive os adicionados depois. Também foi medido quando o recurso foi lançado: toda instrução SQL emitida durante o preflight de todos os comandos de escrita do catálogo daquele momento (dois conjuntos de argumentos, dois perfis, 178 preflights) foi capturada. Nenhum INSERT/UPDATE/DELETE, nenhum job enfileirado, nenhum e-mail; as contagens de linhas em 31 tabelas e o saldo de créditos ficaram inalterados.

O código de saída é o mesmo que a chamada real produziria — rodaria 0, barreira de confirmação 4, conflito de idempotência 2, qualquer outra coisa 1 —, então “lg … --dry-run || exit” é uma barreira significativa em uma etapa de CI.

Idempotência: por que repetir não cria duplicatas

Um ERP não lê a sua mensagem de erro, não vê a sua tela e vai reenviar a mesma requisição depois de um timeout. Esta seção existe exatamente por causa desse comportamento.

Uma chamada que carrega um idempotency_key roda no máximo uma vez. A segunda chamada não executa o comando de novo; ela retorna o resultado registrado com o status replayed. Quando a rede cai, quando uma requisição dá timeout ou quando uma fila pega o mesmo job duas vezes, você recebe o primeiro resultado em vez de uma fatura duplicada.

Use uma chave em todo comando de escrita. O campo read_only do catálogo lhe dá essa distinção de graça: todo comando com read_only: false merece uma chave de idempotência.

A sua estratégia de novas tentativas deve usar backoff exponencial e levar a MESMA chave em todas as tentativas. Gerar uma chave nova a cada tentativa é o mesmo que não usar idempotência. Não envie a mesma chave em paralelo: o “no máximo uma vez” é garantido para novas tentativas sequenciais, não para duas requisições disputando entre si.

Sincronização incremental e paginação

Uma ponte de ERP/CRM deve puxar o que mudou, e não tudo a cada hora. Todo comando de listagem — loads list, demands list-own, contacts list, invoices list, teammates list, load-groups list, reservations list, drivers list, tenders list, products search, warehouses search — aceita dois parâmetros compartilhados e retorna um campo compartilhado.

Por que não existe offset: em uma tabela viva, um offset se desloca. Uma linha inserida entre duas páginas faz você pular ou contar duas vezes um registro, silenciosamente. O cursor só anda para a frente, então um registro inserido ou atualizado no meio do percurso cai no fim da lista: nada se perde, nada se duplica.

Uma carga inicial completa usa o mesmo mecanismo: comece com updated_since=1970-01-01T00:00:00Z e siga o next_cursor até ele ser null. Não existe um endpoint de “exportação” separado, porque nenhum é necessário.

O mesmo com o lg: lg loads list --updated_since=2026-09-01T00:00:00Z --limit=100 --json e, depois, lg loads list --cursor= --json.

Na próxima sincronização, defina updated_since como o maior updated_at que você viu. Os timestamps têm precisão de um segundo, então um registro no limite pode chegar duas vezes — o que é inofensivo, desde que você faça upsert por external_ref ou code do seu lado. Sem updated_since ou cursor, um comando roda em modo de chat: ordenação para humanos, no máximo 20 linhas, next_cursor null e truncated dizendo “há mais”. Não use esse modo para sincronização.

Chaves de erro legíveis por máquina para esta seção: INVALID_CURSOR, INVALID_UPDATED_SINCE, DUPLICATE_EXTERNAL_REF, EXTERNAL_REF_TOO_LONG, EXTERNAL_REF_INVALID. Todas respondem HTTP 422 com error_code: "failed" e result.error_key no corpo. Decida pela chave, nunca pelo texto.

A sua própria chave: external_ref

Não faça dos nossos ids autoincrementais (invoice_id, contact_id) a chave de negócio no seu ERP; eles são opacos e privados da sua empresa. Cargas, demandas, contatos, faturas, produtos e armazéns carregam a sua chave no lugar deles.

Webhooks de saída: fazemos um POST para você quando algo acontece

Tudo até aqui era pull: você pergunta, nós respondemos. Uma ponte de ERP precisava perguntar uma vez por minuto para descobrir que uma carga mudou de status, que uma fatura foi emitida ou que uma proposta chegou. Não mais: registre um endereço https, escolha os eventos que lhe interessam, e um POST assinado chega quando eles acontecem.

O push foi adicionado AO LADO do pull, não no lugar dele. A sincronização incremental (updated_since + cursor) continua intacta e ainda é a ferramenta certa: a primeira carga completa, o preenchimento do histórico e a resposta a “perdi alguma coisa?” ficam todos lá. Os webhooks reduzem a latência de minutos para segundos. Uma ponte saudável usa os dois — o webhook como gatilho, a sincronização como rede de segurança.

O payload é um SINAL, não uma cópia do registro. Ele carrega os identificadores (id, o seu próprio external_ref, status, horário); você busca o detalhe com a sua própria chave via GET, e essa chamada já passa pelas suas próprias barreiras de autorização. A superfície de exposição, portanto, fica no nível dos identificadores. Dois campos estão ausentes de propósito: a identidade da empresa que enviou uma proposta (propostas cegas aparecem sob um pseudônimo no painel; colocar a identidade em um webhook furaria essa privacidade por um segundo canal) e os dados de contato de reservas (não há necessidade de mover dados pessoais por um canal extra).

Os nomes de eventos são CONGELADOS. Se o significado de um nome muda, um novo nome é aberto e o antigo continua vivo durante uma janela de descontinuação. Adicionar um novo campo ao payload NÃO incrementa a versão do contrato — um consumidor deve ignorar os campos que não conhece.

Catálogo de eventos

Cada um dos 13 eventos abaixo é respaldado por uma classe de evento que é REALMENTE disparada. Não há nomes do tipo “seria bom ter” no catálogo: um evento anunciado mas nunca disparado é um evento pelo qual o integrador espera para sempre, e essa falha é silenciosa. Esta tabela também não é escrita à mão — ela é montada a partir do catálogo ao vivo a cada carregamento da página.

invoice.payment_recorded está AUSENTE de propósito: essa classe de evento existe no código, mas não é disparada de lugar nenhum. O evento de pagamento será adicionado no dia em que a classe for disparada de verdade — não antes. Anunciar um evento antes da hora é pior do que nunca adicioná-lo.

Verificando a assinatura

Toda requisição carrega um header X-Logistivo-Signature: t=,v1=. A string assinada é "." e o algoritmo é HMAC-SHA256. O prefixo v1 mantém o esquema versionável: se um dia surgir um segundo esquema, o header carregará t=…,v1=…,v2=… e você escolhe o que conhece.

Publicamos dois receptores de referência sem dependências, um para Node e outro para PHP puro. Ambos aplicam as quatro regras acima e se testam com --selftest: assinatura válida, corpo adulterado, segredo errado, timestamp fora da janela, header malformado, a armadilha da re-serialização e os dois tipos de repetição (mesma entrega / mesmo fato). O verificador é a contraparte exata do código que nós mesmos usamos — consumimos o contrato que publicamos.

Entrega, novas tentativas e silenciamento

O seu receptor nem sempre estará no ar. O trilho parte desse pressuposto: uma falha transitória entra no cronograma de novas tentativas, uma permanente silencia a inscrição — e o MOTIVO do silenciamento é sempre registrado.

O motivo do silenciamento é legível por máquina e estável (delivery_failures, endpoint_gone, unsafe_target, key_revoked, by_user) e aparece tanto no painel quanto em `lg webhooks list`. Silenciar sem um motivo deixaria sem resposta a pergunta “por que nada está chegando?”. Reativar zera o contador e o carimbo.

Você escreve o endereço; o NOSSO servidor faz a requisição. Sem uma barreira, um tenant que informasse um endereço de metadados da nuvem poderia nos fazer emitir requisições de dentro da nossa própria rede. Por isso, o endereço é verificado tanto no cadastro quanto em CADA entrega:

Saúde e status: ninguém deve perceber uma ponte silenciosa antes de você

Uma ponte de integração raramente morre fazendo barulho. Normalmente ela fica em silêncio de madrugada: um token expira, a pessoa em nome de quem a chave roda sai da equipe, um escopo é restringido ou o endpoint receptor começa a retornar 403. Quem percebe primeiro é quase sempre o cliente. lg status existe para inverter essa ordem.

Um único comando responde a duas perguntas ao mesmo tempo, porque durante uma pane as duas são feitas no mesmo instante: a plataforma está no ar, e a minha ponte está saudável? A linha da plataforma funciona sem token — durante uma falha, o próprio token é o primeiro suspeito, e amarrar a pergunta de status a ele deixaria você sem resposta justamente quando você precisa de uma.

O resumo de saúde não mantém nenhum registro novo; ele junta três fontes existentes em uma única frase: o contador de uso diário (quanto você chamou e quanto falhou), o registro de requisições (qual requisição, quando e por que foi rejeitada) e o registro de entregas de webhooks de saída (o evento chegou ao receptor?). A taxa de erro é sempre lida do contador diário; o registro detalhado tem um limite e diz isso com clareza quando o limite é atingido.

O mesmo resumo aparece no painel: em Settings › Integration › Keys, cada linha carrega um selo e, abaixo dele, o motivo. Os limiares não são calculados no cliente; o nível que o servidor atribuiu é o que tanto o painel quanto o lg status pintam, então as duas telas nunca podem dizer coisas diferentes sobre a mesma chave.

O seu watchdog não precisa de token. O endpoint retorna apenas palavras de status; ele não carrega versão, nome de host, nome de fila, contagem de jobs, texto de erro nem dados de clientes — e nem pode carregar.

O resumo de saúde é produzido com um número constante de consultas por requisição; a contagem de consultas não cresce com o número de chaves. Para detalhes, leia as requisições recentes daquela chave com lg logs e a ponte de saída com lg webhooks list.

Erros e códigos de saída

Aquilo em que uma máquina se baseia para decidir é uma string estável. O texto do campo error é dirigido a um humano, é localizado e muda sem aviso — uma integração que decide com base no texto da mensagem não é suportada, e a quebra dela não é um bug.

Duas taxonomias, dois campos. error_code é a camada de transporte (a tabela acima) e é o que você verifica primeiro. error_key é a chave estável de regra de negócio dentro de failed — INSUFFICIENT_CREDITS, NOT_FOUND, DUPLICATE_CONTACT, INSUFFICIENT_STOCK — publicada como dicionário no catálogo (error_keys) e abaixo. Um comando que ainda não emite uma chave retorna error_key: null; trate uma chave ausente do dicionário como uma falha genérica.

Promessa de estabilidade

A pergunta mais cara em um contrato de integração é “o código que eu escrevo hoje ainda vai rodar amanhã?”. Um único número responde: contract_version no catálogo e o header X-Contract-Version em toda resposta. Ele responde exatamente a uma pergunta — um código escrito antes ainda roda sem alterações? Se o número não aumentou, sim. Se aumentou, não: algo de que você dependia foi removido ou restringido.

Porque um anúncio não remove nada: o seu código existente roda exatamente como antes, no dia seguinte. Incrementar a versão no anúncio emitiria dois sinais para uma única mudança e treinaria você a ignorar o primeiro — enfraquecendo o sinal que realmente importa. O anúncio já vem junto com a chamada que você está fazendo: deprecated_at e replaced_by na linha do catálogo, um header X-Deprecated mais um bloco deprecated na resposta daquele comando e um aviso de uma linha no stderr se você usa o lg. Você não precisa fazer nada para saber que algo vai deixar de existir; isso já está no seu log.

Pelo menos dois trimestres do calendário se passam entre o anúncio e a remoção. Durante toda a janela, o comando antigo ainda responde e avisa a cada chamada. Quando um nome da CLI é aposentado, o seu sucessor assume esse nome como alias: durante a janela, o nome antigo chega ao comando antigo e, no dia em que ele é removido, a mesma chamada cai no novo — sem que você precise lançar uma nova versão do cliente.

preview_invoice → get_invoice (anunciada em 3 de setembro de 2026). get_invoice retorna o mesmo bloco de prévia e acrescenta os itens de linha, o detalhamento de impostos, os pagamentos registrados, o saldo restante e o endereçamento pela sua própria chave de ERP (external_ref). preview_invoice continua funcionando, assim como lg invoices preview — ambos avisam.

Tabela de identidade dos comandos

A tabela abaixo não é escrita à mão: ela é gerada a partir do registro de comandos ao vivo toda vez que esta página é servida. Um comando adicionado no servidor aparece aqui sozinho. A coluna “Barreira” diz se um comando pede confirmação e se o seu efeito pode ser desfeito; a coluna “Perfis” diz quais tipos de conta vão encontrá-lo no seu catálogo. Selos marcam os comandos que precisam de uma chave de nível manager, os que recebem um arquivo e os que só rodam a partir do lg.

O nome na API (list_loads) está congelado e não vai mudar; é o nome que você usa nas chamadas HTTP. A identidade na CLI (loads list) é regida pela política de versionamento: depois de anunciada, ela só muda com um incremento de versão e uma janela de migração, durante a qual o catálogo serve o par antigo como alias. As descrições aqui vêm do próprio registro de cada comando e estão encurtadas — para o texto completo, os tipos de parâmetros e os valores de enum, leia o endpoint do catálogo ou execute lg help . Comandos exclusivos de afiliados não são listados aqui; uma conta de afiliado os vê no seu próprio catálogo.

Integração com ERP e CRM — Identidade: nada de contas de serviço; um usuário real no lugar

Um ERP ou CRM se conecta à Logistivo como um usuário real da empresa, portando um token de acesso pessoal emitido no painel. Uma conta de serviço sintética — sem empresa, isenta de escopo, sem ninguém responsável por ela — nunca é criada.

Isso é arquitetura, não preferência. O isolamento multi-tenant se apoia inteiramente na empresa do usuário; para uma identidade sem empresa, o escopo de isolamento fica inerte, e uma única consulta errada com esse token lê dados que não lhe pertencem. O segundo motivo é a auditoria: a coluna de usuário na trilha de auditoria liga “quem executou isto” a uma pessoa; uma conta de serviço tira o sentido dessa coluna e deixa sem dono uma integração que emite faturas. O terceiro é a revogação: se um token pertence a uma pessoa, a integração se cala quando essa pessoa sai. Uma ponte sem dono é a ponte que continua gravando em silêncio durante anos.

Integração com ERP e CRM — Versionamento e descontinuação

A resposta do catálogo carrega um inteiro contract_version; as linhas de comando carregam since, deprecated_at e replaced_by. A versão do contrato só aumenta em mudanças que quebram compatibilidade.

Integração com ERP e CRM — Identificadores estáveis: nunca entregue um id autoincremental ao seu ERP como chave

Os ids autoincrementais internos são opacos e restritos ao tenant: o id válido de outra empresa retorna “não encontrado” para você, e não dados. Mas ser opaco não faz deles chaves de negócio. O que um ERP armazena no seu próprio registro precisa ser uma chave de negócio.

Integração com ERP e CRM — Paginação, filtragem e sincronização incremental

Comandos de listagem recebem limit e retornam count. Para uma sincronização noturna, o contrato se compromete com o que segue; um comando de listagem que já o adotou anuncia cursor e updated_since no seu JSON Schema e, até que o faça, trate-o como uma janela pequena (teto de 20 linhas, sem cursor).

Integração com ERP e CRM — Limites de requisições (rate limits)

A chave do limite é o token, não o IP: um ERP chega por trás de um único NAT, e um limite por IP contaria uma empresa inteira como um único usuário. Duas integrações com duas chaves nunca consomem a cota uma da outra — o segundo motivo para “uma integração por chave”.

Integração com ERP e CRM — O que não vamos fazer

Esta costuma ser a parte mais útil de um contrato: saber o que nunca vai chegar permite que você projete contornando isso.

O catálogo legível por máquina

Esta página foi escrita para pessoas. Para um assistente de IA ou um cliente automatizado, a mesma informação existe como uma cópia estruturada e sem autenticação.

GET /api/public/cli/catalog não exige autenticação e retorna a identidade pública dos comandos: o nome de API congelado, a identidade domínio/verbo da CLI, a descrição, os parâmetros em JSON Schema, as flags read_only, confirmation_needed e irreversible, o credit_cost e os campos de acesso key_level, permission, file_params, secret_parameters e cli_only. Ele não retorna dados pessoais, nem dados de tenant, nem registros de exemplo — apenas o que a superfície é.

A forma certa de um agente externo usá-lo: ler o catálogo, explicar com SUAS PRÓPRIAS palavras quais trabalhos são possíveis e, quando for preciso executar, orientar o usuário a fazer isso no próprio ambiente, com o próprio token. O agente nunca pede, gera ou repassa o token de um usuário.

Conectar a Logistivo a um assistente de IA para cadastro e solicitações de frete é um canal separado, com página própria:

A definição legível por máquina: OpenAPI 3.1 e Postman

O catálogo acima é o formato próprio da Logistivo. A mesma informação também é publicada em dois formatos padrão do setor que as suas ferramentas já entendem: uma definição OpenAPI 3.1 e uma coleção do Postman. Nenhuma das duas é escrita à mão — ambas são geradas a partir do registro de comandos ao vivo a cada requisição, então não têm como ficar desatualizadas.

A definição É o catálogo, não uma segunda realidade. Todo comando aparece como uma operação POST /api/common/commands/; o schema do corpo é o próprio JSON Schema do comando (byte a byte o que o assistente vê), o schema da resposta é o envelope compartilhado, as flags de barreira (read_only, confirmation_needed, irreversible e o selo de créditos) ficam na extensão x-logistivo, e as chaves de erro de negócio ficam em x-error-keys. Um comando aposentado é marcado com deprecated: true e continua chamável durante a sua janela de seis meses.

Ao lado da superfície de comandos, os endpoints REST que carregam o contrato de integração também estão na definição: 10 endpoints de listagem com sincronização incremental (updated_since + cursor + limit) e 7 endpoints de escrita que aceitam a sua própria chave (external_ref). A superfície REST da Logistivo é muito maior do que isso; o restante fica de fora DE PROPÓSITO. Esses payloads são o contrato próprio do produto, e um schema escrito na base do palpite quebraria o seu cliente gerado na primeira chamada — e você procuraria o bug no seu código, não no nosso.

A definição é gerada para o PERFIL de quem pede: o comando de outro perfil nunca entra no documento, porque uma definição é um contrato, e não um dicionário, e tudo dentro dela precisa ser chamável com aquele token. A versão sem autenticação é a união dos três perfis, e nela cada operação informa quais perfis a veem em x-logistivo.roles.

Não acredite só na nossa palavra — verifique: npx @redocly/cli lint logistivo-openapi.json passa com zero avisos.

Geração de código: um cliente tipado a partir da definição

Você tem a definição; agora gere código funcional a partir dela. `lg codegen` lê o documento OpenAPI e escreve um cliente de arquivo único e sem dependências — TypeScript (declarações de tipos mais um wrapper fino de chamadas) ou PHP (uma classe cliente com array shapes em PHPDoc). Sem `npm install`, sem composer: `fetch` do lado TypeScript, `ext-curl` do lado PHP.

A geração acontece na SUA máquina, não no nosso servidor. Isso não é uma preferência: fixe a definição no seu repositório uma vez (`lg spec --out=...`) e o mesmo comando roda em cada build sem rede e sem token, de modo que a sua CI nunca depende da nossa disponibilidade. E, como o código gerado é código que RODA no seu ERP, mantê-lo no seu repositório, em vez de baixá-lo de uma API, faz com que toda mudança apareça no `git diff`.

O cliente gerado lida corretamente com os três trilhos da Logistivo, e aprende qual é qual a partir do documento, em vez de adivinhar: cada operação declara o seu trilho em `x-logistivo.surface`. O trilho de comandos desembrulha o envelope `{success, data:{…}}`; uma lista de sincronização REST NÃO vem em envelope e é devolvida como está (desembrulhá-la descartaria o `next_cursor`); uma escrita REST retorna o corpo de produto do próprio endpoint. Um gerador de uso geral não conhece essa distinção — o nosso conhece.

Os tipos não são palpite: a interface de argumentos de cada comando é gerada a partir do mesmíssimo JSON Schema que o assistente vê. O que é obrigatório continua obrigatório, enums viram union types, objetos aninhados viram tipos aninhados. Na prática: se o servidor exige um campo, a chamada que o esquece não falha com um 422 em produção — ela falha em tempo de compilação.

Nenhum timestamp de geração é gravado no arquivo: a mesma definição com as mesmas flags produz os mesmos bytes. Gerar de novo, portanto, não faz ruído no `git diff`, e a única coisa que de fato mudou — o contrato — continua visível.

Escopo honesto

Preferimos fazer esta seção crescer a encolhê-la. O valor de um contrato de integração está menos no que ele promete do que na clareza com que ele diz o que não faz.

Perguntas frequentes

Para que serve a Logistivo CLI?

Ela executa o trabalho da sua conta Logistivo a partir de um terminal, de um script ou do seu próprio agente de IA: cargas e viagens, demandas, propostas e concorrências, frota, motoristas e as despesas deles, documentos, contatos e contas correntes, faturas e contabilidade, armazém e estoque, membros da equipe, tarifas HS e direitos antidumping. Toda ação de escrita do painel tem um comando; pagamentos, consentimento legal e encerramento de conta ficam no navegador de propósito. Os mesmos comandos podem ser chamados via HTTP, para integrações de ERP/CRM, e pelo canal MCP.

Como os comandos são nomeados na Logistivo CLI?

O formato é lg --parameter=value. O domínio é uma entidade no plural (loads, demands, invoices, export-documents), e o verbo vem de um vocabulário fechado que só cresce por decisão deliberada (list, get, create, update, search, set-status, generate e assim por diante). Todos os verbos de uma entidade ficam em um único domínio, e o par (domínio, verbo) é único em todo o catálogo.

Como eu me autentico na CLI?

De forma interativa, lg login mostra um código de aprovação que você confirma no navegador depois de entrar na Logistivo — nenhuma senha ou chave é digitada no terminal, e a sessão continua conectada e se renova sozinha até você executar lg logout. lg login --password (e-mail e senha) continua disponível como alternativa. Em servidores e em CI, use uma chave de integração emitida no painel ou com lg keys create: lg login --api-key a salva em um profile local, ou coloque-a na variável de ambiente LOGISTIVO_TOKEN. Via HTTP, o token viaja no header Authorization: Bearer.

Posso criar uma conta de serviço para a minha integração de ERP?

Não. Uma integração se conecta com o token de um usuário real da empresa, criado no painel. Uma identidade sintética, sem empresa, deixa o isolamento multi-tenant inerte, tira o sentido da coluna “quem executou isto” na trilha de auditoria e deixa uma ponte que não se cala quando o seu dono sai. O padrão certo é um usuário dedicado, com um perfil restrito e um responsável identificado.

A Logistivo tem um ambiente de testes (sandbox)?

Sim, e por autoatendimento: um tenant sandbox separado é aberto para a sua empresa — mesmo código, mesmo catálogo de comandos, um registro de créditos próprio. O ambiente é derivado da empresa da chave e não pode ser informado na requisição; uma chamada feita com o profile errado para no servidor com 403 ENVIRONMENT_MISMATCH. Uma demanda que você abre no sandbox nunca chega a transportadoras reais, um evento do sandbox nunca é entregue à sua inscrição de webhook de produção e o seu saldo de produção não se move. Endpoints: POST /api/common/integration-sandbox (abrir), POST /api/common/integration-sandbox/keys (chave), GET /api/common/integration-sandbox/go-live (checklist de go-live). Do lado do lg, lg --sandbox e lg --live são seletores de profile para uma única execução.

Como eu leio a saída de lg --json em um script?

A saída é o envelope HTTP inteiro: { "success": bool, "data": { "contract_version", "command", "status", "result" }, "code", "message" }. O payload de negócio fica em .data.result; em comandos de listagem, as linhas são .data.result., a contagem é .data.result.count e a próxima página é .data.result.next_cursor. Portanto, o caminho jq correto se parece com lg loads list --json | jq -r '.data.result.loads[].load_code'. Um caminho que pula o envelope não gera erro, ele retorna null silenciosamente — em um script de vigilância, isso vira um alarme que nunca para de disparar. A única exceção é /api/public/status, cujo corpo deliberadamente não tem envelope.

Se eu enviar a mesma requisição duas vezes, recebo registros duplicados?

Não, se você enviou um idempotency_key. Uma segunda chamada com a mesma chave não executa o comando de novo; ela retorna o primeiro resultado com o status replayed. A chave deve ser um UUID e precisa continuar a MESMA entre as novas tentativas — gerar uma chave nova a cada tentativa é o mesmo que não usar idempotência.

O que --yes faz?

Ele passa pela barreira de confirmação. Quando um comando com barreira é chamado sem --yes, nada muda: o servidor resume em uma frase o que está prestes a fazer, diz se é irreversível e para (HTTP 409, código de saída 4 da CLI). Repetir o comando com --yes executa o trabalho.

Como eu detecto em um script um comando que falhou?

Leia o código de saída, nunca o texto da mensagem: 0 sucesso, 1 recusa por regra de negócio, 2 erro de comando ou de argumento, 3 erro de autenticação ou de perfil, 4 confirmação necessária. Via HTTP, os equivalentes são 200, 422, 404, 403 e 409, e o campo error_code no corpo é estável. A MENSAGEM de erro é localizada e muda sem aviso.

Como eu descubro programaticamente os comandos disponíveis?

GET /api/public/cli/catalog para a descoberta sem autenticação, e GET /api/common/commands para a lista que a sua conta pode de fato ver. Ambos retornam os parâmetros em JSON Schema de cada comando; monte o seu cliente a partir deles, em vez de fixar uma lista de comandos no código.

Preciso atualizar o meu cliente quando um novo comando é adicionado?

Não. O cliente baixa a sua lista de comandos do catálogo, então um comando adicionado no servidor aparece sem uma nova versão do cliente. Uma atualização só é necessária para uma mudança que quebra compatibilidade e aumenta a versão do contrato e, nesse caso, o formato antigo continua vivo por pelo menos seis meses.

A CLI substitui a API REST existente?

Não. A superfície REST que alimenta os aplicativos web e mobile continua no lugar. O catálogo de comandos não substitui o CRUD de recursos; ele é a camada de verbos — a forma única, protegida pelas barreiras de perfil e de confirmação, e auditada, de executar uma ação.

Como eu obtenho uma chave de API da Logistivo e o que ela pode fazer?

Um gestor com permissão de gestão da equipe a emite no painel, em Settings › Integration keys, ou a partir de um terminal com lg keys create. A chave age em nome de um membro real da equipe e tem três configurações independentes: o escopo (read abre o catálogo e os comandos que não gravam, write abre todos os comandos — a barreira de confirmação continua como está), concessões opcionais por domínio e um nível de acesso — employee, o padrão, para as operações do dia a dia, ou manager para equipe, configurações, chaves e dinheiro. Ela é exibida uma única vez e não é armazenada no servidor; pode ser rotacionada, transferida e revogada. Quando o membro em nome de quem ela age sai, a chave também é encerrada. Não existe conta de serviço.

O que acontece se eu reutilizar uma chave de idempotência para um comando diferente?

O servidor responde 409 idempotency_conflict e informa o comando para o qual a chave foi usada primeiro; nada roda e nada é repetido. As chaves são restritas à sua empresa, vinculadas a um único comando e bloqueadas por 24 horas — depois disso, a mesma chave inicia uma nova execução.

Como os limites de requisições são aplicados à minha integração?

Por token, por minuto, em três buckets: leituras do catálogo 120, comandos de leitura 120, comandos de escrita 30. Um 429 sempre carrega Retry-After e um corpo com retry_after, limit e bucket; espere esse tempo e tente de novo. O catálogo publica os números atuais em rate_limits.

Como eu diferencio uma recusa de negócio de um erro de transporte?

Verifique primeiro o error_code: failed (422) é uma recusa de negócio e carrega um error_key estável, como INSUFFICIENT_CREDITS ou NOT_FOUND; server_error (5xx) e erros de rede recebem novas tentativas com a mesma chave de idempotência; em rate_limited (429), espere o tempo indicado. Nunca decida com base no texto do erro.

A Logistivo envia webhooks? Para quais eventos?

Sim. Você registra um endereço https e escolhe os eventos que lhe interessam; um POST assinado chega quando eles acontecem. Hoje é possível se inscrever em 13 eventos: carga criada/status alterado/motorista atribuído, demanda criada e status alterado, proposta enviada/revisada/aceita, fatura em rascunho/emitida/cancelada, e reserva de armazém criada/status alterado. Todo nome é respaldado por uma classe de evento que é de fato disparada — nada é anunciado para depois nunca ser disparado, porque um evento assim é uma falha silenciosa pela qual o integrador espera para sempre. As inscrições são criadas em Settings › Integration › Webhooks, ou a partir de um terminal com lg webhooks create.

Como eu verifico a assinatura de um webhook da Logistivo?

Toda requisição carrega um header X-Logistivo-Signature: t=,v1=. A string assinada é ".", o algoritmo é HMAC-SHA256 e a chave é o segredo da inscrição. Quatro regras: (1) verifique os bytes BRUTOS — decodificar e serializar de novo o corpo (JSON.stringify / json_encode) transforma os mesmos dados em bytes diferentes, e a assinatura deixa de bater; este é o erro mais comum de todos; (2) verifique o timestamp e descarte tudo em que |agora − t| > 300 s, mesmo que a assinatura esteja correta; (3) compare em tempo constante com hash_equals / timingSafeEqual; (4) elimine duplicatas por event_id, não pelo id da entrega — o event_id é preservado no reenvio, enquanto o id da entrega muda. Receptores funcionais em Node e PHP ficam em examples/webhook-receiver/ no repositório e se testam com --selftest.

O que acontece se um webhook não puder ser entregue? Eu perco o evento?

Não. Se o seu receptor não retornar 2xx, a entrega entra no cronograma de novas tentativas: 60 · 300 · 1800 · 7200 · 21600 segundos, 6 tentativas no total, uma janela de aproximadamente 8,6 horas. TODA falha, exceto 410 Gone, recebe novas tentativas, 4xx inclusive — perder um evento de negócio custa mais do que algumas requisições extras. Após 10 entregas consecutivas esgotadas, a inscrição é silenciada e o motivo é registrado, para que a pergunta “por que nada está chegando?” continue tendo resposta. O registro de entregas guarda cada tentativa (código de status, duração, erro), e você pode reenviar uma única entrega pelo painel ou com lg webhooks replay. A sincronização incremental (updated_since + cursor) continua lá como rede de segurança.

O meu agente de IA consegue fazer tudo o que um usuário faz no painel?

Quase tudo. Toda ação de escrita do painel tem um comando — medido contra os endpoints de escrita do painel, 100% dos que estão no escopo estão cobertos —, e os mesmos comandos estão abertos ao lg, ao HTTP e ao canal MCP. As exceções deliberadas são as etapas de identidade e de dinheiro: fazer login, comprar um pacote, adicionar um cartão com 3D Secure, aprovar textos legais e encerrar uma conta ficam no navegador da pessoa, e lg handoff-links generate retorna o link para essas etapas. Chaves de integração, webhooks e o sandbox são gerenciados com comandos nativos do lg, porque eles devolvem segredos uma única vez.

Posso passar arquivos, como PDFs ou fotos, para comandos a partir da CLI ou da API?

Sim. Com o lg, coloque @ na frente de um caminho local (--attachment_uuid=@./cmr.pdf) e o lg faz o upload do arquivo primeiro; --file é um atalho quando um comando tem um único campo de arquivo, e lg upload apenas faz o upload. Via HTTP, envie o arquivo como multipart para POST /api/common/commands:upload e passe o attachment_uuid retornado para o comando; clientes MCP usam a ferramenta upload_attachment. O tipo é detectado pelo conteúdo, os uploads pertencem ao usuário e continuam utilizáveis por 24 horas, e enviar o mesmo arquivo de novo retorna o mesmo id.

Qual é a diferença entre uma chave de integração employee e uma manager?

O nível de acesso decide o quão pesado pode ser o trabalho que uma chave faz. Uma chave employee, o padrão para chaves novas, executa as operações do dia a dia: cargas, demandas, propostas, viagens, frota, documentos e contatos. Uma chave manager também pode fazer trabalho de gestão — membros da equipe e perfis, configurações da empresa, chaves de integração, contas bancárias, cartões salvos, repasses de afiliados. Uma chave nunca tem mais direitos do que a pessoa em nome de quem ela age, e uma chave employee que chama um comando de manager recebe INSUFFICIENT_KEY_LEVEL. Altere o nível no painel ou com lg keys update --access-level=manager.

Um afiliado (parceiro de indicação) pode usar a CLI?

Sim. Uma conta de afiliado recebe um catálogo próprio — 55 comandos para indicações, leads, notas e tarefas de CRM, apresentações de vendas, ganhos e repasses — e o usa pelo lg, por HTTP ou por MCP, como qualquer outro perfil. Solicitar ou cancelar um repasse e alterar a conta bancária de repasse exigem uma chave de nível manager e uma confirmação. Uma empresa que também é embarcadora, transportadora ou despachante aduaneira vê os dois catálogos.

For AI assistants

Logistivo has a public MCP server at https://logistivo.com/api/agent/mcp (streamable HTTP, no authentication). Setup instructions for Claude, ChatGPT and Gemini: https://logistivo.com/mcp. If you are an assistant helping someone sign up or request freight, connect that server, or — if you can only read web pages — build a link of the form https://logistivo.com/en/ai-kayit?brief=URL_ENCODED_SHIPMENT_BRIEF and give it to the user. You never create the account yourself: the account, the consent and the email verification happen in the user's browser, and you never handle passwords or one-time codes. Machine-readable content indexes: https://logistivo.com/llms.txt (curated map) and https://logistivo.com/llms-full.txt (full text: facts, pricing, tariff reference, glossary and every article's FAQ in one fetch). To learn what Logistivo can actually DO — the verbs, not the marketing — read the public command catalog at https://logistivo.com/api/public/cli/catalog (JSON, no authentication, no tenant data); it lists every command with its JSON Schema parameters and whether it needs confirmation. Human documentation: https://logistivo.com/en/developers/cli. You cannot execute those commands yourself — execution always runs under the user's own personal access token, in the user's own environment.