Logistivo CLI: gestiona tu operación logística desde una terminal y desde tu ERP

Un único catálogo de comandos alimenta tres superficies: el cliente de terminal lg, el asistente de IA dentro del panel y el canal al que se conectan los asistentes de IA externos. Una capacidad se escribe una sola vez y aparece en las tres; además, cada acción de escritura del panel ya tiene un comando, así que tus scripts y tus propios agentes de IA pueden hacer el trabajo que tu equipo hace en pantalla. Los pagos, el consentimiento legal y el cierre de la cuenta se quedan deliberadamente en el navegador. Esta página es un contrato. La tabla de comandos de abajo no está escrita a mano: se genera a partir del registro de comandos en vivo en cada petición.

¿Qué es Logistivo CLI?

Logistivo CLI (lg) es un cliente de línea de comandos que ejecuta el trabajo de tu cuenta de Logistivo (cargas y viajes, solicitudes, ofertas y licitaciones, flota y conductores, documentos, contactos y cuentas corrientes, facturas y contabilidad, almacén y stock, miembros del equipo y aranceles aduaneros) desde una terminal, un script o tu propio agente de IA. Los comandos no vienen integrados en el cliente: el cliente descarga el catálogo de comandos del servidor y se construye a partir de él. Los mismos comandos se pueden llamar por HTTP sin instalar nada (es la vía que usan las integraciones con ERP y CRM) y a través del canal MCP al que se conectan los asistentes de IA externos.

Qué está disponible hoy y qué está en camino

Esta sección está arriba del todo a propósito. El error más caro que puede cometer un documento de integración es describir como disponible un endpoint que todavía no está abierto: el integrador escribe el código, el endpoint devuelve 404 y la confianza ya no vuelve.

Como el contrato está congelado, el código de integración que escribas hoy seguirá funcionando a medida que se abran las piezas restantes. Los campos que no conozcas deben ignorarse; esa regla es la que nos permite añadir sin romper.

Qué lo hace diferente: el cliente no conoce ningún comando, los dibuja

Esta es la decisión que da sentido al resto de la página. En una CLI corriente los comandos viven dentro del cliente; en Logistivo viven en el servidor.

En el esquema clásico, cuando el servidor gana una capacidad, el cliente necesita una nueva versión y el usuario tiene que instalarla. Entre tanto, los dos lados saben cosas distintas, y ese es el modo de fallo más silencioso de las integraciones: el script se ejecuta, el código de salida es 0 y el comando simplemente no describe lo que el servidor puede hacer.

En Logistivo, la definición de un comando es un único registro: su nombre, lo que hace, sus parámetros en JSON Schema, qué roles pueden verlo, si necesita confirmación y si su efecto es reversible. Cuando lg arranca, descarga ese catálogo y genera a partir de él su texto de ayuda, sus flags y su validación de entrada. En el momento en que se añade un comando en el servidor, lg lo conoce, sin actualizar el cliente.

El mismo catálogo alimenta al asistente de IA del panel y al canal al que se conectan los asistentes de IA externos. Una capacidad se escribe una vez y aparece en tres sitios, y los tres pasan por el mismo control de rol, el mismo control de confirmación y el mismo registro de auditoría. No hay un inventario de comandos aparte para la CLI; si lo hubiera, las tres superficies divergirían en silencio en menos de seis meses.

La regla de nombres: cómo crecen los nombres de los comandos

El catálogo va a crecer. Los comandos nuevos no se nombran sobre la marcha; se aplican seis reglas. Las publicamos aquí porque son los nombres que tu integración va a escribir durante años, y que sean predecibles tiene valor para ti.

Cobertura: todo lo del panel, desde un comando

Muchos equipos conectan Logistivo a su propio agente de IA o a su sistema de automatización, y un agente solo es útil si puede terminar el trabajo. Así que la regla es sencilla: todo lo que un usuario de oficina puede cambiar en el panel, también lo puede cambiar un comando, detrás del mismo control de rol, el mismo control de confirmación y el mismo registro de auditoría.

Esto está medido, no afirmado. Cada endpoint de escritura que hay detrás del panel se contrasta con el registro de comandos, y un endpoint solo cuenta como cubierto cuando un comando lo declara como su espejo HTTP o llama al mismo servicio compartido. De los endpoints de escritura dentro del alcance, el 100 % está cubierto y ninguno queda sin demostrar. Cada endpoint que se deja fuera lleva un motivo por escrito (pasos de identidad y de pago, el plano de control de la integración, los endpoints propios de la app del conductor, la telemetría del cliente, copias muertas sin ningún llamante), y los que le importan a un integrador se enumeran más abajo.

Los comandos que reciben un archivo siguen la misma regla: 33 comandos de la tabla reciben un documento o una foto, y todos funcionan desde lg, por HTTP y por MCP (consulta Comandos que reciben archivos).

El trabajo de gestión también está cubierto, detrás de una clave más fuerte: 62 comandos de la tabla (miembros del equipo y roles, configuración de la empresa, cuentas bancarias, tarjetas guardadas, claves de integración) necesitan una clave de integración de nivel manager. Una clave de nivel employee, la predeterminada, ejecuta la operativa diaria y recibe INSUFFICIENT_KEY_LEVEL en esos comandos.

Las cuentas de afiliado (socio de referidos) tienen un catálogo propio: 55 comandos en 20 dominios (referidos, leads, notas y tareas de CRM, presentaciones comerciales, ganancias y pagos), más los comandos de cuenta y de equipo que tiene cualquier rol. Los comandos exclusivos de afiliados no están en la tabla pública de abajo, que enumera los tres roles del panel; una cuenta de afiliado los ve en GET /api/common/commands y en lg commands. Una empresa que es a la vez afiliado y cargador, transportista o agente de aduanas ve ambos conjuntos.

Un agente debería empezar con lg users get (nombre de API get_current_user): devuelve el rol, el nivel de acceso de la clave en uso, los permisos efectivos y si una aprobación legal pendiente o un número de teléfono sin verificar está bloqueando ahora mismo las escrituras, para que el agente pueda prever un rechazo en lugar de encontrárselo.

La cobertura se mide con un informe que compara el registro de comandos con los endpoints de escritura del panel; un endpoint del panel añadido sin comando aparece en él como no demostrado. Si encuentras una acción del panel a la que no puedes llegar desde un comando, comunícalo a soporte.

Instalación e identidad

lg es un script de Node de un solo archivo y sin dependencias (Node 18+), publicado en npm como @lgstv/lg. El paquete instala dos comandos idénticos, lg y logistivo; usa logistivo si lg ya es un alias en tu máquina. Como es un solo archivo, copiar lg.mjs en el servidor ERP de un cliente también funciona. No necesitas el cliente para construir una integración con ERP/CRM: los mismos comandos están disponibles por HTTP.

1. Instalar — Solo necesitas Node 18 o posterior; no hay más dependencias. Instálalo de forma global o ejecútalo sin instalar con npx.

2. Iniciar sesión con un código de aprobación — lg login muestra un código de aprobación corto y abre logistivo.com/connect en tu navegador; por SSH, en CI o con --no-browser solo imprime la dirección, que puedes abrir en cualquier dispositivo, incluido tu teléfono. Inicia sesión allí como siempre, comprueba que el código de la página es el mismo que el de tu terminal, elige acceso de solo lectura o de lectura y acción, y apruébalo. No se escribe nada secreto en la terminal: ni contraseña, ni clave, ni código de un solo uso. Después, la sesión sigue iniciada en ese ordenador: cerrar la terminal no cierra la sesión, y lg renueva por sí solo el token de acceso antes de que caduque. lg logout también cierra la sesión en el servidor, y puedes eliminarla en el panel, en Settings › Integration keys › AI and CLI connections. lg login --password (correo y contraseña) sigue disponible como alternativa de respaldo. Requiere lg 0.11 o posterior.

3. Para servidores y CI: una clave, no un código de aprobación — En un servidor o en un job de CI no hay nadie que apruebe un código en un navegador. Emite una clave en el panel (Settings › Integration keys) o con lg keys create (opcionalmente en nombre de un miembro del equipo dedicado y con un alcance reducido) y ponla en LOGISTIVO_TOKEN, o guárdala con lg login --api-key. Mientras la variable esté definida, el archivo de configuración nunca se lee. Detalles: la sección «Conectarse con una clave de API».

4. Verifica y explora el catálogo — lg commands lista todos los comandos que tu rol puede ver, agrupados por dominio; lg help muestra los verbos de ese dominio, y lg help muestra la lista completa de parámetros de un comando con sus tipos y cuáles son obligatorios. Nada de ese texto vive dentro del cliente: todo sale del catálogo. Recuerda que la lista depende de tu rol: los verbos de un dominio no son los mismos para un transportista que para un cargador (loads set-status, por ejemplo, solo está en el catálogo del transportista). Si falta un verbo, revisa tu rol antes de revisar tu instalación.

Nunca pongas la clave en el control de versiones, en un log de CI ni en una ventana de chat. Si iniciaste sesión con un código de aprobación o con una clave de integración, lg logout también cierra la sesión en el servidor; en una sesión con contraseña (lg login --password) solo borra la copia LOCAL y lo dice explícitamente. El registro de auditoría guarda tus argumentos, pero enmascara como *** cualquier campo cuyo nombre contenga token, password, IBAN, card u OTP, y todos los parámetros que un comando declara secretos.

Ergonomía del cliente

Estas funciones facilitan el uso diario del cliente `lg`. Ninguna cambia el contrato HTTP; sáltate esta sección si te integras directamente por HTTP.

Conectarse con una clave de API

Un ERP, un CRM, un script o un agente de IA se conecta a Logistivo con una clave de integración emitida en el panel o con lg keys create. La clave actúa en nombre de un miembro real del equipo: el rol de esa persona decide la lista de comandos, el nivel de acceso de la clave decide lo delicado que puede ser el trabajo y, cuando esa persona deja el equipo, la clave muere con ella. No existe una «cuenta de servicio» sintética ni existirá: el aislamiento multi-tenant está vinculado al usuario, y un puente sin dueño es de los que siguen escribiendo en silencio durante años.

1. Emite una clave en el panel — Settings › Integration keys › New key. Ponle un nombre («Sincronización nocturna del ERP»), elige el alcance y el nivel de acceso y, si quieres, acorta su vida útil. La clave se muestra una sola vez, solo en esa pantalla; el servidor no la guarda. Si la pierdes, Rotate (rotar) emite una nueva y la antigua deja de ser válida al instante.

2. Conéctate desde la terminal — lg login --api-key valida la clave, imprime la identidad y el alcance, y descarga el catálogo de comandos. lg whoami pregunta al servidor con qué usuario, empresa, rol y alcance estás ejecutando. lg logout también revoca la clave en el servidor, igual que cierra allí una sesión iniciada con código de aprobación (solo el lg logout de una sesión con contraseña no puede hacerlo).

3. O HTTP directamente — La clave es un token de acceso que viaja en la cabecera Authorization: Bearer y funciona directamente contra los tres endpoints de comandos. En un servidor o en CI, ponla en LOGISTIVO_TOKEN; mientras esa variable esté definida, el archivo de configuración nunca se lee.

4. O gestiona las claves desde la terminal: lg keys — lg keys es el gemelo en terminal de la pantalla del panel: los mismos endpoints, el mismo control (el permiso de gestión del equipo y, si lo llamas con una clave, una clave de nivel manager). create, rotate y transfer devuelven un token nuevo una sola vez: con --save-profile=NAME va directamente a un perfil local y nunca se imprime; si no, lg lo imprime una vez. En modo --json, el token solo se incluye si añades --show-secret, y una llamada --json sin ninguno de los dos flags se rechaza antes de enviar la petición, así que el token no puede acabar en silencio en la salida de un script. rotate, transfer y revoke piden confirmación (--yes); a una clave se la identifica por su nombre exacto o por su uuid, nunca por aproximación. El nivel de acceso se mantiene sin cambios en una clave rotada o transferida.

El alcance se aplica en toda la superficie de la API, no solo en el catálogo de comandos: una clave de solo lectura deja pasar las peticiones de lectura (GET/HEAD/OPTIONS) y cualquier otra petición de escritura responde 403 forbidden. Hay dos excepciones deliberadas y ambas están protegidas por un control más fino: el endpoint de ejecución de comandos (la decisión sale del propio indicador read_only del comando, porque los comandos que no escriben también se llaman con POST) y una clave que se revoca a sí misma (lg logout, permitido también con una clave de solo lectura). El alcance, los permisos por dominio y el nivel de acceso son tres ejes independientes y todos deben cumplirse: una clave read no puede escribir ni siquiera con nivel manager, una clave write de nivel employee no puede gestionar el equipo, y un permiso de dominio loads:write no abre el trabajo de gestión. Emite la clave en nombre del miembro con menos privilegios que cubra tus necesidades: el rol decide qué registros puedes ver siquiera.

Los diez primeros minutos: del panel a tu primera llamada

Los minutos más caros de una integración son los diez primeros. Los cuatro pasos de abajo trazan ese camino en orden; el mismo orden existe en el panel como asistente (Settings › Integration › Setup wizard), y el asistente llama exactamente a los endpoints que se describen aquí. Un desarrollador sin acceso al asistente llega al mismo sitio leyendo esta sección.

Ninguno de los cuatro pasos termina con «ya lo envié». Si la clave funciona de verdad lo responde el registro de peticiones; si el webhook llega de verdad lo responde el registro de entregas. Ambos están abiertos en el panel y en lg. Una configuración que nunca se mide es una configuración de la que solo se cree que funciona.

Sandbox: practica escrituras sin tocar producción

La verificación previa (dry_run) te dice lo que HARÍA un comando; pero si la idempotencia repite de verdad, si el webhook se entrega de verdad, qué aspecto tienen las ramas de error y de dónde sale el crédito solo se ve en una escritura REAL. Para eso está el sandbox: un tenant separado que refleja tu empresa (el mismo código, el mismo catálogo de comandos) y que nunca toca tu registro live.

El sandbox no es un indicador de la petición, es un TENANT. Una empresa de Logistivo separada abierta para la tuya: su propio usuario, su propio paquete, su propio registro de créditos, sus propias suscripciones de webhooks. El entorno SE DEDUCE de la empresa de la clave y quien llama no puede indicarlo, así que aunque elijas el perfil equivocado la petición se detiene en el servidor con 403 ENVIRONMENT_MISMATCH. «Creía que escribía en el sandbox y escribí en producción» es estructuralmente imposible.

La superficie es idéntica, y está medido: el catálogo que obtiene una clave sandbox es el mismo que obtiene tu clave live (con una clave de cargador, 448 comandos en ambos lados). Un puente que funciona en el sandbox funciona en producción; nada de lo que aprendas tiene que aprenderse dos veces.

El sandbox no se abre vacío: contiene datos semilla generados (NO copiados): contactos, cargas en ambas direcciones y solicitudes. Los nombres son abiertamente inventados y las direcciones de correo están en el dominio @sandbox.invalid; nunca se lee, se clona ni se usa como plantilla ninguna fila de un tenant real.

A propósito, NO hay botón de reinicio, y nunca se borra ningún registro, archivo ni documento. Si quieres empezar de cero, la renovación abre una nueva generación y la antigua se queda, retirada. Unos cientos de filas de ensayo acumuladas no cuestan nada al lado de una rutina de borrado conectada al tenant equivocado.

El modelo de comandos

Hay una sola gramática y no tiene excepciones. Para saber cómo llamar a un comando, lo único que necesitas es lo que el catálogo dice de él.

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

Los nombres de los flags son los nombres de parámetro del esquema, y lg acepta las dos grafías: --load_code y --load-code llegan al mismo campo (lg help muestra la forma kebab-case). Por eso, al ver un flag sabes en qué campo JSON acaba, y pasar de la CLI a HTTP no requiere traducir nombres. Un parámetro booleano escrito sin valor es true (--only_open); lo contrario es --no-only_open. Escribe un argumento cuyo valor empiece por guion como --flag=value; si no, un flag olvidado se traga en silencio el siguiente como su valor. En un comando que recibe un archivo, pon @ delante de una ruta local (--attachment_uuid=@./cmr.pdf); consulta Comandos que reciben archivos.

Ejemplos reales

Todos los comandos de abajo existen hoy en el catálogo; aquí no hay ningún nombre ni parámetro inventado. Las salidas están abreviadas. Las salidas muestran el nivel `data.result`; por HTTP el mismo objeto llega dentro del sobre `{success, data:{…}}` (consulta la API de comandos HTTP).

Sin el cliente: la API de comandos HTTP

lg es una comodidad, no la puerta. Tanto el catálogo como la ejecución pasan por tres endpoints HTTP (los archivos tienen un cuarto, el endpoint de subida; consulta Comandos que reciben archivos); una integración con ERP o CRM se conecta aquí directamente. La identidad es un token de acceso personal enviado en Authorization: Bearer. TODAS las respuestas llegan en el mismo sobre: `{ "success": bool, "data": { … }, "code": "…", "message": { … } }`. Todo el contrato vive dentro de `data`: `contract_version`, `command`, `status` y, como salida del comando, `result`; en caso de fallo, `error_code`, `error_key` y `error`. Los ejemplos de respuesta de esta página muestran el objeto `data` en lugar del sobre, para ahorrar espacio; no olvides desenvolver `data` en tu propio código. `lg --json` también imprime el sobre COMPLETO, y por eso las rutas jq empiezan por `.data.result…`. La única excepción es `/api/public/status`: ese endpoint no lleva sobre para que los sistemas de vigilancia puedan alertar sin analizarlo.

Pedir confirmación con 200 + ok:false está prohibido y nunca ocurrirá. La razón es sencilla: los clientes tratan 200 como éxito, así que una petición de confirmación devuelta como 200 cortaría el flujo en silencio y nadie se daría cuenta. La confirmación siempre es 409.

Comandos que reciben archivos

33 comandos de la tabla reciben un documento o una foto: adjuntar un CMR a una carga, leer los códigos HS de una factura, crear un vehículo a partir de su permiso de circulación, estimar un palé a partir de fotos, subir un documento de empresa, cotejar una factura con una lista de bultos (packing list). En el navegador, sueltas el archivo en un formulario; en cualquier otro sitio son dos pasos: subir el archivo y pasar su id al comando.

Un parámetro recibe un archivo cuando su nombre termina en attachment_uuid o attachment_uuids: photos[].attachment_uuid dentro de una lista, front_attachment_uuid en un cheque. No tienes que recordarlo: cada fila del catálogo lleva file_params con las rutas exactas, el esquema marca esos campos (format: uuid, x-logistivo-file: true) y la respuesta del catálogo lleva un bloque attachments con la dirección de subida, los tipos aceptados, los límites de tamaño y el tiempo de vida. lg help también los muestra.

Subir es una escritura, así que una clave con alcance de lectura no puede subir; cualquier clave con alcance de escritura puede, con cualquiera de los dos niveles de acceso. Las subidas pertenecen al usuario, no a la clave: dos claves del mismo usuario ven las mismas subidas, y nadie puede usar las de otra persona. Un id desconocido, caducado o que no es tuyo simplemente no se encuentra, y no se revela el motivo.

Los adjuntos del chat del panel y las subidas de comandos son espacios separados: un archivo adjuntado en el chat del asistente no se puede pasar a un comando por su id, y una subida de comando nunca aparece en un hilo de chat.

Una subida no es un comando y no tiene ningún efecto de negocio por sí sola: no pasa nada con tus datos hasta que un comando usa el id. Por eso subir nunca pide confirmación: el control lo lleva el comando que consume el archivo.

El control de confirmación y los comandos irreversibles

Cada comando del catálogo lleva dos indicadores: confirmation_needed (si requiere aprobación explícita antes de ejecutarse) e irreversible (si su efecto se puede deshacer). Son preguntas distintas, y ambas se aplican en el servidor en lugar de dejarse a la buena educación del cliente.

El control tiene que ver con la responsabilidad, no con las reglas de negocio. Hacer una oferta es el primer paso hacia un contrato de transporte; emitir una factura crea un asiento contable; un movimiento de stock modifica un libro de registro. Nada de esto debería poder ejecutarse dos veces por accidente.

El control se lee del comando EFECTIVO. Cuando un comando envuelve a otro (como hace el ejecutor genérico del chat), el control sale de los indicadores del comando que realmente se va a ejecutar, no del envoltorio. Con un indicador fijo, uno de dos fallos sería inevitable: o el comando de facturas se ejecutaría sin confirmar, o cada consulta de país mostraría una tarjeta de confirmación.

read_only y sin control no son lo mismo. Hay tres estados distintos: invoices preview no tiene control y no escribe nada; loads create tiene control y escribe; invoices create no tiene control pero escribe (nace un borrador). Lo decide una sola prueba: cuando el comando termina, ¿ha cambiado alguna fila en la base de datos?

Algunas acciones no están detrás de este control porque directamente no son comandos: comprar un paquete, añadir una tarjeta con 3D Secure, aprobar textos legales y cerrar una cuenta ocurren en el propio navegador de la persona. Para eso, lg handoff-links generate devuelve un enlace de logistivo.com (gratuito, sin escribir nada y sin llevar ningún secreto) y la persona termina el paso allí. Ningún indicador, lo pongas como lo pongas, convierte un pago o un consentimiento en una llamada a la API.

Técnicamente es posible que una integración supere el control automáticamente en cada llamada, pero no es recomendable. El patrón correcto es automatizar el control solo para los comandos que sabes que son reversibles y enviar los irreversibles a la cola de una persona.

Verificación previa: mira lo que haría un comando de escritura sin ejecutarlo

El paso más caro al conectar un puente con un ERP es descubrir si tu cuerpo de petición es correcto ejecutando REALMENTE el comando: un borrador de factura en el libro real, un crédito quemado, limpieza manual. dry_run (ejecución en seco) acaba con eso: añades un campo a la petición, el comando no se ejecuta y lees lo que pasaría.

Hoy el registro contiene 519 comandos; 233 de ellos solo leen y no escriben nada. De los 286 comandos de escritura restantes, 251 tienen control: llamados sin confirm ya responden 409 y describen lo que harían. El hueco eran los otros 35: borradores de factura, borradores de documentos de exportación, líneas; justo la cadena que un puente conecta primero. En esos, llamar era hacer.

Había un segundo hueco incluso en los comandos con control: la validación del esquema se ejecuta DESPUÉS del control. Así que una llamada a la que le faltaba un argumento recibía primero la tarjeta de confirmación, la confirmaba y solo entonces oía «falta este campo». Una verificación previa responde a ambas cosas en un solo viaje de ida y vuelta.

Y el error de puente más común (y el que más tarde se descubre) es este: envías un campo que no está en el esquema (customer_id donde se espera recipient_contact_id), el servidor lo descarta en silencio, el comando devuelve 200 y el registro acaba en el sitio equivocado. La respuesta de la verificación previa enumera esos campos por su nombre bajo arguments.ignored; ningún mensaje de error lo hará nunca.

Una verificación previa no escribe nada, y eso es estructural antes que una medición: la ruta de verificación previa nunca entra en el handler del comando, así que se cumple para todos los comandos, incluidos los que se añadan más adelante. También se midió cuando se lanzó: se capturó cada sentencia SQL emitida al verificar previamente todos los comandos de escritura del catálogo de ese momento (dos conjuntos de argumentos, dos roles, 178 verificaciones previas). Ni un solo INSERT/UPDATE/DELETE, ni un solo job en cola, ni un solo correo; el número de filas en 31 tablas y el saldo de créditos no cambiaron.

El código de salida es el mismo que produciría la llamada real (se ejecutaría 0, control de confirmación 4, conflicto de idempotencia 2, cualquier otra cosa 1), así que «lg … --dry-run || exit» es un control con sentido en un paso de CI.

Idempotencia: por qué reintentar no crea duplicados

Un ERP no lee tu mensaje de error, no ve tu pantalla y volverá a enviar la misma petición tras un tiempo de espera agotado. Esta sección existe exactamente para ese comportamiento.

Una llamada que lleva un idempotency_key (clave de idempotencia) se ejecuta como máximo una vez. La segunda llamada no vuelve a ejecutar el comando; devuelve el resultado registrado con el estado replayed. Cuando se cae la red, cuando una petición agota el tiempo de espera o cuando una cola recoge el mismo trabajo dos veces, recibes el primer resultado en lugar de una factura duplicada.

Usa una clave en todos los comandos de escritura. El campo read_only del catálogo te da esa distinción gratis: todo comando con read_only: false merece una clave de idempotencia.

Tu estrategia de reintentos debe usar backoff exponencial y llevar la MISMA clave en cada intento. Generar una clave nueva en cada intento equivale a no usar idempotencia en absoluto. No envíes la misma clave en paralelo: el «como máximo una vez» está garantizado para reintentos secuenciales, no para dos peticiones que compiten entre sí.

Sincronización incremental y paginación

Un puente ERP/CRM debe traer lo que ha cambiado, no todo cada hora. Todos los comandos de listado (loads list, demands list-own, contacts list, invoices list, teammates list, load-groups list, reservations list, drivers list, tenders list, products search, warehouses search) aceptan dos parámetros compartidos y devuelven un campo compartido.

Por qué no hay offset: en una tabla viva, un offset se desplaza. Una fila insertada entre dos páginas hace que te saltes o cuentes dos veces un registro, en silencio. El cursor solo avanza, así que un registro insertado o actualizado a mitad del recorrido acaba al final de la lista: no se pierde nada ni se duplica nada.

Una carga inicial completa es el mismo mecanismo: empieza con updated_since=1970-01-01T00:00:00Z y sigue next_cursor hasta que sea null. No hay un endpoint de «exportación» aparte porque no hace falta.

Lo mismo con lg: lg loads list --updated_since=2026-09-01T00:00:00Z --limit=100 --json, y después lg loads list --cursor= --json.

Para la siguiente sincronización, pon en updated_since el mayor updated_at que hayas visto. Las marcas de tiempo tienen precisión de un segundo, así que un registro en el límite puede llegar dos veces; es inofensivo siempre que hagas upsert por external_ref o por código en tu lado. Sin updated_since ni cursor, un comando se ejecuta en modo chat: orden pensado para personas, como máximo 20 filas, next_cursor a null y truncated indicando que «hay más». No uses ese modo para sincronizar.

Claves de error legibles por máquina de esta sección: INVALID_CURSOR, INVALID_UPDATED_SINCE, DUPLICATE_EXTERNAL_REF, EXTERNAL_REF_TOO_LONG, EXTERNAL_REF_INVALID. Todas responden HTTP 422 con error_code: "failed" y result.error_key en el cuerpo. Ramifica según la clave, nunca según el texto.

Tu propia clave: external_ref

No conviertas nuestros ids autoincrementales (invoice_id, contact_id) en la clave de negocio de tu ERP; son opacos y privados de tu empresa. Las cargas, solicitudes, contactos, facturas, productos y almacenes llevan tu clave en su lugar.

Webhooks salientes: te hacemos un POST cuando ocurre algo

Todo lo anterior era pull: tú preguntas y nosotros respondemos. Un puente ERP tenía que preguntar una vez por minuto para enterarse de que una carga había cambiado de estado, de que se había emitido una factura o de que había llegado una oferta. Ya no: registra una dirección https, elige los eventos que te interesan y te llegará un POST firmado cuando ocurran.

El push se añadió JUNTO AL pull, no en su lugar. La sincronización incremental (updated_since + cursor) sigue intacta y sigue siendo la herramienta adecuada: la primera carga completa, el relleno del histórico y la respuesta a «¿me he perdido algo?» viven ahí. Los webhooks reducen la latencia de minutos a segundos. Un puente sano usa ambos: el webhook como disparador y la sincronización como red de seguridad.

El payload es una SEÑAL, no una copia del registro. Lleva los identificadores (id, tu propio external_ref, status, hora); el detalle lo obtienes tú con tu propia clave mediante GET, y esa llamada ya pasa por tus propios controles de autorización. Por eso la superficie de exposición se queda en el nivel de identificadores. Faltan dos campos de forma deliberada: la identidad de la empresa que hizo una oferta (las ofertas ciegas aparecen con un alias en el panel; poner la identidad en un webhook rompería esa privacidad por un segundo canal) y los datos de contacto de las reservas (no hace falta mover datos personales por un canal adicional).

Los nombres de los eventos están CONGELADOS. Si cambia el significado de un nombre, se abre un nombre nuevo y el antiguo sigue vivo durante una ventana de obsolescencia. Añadir un campo nuevo al payload NO sube la versión del contrato: un consumidor debe ignorar los campos que no conoce.

Catálogo de eventos

Cada uno de los 13 eventos de abajo está respaldado por una clase de evento que se despacha REALMENTE. En el catálogo no hay nombres del tipo «estaría bien»: un evento que se anuncia pero nunca se dispara es un evento que el integrador espera para siempre, y ese fallo es silencioso. Esta tabla tampoco está escrita a mano: se genera a partir del catálogo en vivo en cada carga de la página.

invoice.payment_recorded está AUSENTE a propósito: esa clase de evento existe en el código pero no se despacha desde ningún sitio. El evento de pago se añadirá el día en que la clase se dispare de verdad, no antes. Anunciar un evento antes de tiempo es peor que no añadirlo nunca.

Verificar la firma

Cada petición lleva una cabecera X-Logistivo-Signature: t=,v1=. La cadena firmada es «.» y el algoritmo es HMAC-SHA256. El prefijo v1 permite versionar el esquema: si algún día llega un segundo esquema, la cabecera llevará t=…,v1=…,v2=… y tú eliges el que conoces.

Publicamos dos receptores de referencia sin dependencias, uno para Node y otro para PHP puro. Ambos aplican las cuatro reglas anteriores y se prueban a sí mismos con --selftest: firma válida, cuerpo manipulado, secreto incorrecto, marca de tiempo fuera de la ventana, cabecera malformada, la trampa de la reserialización y los dos tipos de repetición (misma entrega / mismo hecho). El verificador es la contrapartida exacta del código que usamos nosotros: consumimos el contrato que publicamos.

Entrega, reintentos y silenciamiento

Tu receptor no siempre estará disponible. La vía lo asume: un fallo transitorio entra en el calendario de reintentos, uno permanente silencia la suscripción, y el MOTIVO del silenciamiento siempre se registra.

El motivo del silenciamiento es legible por máquina y estable (delivery_failures, endpoint_gone, unsafe_target, key_revoked, by_user) y aparece tanto en el panel como en `lg webhooks list`. Silenciar sin motivo dejaría sin respuesta la pregunta «¿por qué no llega nada?». Al volver a activar la suscripción se reinician el contador y la marca.

Tú escribes la dirección; NUESTRO servidor hace la petición. Sin un control, un tenant que introdujera una dirección de metadatos de la nube podría hacer que emitiéramos peticiones desde dentro de nuestra propia red. Por eso la dirección se comprueba tanto al registrarla como en CADA entrega:

Salud y estado: nadie debería notar un puente silencioso antes que tú

Un puente de integración rara vez muere haciendo ruido. Lo normal es que se quede callado de madrugada: un token caduca, la persona en cuyo nombre actúa la clave deja el equipo, se reduce un alcance o el endpoint receptor empieza a devolver 403. Quien lo nota primero es casi siempre el cliente. lg status existe para invertir ese orden.

Un solo comando responde a dos preguntas a la vez, porque durante una incidencia ambas se hacen en el mismo momento: ¿está activa la plataforma? y ¿está sano mi puente? La línea de la plataforma funciona sin token: durante un fallo, el propio token es el primer sospechoso, y atar la pregunta del estado a él te dejaría sin respuesta justo cuando la necesitas.

El resumen de salud no mantiene ningún registro nuevo; condensa tres fuentes existentes en una sola frase: el contador de uso diario (cuánto llamaste y cuánto falló), el registro de peticiones (qué petición, cuándo y por qué se rechazó) y el registro de entregas de webhooks salientes (si el evento llegó al receptor). La tasa de error siempre se lee del contador diario; el registro detallado tiene un tope y lo dice claramente cuando se alcanza.

El mismo resumen aparece en el panel: en Settings › Integration › Keys, cada fila lleva un distintivo y, debajo, el motivo. Los umbrales no se calculan en el cliente; el nivel que asignó el servidor es el que pintan tanto el panel como lg status, así que las dos pantallas nunca pueden decir cosas distintas sobre la misma clave.

Tu sistema de vigilancia no necesita token. El endpoint devuelve solo palabras de estado; no lleva versión, nombre de host, nombre de cola, número de trabajos, texto de error ni datos de clientes, y no puede llevarlos.

El resumen de salud se genera con un número constante de consultas por petición; el número de consultas no crece con el número de claves. Para el detalle, lee las peticiones recientes de esa clave con lg logs y el puente saliente con lg webhooks list.

Errores y códigos de salida

Aquello sobre lo que ramifica una máquina es una cadena estable. El texto del campo de error va dirigido a una persona, está localizado y cambia sin aviso: una integración que ramifica según el texto del mensaje no está soportada, y que se rompa no es un bug.

Dos taxonomías, dos campos. error_code es la capa de transporte (la tabla de arriba) y es sobre lo que ramificas primero. error_key es la clave estable de regla de negocio dentro de failed (INSUFFICIENT_CREDITS, NOT_FOUND, DUPLICATE_CONTACT, INSUFFICIENT_STOCK) y se publica como diccionario en el catálogo (error_keys) y más abajo. Un comando que todavía no emite una clave devuelve error_key: null; trata una clave que no esté en el diccionario como un fallo genérico.

Promesa de estabilidad

La pregunta más cara de un contrato de integración es «¿el código que escribo hoy seguirá funcionando mañana?». La responde un solo número: contract_version en el catálogo, y la cabecera X-Contract-Version en cada respuesta. Responde exactamente a una pregunta: ¿el código escrito antes sigue funcionando sin cambios? Si el número no ha subido, sí. Si ha subido, no: se eliminó o se restringió algo de lo que dependías.

Porque un anuncio no elimina nada: tu código existente funciona exactamente igual que antes, también al día siguiente. Subir la versión con el anuncio emitiría dos señales para un solo cambio y te enseñaría a ignorar la primera, debilitando la señal que de verdad importa. El anuncio ya viaja en la llamada que estás haciendo: deprecated_at y replaced_by en la fila del catálogo, una cabecera X-Deprecated más un bloque deprecated en la respuesta de ese comando, y un aviso de una línea en stderr si usas lg. No tienes que hacer nada para enterarte de que algo va a desaparecer; ya está en tu log.

Entre el anuncio y la eliminación pasan al menos dos trimestres naturales. Durante toda la ventana, el comando antiguo sigue respondiendo y avisa en cada llamada. Cuando se retira un nombre de la CLI, su sucesor adopta ese nombre como alias: durante la ventana, el nombre antiguo llega al comando antiguo, y el día en que se elimina, la misma llamada aterriza en el nuevo, sin que tengas que publicar una nueva versión del cliente.

preview_invoice → get_invoice (anunciada el 3 de septiembre de 2026). get_invoice devuelve el mismo bloque de vista previa y añade las líneas, el desglose de impuestos, los pagos registrados, el saldo pendiente y el acceso por tu propia clave de ERP (external_ref). preview_invoice sigue funcionando, igual que lg invoices preview; ambos avisan.

Tabla de identidad de comandos

La tabla de abajo no está escrita a mano: se genera a partir del registro de comandos en vivo cada vez que se sirve esta página. Un comando añadido en el servidor aparece aquí solo. La columna «Control» te dice si un comando pide confirmación y si su efecto se puede deshacer; la columna «Roles» te dice qué tipos de cuenta lo encontrarán en su catálogo. Los distintivos marcan los comandos que necesitan una clave de nivel manager, los que reciben un archivo y los que solo se ejecutan desde lg.

El nombre de API (list_loads) está congelado y no cambiará; es el nombre que usas en las llamadas HTTP. La identidad de la CLI (loads list) se rige por la política de versionado: una vez anunciada, solo cambia con una subida de versión y una ventana de migración, durante la cual el catálogo sirve el par antiguo como alias. Las descripciones de aquí salen del propio registro de cada comando y están acortadas; para el texto completo, los tipos de parámetro y los valores enum, lee el endpoint del catálogo o ejecuta lg help . Los comandos exclusivos de afiliados no se listan aquí; una cuenta de afiliado los ve en su propio catálogo.

Integración con ERP y CRM — Identidad: nada de cuentas de servicio, un usuario real en su lugar

Un ERP o un CRM se conecta a Logistivo como un usuario real de la empresa con un token de acceso personal emitido desde el panel. Nunca se crea una cuenta de servicio sintética (sin empresa, exenta del acotamiento y sin nadie responsable de ella).

Esto es arquitectura, no preferencia. El aislamiento multi-tenant descansa por completo en la empresa del usuario; para una identidad sin empresa, el ámbito de aislamiento queda inerte, y una sola consulta errónea con ese token lee datos que no le pertenecen. La segunda razón es la auditoría: la columna de usuario del registro vincula «quién ejecutó esto» con una persona; una cuenta de servicio vacía de sentido esa columna y deja sin dueño una integración que emite facturas. La tercera es la revocación: si un token pertenece a una persona, la integración se calla cuando esa persona se va. Un puente sin dueño es el puente que sigue escribiendo en silencio durante años.

Integración con ERP y CRM — Versionado y obsolescencia

La respuesta del catálogo lleva un entero contract_version; las filas de comandos llevan since, deprecated_at y replaced_by. La versión del contrato solo sube con cambios incompatibles.

Integración con ERP y CRM — Identificadores estables: nunca le des a tu ERP un id autoincremental como clave

Los ids autoincrementales internos son opacos y están acotados al tenant: el id válido de otra empresa te devuelve «no encontrado», no datos. Pero ser opacos no los convierte en claves de negocio. Lo que un ERP guarda en su propio registro tiene que ser una clave de negocio.

Integración con ERP y CRM — Paginación, filtrado y sincronización incremental

Los comandos de listado reciben limit y devuelven count. Para una sincronización nocturna, el contrato se compromete a lo siguiente; un comando de listado que lo ha adoptado anuncia cursor y updated_since en su JSON Schema, y hasta que lo haga, trátalo como una ventana pequeña (máximo 20 filas, sin cursor).

Integración con ERP y CRM — Límites de peticiones

La clave del límite es el token, no la IP: un ERP llega desde detrás de un único NAT, y un límite basado en IP contaría a toda una empresa como un solo usuario. Dos integraciones con dos claves nunca se comen la cuota la una a la otra; es la segunda razón de «una integración por clave».

Integración con ERP y CRM — Cosas que no haremos

Esta suele ser la parte más útil de un contrato: saber lo que nunca llegará te permite diseñar teniéndolo en cuenta.

El catálogo legible por máquina

Esta página se escribió para personas. Para un asistente de IA o un cliente automatizado, la misma información existe como copia estructurada y sin autenticación.

GET /api/public/cli/catalog no requiere autenticación y devuelve la identidad pública de los comandos: el nombre de API congelado, la identidad dominio/verbo de la CLI, la descripción, los parámetros en JSON Schema, los indicadores read_only, confirmation_needed e irreversible, el credit_cost y los campos de acceso key_level, permission, file_params, secret_parameters y cli_only. No devuelve datos personales, ni datos de tenant, ni registros de ejemplo: solo lo que es la superficie.

La forma correcta de que un agente externo lo use: leer el catálogo, explicar con SUS PROPIAS palabras qué trabajos son posibles y, cuando haga falta ejecutar algo, indicar al usuario que lo ejecute en su propio entorno con su propio token. El agente nunca pide, genera ni retransmite el token de un usuario.

Conectar Logistivo a un asistente de IA para el registro y las solicitudes de transporte es un canal aparte con su propia página:

La definición legible por máquina: OpenAPI 3.1 y Postman

El catálogo de arriba es el formato propio de Logistivo. La misma información se publica también en dos formatos estándar del sector que tus herramientas ya entienden: una definición OpenAPI 3.1 y una colección de Postman. Ninguna de las dos está escrita a mano: ambas se generan a partir del registro de comandos en vivo en cada petición, así que no pueden quedarse desfasadas.

La definición ES el catálogo, no una segunda realidad. Cada comando aparece como una operación POST /api/common/commands/; el esquema del cuerpo es el propio JSON Schema del comando (byte a byte lo que ve el asistente), el esquema de la respuesta es el sobre compartido, los indicadores de control (read_only, confirmation_needed, irreversible y el distintivo de créditos) están en la extensión x-logistivo, y las claves de error de negocio están bajo x-error-keys. Un comando retirado se marca como deprecated: true y sigue pudiéndose llamar durante su ventana de seis meses.

Junto a la superficie de comandos, en la definición también están los endpoints REST que llevan el contrato de integración: 10 endpoints de listado con sincronización incremental (updated_since + cursor + limit) y 7 endpoints de escritura que aceptan tu propia clave (external_ref). La superficie REST de Logistivo es mucho mayor; el resto se deja fuera A PROPÓSITO. Esos payloads son el contrato propio del producto, y un esquema escrito a base de suposiciones rompería tu cliente generado en la primera llamada, y buscarías el error en tu código, no en el nuestro.

La definición se genera para el ROL de quien la pide: el comando de otro rol nunca entra en el documento, porque una definición es un contrato y no un diccionario, y todo lo que contiene debe poder llamarse con ese token. La versión sin autenticación es la unión de los tres roles, y en ella cada operación indica qué roles la ven en x-logistivo.roles.

No te fíes de nuestra palabra, compruébalo: npx @redocly/cli lint logistivo-openapi.json pasa sin ningún aviso.

Generación de código: un cliente tipado a partir de la definición

Ya tienes la definición; ahora genera código que funcione a partir de ella. `lg codegen` lee el documento OpenAPI y escribe un cliente de un solo archivo y sin dependencias: TypeScript (declaraciones de tipos más un envoltorio fino de llamadas) o PHP (una clase cliente con array shapes de PHPDoc). Sin `npm install`, sin composer: `fetch` en el lado de TypeScript, `ext-curl` en el lado de PHP.

La generación ocurre en TU máquina, no en nuestro servidor. No es una preferencia: fija la definición en tu repositorio una vez (`lg spec --out=...`) y el mismo comando se ejecuta en cada build sin red y sin token, así que tu CI nunca depende de nuestra disponibilidad. Y como el código generado es código que SE EJECUTA en tu ERP, tenerlo en tu repositorio en lugar de descargarlo de una API hace que cada cambio aparezca en `git diff`.

El cliente generado maneja correctamente las tres vías de Logistivo, y sabe cuál es cuál por el documento en lugar de adivinarlo: cada operación declara su vía en `x-logistivo.surface`. La vía de comandos desenvuelve el sobre `{success, data:{…}}`; un listado de sincronización REST NO lleva sobre y se devuelve tal cual (desenvolverlo eliminaría `next_cursor`); una escritura REST devuelve el propio cuerpo de producto del endpoint. Un generador de uso general no conoce esta distinción; el nuestro sí.

Los tipos no son suposiciones: la interfaz de argumentos de cada comando se genera a partir del mismo JSON Schema que ve el asistente. Lo obligatorio sigue siendo obligatorio, los enums se convierten en tipos unión y los objetos anidados en tipos anidados. En la práctica: si el servidor exige un campo, la llamada que lo olvida no falla con un 422 en producción, sino en tiempo de compilación.

En el archivo no se escribe ninguna marca de tiempo de generación: la misma definición con los mismos flags produce los mismos bytes. Por eso regenerar no hace ruido en `git diff`, y lo único que de verdad cambió (el contrato) sigue siendo visible.

Alcance honesto

Preferimos ampliar esta sección antes que reducirla. El valor de un contrato de integración está menos en lo que promete que en la claridad con la que dice lo que no hace.

Preguntas frecuentes

¿Para qué sirve Logistivo CLI?

Ejecuta el trabajo de tu cuenta de Logistivo desde una terminal, un script o tu propio agente de IA: cargas y viajes, solicitudes, ofertas y licitaciones, flota, conductores y sus gastos, documentos, contactos y cuentas corrientes, facturas y contabilidad, almacén y stock, miembros del equipo, aranceles HS y derechos antidumping. Cada acción de escritura del panel tiene un comando; los pagos, el consentimiento legal y el cierre de la cuenta se quedan deliberadamente en el navegador. Los mismos comandos se pueden llamar por HTTP para integraciones con ERP/CRM y a través del canal MCP.

¿Cómo se nombran los comandos en Logistivo CLI?

La forma es lg --parameter=value. El dominio es una entidad en plural (loads, demands, invoices, export-documents) y el verbo sale de un vocabulario cerrado que solo crece por decisión deliberada (list, get, create, update, search, set-status, generate, etc.). Todos los verbos de una entidad viven en un mismo dominio, y el par (dominio, verbo) es único en todo el catálogo.

¿Cómo me autentico en la CLI?

De forma interactiva, lg login muestra un código de aprobación que confirmas en tu navegador después de iniciar sesión en Logistivo: no se escribe ninguna contraseña ni clave en la terminal, y la sesión sigue iniciada y se renueva sola hasta que ejecutes lg logout. lg login --password (correo y contraseña) sigue disponible como alternativa de respaldo. En servidores y en CI, usa una clave de integración emitida en el panel o con lg keys create: lg login --api-key la guarda en un perfil local, o bien ponla en la variable de entorno LOGISTIVO_TOKEN. Por HTTP, el token viaja en la cabecera Authorization: Bearer.

¿Puedo crear una cuenta de servicio para mi integración con el ERP?

No. Una integración se conecta con el token de un usuario real de la empresa creado en el panel. Una identidad sintética sin empresa deja inerte el aislamiento multi-tenant, vacía de sentido la columna «quién ejecutó esto» del registro de auditoría y deja un puente que no se calla cuando su dueño se va. El patrón correcto es un usuario dedicado con un rol restringido y un responsable con nombre y apellidos.

¿Tiene Logistivo un entorno de pruebas (sandbox)?

Sí, y es de autoservicio: se abre un tenant sandbox separado para tu empresa, con el mismo código, el mismo catálogo de comandos y su propio registro de créditos. El entorno se deduce de la empresa de la clave y no se puede indicar en la petición; una llamada hecha con el perfil equivocado se detiene en el servidor con 403 ENVIRONMENT_MISMATCH. Una solicitud que abras en el sandbox nunca llega a transportistas reales, un evento del sandbox nunca se entrega a tu suscripción de webhooks live y tu saldo live no se mueve. Endpoints: POST /api/common/integration-sandbox (abrir), POST /api/common/integration-sandbox/keys (clave), GET /api/common/integration-sandbox/go-live (lista de comprobación para pasar a producción). En el lado de lg, lg --sandbox y lg --live son selectores de perfil para una sola ejecución.

¿Cómo leo la salida de lg --json en un script?

La salida es el sobre HTTP completo: { "success": bool, "data": { "contract_version", "command", "status", "result" }, "code", "message" }. La carga útil de negocio está bajo .data.result; en los comandos de listado, las filas son .data.result., el recuento es .data.result.count y la página siguiente es .data.result.next_cursor. Así que la ruta jq correcta tiene este aspecto: lg loads list --json | jq -r '.data.result.loads[].load_code'. Una ruta que se salta el sobre no da error: devuelve null en silencio, y en un script de vigilancia eso se convierte en una alarma que nunca deja de sonar. La única excepción es /api/public/status, cuyo cuerpo no lleva sobre a propósito.

Si envío la misma petición dos veces, ¿obtengo registros duplicados?

No, si enviaste un idempotency_key. Una segunda llamada con la misma clave no vuelve a ejecutar el comando; devuelve el primer resultado con el estado replayed. La clave debería ser un UUID y debe ser la MISMA en todos los reintentos: generar una clave nueva en cada intento equivale a no usar idempotencia.

¿Qué hace --yes?

Supera el control de confirmación. Cuando se llama a un comando con control sin --yes, no cambia nada: el servidor resume en una frase lo que va a hacer, indica si es irreversible y se detiene (HTTP 409, código de salida 4 en la CLI). Al repetir el comando con --yes se realiza el trabajo.

¿Cómo detecto en un script que un comando ha fallado?

Lee el código de salida, nunca el texto del mensaje: 0 éxito, 1 rechazo por regla de negocio, 2 error de comando o de argumento, 3 error de autenticación o de rol, 4 confirmación requerida. Por HTTP, los equivalentes son 200, 422, 404, 403 y 409, y el campo error_code del cuerpo es estable. El MENSAJE de error está localizado y cambia sin aviso.

¿Cómo descubro los comandos disponibles de forma programática?

GET /api/public/cli/catalog para el descubrimiento sin autenticación, y GET /api/common/commands para la lista que tu cuenta puede ver realmente. Ambos devuelven los parámetros en JSON Schema de cada comando; construye tu cliente a partir de ellos en lugar de codificar a mano una lista de comandos.

¿Tengo que actualizar mi cliente cuando se añade un comando nuevo?

No. El cliente obtiene su lista de comandos del catálogo, así que un comando añadido en el servidor aparece sin una nueva versión del cliente. Solo hace falta actualizar ante un cambio incompatible que sube la versión del contrato, y en ese caso la forma antigua sigue viva durante al menos seis meses.

¿La CLI sustituye a la API REST existente?

No. La superficie REST que mueve las apps web y móviles sigue en su sitio. El catálogo de comandos no sustituye al CRUD de recursos; es la capa de los verbos: la forma única, con control de rol, con control de confirmación y auditada de realizar una acción.

¿Cómo obtengo una clave de API de Logistivo y qué puede hacer?

La emite un responsable con permiso de gestión del equipo en el panel, en Settings › Integration keys, o desde una terminal con lg keys create. La clave actúa en nombre de un miembro real del equipo y tiene tres ajustes independientes: el alcance (read abre el catálogo y los comandos que no escriben; write abre todos los comandos, y el control de confirmación se mantiene igual), permisos opcionales por dominio y un nivel de acceso: employee, el predeterminado, para la operativa diaria, o manager para equipo, configuración, claves y dinero. Se muestra una sola vez y no se guarda en el servidor; se puede rotar, transferir y revocar. Cuando el miembro en cuyo nombre actúa se va, la clave también se cierra. No existe la cuenta de servicio.

¿Qué pasa si reutilizo una clave de idempotencia para otro comando?

El servidor responde 409 idempotency_conflict e indica el comando para el que se usó la clave por primera vez; no se ejecuta nada y no se repite nada. Las claves están acotadas a tu empresa, vinculadas a un comando y bloqueadas durante 24 horas; después, la misma clave inicia una ejecución nueva.

¿Cómo se aplican los límites de peticiones a mi integración?

Por token y por minuto, en tres grupos: lecturas del catálogo 120, comandos de lectura 120, comandos de escritura 30. Un 429 siempre lleva Retry-After y un cuerpo con retry_after, limit y bucket; espera ese tiempo y reintenta. El catálogo publica las cifras actuales en rate_limits.

¿Cómo distingo un rechazo de negocio de un error de transporte?

Ramifica primero según error_code: failed (422) es un rechazo de negocio y lleva un error_key estable como INSUFFICIENT_CREDITS o NOT_FOUND; server_error (5xx) y los errores de red se reintentan con la misma clave de idempotencia; ante rate_limited (429) se espera. Nunca ramifiques según el texto del error.

¿Envía Logistivo webhooks? ¿Para qué eventos?

Sí. Registras una dirección https y eliges los eventos que te interesan; cuando ocurren, llega un POST firmado. Hoy se puede suscribir a 13 eventos: carga creada/cambio de estado/conductor asignado, solicitud creada y cambio de estado, oferta presentada/revisada/aceptada, factura en borrador/emitida/anulada, y reserva de almacén creada/cambio de estado. Cada nombre está respaldado por una clase de evento que realmente se despacha: nada se anuncia para luego no dispararse nunca, porque un evento así es un fallo silencioso que el integrador espera para siempre. Las suscripciones se crean en Settings › Integration › Webhooks, o desde una terminal con lg webhooks create.

¿Cómo verifico la firma de un webhook de Logistivo?

Cada petición lleva una cabecera X-Logistivo-Signature: t=,v1=. La cadena firmada es «.», el algoritmo es HMAC-SHA256 y la clave es el secreto de la suscripción. Cuatro reglas: (1) verifica los bytes EN BRUTO: decodificar y volver a serializar el cuerpo (JSON.stringify / json_encode) convierte los mismos datos en bytes distintos y la firma deja de coincidir; es el error más común; (2) comprueba la marca de tiempo y descarta todo lo que cumpla |now − t| > 300 s aunque la firma sea correcta; (3) compara en tiempo constante con hash_equals / timingSafeEqual; (4) deduplica por event_id, no por el id de entrega: event_id se conserva en un reenvío, mientras que el id de entrega cambia. En el repositorio, en examples/webhook-receiver/, hay receptores para Node y PHP que funcionan y se prueban a sí mismos con --selftest.

¿Qué pasa si un webhook no se puede entregar? ¿Pierdo el evento?

No. Si tu receptor no devuelve 2xx, la entrega entra en el calendario de reintentos: 60 · 300 · 1800 · 7200 · 21600 segundos, 6 intentos en total, una ventana de aproximadamente 8,6 horas. Se reintenta CUALQUIER fallo salvo 410 Gone, incluidos los 4xx: perder un evento de negocio cuesta más que unas pocas peticiones de más. Tras 10 entregas agotadas consecutivas, la suscripción se silencia y se registra el motivo, así que la pregunta «¿por qué no llega nada?» sigue teniendo respuesta. El registro de entregas guarda cada intento (código de estado, duración, error) y puedes reenviar una sola entrega desde el panel o con lg webhooks replay. La sincronización incremental (updated_since + cursor) sigue ahí como red de seguridad.

¿Puede mi agente de IA hacer todo lo que un usuario puede hacer en el panel?

Casi todo. Cada acción de escritura del panel tiene un comando (medido contra los endpoints de escritura del panel, el 100 % de los que entran en el alcance está cubierto), y los mismos comandos están abiertos a lg, a HTTP y al canal MCP. Las excepciones deliberadas son los pasos de identidad y de dinero: iniciar sesión, comprar un paquete, añadir una tarjeta con 3D Secure, aprobar textos legales y cerrar una cuenta se quedan en el navegador de la persona, y lg handoff-links generate devuelve el enlace para ellos. Las claves de integración, los webhooks y el sandbox se gestionan con comandos integrados de lg porque devuelven secretos una sola vez.

¿Puedo pasar archivos, como PDF o fotos, a los comandos desde la CLI o la API?

Sí. Con lg, pon @ delante de una ruta local (--attachment_uuid=@./cmr.pdf) y lg sube primero el archivo; --file es un atajo cuando un comando tiene un solo campo de archivo, y lg upload solo sube el archivo. Por HTTP, envía el archivo como multipart a POST /api/common/commands:upload y pasa el attachment_uuid devuelto al comando; los clientes MCP usan la herramienta upload_attachment. El tipo se detecta a partir del contenido, las subidas pertenecen al usuario y siguen siendo utilizables durante 24 horas, y volver a subir el mismo archivo devuelve el mismo id.

¿Qué diferencia hay entre una clave de integración employee y una manager?

El nivel de acceso decide lo delicado que puede ser el trabajo que hace una clave. Una clave employee, la predeterminada para claves nuevas, ejecuta la operativa diaria: cargas, solicitudes, ofertas, viajes, flota, documentos y contactos. Una clave manager puede hacer además trabajo de gestión: miembros del equipo y roles, configuración de la empresa, claves de integración, cuentas bancarias, tarjetas guardadas y pagos a afiliados. Una clave nunca tiene más derechos que la persona en cuyo nombre actúa, y una clave employee que llama a un comando de manager recibe INSUFFICIENT_KEY_LEVEL. Cambia el nivel en el panel o con lg keys update --access-level=manager.

¿Puede un afiliado (socio de referidos) usar la CLI?

Sí. Una cuenta de afiliado tiene un catálogo propio (55 comandos para referidos, leads, notas y tareas de CRM, presentaciones comerciales, ganancias y pagos) y lo usa desde lg, HTTP o MCP como cualquier otro rol. Solicitar o cancelar un pago y cambiar la cuenta bancaria de cobro requieren una clave de nivel manager y una confirmación. Una empresa que además es cargador, transportista o agente de aduanas ve ambos 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.