Logistivo CLI: run your logistics operation from a terminal and from your ERP
One command catalog feeds three surfaces: the lg terminal client, the AI assistant inside the dashboard, and the channel external AI assistants connect to. A capability is written once and appears in all three. This page is a contract. The command table below is not hand-written; it is generated from the live command registry on every request.
What is the Logistivo CLI?
The Logistivo CLI (lg) is a command line client that runs the freight, demand, bid, export-document, stock, invoice and customs-tariff operations of your Logistivo account from a terminal or a script. The commands are not baked into the client: the client pulls the command catalog from the server and draws itself from it. The same commands can be called over HTTP without installing anything — that is the path ERP and CRM integrations use.
One command shape: lg --parameter=value. The catalog holds 34 commands across 14 domains today; the widest list a single role sees is 29 commands across 11 domains.
Flags are never invented: every flag is a parameter name from the command's own JSON Schema, and so are its type and whether it is required.
Writing commands ask for confirmation; irreversible ones never run without it (HTTP 409, CLI exit code 4).
Retrying does not create duplicates: a call carrying an idempotency_key runs at most once.
Every execution lands in one audit ledger — whether it came from a terminal, from the chat, or from an assistant is on the record.
What is live today, and what is on the way
This section sits at the top of the page on purpose. The most expensive mistake an integration document can make is to describe an endpoint that is not open yet as if it were: the integrator writes the code, the endpoint returns 404, and trust does not come back.
Because the contract is frozen, the integration code you write today will keep working as the remaining pieces open.
Command catalog and identity table — The public domain/verb identity of all 34 commands is frozen and the contract version is published as 1. The table on this page is generated from the live registry: a command added on the server appears here on its own, and a command removed disappears.
Unauthenticated discovery endpoint — GET /api/public/cli/catalog — command names, domain/verb identity, description, JSON Schema parameters and gate flags. No authentication: minting a token is a step that comes AFTER the integration decision, not before it. The response carries no tenant data at all.
Authenticated catalog and execution endpoints — GET /api/common/commands, GET /api/common/commands/{name} and POST /api/common/commands/{name}. The role gate, the confirmation gate, idempotency and the audit ledger live at a single enforcement point that the chat and the terminal pass through as well.
The lg terminal client — The single-file, dependency-free client is in early access. Because the HTTP surface is open you can integrate without waiting for it; lg is only a convenience placed in front of the same endpoints.
Incremental sync and machine-readable business errors — updated_since plus cursor pagination on list commands, and a stable error_key for business errors, do not exist yet. Both are written into the contract and are the first thing a nightly-sync integration needs; until then, branch only on error_code.
What makes this different: the client knows no commands, it draws them
This is the single decision that makes the rest of the page meaningful. In an ordinary CLI the commands live inside the client; in Logistivo they live on the server.
In the classic arrangement, when the server gains a capability the client needs a new release and the user needs to install it. In between, the two sides know different things — and that is the quietest failure mode in integrations: the script runs, the exit code is 0, and the command simply does not describe what the server can do.
In Logistivo a command's definition is a single record: its name, what it does, its JSON Schema parameters, which roles may see it, whether it needs confirmation and whether its effect is reversible. When lg starts it pulls that catalog and generates its help text, its flags and its input validation from it. The moment a command is added on the server, lg knows it — with no client update.
The same catalog feeds the AI assistant in the dashboard and the channel external AI assistants connect to. A capability is written once and appears in three places — and all three pass through the same role gate, the same confirmation gate and the same audit ledger. There is no separate CLI command inventory; if there were, the three surfaces would quietly diverge within six months.
No version drift — The command list the client knows is, by construction, the list the server has. "Which lg version has this command?" is not a question that exists.
Flag names are never guessed — A command's flags are exactly the parameter names in its JSON Schema. The way to learn what a command accepts is not to read a document but to read the catalog — and the catalog is always current.
Your CLI is as large as your account — The catalog is role-filtered: a command your role cannot see never appears in your list. A carrier account sees 29 commands, a shipper 28, a customs broker 22.
The documentation cannot fall behind — The command table on this page comes from the same registry. There is no list anywhere that a human has to keep in sync with reality.
The naming rule — how command names grow
The catalog will grow. New commands are not named ad hoc; six rules are applied. We publish them here because these are the names your integration will be writing for years, and their predictability is worth something to you.
A domain is a plural entity — loads, demands, invoices, export-documents. Two-word domains are kebab-case. The single exception is an uncountable noun: stock is never pluralised, because "stocks" means something else.
Every verb of one entity lives in one domain — loads carries six verbs: create, list, get, list-problems, set-status, assign-driver. Opening a new domain per verb (load-statuses, load-drivers) would make a user hunt for the same entity in three places.
The verb never leaks into the domain; the domain is what CHANGES — Assigning a driver changes the load, not the driver, so it is lg loads assign-driver. The indirect object (-driver) is attached to the verb, never to the domain.
A sub-resource never gets its own domain — An invoice line and a document item cannot be listed on their own; they are addressed through their owner — lg invoices add-line, lg export-documents add-item. One test decides it: if you cannot list or get the thing independently, it is not a domain.
Narrowing is a flag, not a new verb — lg fleet-documents list --days=30 is correct; there is no separate "list-expiring" verb, because the window is already a parameter. A qualified verb is used only when the rows it returns are not the domain entity: loads list-problems returns problem flags, not loads.
The (domain, verb) pair is unique across the whole catalog — Even for two commands whose roles never overlap. If the same pair were given to two commands, your integration code would be looking at one name while the CLI ran different things depending on who invoked it.
Install and identity
lg is a single-file, dependency-free Node script (Node 18+). It was written that way so it can be copied onto a customer's ERP server as one file: a client that needs npm install never gets there. The client is in early access and the download link will be added to this section when it opens. You do not have to wait for it to build an ERP/CRM integration — the same commands are available over HTTP.
1. Log in — lg login asks for your email and password, writes the returned access token to ~/.logistivo/config.json and fetches the catalog straight away. If email verification is enabled on your account it asks for the code here too — skipping that step would leave you holding a token while every protected endpoint refuses it.
2. For servers and CI: a token, not a password — Never use lg login in a non-interactive environment. Create a user dedicated to the integration in the panel, give it a narrow role, and put its personal access token in the LOGISTIVO_TOKEN environment variable. While that variable is set the config file is never read. That user must have email verification switched off: an integration that mails a code to an inbox nobody watches stops on every restart.
3. Verify and explore the catalog — lg commands lists every command your role can see, grouped by domain; lg help prints that domain's verbs, and lg help prints one command's full parameter list with types and which ones are required. None of that text lives inside the client — all of it comes from the catalog.
Never put the token in version control, a CI log or a chat window. lg logout deletes only the LOCAL copy — the token stays valid on the server, and permanent revocation happens in the panel. The audit ledger stores your arguments but masks fields whose key contains token, password, IBAN, card or OTP as ***.
LOGISTIVO_TOKEN — Personal access token. While set, the config file is never read — this is the preferred path in CI and on servers.
LOGISTIVO_BASE_URL — Server root. Defaults to https://logistivo.com. Change it only if you are pointing at your own environment; the --base-url flag does the same for a single command.
LOGISTIVO_HOME — Directory for the config file and the catalog cache. Defaults to ~/.logistivo. Separate it if you work with two accounts on one machine.
The command model
There is one grammar and it has no exceptions. To know how to call a command, the only thing you need is what the catalog says about it.
lg loads list --status=in_transport --limit=20 --json
Flag names are schema names and may contain underscores (--load_code, --amount_per_vehicle). That is deliberate: seeing a flag tells you which JSON field it lands in, so moving from the CLI to HTTP requires no name translation. A boolean parameter written without a value is true (--only_open); the opposite is --no-only_open. Write an argument whose value starts with a dash as --flag=value, otherwise a forgotten flag silently swallows the next one as its value.
lg — The client.
loads — The domain — the entity you are working on, plural.
list — The verb — drawn from a closed vocabulary (list, get, create, update, delete, search, preview, set, issue, generate, extract, inquiry, move, adjust, and compounds prefixed add-, update-, remove-, set-, assign-).
--status=in_transport — A parameter — its name is exactly the JSON Schema parameter name and its value must match the schema type. An invalid value on an enum field never reaches the server.
--limit=20 — Row count on list commands. The ceiling is written per command in the schema (20 on list commands).
--json — Raw JSON output.
--json — Prints raw JSON. This is the only correct shape for scripts: the default aligned table is for humans, picks its columns from the data and may change layout without notice. Never write a script that parses the table output.
--yes, -y — Passes the confirmation gate (confirm: true over HTTP). Use it only in a script that knows what it is about to do; on an irreversible command this flag is shorthand for "there is no undo".
--idempotency-key= — Makes retrying a writing command safe. A second call with the same key does not re-run the command; it returns the first result. If you build a retry loop, generate the key YOURSELF and send the same one on every attempt.
--refresh — Forces a catalog refresh. lg caches the catalog for an hour; if you know a command was just added on the server, use this instead of waiting.
--help, -h — Prints the command's catalog description, every parameter, its type, its enum values and whether it is required. There is no help file inside the client.
--base-url, --timeout, --verbose, --no-color, --version — Respectively: point one command at another server, a timeout in seconds, request/response detail, disable ANSI colour, and the client version.
Real examples
Every command below exists in the catalog today; no name or parameter here is invented. Outputs are abbreviated.
Loads currently on the road — lg loads list --status=in_transport --limit=20
One load in full, raw JSON — lg loads get --load_code=FSK2158 --json
Open document-consistency flags — lg loads list-problems --only_open=true --limit=10
Resolve the country — lg countries search --query=Germany --json
Filter with the resolved id — lg loads list --receiving_country_id=57 --date_from=2026-09-01 --limit=20
Find an HS code from a product name — lg tariffs search --query="aluminium profile"
Move a load forward (carrier role only) — lg loads set-status --load_code=FSK2158 --status=transit --date=2026-08-21 --yes
Draft — lg export-documents create --doc_type=proforma_invoice --currency_code=EUR --json
Generate it (gated) — lg export-documents generate --document_id=8412 --yes
Without the client: the HTTP command API
lg is a convenience, not the door. Catalog and execution both go through three HTTP endpoints; an ERP or CRM integration connects here directly. Identity is a personal access token carried in Authorization: Bearer.
Asking for confirmation with 200 + ok:false is forbidden and will never happen. The reason is simple: clients treat 200 as success, so a confirmation request returned as 200 would kill the flow silently and nobody would notice. Confirmation is always 409.
GET /api/common/commands — The whole catalog your role can see: name, domain, verb, description, JSON Schema, read_only, confirmation_needed, irreversible. Draw your client from this; do not hard-code a command list.
GET /api/common/commands/{name} — One command in full. name is the frozen command name from the catalog (list_loads), not the CLI identity.
POST /api/common/commands/{name} — Execution. Body: { args, confirm?, idempotency_key? }. The HTTP status of the response is the result itself — anything other than 200 is information, not noise.
The confirmation gate and irreversible commands
Every command in the catalog carries two flags: confirmation_needed (does it require explicit approval before running) and irreversible (can its effect be undone). They are separate questions, and both are enforced on the server rather than left to the client's good manners.
The gate is about responsibility, not about business rules. Placing a bid is the first step toward a freight contract; issuing an invoice creates an accounting entry; a stock movement changes a ledger. None of these should ever be something you can run twice by accident.
The gate is read from the EFFECTIVE command. When one command wraps another — as the generic executor in the chat does — the gate comes from the flags of the command that will actually run, not from the wrapper. With a fixed flag one of two failures would be inevitable: either the invoice command would run unconfirmed, or every country lookup would raise a confirmation card.
read_only and ungated are not the same thing. There are three distinct states: invoices preview is ungated and writes nothing; loads create is gated and writes; invoices create is ungated but writes — a draft comes into being. One test settles it: when the command returns, has a row in the database changed?
It is technically possible for an integration to pass the gate automatically on every call, but it is not advisable. The right pattern is to automate the gate only for commands you know to be reversible, and to route irreversible ones into a human's queue.
1. Call it — Call the gated command without confirm. Nothing changes.
2. Read the summary — You get a 409; confirmation.summary in the body is a sentence written for a human ("About to issue an invoice: X Ltd, 1,200.00 TRY"), not a dump of raw arguments. Monetary commands also carry a structured preview: line items, totals, warnings.
3. Confirm — Repeat the same call with confirm: true (--yes in the CLI). Irreversible commands are marked so that they run at most once.
Idempotency: why retrying does not create duplicates
An ERP does not read your error message, does not see your screen, and will resend the same request after a timeout. This section exists for exactly that behaviour.
A call carrying an idempotency_key runs at most once. The second call does not re-execute the command; it returns the recorded result with status replayed. When the network drops, when a request times out, or when a queue picks up the same job twice, you get the first result instead of a duplicate invoice.
Use a key on every writing command. The catalog's read_only field gives you that distinction for free: every command with read_only: false deserves an idempotency key.
Your retry strategy should use exponential backoff and carry the SAME key on every attempt. Generating a fresh key per attempt is the same as not using idempotency at all.
Trap 1 — failure is locked in too — If the first execution was rejected by a business rule (failed), a second call with the same key returns the same error and does not retry. Fixing the argument and trying again requires a NEW key. A key represents an attempt, not an intention.
Trap 2 — a large result replays without a body — The ledger stores a result only if it is under 64 KB. A larger result replays with status replayed but an empty body. Your integration should rely on the "replayed" signal rather than the body, and fetch the data with the matching get command.
Key format — use a UUID — A guessable key such as INV-1001 can collide with one of your own records or with another tenant's. Use a plain UUID, or a compound that is unique by construction such as {company-id}:{ERP-document-no}:{verb}.
Errors and exit codes
What a machine branches on is a stable string. The text in the error field targets a human, is localised, and changes without notice — an integration that branches on message text is not supported, and its breaking is not a bug.
An honest gap: the business error inside failed is free text today. A machine cannot tell "insufficient stock" from "contact not found". Adding a stable error_key to command results is planned; until then, branch only at the error_code level.
ok — HTTP 200 · error_code — · CLI 0 · —
replayed — HTTP 200 · error_code — · CLI 0 · Idempotent replay; the work did NOT run again.
failed — HTTP 422 · error_code failed · CLI 1 · No — a business rule rejected it. Pointless until the argument changes.
forbidden — HTTP 403 · error_code forbidden · CLI 3 · No — role gate.
not_found — HTTP 404 · error_code not_found · CLI 2 · No — wrong or removed command name.
401 unauthenticated — The token expired or was revoked. Retrying will not help; the token must be replaced.
429 rate_limited — Rate limited. The response always carries Retry-After; honour it instead of inventing a fixed delay.
5xx server_error — Retry with exponential backoff and the SAME idempotency key. Changing the key re-runs work the server may already have completed.
Command identity table
The table below is not hand-written: it is generated from the live command registry every time this page is served. A command added on the server appears here on its own. The "Gate" column tells you whether a command asks for confirmation and whether its effect can be undone; the "Roles" column tells you which account types will find it in their catalog.
The API name (list_loads) is frozen and will not change; it is the name you use in HTTP calls. The CLI identity (loads list) is governed by the versioning policy: once announced it changes only with a version bump and a migration window, during which the catalog serves the old pair as an alias. The descriptions here come from each command's own record and are shortened — for the full text, parameter types and enum values, read the catalog endpoint or run lg help .
lg bids create — Submit a bid (teklif ver) on an open demand. amount_per_vehicle is the freight PER VEHICLE, not the total. (write · confirm + irreversible · carrier · API: create_demand_bid)
lg bids list — List the bids (teklif) THIS carrier company has submitted, newest first, with their outcome. (read · — · carrier · API: list_my_bids)
lg contacts search — Resolve a business contact (customer/partner/recipient) by name, legal name or tax number to its contact_id. (read · — · all · API: lookup_business_contact)
lg countries search — Resolve a country by name or ISO code to its country_id for sending/receiving country on a load. (read · — · all · API: lookup_country)
lg demands list — List freight demands (talep) this carrier is allowed to bid on, newest first. (read · — · carrier · API: list_open_demands)
lg drivers search — Resolve one of this company's drivers by name to its driver_id. (read · — · carrier · API: lookup_driver)
lg export-documents add-item — Append a line item to an export document. (write · — · all · API: add_export_document_item)
lg export-documents create — Create a new export document draft. (write · — · all · API: create_export_document_draft)
lg export-documents extract — Read a pasted order e-mail, offer or confirmation and fill an export document from it. (write · — · all · API: extract_export_document_from_text)
lg export-documents generate — Produce the final PDF of an export document. (write · confirm + irreversible · all · API: generate_export_document)
lg export-documents get — Read one export document in full: every filled field as dot-paths, the line items with their 1-based positions, the server-computed totals and which required fields are still empty. (read · — · all · API: get_export_document)
lg export-documents list — List the export documents of the current company (proforma invoice, commercial invoice, packing list, shipping instruction, delivery note, certificate/movement applications, exporter declaration, insurance request). (read · — · all · API: list_export_documents)
lg export-documents remove-item — Delete one line item by its 1-based position. (write · — · all · API: remove_export_document_item)
lg export-documents set — Set one or more fields on an export document draft. (write · — · all · API: set_export_document_fields)
lg export-documents update-item — Change columns of one existing line item, addressed by its 1-based position from get_export_document. (write · — · all · API: update_export_document_item)
lg fleet-documents list — List fleet documents that expire soon (or already expired): vehicle papers (insurance, inspection, permits) and — for carriers — driver papers (passport, visa, licence, SRC). (read · — · shipper, carrier · API: list_expiring_documents)
lg invoices add-line — Add a line item to a DRAFT invoice and recompute totals (tax auto-resolved if tax_rate_id omitted). (write · — · all · API: add_invoice_line)
lg invoices create — Create a DRAFT invoice (reversible) issued by the current company to a recipient business contact. (write · — · all · API: create_invoice_draft)
lg invoices issue — Issue (finalize) a DRAFT invoice: assigns a number, posts accounting entries, and emails the recipient. (write · confirm + irreversible · all · API: issue_invoice)
lg invoices preview — Show the user a full preview of a DRAFT invoice (recipient, line items, tax breakdown, totals) WITHOUT issuing it. (read · — · all · API: preview_invoice)
lg load-types search — Resolve a load/transport type (e.g. (read · — · all · API: lookup_load_type)
lg loads assign-driver — Assign one of this company's drivers to a load so the driver sees it in the mobile app and starts reporting position. (write · confirm · carrier · API: assign_driver_to_load)
lg loads create — Open a freight load (yük). (write · confirm + irreversible · all · API: create_load)
lg loads get — Read one load in full by its code: route, dates, weight, current status, parties (sender/receiver/carrier), latest reported position and any open AI document-consistency flag. (read · — · all · API: get_load)
lg loads list — List the freight loads (yük) this company can see, newest first. (read · — · all · API: list_loads)
lg loads list-problems — List loads whose uploaded documents the AI consistency check found to CONTRADICT each other (e.g. invoice weight vs CMR weight). (read · — · all · API: list_load_problem_flags)
lg loads set-status — Move a load to a new transport status and append it to the load's status history (this is what the customer sees on the tracking screen). (write · confirm + irreversible · carrier · API: update_load_status)
lg products search — Resolve a product to its product_id + on-hand quantity. (read · — · shipper · API: lookup_product)
lg stock adjust — Record a stock count correction (sayım düzeltme) or write-off (fire). mode=set sets the absolute on-hand at the slot; mode=delta applies a signed change (negative reduces). reason is required. (write · confirm + irreversible · shipper · API: create_stock_adjustment)
lg stock list — Read current on-hand stock levels. (read · — · shipper · API: check_stock_level)
lg stock move — Record a stock movement: inbound (giriş), outbound (çıkış), or transfer between locations. (write · confirm + irreversible · shipper · API: create_stock_movement)
lg tariffs inquiry — Start an official duty / anti-dumping inquiry for one GTİP code and one counterpart country. direction=import means goods coming INTO Turkey from origin_country_id; direction=export means goods leaving Turkey to… (write · confirm + irreversible · all · API: run_tariff_inquiry)
lg tariffs search — Search the Turkish customs nomenclature (GTİP / HS) by goods description or by a partial code, and return matching codes with their official descriptions. (read · — · all · API: lookup_tariff_code)
lg warehouses search — Resolve a warehouse by name to its warehouse_id, with its areas. (read · — · shipper · API: lookup_warehouse)
ERP and CRM integration — Identity: no service accounts, a real user instead
An ERP or CRM connects to Logistivo as a real company user holding a personal access token minted from the panel. A synthetic service account — no company, exempt from scoping, nobody responsible for it — is never created.
This is architecture, not preference. Multi-tenant isolation rests entirely on the user's company; for an identity with no company the isolation scope becomes inert, and one wrong query on that token reads data that does not belong to it. The second reason is audit: the user column in the ledger ties "who ran this" to a person; a service account makes that column meaningless and leaves an invoice-issuing integration with no owner. The third is revocation: if a token belongs to a person, the integration falls silent when that person leaves. An ownerless bridge is the bridge that keeps writing quietly for years.
In practice: the company creates a normal user in the panel (for example "ERP Bridge", owned by a named employee), gives it a narrow role, mints a token from that user's session and puts it in the ERP.
That user is visible in the permission system, its rights can be trimmed, and its token can be revoked on its own. What is forbidden is not creating an account — it is creating an account that is not bound to a tenant.
One integration per token. If two systems share a token, revocation granularity is gone: cutting off the CRM means cutting off the ERP too.
ERP and CRM integration — Versioning and deprecation
The catalog response carries a contract_version integer; command rows carry since, deprecated_at and replaced_by. The contract version rises only on breaking changes.
The version does NOT rise for: a new command, a new optional parameter, a new field in a response. A consumer must ignore fields it does not know; a client that errors on an unknown field is non-compliant.
The version DOES rise for: removing or renaming a command, removing a response field, making an optional parameter required, narrowing an enum. The old shape stays alive for at least two calendar quarters (6 months) with deprecated_at and replaced_by.
Deprecation is announced in the catalog, not only in a changelog. Because the client draws itself from the catalog, a command with deprecated_at set prints a warning without any client release, and the integrator sees it in their logs.
The API name is frozen forever. The CLI identity (domain/verb) could be recut freely before it was announced; after announcement it changes only with a version bump and a migration window, during which the old pair is served as an alias.
ERP and CRM integration — Stable identifiers: never hand an auto-increment id to your ERP as a key
Internal auto-increment ids are opaque and tenant-scoped: another company's valid id returns "not found" to you, not data. But being opaque does not make them business keys. What an ERP stores in its own record has to be a business key.
Load: code (the customer code, e.g. FSK2158). loads get already works by code.
Country: ISO 3166-1 alpha-2. countries search returns the code.
HS / tariff: the code itself — the nomenclature is already universal.
Invoice: the invoice number, once issued. A draft has only a surface id, and a draft id is NOT a business key — the ERP should carry its own reference and never persist a draft id.
Product, warehouse, contact: only a surface id today. The rule for new commands is explicit: any command returning a surface id also returns the tenant's own business key (SKU, code, tax number) so the ERP can match on its side.
ERP and CRM integration — Pagination, filtering and incremental sync
Today list commands take limit (ceiling 20) and return count. That is enough for a human conversation and not enough for a nightly sync. The contract commits to this:
Request: limit (1..the per-command ceiling) plus cursor (opaque, forward-only). offset is NOT used — in a live table an offset shifts; a record inserted between two pages makes the ERP skip a row or count it twice, and it does so silently.
Response: { count, total?, next_cursor|null }. count is the number of rows in this response; when next_cursor is absent the list is complete.
Filtering: the named parameters in the JSON Schema, combined with AND. There is no free query language — a DSL would be a second query surface we would have to defend.
Incremental sync: updated_since (ISO-8601, UTC) plus a cursor. No list command has it today; it is the first thing to open. Without it every ERP re-fetches everything every hour.
ERP and CRM integration — Rate limits
The limit key is the token, not the IP: an ERP arrives from behind a single NAT, and an IP-based limit would count a whole company as one user.
Unauthenticated discovery (public/cli/catalog): 30 per minute. It is a dictionary, not a data source; read it once while writing your bridge, never in a production loop.
Catalog endpoints (common/commands and common/commands/{name}): 120 per minute. The catalog rarely changes; do not re-fetch it before every command — lg caches it for an hour.
Execution endpoint (POST common/commands/{name}): 60 per minute.
AI or credit-consuming commands (tariffs inquiry, export-documents extract, export-documents generate): the real limit is CREDITS, not request count. A 200 response may have spent a credit.
429 always carries Retry-After; honour it instead of inventing a fixed delay.
ERP and CRM integration — Things we will not do
This is often the most useful part of a contract: knowing what will never arrive lets you design around it.
No service account and no principal that is not bound to a tenant.
No second path: there is no direct database access, no shell access and no bulk endpoint that bypasses the single enforcement point. The role gate, the confirmation gate, idempotency and the audit ledger are enforced only there.
Branching on error message text is not supported; the text is localised and changes without notice.
No offset pagination.
Using a surface id as a business key in your ERP is not supported.
Outgoing webhooks are not squeezed into this contract. The catalog is a PULL surface; the event stream (push) is a separate contract and will get its own document.
The machine-readable catalog
This page was written for people. For an AI assistant or an automated client, the same information exists as a structured, unauthenticated copy.
GET /api/public/cli/catalog requires no authentication and returns the public identity of the commands: the frozen API name, the CLI domain/verb identity, the description, the JSON Schema parameters, and the read_only, confirmation_needed and irreversible flags. It returns no personal data, no tenant data and no sample records — only what the surface is.
The right way for an external agent to use it: read the catalog, explain in YOUR OWN words which jobs are possible, and when execution is needed, point the user at running it in their own environment with their own token. The agent never asks for, generates or relays a user's token.
Connecting Logistivo to an AI assistant for signup and freight requests is a separate channel with its own page:
The catalog is unauthenticated; execution is not. There is no way to run a command on an agent's behalf — what runs is always the user's own identity.
A Logistivo password, verification code or access token never passes through a chat. Do not build a flow that asks for, generates or relays one.
Whether a command is irreversible is written in the catalog. Never suggest "let's try it" on a command with irreversible: true.
Credit-consuming commands (tariffs inquiry, export-documents extract, export-documents generate) have a monetary result; do not call them in an automated loop.
A plain-text twin of this page lives at /cli.md; use it if you parse markdown more reliably than HTML.
Honest scope
We would rather grow this section than shrink it. The value of an integration contract lies less in what it promises than in how clearly it states what it does not.
The CLI runs in a tenant user's context — Every command runs under the company and role of the user whose token is presented. A platform-operator mode — an administrative mode that reads across companies — is outside this surface and will not be opened from here.
Your command list depends on your role — A shipper account sees 28 commands, a carrier 29, a customs broker 22. If a command is missing from your catalog, the problem is the role, not the token. Driver accounts have no access to this surface at all.
Some commands spend credits — Tariff inquiries, field extraction from documents and document generation draw on your plan's credit balance. The catalog does not carry a credit cost field today; the information lives in the command description and adding the field is planned.
The catalog does not replace the existing REST API — The REST surface that powers the Logistivo web and mobile apps stays where it is and remains under the backwards-compatibility rule. The command catalog does not replace resource CRUD; it is the VERBS layer — the single, audited way to do a thing.
List commands return small windows today — The ceiling is 20 rows and there is no cursor. It is not designed for extracting a full copy of your data; that changes when incremental sync opens.
Frequently asked questions
What is the Logistivo CLI used for?
It runs the operational work in your Logistivo account — listing and reading loads, seeing open freight demands, placing bids, moving a load's status forward, assigning drivers, preparing export documents, recording stock movements, issuing invoices, looking up HS tariffs and anti-dumping duties — from a terminal or a script. The same commands are callable over HTTP for ERP/CRM integrations.
How are commands named in the Logistivo CLI?
The shape is lg --parameter=value. The domain is a plural entity (loads, demands, invoices, export-documents) and the verb comes from a closed vocabulary (list, get, create, search, set, issue, generate and so on). Every verb of one entity lives in one domain, and the (domain, verb) pair is unique across the whole catalog.
How do I authenticate to the CLI?
With a personal access token minted from the Logistivo panel. lg login asks for the token and writes it to a local config file; in scripted environments set the LOGISTIVO_TOKEN environment variable instead. Over HTTP the token travels in the Authorization: Bearer header.
Can I create a service account for my ERP integration?
No. An integration connects with the token of a real company user created in the panel. A synthetic identity with no company makes multi-tenant isolation inert, makes the "who ran this" column in the audit ledger meaningless, and leaves a bridge that does not fall silent when its owner leaves. The right pattern is a dedicated user with a narrow role and a named owner.
If I send the same request twice, do I get duplicate records?
Not if you sent an idempotency_key. A second call with the same key does not re-run the command; it returns the first result with status replayed. The key should be a UUID and must stay the SAME across retries — generating a fresh key per attempt is the same as not using idempotency.
What does --yes do?
It passes the confirmation gate. When a gated command is called without --yes nothing changes: the server summarises what it is about to do in one sentence, says whether it is irreversible, and stops (HTTP 409, CLI exit code 4). Repeating the command with --yes performs the work.
How do I detect a failed command in a script?
Read the exit code, never the message text: 0 success, 1 business rule rejection, 2 command or argument error, 3 authentication or role error, 4 confirmation required. Over HTTP the equivalents are 200, 422, 404, 403 and 409, and the error_code field in the body is stable. The error MESSAGE is localised and changes without notice.
How do I discover the available commands programmatically?
GET /api/public/cli/catalog for unauthenticated discovery, and GET /api/common/commands for the list your account can actually see. Both return each command's JSON Schema parameters; draw your client from them rather than hard-coding a command list.
Do I have to update my client when a new command is added?
No. The client pulls its command list from the catalog, so a command added on the server appears without a new client release. An update is needed only for a breaking change that raises the contract version, and in that case the old shape stays alive for at least six months.
Does the CLI replace the existing REST API?
No. The REST surface that powers the web and mobile apps stays in place. The command catalog does not replace resource CRUD; it is the verbs layer — the single, role-gated, confirmation-gated and audited way to perform an action.
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.