Logistivo CLI: conduceți operațiunile logistice din terminal și din ERP-ul dumneavoastră

Un singur catalog de comenzi alimentează trei interfețe: clientul de terminal lg, asistentul AI din panoul de control și canalul la care se conectează asistenții AI externi. O capabilitate se scrie o singură dată și apare în toate trei — iar acum fiecare acțiune de scriere din panou are o comandă, astfel încât scripturile și propriii dumneavoastră agenți AI pot face munca pe care echipa o face pe ecran. Plățile, consimțământul legal și închiderea contului rămân în mod deliberat în browser. Această pagină este un contract. Tabelul de comenzi de mai jos nu este scris de mână; este generat la fiecare cerere din registrul live de comenzi.

Ce este Logistivo CLI?

Logistivo CLI (lg) este un client de linie de comandă care execută munca din contul dumneavoastră Logistivo — încărcături și curse, cereri de transport, oferte și licitații, flotă și șoferi, documente, contacte și conturi curente, facturi și contabilitate, depozit și stoc, membri ai echipei și tarife vamale — dintr-un terminal, dintr-un script sau din propriul dumneavoastră agent AI. Comenzile nu sunt încorporate în client: clientul preia catalogul de comenzi de pe server și se construiește pe baza lui. Aceleași comenzi pot fi apelate prin HTTP fără a instala nimic — aceasta este calea folosită de integrările ERP și CRM — precum și prin canalul MCP la care se conectează asistenții AI externi.

Ce este live astăzi și ce urmează

Această secțiune se află intenționat în partea de sus a paginii. Cea mai costisitoare greșeală pe care o poate face un document de integrare este să descrie un endpoint care nu este încă deschis ca și cum ar fi: integratorul scrie codul, endpoint-ul returnează 404, iar încrederea nu se mai întoarce.

Deoarece contractul este înghețat, codul de integrare pe care îl scrieți astăzi continuă să funcționeze pe măsură ce restul pieselor se deschid. Câmpurile pe care nu le cunoașteți trebuie ignorate; această regulă ne permite să adăugăm fără a strica nimic.

Ce face diferența: clientul nu cunoaște nicio comandă, ci le extrage

Aceasta este singura decizie care dă sens restului paginii. Într-un CLI obișnuit, comenzile trăiesc în interiorul clientului; în Logistivo ele trăiesc pe server.

În aranjamentul clasic, când serverul capătă o capabilitate, clientul are nevoie de o versiune nouă, iar utilizatorul trebuie să o instaleze. Între timp, cele două părți știu lucruri diferite — și acesta este cel mai discret mod de eșec al integrărilor: scriptul rulează, codul de ieșire este 0, iar comanda pur și simplu nu descrie ceea ce poate face serverul.

În Logistivo, definiția unei comenzi este o singură înregistrare: numele ei, ce face, parametrii JSON Schema, ce roluri o pot vedea, dacă are nevoie de confirmare și dacă efectul ei este reversibil. Când lg pornește, preia acest catalog și generează din el textul de ajutor, flag-urile și validarea intrărilor. În momentul în care o comandă este adăugată pe server, lg o cunoaște — fără nicio actualizare a clientului.

Același catalog alimentează asistentul AI din panoul de control și canalul la care se conectează asistenții AI externi. O capabilitate se scrie o singură dată și apare în trei locuri — iar toate trei trec prin aceeași poartă de rol, aceeași poartă de confirmare și același registru de audit. Nu există un inventar separat de comenzi CLI; dacă ar exista, cele trei interfețe s-ar despărți pe nesimțite în șase luni.

Regula de denumire — cum cresc numele comenzilor

Catalogul va crește. Comenzile noi nu sunt denumite ad-hoc; se aplică șase reguli. Le publicăm aici pentru că acestea sunt numele pe care integrarea dumneavoastră le va scrie ani de zile, iar predictibilitatea lor are valoare pentru dumneavoastră.

Acoperire: tot ce este în panou, printr-o comandă

Multe echipe conectează Logistivo la propriul agent AI sau la propriul sistem de automatizare, iar un agent este util doar dacă poate duce treaba la capăt. Așa că regula este simplă: orice poate modifica un utilizator de birou în panou, poate modifica și o comandă — în spatele aceleiași porți de rol, aceleiași porți de confirmare și aceluiași registru de audit.

Acest lucru este măsurat, nu afirmat. Fiecare endpoint de scriere din spatele panoului este verificat față de registrul de comenzi, iar un endpoint se consideră acoperit doar atunci când o comandă îl declară drept oglinda sa HTTP sau apelează același serviciu partajat. Dintre endpoint-urile de scriere aflate în perimetru, 100% sunt acoperite și niciunul nu rămâne nedovedit. Fiecare endpoint lăsat deoparte are un motiv scris — pași de identitate și de plată, planul de control al integrării, endpoint-urile proprii ale aplicației pentru șoferi, telemetria clientului, copii moarte fără apelant — iar cele care contează pentru un integrator sunt listate mai jos.

Comenzile care primesc un fișier se supun aceleiași reguli: 33 de comenzi din tabel primesc un document sau o fotografie și toate funcționează din lg, prin HTTP și prin MCP (vedeți Comenzi care primesc fișiere).

Și munca de management este acoperită, în spatele unei chei mai puternice: 62 de comenzi din tabel — membri ai echipei și roluri, setările companiei, conturi bancare, carduri salvate, chei de integrare — necesită o cheie de integrare de nivel manager. O cheie de nivel employee, cea implicită, rulează operațiunile de zi cu zi și este refuzată pentru acestea cu INSUFFICIENT_KEY_LEVEL.

Conturile de afiliat (partener de recomandare) au un catalog propriu: 55 de comenzi în 20 domenii — recomandări, lead-uri, note și sarcini CRM, prezentări de vânzare, câștiguri și plăți, plus comenzile de cont și de echipă pe care le are fiecare rol. Comenzile exclusive afiliaților nu se află în tabelul public de mai jos, care listează cele trei roluri ale panoului; un cont de afiliat le vede în GET /api/common/commands și în lg commands. O companie care este atât afiliat, cât și expeditor, transportator sau comisionar vamal vede ambele seturi.

Un agent ar trebui să înceapă cu lg users get (nume API get_current_user): returnează rolul, nivelul de acces al cheii folosite, permisiunile efective și dacă o aprobare legală în așteptare sau un număr de telefon neverificat blochează în acest moment scrierile — astfel încât agentul să poată anticipa un refuz în loc să-l colecționeze.

Acoperirea este măsurată printr-un raport care compară registrul de comenzi cu endpoint-urile de scriere ale panoului; un endpoint de panou adăugat fără o comandă apare în acest raport ca nedovedit. Dacă găsiți o acțiune din panou pe care nu o puteți atinge printr-o comandă, raportați-o suportului.

Instalare și identitate

lg este un script Node într-un singur fișier, fără dependențe (Node 18+), publicat pe npm ca @lgstv/lg. Pachetul instalează două comenzi identice, lg și logistivo — folosiți logistivo dacă lg este deja un alias pe mașina dumneavoastră. Fiind un singur fișier, funcționează și copierea lui lg.mjs pe serverul ERP al unui client. Nu aveți nevoie de client pentru a construi o integrare ERP/CRM — aceleași comenzi sunt disponibile prin HTTP.

1. Instalare — Aveți nevoie doar de Node 18 sau mai nou; nu există alte dependențe. Instalați-l global sau rulați-l fără instalare prin npx.

2. Autentificare cu un cod de aprobare — lg login afișează un cod de aprobare scurt și deschide logistivo.com/connect în browser; prin SSH, în CI sau cu --no-browser doar afișează adresa, pe care o puteți deschide pe orice dispozitiv, inclusiv pe telefon. Autentificați-vă acolo ca de obicei, verificați că codul de pe pagină este același cu cel din terminal, alegeți accesul doar de citire sau de citire și acțiune și aprobați. În terminal nu se tastează nimic secret: nicio parolă, nicio cheie, niciun cod de unică folosință. Sesiunea rămâne apoi autentificată pe acel computer — închiderea terminalului nu vă deconectează, iar lg reînnoiește singur token-ul de acces înainte să expire. lg logout închide sesiunea și pe server, iar o puteți elimina și din panou, la Settings › Integration keys › AI and CLI connections (Setări › Chei de integrare › Conexiuni AI și CLI). lg login --password (e-mail și parolă) rămâne ca variantă de rezervă. Necesită lg 0.11 sau o versiune mai nouă.

3. Pentru servere și CI: o cheie, nu un cod de aprobare — Pe un server sau într-un job de CI nu există nimeni care să aprobe un cod în browser. Emiteți o cheie în panou (Settings › Integration keys, adică Setări › Chei de integrare) sau cu lg keys create — opțional în numele unui membru al echipei dedicat, cu drepturi restrânse — și puneți-o în LOGISTIVO_TOKEN, sau salvați-o cu lg login --api-key. Cât timp variabila este setată, fișierul de configurare nu este citit niciodată. Detalii: secțiunea „Conectarea cu o cheie API”.

4. Verificați și explorați catalogul — lg commands listează fiecare comandă pe care o poate vedea rolul dumneavoastră, grupată pe domenii; lg help afișează verbele acelui domeniu, iar lg help afișează lista completă de parametri ai unei comenzi, cu tipurile lor și cu cei obligatorii. Niciunul dintre aceste texte nu trăiește în client — toate vin din catalog. Rețineți că lista depinde de rolul dumneavoastră: verbele unui domeniu nu sunt aceleași pentru un transportator și pentru un expeditor (loads set-status, de exemplu, există doar în catalogul transportatorului). Dacă lipsește un verb, verificați-vă rolul înainte de a verifica instalarea.

Nu puneți niciodată cheia în sistemul de control al versiunilor, într-un log de CI sau într-o fereastră de chat. Dacă v-ați autentificat cu un cod de aprobare sau cu o cheie de integrare, lg logout închide accesul și pe server; într-o sesiune cu parolă (lg login --password) șterge doar copia LOCALĂ și spune acest lucru explicit. Registrul de audit stochează argumentele dumneavoastră, dar maschează ca *** orice câmp al cărui nume conține token, password, IBAN, card sau OTP, precum și fiecare parametru pe care o comandă îl declară secret.

Ergonomia clientului

Aceste funcționalități ușurează utilizarea de zi cu zi a clientului `lg`. Niciuna nu schimbă contractul HTTP — săriți peste această secțiune dacă vă integrați direct prin HTTP.

Conectarea cu o cheie API

Un ERP, un CRM, un script sau un agent AI se conectează la Logistivo cu o cheie de integrare emisă în panou sau cu lg keys create. Cheia acționează în numele unui membru real al echipei: rolul acelei persoane decide lista de comenzi, nivelul de acces al cheii decide cât de grea poate fi munca, iar când acea persoană pleacă din echipă, cheia dispare odată cu ea. Nu există un „cont de serviciu” sintetic și nici nu va exista — izolarea multi-tenant este legată de utilizator, iar un bridge fără proprietar este genul care continuă să scrie în tăcere ani de zile.

1. Emiteți o cheie în panou — Settings › Integration keys › New key. Dați-i un nume („Sincronizare nocturnă ERP”), alegeți scope-ul și nivelul de acces și, opțional, scurtați durata de viață. Cheia este afișată o singură dată, doar pe acel ecran; serverul nu o stochează. Dacă o pierdeți, Rotate emite una nouă — cea veche devine imediat invalidă.

2. Conectați-vă din terminal — lg login --api-key validează cheia, afișează identitatea și scope-ul și descarcă catalogul de comenzi. lg whoami întreabă serverul cu ce utilizator, companie, rol și scope rulați. lg logout revocă și cheia pe server, la fel cum închide acolo și o sesiune deschisă cu un cod de aprobare (doar lg logout dintr-o sesiune cu parolă nu poate face asta).

3. Sau HTTP simplu — Cheia este un token de acces transmis în antetul Authorization: Bearer și funcționează direct cu cele trei endpoint-uri de comenzi. Pe un server sau în CI, puneți-o în LOGISTIVO_TOKEN; cât timp această variabilă este setată, fișierul de configurare nu este citit niciodată.

4. Sau gestionați cheile din terminal: lg keys — lg keys este geamănul de terminal al ecranului din panou: aceleași endpoint-uri, aceeași poartă (permisiunea de gestionare a echipei și o cheie de nivel manager atunci când o apelați cu o cheie). create, rotate și transfer returnează o singură dată un token nou: cu --save-profile=NAME acesta ajunge direct într-un profil local și nu este afișat niciodată; altfel, lg îl afișează o singură dată. În modul --json, token-ul este inclus doar dacă adăugați --show-secret, iar un apel --json fără niciunul dintre aceste flag-uri este refuzat înainte ca cererea să fie trimisă, astfel încât token-ul nu poate ajunge pe nesimțite în output-ul unui script. rotate, transfer și revoke cer confirmare (--yes); o cheie este adresată prin numele ei exact sau prin uuid, niciodată prin ghicire. Nivelul de acces se transmite neschimbat unei chei rotite sau transferate.

Scope-ul este aplicat pe întreaga suprafață API, nu doar pe catalogul de comenzi: o cheie doar de citire trece de cererile de citire (GET/HEAD/OPTIONS), iar orice altă cerere de scriere răspunde cu 403 forbidden. Există două excepții deliberate și ambele sunt păzite de o poartă mai fină: endpoint-ul de execuție a comenzilor (decizia vine din flag-ul read_only propriu al comenzii, pentru că și comenzile care nu scriu sunt apelate cu POST) și o cheie care se revocă singură (lg logout, permis și pe o cheie doar de citire). Scope-ul, permisiunile pe domenii și nivelul de acces sunt trei axe independente și toate trebuie trecute: o cheie de citire nu poate scrie nici la nivel manager, o cheie de scriere employee nu poate gestiona echipa, iar o permisiune pe domeniul loads:write nu deschide munca de management. Emiteți cheia în numele membrului cu cele mai puține privilegii care vă acoperă nevoile: rolul decide ce înregistrări puteți vedea în primul rând.

Primele zece minute: de la panou la primul apel

Cele mai costisitoare minute ale unei integrări sunt primele zece. Cei patru pași de mai jos parcurg acest drum în ordine; aceeași ordine există în panou sub forma unui asistent de configurare (Settings › Integration › Setup wizard), iar asistentul apelează exact endpoint-urile descrise aici. Un dezvoltator fără acces la asistent ajunge în același loc citind această secțiune.

Niciunul dintre cei patru pași nu se încheie cu „l-am trimis”. Dacă cheia funcționează cu adevărat aflați din registrul de cereri; dacă webhook-ul ajunge cu adevărat aflați din registrul de livrări. Ambele sunt deschise în panou și în lg. O configurare care nu este măsurată niciodată este o configurare despre care doar se crede că funcționează.

Sandbox: exersați scrierea fără a atinge producția

Preflight-ul (dry_run) vă spune ce AR face o comandă; dar dacă idempotența chiar reia, dacă webhook-ul chiar este livrat, cum arată ramurile de eroare și de unde provine creditul se poate vedea doar la o scriere REALĂ. La asta folosește sandbox-ul: un tenant separat care oglindește compania dumneavoastră — același cod, același catalog de comenzi — și care nu atinge niciodată registrul live.

Sandbox-ul nu este un flag al cererii, este un TENANT. O companie Logistivo separată, deschisă pentru a dumneavoastră: propriul utilizator, propriul pachet, propriul registru de credite, propriile abonamente webhook. Mediul este DEDUS din compania cheii și nu poate fi furnizat de apelant, așa că, chiar dacă alegeți profilul greșit, cererea se oprește pe server cu 403 ENVIRONMENT_MISMATCH. „Credeam că scriu în sandbox și am scris în producție” este imposibil prin structură.

Suprafața este identică — măsurat: catalogul pe care îl obține o cheie sandbox este același cu cel pe care îl obține cheia live (pe o cheie de expeditor, 448 de comenzi de ambele părți). Un bridge care funcționează în sandbox funcționează și în producție; nimic din ce învățați nu trebuie învățat de două ori.

Sandbox-ul nu se deschide gol: conține date inițiale generate (NU copiate) — contacte, încărcături în ambele direcții și cereri de transport. Numele sunt în mod vădit inventate, iar adresele de e-mail se află în domeniul @sandbox.invalid; niciun rând al vreunui tenant real nu este citit, clonat sau folosit vreodată ca șablon.

În mod deliberat NU există un buton de resetare și nicio înregistrare, niciun fișier și niciun document nu este șters vreodată. Dacă doriți un nou început curat, reînnoirea deschide o generație nouă, iar cea veche rămâne, retrasă. Câteva sute de rânduri de repetiție acumulate nu costă nimic pe lângă o rutină de ștergere legată de tenant-ul greșit.

Modelul de comandă

Există o singură gramatică și nu are excepții. Pentru a ști cum să apelați o comandă, tot ce vă trebuie este ce spune catalogul despre ea.

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

Numele flag-urilor sunt numele parametrilor din schemă, iar lg acceptă ambele grafii: --load_code și --load-code ajung în același câmp (lg help afișează forma kebab-case). Văzând un flag, știți deci în ce câmp JSON ajunge, iar trecerea de la CLI la HTTP nu necesită nicio traducere de nume. Un parametru boolean scris fără valoare este true (--only_open); opusul este --no-only_open. Scrieți un argument a cărui valoare începe cu o liniuță sub forma --flag=value, altfel un flag uitat înghite în tăcere următorul argument ca valoare. Pe o comandă care primește un fișier, puneți @ în fața unei căi locale (--attachment_uuid=@./cmr.pdf) — vedeți Comenzi care primesc fișiere.

Exemple reale

Fiecare comandă de mai jos există astăzi în catalog; niciun nume și niciun parametru de aici nu este inventat. Output-urile sunt prescurtate. Output-urile arată nivelul `data.result`; prin HTTP, același obiect sosește în interiorul plicului `{success, data:{…}}` (vedeți Command API prin HTTP).

Fără client: Command API prin HTTP

lg este o comoditate, nu ușa de intrare. Atât catalogul, cât și execuția trec prin trei endpoint-uri HTTP (fișierele au un al patrulea, endpoint-ul de încărcare — vedeți Comenzi care primesc fișiere); o integrare ERP sau CRM se conectează direct aici. Identitatea este un token de acces personal transmis în Authorization: Bearer. FIECARE răspuns vine în același plic: `{ "success": bool, "data": { … }, "code": "…", "message": { … } }`. Întregul contract se află în `data` — `contract_version`, `command`, `status` și, ca output al comenzii, `result`; la eșec `error_code`, `error_key` și `error`. Exemplele de răspuns de pe această pagină arată obiectul `data` în locul plicului, pentru a economisi spațiu; nu uitați să despachetați `data` în propriul cod. `lg --json` afișează și el ÎNTREGUL plic, de aceea căile jq încep cu `.data.result…`. Singura excepție este `/api/public/status`: acest endpoint nu are plic, astfel încât scripturile de supraveghere pot trimite alerte fără să-l parseze.

Cererea de confirmare prin 200 + ok:false este interzisă și nu se va întâmpla niciodată. Motivul este simplu: clienții tratează 200 drept succes, așa că o cerere de confirmare returnată ca 200 ar opri fluxul în tăcere și nimeni nu ar observa. Confirmarea este întotdeauna 409.

Comenzi care primesc fișiere

33 de comenzi din tabel primesc un document sau o fotografie: atașarea unui CMR la o încărcătură, citirea codurilor HS dintr-o factură, crearea unui vehicul din certificatul său de înmatriculare, estimarea unui palet din fotografii, încărcarea unui document al companiei, verificarea încrucișată a unei facturi cu o listă de colisaj. În browser trageți fișierul într-un formular; oriunde altundeva sunt necesari doi pași — încărcați fișierul, apoi transmiteți id-ul lui comenzii.

Un parametru primește un fișier atunci când numele lui se termină în attachment_uuid sau attachment_uuids — photos[].attachment_uuid în interiorul unei liste, front_attachment_uuid pe un cec. Nu trebuie să rețineți asta: fiecare rând din catalog poartă file_params cu căile exacte, schema marchează acele câmpuri (format: uuid, x-logistivo-file: true), iar răspunsul catalogului conține un bloc attachments cu adresa de încărcare, tipurile acceptate, limitele de dimensiune și durata de valabilitate. lg help le afișează și el.

Încărcarea este o scriere, așa că o cheie cu scope de citire nu poate încărca; orice cheie cu scope de scriere poate, la oricare nivel de acces. Încărcările aparțin utilizatorului, nu cheii: două chei ale aceluiași utilizator văd aceleași încărcări și nimeni nu le poate folosi pe ale altcuiva. Un id necunoscut, expirat sau care nu vă aparține pur și simplu nu este găsit, iar motivul nu este dezvăluit.

Atașamentele din chatul panoului de control și încărcările pentru comenzi sunt spații separate: un fișier atașat în chatul asistentului nu poate fi transmis unei comenzi prin id, iar o încărcare pentru comenzi nu apare niciodată într-un fir de chat.

O încărcare nu este o comandă și nu are de una singură niciun efect de business: datelor dumneavoastră nu li se întâmplă nimic până când o comandă nu folosește id-ul. Tot de aceea încărcarea nu cere niciodată confirmare — comanda care consumă fișierul poartă poarta.

Poarta de confirmare și comenzile ireversibile

Fiecare comandă din catalog poartă două flag-uri: confirmation_needed (necesită aprobare explicită înainte de rulare) și irreversible (efectul ei poate fi anulat sau nu). Sunt întrebări separate și ambele sunt aplicate pe server, nu lăsate la bunele maniere ale clientului.

Poarta ține de responsabilitate, nu de regulile de business. Plasarea unei oferte este primul pas spre un contract de transport; emiterea unei facturi creează o înregistrare contabilă; o mișcare de stoc modifică un registru. Niciuna dintre acestea nu ar trebui să fie vreodată ceva ce puteți rula de două ori din greșeală.

Poarta se citește din comanda EFECTIVĂ. Când o comandă o înglobează pe alta — așa cum face executorul generic din chat — poarta provine din flag-urile comenzii care va rula efectiv, nu din cea care o înglobează. Cu un flag fix, unul dintre două eșecuri ar fi inevitabil: fie comanda de facturare ar rula neconfirmată, fie fiecare căutare de țară ar afișa un card de confirmare.

read_only și fără poartă de confirmare nu sunt același lucru. Există trei stări distincte: invoices preview nu e supusă confirmării și nu scrie nimic; loads create e supusă confirmării și scrie; invoices create nu e supusă confirmării, dar scrie — ia naștere o ciornă. Un singur test lămurește: când comanda se întoarce, s-a schimbat vreun rând în baza de date?

Unele acțiuni nu se află în spatele acestei porți pentru că nu sunt deloc comenzi: cumpărarea unui pachet, adăugarea unui card cu 3D Secure, aprobarea textelor legale și închiderea unui cont au loc în browserul propriu al persoanei. Pentru acestea, lg handoff-links generate returnează un link logistivo.com — gratuit, fără să scrie nimic, fără să poarte vreun secret — iar persoana finalizează pasul acolo. Niciun flag, oricum ar fi setat, nu transformă o plată sau un consimțământ într-un apel API.

Tehnic, este posibil ca o integrare să treacă automat de poartă la fiecare apel, dar nu este recomandabil. Modelul corect este să automatizați poarta doar pentru comenzile despre care știți că sunt reversibile și să direcționați comenzile ireversibile spre coada unui om.

Preflight: vedeți ce ar face o comandă de scriere, fără să o rulați

Cel mai costisitor pas în conectarea unui bridge ERP este să aflați dacă un corp de cerere este corect rulând EFECTIV comanda: o ciornă de factură în registrul live, un credit consumat, curățenie manuală. dry_run pune capăt acestui lucru — adăugați un singur câmp la cerere, comanda nu rulează, iar dumneavoastră citiți ce s-ar întâmpla.

Registrul conține astăzi 519 de comenzi; 233 dintre ele doar citesc și nu scriu nimic. Dintre cele 286 de comenzi de scriere rămase, 251 sunt supuse confirmării: apelate fără confirm, ele răspund deja cu 409 și descriu ce ar face. Golul era la celelalte 35 — ciorne de factură, ciorne de documente de export, linii de articole: exact lanțul pe care un bridge îl conectează primul. Pentru acestea, a apela însemna a executa.

Exista un al doilea gol chiar și la comenzile supuse confirmării: validarea schemei rulează DUPĂ poartă. Astfel, un apel cu un argument lipsă primea mai întâi cardul de confirmare, îl confirma și abia apoi afla „acest câmp lipsește”. Un preflight răspunde la ambele într-un singur dus-întors.

Iar cel mai frecvent — și cel mai târziu descoperit — bug de bridge este acesta: trimiteți un câmp care nu se află în schemă (customer_id acolo unde se așteaptă recipient_contact_id), serverul îl elimină în tăcere, comanda returnează 200, iar înregistrarea ajunge în locul greșit. Răspunsul preflight listează aceste câmpuri după nume sub arguments.ignored; niciun mesaj de eroare nu o va face vreodată.

Un preflight nu scrie nimic, iar acest lucru ține de structură înainte de a fi o măsurătoare: calea de preflight nu intră niciodată în handler-ul comenzii, deci regula este valabilă pentru orice comandă, inclusiv pentru cele adăugate ulterior. A fost și măsurat la lansare: fiecare instrucțiune SQL emisă în timpul preflight-ului tuturor comenzilor de scriere existente atunci în catalog (două seturi de argumente, două roluri, 178 de preflight-uri) a fost capturată. Niciun INSERT/UPDATE/DELETE, niciun job pus în coadă, niciun e-mail; numărul de rânduri din 31 de tabele și soldul de credit au rămas neschimbate.

Codul de ieșire este același pe care l-ar produce apelul real — comanda ar rula: 0, poarta de confirmare 4, conflict de idempotență 2, orice altceva 1 — așa că „lg … --dry-run || exit” este o poartă cu sens într-un pas de CI.

Idempotență: de ce reîncercarea nu creează duplicate

Un ERP nu vă citește mesajul de eroare, nu vă vede ecranul și va retrimite aceeași cerere după un timeout. Această secțiune există exact pentru acest comportament.

Un apel care poartă un idempotency_key (cheie de idempotență) rulează cel mult o dată. Al doilea apel nu reexecută comanda; returnează rezultatul înregistrat cu statusul replayed. Când rețeaua cade, când o cerere expiră sau când o coadă preia același job de două ori, primiți primul rezultat în locul unei facturi duplicate.

Folosiți o cheie la fiecare comandă de scriere. Câmpul read_only din catalog vă oferă gratuit această distincție: fiecare comandă cu read_only: false merită o cheie de idempotență.

Strategia de reîncercare ar trebui să folosească backoff exponențial și să poarte ACEEAȘI cheie la fiecare încercare. A genera o cheie nouă la fiecare încercare este totuna cu a nu folosi deloc idempotența. Nu trimiteți aceeași cheie în paralel: execuția cel mult o dată este garantată pentru reîncercări secvențiale, nu pentru două cereri care se întrec una cu cealaltă.

Sincronizare incrementală și paginare

Un bridge ERP/CRM ar trebui să preia ce s-a schimbat, nu totul în fiecare oră. Fiecare comandă de listare — loads list, demands list-own, contacts list, invoices list, teammates list, load-groups list, reservations list, drivers list, tenders list, products search, warehouses search — acceptă doi parametri comuni și returnează un câmp comun.

De ce nu există offset: pe un tabel live, un offset alunecă. Un rând inserat între două pagini vă face să săriți peste o înregistrare sau să o numărați de două ori, în tăcere. Cursorul merge doar înainte, așa că o înregistrare inserată sau actualizată în timpul parcurgerii ajunge la sfârșitul listei: nu se pierde nimic, nu se dublează nimic.

O încărcare inițială completă folosește același mecanism: porniți cu updated_since=1970-01-01T00:00:00Z și urmați next_cursor până când este null. Nu există un endpoint separat de „export”, pentru că nu este nevoie de unul.

Același lucru cu lg: lg loads list --updated_since=2026-09-01T00:00:00Z --limit=100 --json, apoi lg loads list --cursor= --json.

Pentru următoarea sincronizare, setați updated_since la cel mai mare updated_at pe care l-ați văzut. Timestamp-urile au precizie de o secundă, așa că o înregistrare aflată la limită poate sosi de două ori — inofensiv, atâta timp cât pe partea dumneavoastră faceți upsert după external_ref sau după cod. Fără updated_since sau cursor, o comandă rulează în modul chat: ordonare pentru oameni, cel mult 20 de rânduri, next_cursor null și truncated care spune „mai există”. Nu folosiți acest mod pentru sincronizare.

Cheile de eroare lizibile automat pentru această secțiune: INVALID_CURSOR, INVALID_UPDATED_SINCE, DUPLICATE_EXTERNAL_REF, EXTERNAL_REF_TOO_LONG, EXTERNAL_REF_INVALID. Toate răspund cu HTTP 422, cu error_code: "failed" și result.error_key în corp. Ramificați după cheie, niciodată după text.

Propria dumneavoastră cheie: external_ref

Nu faceți din id-urile noastre auto-increment (invoice_id, contact_id) cheia de business din ERP-ul dumneavoastră; ele sunt opace și private pentru compania dumneavoastră. Încărcăturile, cererile de transport, contactele, facturile, produsele și depozitele poartă în schimb cheia dumneavoastră.

Webhook-uri de ieșire: vă trimitem un POST când se întâmplă ceva

Tot ce a fost până aici era pull: dumneavoastră întrebați, noi răspundem. Un bridge ERP trebuia să întrebe o dată pe minut ca să afle că o încărcătură și-a schimbat statusul, că a fost emisă o factură sau că a sosit o ofertă. Nu mai este cazul: înregistrați o adresă https, alegeți evenimentele care vă interesează, iar un POST semnat sosește atunci când acestea au loc.

Push-ul a fost adăugat LÂNGĂ pull, nu în locul lui. Sincronizarea incrementală (updated_since + cursor) rămâne neatinsă și este în continuare instrumentul potrivit: prima încărcare completă, completarea istoricului și răspunsul la întrebarea „am ratat ceva” țin toate de ea. Webhook-urile reduc latența de la minute la secunde. Un bridge sănătos le folosește pe amândouă — webhook-ul ca declanșator, sincronizarea ca plasă de siguranță.

Payload-ul este un SEMNAL, nu o copie a înregistrării. Poartă identificatorii (id, propriul dumneavoastră external_ref, statusul, momentul); detaliile le preluați cu propria cheie prin GET, iar acel apel trece deja prin propriile dumneavoastră porți de autorizare. Suprafața de expunere rămâne astfel la nivel de identificator. Două câmpuri lipsesc în mod deliberat: identitatea companiei care a plasat o ofertă (ofertele oarbe apar sub un alias în panou; a pune identitatea într-un webhook ar străpunge această confidențialitate printr-un al doilea canal) și datele de contact ale rezervării (nu este nevoie să mutăm date personale printr-un canal suplimentar).

Numele evenimentelor sunt ÎNGHEȚATE. Dacă sensul unui nume se schimbă, se deschide un nume nou, iar cel vechi trăiește pe durata unei ferestre de depreciere. Adăugarea unui câmp nou în payload NU crește versiunea contractului — un consumator trebuie să ignore câmpurile pe care nu le cunoaște.

Catalogul de evenimente

Fiecare dintre cele 13 evenimente de mai jos este susținut de o clasă de eveniment care este emisă EFECTIV. În catalog nu există nume de tip „ar fi frumos”: un eveniment anunțat, dar niciodată declanșat, este un eveniment pe care integratorul îl așteaptă la nesfârșit, iar acest eșec este tăcut. Nici acest tabel nu este scris de mână — este construit din catalogul live la fiecare încărcare a paginii.

invoice.payment_recorded LIPSEȘTE în mod deliberat: această clasă de eveniment există în cod, dar nu este emisă de nicăieri. Evenimentul de plată va fi adăugat în ziua în care clasa este declanșată cu adevărat — nu mai devreme. A anunța prematur un eveniment este mai rău decât a nu-l adăuga niciodată.

Verificarea semnăturii

Fiecare cerere poartă un antet X-Logistivo-Signature: t=,v1=. Șirul semnat este ".", iar algoritmul este HMAC-SHA256. Prefixul v1 păstrează schema versionabilă: dacă va apărea vreodată o a doua schemă, antetul va purta t=…,v1=…,v2=…, iar dumneavoastră o alegeți pe cea pe care o cunoașteți.

Publicăm două receptoare de referință fără dependențe, unul pentru Node și unul pentru PHP simplu. Amândouă aplică cele patru reguli de mai sus și se testează singure cu --selftest: semnătură validă, corp alterat, secret greșit, timestamp în afara ferestrei, antet malformat, capcana reserializării și ambele tipuri de repetiție (aceeași livrare / același fapt). Verificatorul este perechea exactă a codului pe care îl folosim noi înșine — consumăm contractul pe care îl publicăm.

Livrare, reîncercări și dezactivare

Receptorul dumneavoastră nu va fi întotdeauna disponibil. Mecanismul pornește de la această premisă: un eșec tranzitoriu intră în programul de reîncercări, unul permanent dezactivează abonamentul — iar MOTIVUL dezactivării este întotdeauna înregistrat.

Motivul dezactivării este lizibil automat și stabil (delivery_failures, endpoint_gone, unsafe_target, key_revoked, by_user) și apare atât în panou, cât și în `lg webhooks list`. O dezactivare fără motiv ar lăsa fără răspuns întrebarea „de ce nu sosește nimic”. Reactivarea resetează contorul și marcajul.

Dumneavoastră scrieți adresa; serverul NOSTRU face cererea. Fără o poartă, un tenant care ar introduce o adresă de metadate cloud ne-ar putea face să emitem cereri din interiorul propriei noastre rețele. De aceea, adresa este verificată atât la înregistrare, cât și la FIECARE livrare:

Sănătate și status: nimeni nu ar trebui să observe un bridge tăcut înaintea dumneavoastră

Un bridge de integrare moare rareori zgomotos. De obicei amuțește în orele mici ale nopții: un token expiră, persoana în numele căreia rulează cheia pleacă din echipă, un scope este restrâns sau endpoint-ul receptor începe să returneze 403. Primul care observă este aproape întotdeauna clientul. lg status există pentru a inversa această ordine.

O singură comandă răspunde la două întrebări deodată, pentru că în timpul unei întreruperi ambele sunt puse în același moment: funcționează platforma și este sănătos bridge-ul meu? Linia platformei funcționează fără token — în timpul unei defecțiuni, token-ul însuși este primul suspect, iar legarea întrebării despre status de el v-ar lăsa fără răspuns exact atunci când aveți nevoie de unul.

Rezumatul de sănătate nu ține niciun registru nou; el adună trei surse existente într-o singură propoziție: contorul zilnic de utilizare (cât ați apelat și cât a eșuat), registrul de cereri (ce cerere, când și de ce a fost respinsă) și registrul de livrare a webhook-urilor de ieșire (a ajuns evenimentul la receptor). Rata de eroare se citește întotdeauna din contorul zilnic; registrul de detalii este plafonat și spune clar când plafonul a fost atins.

Același rezumat apare și în panou: la Settings › Integration › Keys, fiecare rând poartă un badge și, sub el, motivul. Pragurile nu sunt calculate pe partea clientului; nivelul atribuit de server este cel pe care îl afișează atât panoul, cât și lg status, astfel încât cele două ecrane nu pot spune niciodată lucruri diferite despre aceeași cheie.

Scriptul dumneavoastră de supraveghere nu are nevoie de token. Endpoint-ul returnează doar cuvinte de status; nu conține versiune, nume de gazdă, nume de coadă, număr de job-uri, text de eroare sau date ale clienților — și nici nu poate conține.

Rezumatul de sănătate este produs cu un număr constant de interogări per cerere; numărul de interogări nu crește odată cu numărul de chei. Pentru detalii, citiți cererile recente ale acelei chei cu lg logs, iar bridge-ul de ieșire cu lg webhooks list.

Erori și coduri de ieșire

Ceea ce folosește o mașină pentru ramificare este un șir stabil. Textul din câmpul error se adresează unui om, este localizat și se schimbă fără preaviz — o integrare care se ramifică după textul mesajului nu este suportată, iar faptul că se strică nu este un bug.

Două taxonomii, două câmpuri. error_code este nivelul de transport (tabelul de mai sus) și este primul după care ramificați. error_key este cheia stabilă a regulii de business din interiorul lui failed — INSUFFICIENT_CREDITS, NOT_FOUND, DUPLICATE_CONTACT, INSUFFICIENT_STOCK — publicată ca dicționar în catalog (error_keys) și mai jos. O comandă care nu emite încă o cheie returnează error_key: null; tratați o cheie care lipsește din dicționar ca pe un eșec generic.

Promisiunea de stabilitate

Cea mai costisitoare întrebare dintr-un contract de integrare este „codul pe care îl scriu astăzi va mai rula și mâine?”. Un singur număr îi răspunde: contract_version din catalog și antetul X-Contract-Version de pe fiecare răspuns. El răspunde la exact o întrebare — codul scris anterior rulează în continuare neschimbat? Dacă numărul nu a crescut, da. Dacă a crescut, nu: ceva pe care vă bazați a fost eliminat sau restrâns.

Pentru că un anunț nu elimină nimic: codul existent rulează exact ca înainte, și în ziua de după. Creșterea versiunii la anunț ar emite două semnale pentru o singură schimbare și v-ar obișnui să-l ignorați pe primul — slăbind semnalul care contează cu adevărat. Anunțul călătorește deja odată cu apelul pe care îl faceți: deprecated_at și replaced_by pe rândul din catalog, un antet X-Deprecated plus un bloc deprecated în răspunsul acelei comenzi și un avertisment de o linie pe stderr dacă folosiți lg. Nu trebuie să faceți nimic ca să aflați că ceva va dispărea; este deja în logul dumneavoastră.

Între anunț și eliminare trec cel puțin două trimestre calendaristice. Pe toată durata ferestrei, comanda veche răspunde în continuare și avertizează la fiecare apel. Când un nume CLI este retras, succesorul său preia acel nume ca alias: pe durata ferestrei, numele vechi ajunge la comanda veche, iar în ziua eliminării același apel ajunge la cea nouă — fără ca dumneavoastră să livrați o versiune nouă a clientului.

preview_invoice → get_invoice (anunțat pe 3 septembrie 2026). get_invoice returnează același bloc de previzualizare și adaugă liniile, defalcarea taxelor, plățile înregistrate, soldul rămas și adresarea după propria cheie ERP (external_ref). preview_invoice continuă să funcționeze, la fel și lg invoices preview — ambele avertizează.

Tabelul de identitate al comenzilor

Tabelul de mai jos nu este scris de mână: este generat din registrul live de comenzi de fiecare dată când această pagină este servită. O comandă adăugată pe server apare aici de la sine. Coloana „Poartă” vă spune dacă o comandă cere confirmare și dacă efectul ei poate fi anulat; coloana „Roluri” vă spune ce tipuri de cont o vor găsi în catalogul lor. Badge-urile marchează comenzile care necesită o cheie de nivel manager, pe cele care primesc un fișier și pe cele care rulează doar din lg.

Numele API (list_loads) este înghețat și nu se va schimba; este numele pe care îl folosiți în apelurile HTTP. Identitatea CLI (loads list) este guvernată de politica de versionare: odată anunțată, se schimbă doar cu o creștere de versiune și o fereastră de migrare, în care catalogul servește perechea veche ca alias. Descrierile de aici provin din înregistrarea proprie a fiecărei comenzi și sunt prescurtate — pentru textul complet, tipurile parametrilor și valorile enum, citiți endpoint-ul de catalog sau rulați lg help . Comenzile exclusive afiliaților nu sunt listate aici; un cont de afiliat le vede în propriul catalog.

Integrare ERP și CRM — Identitate: fără conturi de serviciu, în schimb un utilizator real

Un ERP sau un CRM se conectează la Logistivo ca un utilizator real al companiei, care deține un token de acces personal emis din panou. Un cont de serviciu sintetic — fără companie, scutit de delimitare, fără nimeni responsabil de el — nu este creat niciodată.

Aceasta este arhitectură, nu preferință. Izolarea multi-tenant se sprijină în întregime pe compania utilizatorului; pentru o identitate fără companie, domeniul de izolare devine inert, iar o singură interogare greșită pe acel token citește date care nu îi aparțin. Al doilea motiv este auditul: coloana de utilizator din registru leagă „cine a rulat asta” de o persoană; un cont de serviciu golește acea coloană de sens și lasă o integrare care emite facturi fără proprietar. Al treilea este revocarea: dacă un token aparține unei persoane, integrarea amuțește când acea persoană pleacă. Un bridge fără proprietar este bridge-ul care continuă să scrie discret ani de zile.

Integrare ERP și CRM — Versionare și depreciere

Răspunsul catalogului poartă un întreg contract_version; rândurile comenzilor poartă since, deprecated_at și replaced_by. Versiunea contractului crește doar la modificări care rup compatibilitatea.

Integrare ERP și CRM — Identificatori stabili: nu dați niciodată ERP-ului un id auto-increment drept cheie

Id-urile interne auto-increment sunt opace și delimitate per tenant: id-ul valid al altei companii vă returnează „nu a fost găsit”, nu date. Dar faptul că sunt opace nu le face chei de business. Ceea ce stochează un ERP în propria înregistrare trebuie să fie o cheie de business.

Integrare ERP și CRM — Paginare, filtrare și sincronizare incrementală

Comenzile de listare primesc limit și returnează count. Pentru o sincronizare nocturnă, contractul se angajează la următoarele; o comandă de listare care le-a adoptat declară cursor și updated_since în JSON Schema sa, iar până atunci tratați-o ca pe o fereastră mică (plafon de 20 de rânduri, fără cursor).

Integrare ERP și CRM — Limite de rată

Cheia limitei este token-ul, nu IP-ul: un ERP vine din spatele unui singur NAT, iar o limită bazată pe IP ar număra o întreagă companie ca un singur utilizator. Două integrări cu două chei nu își consumă niciodată una alteia cota — al doilea motiv pentru „o integrare per cheie”.

Integrare ERP și CRM — Lucruri pe care nu le vom face

Aceasta este adesea cea mai utilă parte a unui contract: a ști ce nu va sosi niciodată vă permite să proiectați ținând cont de asta.

Catalogul lizibil automat

Această pagină a fost scrisă pentru oameni. Pentru un asistent AI sau un client automat, aceleași informații există ca o copie structurată, fără autentificare.

GET /api/public/cli/catalog nu necesită autentificare și returnează identitatea publică a comenzilor: numele API înghețat, identitatea CLI domeniu/verb, descrierea, parametrii JSON Schema, flag-urile read_only, confirmation_needed și irreversible, credit_cost și câmpurile de acces key_level, permission, file_params, secret_parameters și cli_only. Nu returnează date personale, date ale tenant-ilor sau înregistrări de exemplu — doar ce este suprafața.

Modul corect în care un agent extern îl poate folosi: citește catalogul, explică în PROPRIILE cuvinte ce operațiuni sunt posibile și, când este nevoie de execuție, îl îndrumă pe utilizator să o ruleze în propriul mediu, cu propriul token. Agentul nu cere, nu generează și nu transmite niciodată token-ul unui utilizator.

Conectarea Logistivo la un asistent AI pentru înscriere și cereri de transport este un canal separat, cu propria pagină:

Definiția lizibilă automat: OpenAPI 3.1 și Postman

Catalogul de mai sus este formatul propriu al Logistivo. Aceleași informații sunt publicate și în două formate standard din industrie pe care instrumentele dumneavoastră le înțeleg deja: o definiție OpenAPI 3.1 și o colecție Postman. Niciuna nu este scrisă de mână — ambele sunt generate din registrul live de comenzi la fiecare cerere, deci nu se pot învechi.

Definiția ESTE catalogul, nu o a doua realitate. Fiecare comandă apare ca o operațiune POST /api/common/commands/; schema corpului este propria JSON Schema a comenzii (octet cu octet ce vede asistentul), schema răspunsului este plicul comun, flag-urile de poartă (read_only, confirmation_needed, irreversible și badge-ul de credit) stau în extensia x-logistivo, iar cheile de eroare de business stau sub x-error-keys. O comandă retrasă este marcată deprecated: true și rămâne apelabilă pe durata ferestrei sale de șase luni.

Pe lângă suprafața de comenzi, în definiție se află și endpoint-urile REST care poartă contractul de integrare: 10 endpoint-uri de listare cu sincronizare incrementală (updated_since + cursor + limit) și 7 endpoint-uri de scriere care acceptă propria dumneavoastră cheie (external_ref). Suprafața REST a Logistivo este mult mai mare de atât; restul este lăsat deoparte INTENȚIONAT. Acele payload-uri sunt contractul propriu al produsului, iar o schemă scrisă din presupuneri v-ar strica clientul generat la primul apel — și ați căuta bug-ul în codul dumneavoastră, nu în al nostru.

Definiția este generată pentru ROLUL celui care o cere: comanda unui alt rol nu intră niciodată în document, pentru că o definiție este un contract, nu un dicționar, iar tot ce conține trebuie să fie apelabil cu acel token. Versiunea fără autentificare este reuniunea celor trei roluri și, acolo, fiecare operațiune indică sub x-logistivo.roles ce roluri o văd.

Nu ne credeți pe cuvânt — verificați: npx @redocly/cli lint logistivo-openapi.json trece cu zero avertismente.

Generare de cod: un client tipizat din definiție

Aveți definiția; acum generați din ea cod funcțional. `lg codegen` citește documentul OpenAPI și scrie un client într-un singur fișier, fără dependențe — TypeScript (declarații de tipuri plus un wrapper subțire de apel) sau PHP (o clasă client cu forme de array PHPDoc). Fără `npm install`, fără composer: `fetch` pe partea TypeScript, `ext-curl` pe partea PHP.

Generarea are loc pe mașina DUMNEAVOASTRĂ, nu pe serverul nostru. Nu este o preferință: fixați definiția în repository-ul dumneavoastră o singură dată (`lg spec --out=...`), iar aceeași comandă rulează la fiecare build fără rețea și fără token, astfel încât CI-ul dumneavoastră nu depinde niciodată de disponibilitatea noastră. Și cum codul generat este cod care RULEAZĂ în ERP-ul dumneavoastră, păstrarea lui în repository în loc de a-l prelua dintr-un API înseamnă că fiecare schimbare apare în `git diff`.

Clientul generat tratează corect toate cele trei căi Logistivo și află care este care din document, nu prin ghicit: fiecare operațiune își declară calea în `x-logistivo.surface`. Calea de comenzi despachetează plicul `{success, data:{…}}`; o listă REST de sincronizare NU are plic și este returnată ca atare (despachetarea ar pierde `next_cursor`); o scriere REST returnează corpul propriu al endpoint-ului din produs. Un generator de uz general nu cunoaște această distincție — al nostru o cunoaște.

Tipurile nu sunt presupuneri: interfața de argumente a fiecărei comenzi este generată chiar din JSON Schema pe care o vede asistentul. Ce este obligatoriu rămâne obligatoriu, enum-urile devin tipuri union, obiectele imbricate devin tipuri imbricate. În practică: dacă serverul cere un câmp, apelul care îl uită nu eșuează cu un 422 în producție — eșuează la compilare.

În fișier nu se scrie niciun timestamp de generare: aceeași definiție cu aceleași flag-uri produce aceiași octeți. Prin urmare, regenerarea nu face zgomot în `git diff`, iar singurul lucru care s-a schimbat cu adevărat — contractul — rămâne vizibil.

Perimetru onest

Am prefera să extindem această secțiune decât să o restrângem. Valoarea unui contract de integrare stă mai puțin în ceea ce promite și mai mult în cât de clar spune ce nu face.

Întrebări frecvente

La ce folosește Logistivo CLI?

Execută munca din contul dumneavoastră Logistivo dintr-un terminal, dintr-un script sau din propriul agent AI: încărcături și curse, cereri de transport, oferte și licitații, flotă, șoferi și cheltuielile lor, documente, contacte și conturi curente, facturi și contabilitate, depozit și stoc, membri ai echipei, tarife HS și taxe antidumping. Fiecare acțiune de scriere din panou are o comandă; plățile, consimțământul legal și închiderea contului rămân în mod deliberat în browser. Aceleași comenzi pot fi apelate prin HTTP pentru integrări ERP/CRM și prin canalul MCP.

Cum sunt denumite comenzile în Logistivo CLI?

Forma este lg --parameter=value. Domeniul este o entitate la plural (loads, demands, invoices, export-documents), iar verbul provine dintr-un vocabular închis care crește doar prin decizie deliberată (list, get, create, update, search, set-status, generate și așa mai departe). Toate verbele unei entități stau într-un singur domeniu, iar perechea (domeniu, verb) este unică în întregul catalog.

Cum mă autentific în CLI?

Interactiv, lg login afișează un cod de aprobare pe care îl confirmați în browser după ce vă autentificați în Logistivo — în terminal nu se tastează nicio parolă și nicio cheie, iar sesiunea rămâne autentificată și se reînnoiește singură până când rulați lg logout. lg login --password (e-mail și parolă) rămâne ca variantă de rezervă. Pe servere și în CI, folosiți o cheie de integrare emisă în panou sau cu lg keys create: lg login --api-key o salvează într-un profil local, sau o puteți pune în variabila de mediu LOGISTIVO_TOKEN. Prin HTTP, token-ul călătorește în antetul Authorization: Bearer.

Pot crea un cont de serviciu pentru integrarea ERP?

Nu. O integrare se conectează cu token-ul unui utilizator real al companiei, creat în panou. O identitate sintetică fără companie face inertă izolarea multi-tenant, golește de sens coloana „cine a rulat asta” din registrul de audit și lasă în urmă un bridge care nu amuțește atunci când proprietarul său pleacă. Modelul corect este un utilizator dedicat, cu un rol restrâns și un proprietar numit.

Are Logistivo un mediu de test (sandbox)?

Da, și este self-service: pentru compania dumneavoastră se deschide un tenant sandbox separat — același cod, același catalog de comenzi, propriul registru de credite. Mediul este dedus din compania cheii și nu poate fi transmis odată cu cererea; un apel făcut cu profilul greșit se oprește pe server cu 403 ENVIRONMENT_MISMATCH. O cerere de transport deschisă în sandbox nu ajunge niciodată la transportatori reali, un eveniment din sandbox nu este livrat niciodată abonamentului dumneavoastră webhook live, iar soldul live nu se modifică. Endpoint-uri: POST /api/common/integration-sandbox (deschidere), POST /api/common/integration-sandbox/keys (cheie), GET /api/common/integration-sandbox/go-live (lista de verificare pentru go-live). Pe partea lg, lg --sandbox și lg --live sunt selectoare de profil pentru o singură rulare.

Cum citesc output-ul lg --json într-un script?

Output-ul este întregul plic HTTP: { "success": bool, "data": { "contract_version", "command", "status", "result" }, "code", "message" }. Payload-ul de business se află sub .data.result; la comenzile de listare, rândurile sunt .data.result., numărul este .data.result.count, iar pagina următoare este .data.result.next_cursor. Așadar, calea jq corectă arată astfel: lg loads list --json | jq -r '.data.result.loads[].load_code'. O cale care sare peste plic nu dă eroare, ci returnează în tăcere null — într-un script de supraveghere, asta devine o alarmă care nu se mai oprește. Singura excepție este /api/public/status, al cărui corp este în mod deliberat fără plic.

Dacă trimit aceeași cerere de două ori, obțin înregistrări duplicate?

Nu, dacă ați trimis un idempotency_key. Un al doilea apel cu aceeași cheie nu rulează din nou comanda; returnează primul rezultat cu statusul replayed. Cheia ar trebui să fie un UUID și trebuie să rămână ACEEAȘI între reîncercări — a genera o cheie nouă la fiecare încercare este totuna cu a nu folosi idempotența.

Ce face --yes?

Trece de poarta de confirmare. Când o comandă supusă confirmării este apelată fără --yes, nu se schimbă nimic: serverul rezumă într-o propoziție ce urmează să facă, spune dacă acțiunea este ireversibilă și se oprește (HTTP 409, cod de ieșire CLI 4). Repetarea comenzii cu --yes execută munca.

Cum detectez într-un script o comandă eșuată?

Citiți codul de ieșire, niciodată textul mesajului: 0 succes, 1 respingere de către o regulă de business, 2 eroare de comandă sau de argument, 3 eroare de autentificare sau de rol, 4 confirmare necesară. Prin HTTP, echivalentele sunt 200, 422, 404, 403 și 409, iar câmpul error_code din corp este stabil. MESAJUL de eroare este localizat și se schimbă fără preaviz.

Cum descopăr programatic comenzile disponibile?

GET /api/public/cli/catalog pentru descoperire fără autentificare și GET /api/common/commands pentru lista pe care contul dumneavoastră o poate vedea efectiv. Ambele returnează parametrii JSON Schema ai fiecărei comenzi; construiți-vă clientul pe baza lor, în loc să scrieți în cod o listă fixă de comenzi.

Trebuie să-mi actualizez clientul când se adaugă o comandă nouă?

Nu. Clientul își preia lista de comenzi din catalog, așa că o comandă adăugată pe server apare fără o versiune nouă a clientului. O actualizare este necesară doar pentru o modificare care rupe compatibilitatea și crește versiunea contractului, iar în acest caz forma veche rămâne activă cel puțin șase luni.

Înlocuiește CLI-ul API-ul REST existent?

Nu. Suprafața REST care alimentează aplicațiile web și mobile rămâne pe loc. Catalogul de comenzi nu înlocuiește CRUD-ul pe resurse; el este stratul verbelor — modul unic de a efectua o acțiune, protejat de poarta de rol și de poarta de confirmare și auditat.

Cum obțin o cheie API Logistivo și ce poate face?

Un manager cu permisiunea de gestionare a echipei o emite în panou, la Settings › Integration keys, sau dintr-un terminal cu lg keys create. Cheia acționează în numele unui membru real al echipei și are trei setări independente: scope-ul (read deschide catalogul și comenzile care nu scriu, write deschide orice comandă — poarta de confirmare rămâne așa cum este), permisiuni opționale pe domenii și un nivel de acces — employee, implicit, pentru operațiunile de zi cu zi, sau manager pentru echipă, setări, chei și bani. Este afișată o singură dată și nu este stocată pe server; poate fi rotită, transferată și revocată. Când membrul în numele căruia acționează pleacă, cheia se închide și ea. Nu există cont de serviciu.

Ce se întâmplă dacă refolosesc o cheie de idempotență pentru o altă comandă?

Serverul răspunde cu 409 idempotency_conflict și numește comanda pentru care a fost folosită prima dată cheia; nu rulează nimic și nu se reia nimic. Cheile sunt limitate la compania dumneavoastră, legate de o singură comandă și blocate timp de 24 de ore — după aceea, aceeași cheie pornește o execuție nouă.

Cum se aplică limitele de rată integrării mele?

Per token, pe minut, în trei categorii: citiri de catalog 120, comenzi de citire 120, comenzi de scriere 30. Un 429 poartă întotdeauna Retry-After și un corp cu retry_after, limit și bucket; așteptați atât și reîncercați. Catalogul publică valorile curente sub rate_limits.

Cum deosebesc o respingere de business de o eroare de transport?

Ramificați mai întâi după error_code: failed (422) este o respingere de business și poartă un error_key stabil, precum INSUFFICIENT_CREDITS sau NOT_FOUND; server_error (5xx) și erorile de rețea se reîncearcă cu aceeași cheie de idempotență; la rate_limited (429) se așteaptă. Nu ramificați niciodată după textul erorii.

Trimite Logistivo webhook-uri? Pentru ce evenimente?

Da. Înregistrați o adresă https și alegeți evenimentele care vă interesează; un POST semnat sosește atunci când acestea au loc. Astăzi vă puteți abona la 13 evenimente: încărcătură creată/status schimbat/șofer alocat, cerere de transport creată și status schimbat, ofertă plasată/revizuită/acceptată, factură ciornă/emisă/anulată și rezervare de depozit creată/status schimbat. Fiecare nume este susținut de o clasă de eveniment care este emisă efectiv — nimic nu este anunțat și apoi niciodată declanșat, pentru că un astfel de eveniment este un eșec tăcut pe care integratorul îl așteaptă la nesfârșit. Abonamentele se creează la Settings › Integration › Webhooks sau dintr-un terminal cu lg webhooks create.

Cum verific semnătura unui webhook Logistivo?

Fiecare cerere poartă un antet X-Logistivo-Signature: t=,v1=. Șirul semnat este ".", algoritmul este HMAC-SHA256, iar cheia este secretul abonamentului. Patru reguli: (1) verificați octeții BRUȚI — decodarea și reserializarea corpului (JSON.stringify / json_encode) transformă aceleași date în octeți diferiți, iar semnătura nu se mai potrivește; aceasta este cea mai frecventă greșeală; (2) verificați timestamp-ul și respingeți orice cerere pentru care |now − t| > 300 s, chiar dacă semnătura este corectă; (3) comparați în timp constant cu hash_equals / timingSafeEqual; (4) deduplicați după event_id, nu după id-ul livrării — event_id se păstrează la reluare, în timp ce id-ul livrării se schimbă. Receptoare funcționale pentru Node și PHP se află în examples/webhook-receiver/ din repository și se testează singure cu --selftest.

Ce se întâmplă dacă un webhook nu poate fi livrat? Pierd evenimentul?

Nu. Dacă receptorul dumneavoastră nu returnează 2xx, livrarea intră în programul de reîncercări: intervale (în secunde) 60 · 300 · 1800 · 7200 · 21600, 6 încercări în total, o fereastră de aproximativ 8,6 ore. FIECARE eșec, cu excepția 410 Gone, este reîncercat, inclusiv 4xx — pierderea unui eveniment de business costă mai mult decât câteva cereri în plus. După 10 livrări consecutive epuizate, abonamentul este dezactivat, iar motivul este înregistrat, astfel încât întrebarea „de ce nu sosește nimic” rămâne cu răspuns. Registrul de livrări păstrează fiecare încercare (cod de status, durată, eroare), iar o livrare individuală poate fi retrimisă din panou sau cu lg webhooks replay. Sincronizarea incrementală (updated_since + cursor) rămâne în continuare plasa de siguranță.

Poate agentul meu AI să facă tot ce poate face un utilizator în panou?

Aproape tot. Fiecare acțiune de scriere din panou are o comandă — măsurat față de endpoint-urile de scriere ale panoului, 100% dintre cele aflate în perimetru sunt acoperite — iar aceleași comenzi sunt deschise pentru lg, pentru HTTP și pentru canalul MCP. Excepțiile deliberate sunt pașii legați de identitate și de bani: autentificarea, cumpărarea unui pachet, adăugarea unui card cu 3D Secure, aprobarea textelor legale și închiderea unui cont rămân în browserul persoanei, iar lg handoff-links generate returnează linkul pentru ele. Cheile de integrare, webhook-urile și sandbox-ul se gestionează cu comenzi încorporate în lg, pentru că returnează secrete o singură dată.

Pot transmite comenzilor fișiere, cum ar fi PDF-uri sau fotografii, din CLI sau din API?

Da. Cu lg, puneți @ în fața unei căi locale (--attachment_uuid=@./cmr.pdf), iar lg încarcă mai întâi fișierul; --file este o scurtătură atunci când o comandă are un singur câmp de fișier, iar lg upload doar încarcă. Prin HTTP, trimiteți fișierul ca multipart la POST /api/common/commands:upload și transmiteți comenzii attachment_uuid returnat; clienții MCP folosesc instrumentul upload_attachment. Tipul este detectat din conținut, încărcările aparțin utilizatorului și rămân utilizabile 24 de ore, iar încărcarea din nou a aceluiași fișier returnează același id.

Care este diferența dintre o cheie de integrare employee și una manager?

Nivelul de acces decide cât de grea poate fi munca pe care o face o cheie. O cheie employee, implicită pentru cheile noi, rulează operațiunile de zi cu zi: încărcături, cereri de transport, oferte, curse, flotă, documente și contacte. O cheie manager poate face și muncă de management — membri ai echipei și roluri, setările companiei, chei de integrare, conturi bancare, carduri salvate, plăți către afiliați. O cheie nu are niciodată mai multe drepturi decât persoana în numele căreia acționează, iar o cheie employee care apelează o comandă de manager primește INSUFFICIENT_KEY_LEVEL. Schimbați nivelul în panou sau cu lg keys update --access-level=manager.

Poate un afiliat (partener de recomandare) să folosească CLI-ul?

Da. Un cont de afiliat primește un catalog propriu — 55 de comenzi pentru recomandări, lead-uri, note și sarcini CRM, prezentări de vânzare, câștiguri și plăți — și îl folosește din lg, prin HTTP sau prin MCP, ca orice alt rol. Solicitarea sau anularea unei plăți și schimbarea contului bancar pentru plăți necesită o cheie de nivel manager și o confirmare. O companie care este și expeditor, transportator sau comisionar vamal vede ambele cataloage.

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.