listo is a command-line tool for the Listo Delivery API. It does five things
you would otherwise build yourself:
- Log in without copying a key anywhere.
- Receive your webhooks on your own computer, signed the way production signs them, with no public address and no tunnel.
- Send a test event of any type.
- Follow your API calls as they happen.
- Create, read and cancel deliveries, and play a courier scenario on a test one.
--live. Test mode uses your
test credential and moves no money (see test mode).
Live mode needs --live, and every command that writes in live mode shows you
exactly what it is about to do and asks first.
Install
Pick one. All three install the same program.listo in
$HOME/.listo/bin (set LISTO_INSTALL_DIR to choose another folder), prints the
line to add to your PATH if that folder is not on it, and installs nothing
unless two checks pass first: the signature on the release’s checksum file, made
with Listo’s release key, and the checksum of the download. On Windows, use npm.
Check that it works:
--help with an example. To complete commands and flags in
your shell, load the script listo completion prints for bash, zsh, fish or
PowerShell once from your shell profile:
Log in
1
Compare the code
Press Enter to open the page, or open the address yourself. The page shows a
code. Check that it is the same one your terminal printed before you
authorize: the code is how you know the approval is for the terminal in front
of you and not for somebody else’s.
2
Choose the mode and authorize
The page offers Test (no real money) and Live (real money). Live is
available once your live balance has money or you have saved a card, and
approving it asks for your password. Press Deny if the request is not yours.
3
Go back to the terminal
The terminal finishes by itself. The computer now appears in the partner
dashboard, under API keys and then Connected apps, as Listo CLI on
MacBook de Ana, where Disconnect ends its access at once.
listo login signs this computer in to Test.
listo login --live asks for Live in the same way and adds it; the two logins
are kept apart, and --live on any later command selects the Live one. If the
page approves a different mode than the one you asked for, the terminal tells you
so.
Where the login is kept: in your operating system’s credential store (the macOS
Keychain, the Windows Credential Manager or the Secret Service on Linux). When no
store answers, as in a container, it falls back to a file only you can read, and
the last line of the output says which one it used. LISTO_NO_KEYRING=1 forces
the file.
listo whoami asks the API which company and which mode the credential is for,
and listo logout ends the login of one mode (listo logout --live for Live):
the session is revoked at Listo and the login is removed from this computer.
With an API key instead
Already have a key from the dashboard? Save it, or just set it for one shell:LISTO_API_KEY wins over a saved login. A test key (lst_test_...) works
without --live, and a live key (lst_live_...) only with it; mixing them is
refused before any call, with the flag to add or drop.
Without a terminal
In a script, a container or a CI job there is nobody to press Enter. Ask for the code and finish the login from another shell:verification_url yourself, compare the code and authorize. Then run the
command in next_step on the machine that asked, and it saves the login:
login run with no terminal does the same as --non-interactive. The code
expires, and the terminal says so (“The code expired before it was approved”)
and names the command to run again. --key, --non-interactive and --complete
cannot be combined.
Receive your webhooks on your computer
listen opens a live connection to your account and sends every event Listo
emits for it, in the selected mode, to the address you gave. One line per event:
the time, the type, the event id, what your server answered and how long it took.
Press Ctrl+C to stop. Nothing needs to be reachable from the internet.
Verify exactly as you do in production
Each event is sent as aPOST of the event’s JSON body, byte for byte as Listo’s
webhook sender writes it, with an X-Listo-Signature header (t=...,v1=...)
and a User-Agent of listo-cli/<version>. So the code that verifies it is the
code you ship: the SDK’s verification helper, over the raw body, with a secret.
listen printed. Pass it where that sample reads
LISTO_WEBHOOK_SECRETS (the sample takes a comma separated list, so a single
secret is a list of one). Three things to know about it:
- It is made on your computer, for
listen. It is not your endpoint’s secret from the dashboard, and that one never leaves Listo. In development you verify with the secretlistenprinted; in production, with your endpoint’s. Reading both from the same environment variable in your app is the whole difference. - It does not change. It is created the first time and is the same on every
start, so you set it once.
listo listen --print-secretprints it and exits, which is what you want in a script:export LISTO_WEBHOOK_SECRET=$(listo listen --print-secret). - Test and Live each have their own, and
listo logoutremoves the secret of the mode it ends.
What listen does with your answer
It sends each event once and never retries: a 500 is shown in red so you can
fix your handler and trigger the event again. A reply with a redirect is reported
as the answer it is, not followed. A server that is not running is a line saying
it could not be reached, and listen goes on with the next event. A request that
takes longer than 30 seconds is given up on.
To receive only some types, name them:
When the connection drops
listen reconnects by itself and tells you (“Connection lost, reconnecting in 2
s…”). When it is back, it reads the events written meanwhile from
GET /v1/events, starting from
the last event it saw, and forwards the ones you did not get, oldest first, each
event once (“Reconnected. Events missed while offline: 3.”). The --events filter
applies to them too. What it does not replay:
- Anything from before
listenstarted. The first connection catches up nothing; it starts from now. - Courier positions (
delivery.courier_location). The events list leaves them out unless you ask for that type, and a position that old is no use. - More than 2,000 events at once. An outage of a day does not queue a day of traffic.
listen stops with “Session ended.” and the command to log in again.
Send a test event
trigger records a sample event of that type, marked test: true, and hands it
to the listen sessions of your account. It does not call your webhook URL, so
it needs none to be configured. The eight types are delivery.status_changed,
delivery.updated, delivery.courier_location, withdrawal.updated,
store.updated, store.connected, delivery.message_skipped and
delivery.rated.
Add --deliver to also POST it to the webhook URL set in the dashboard for
the mode, the way a real event goes out:
GET /v1/webhook_deliveries.
With --deliver and no URL for the mode, the API answers
webhook_endpoint_not_configured, and
sending more than 30 test events an hour answers
test_webhook_limit_reached. It is the same
call as POST /v1/test_webhooks.
Follow your API calls
/v1/ call of your account, as it is made, whoever makes it: the
time, the method, the path, the status (green, or red from 400), how long it
took, the error code when it failed and the request id. That id is the one to
quote to support, and the one to look up in
GET /v1/request_logs.
--status failed shows only answers from 400 up, succeeded the rest, and
--endpoint takes the exact value a log row shows. The reads listo makes
itself to catch up after a lost connection are hidden, so they do not fill your
log; add --all to see them. It reconnects and catches up the way listen does.
Create and read deliveries
order.json is the body of POST /v1/deliveries
as it is, and the API checks it: see Quote and create a delivery.
Pass the JSON with --data @order.json, with --data @- or by piping it in, or
write it inline in --data. The command makes an Idempotency-Key for the call
and prints it, so sending the same call again never books a second courier. To
send your own, use --idempotency-key.
cancel takes --reason (customer_canceled, store_closed, item_unavailable,
wrong_address, no_longer_needed or other, the default) and --description,
a sentence of explanation. Canceling after a courier was already working on the
order is billed as a cancellation fee, as it is through the API.
Play a courier scenario
simulate creates a test delivery with the quickstart’s sample drop-off and asks
the simulated courier to play returned, no_courier, delayed_pickup or
courier_reassigned (see test mode). It uses your first
test store, or the one you name with --store st_.... With listo listen running
in another window you watch the events it produces. It runs in test mode only: a
live key is refused before anything is called.
Live mode
Add--live and the command acts on your live account, with real money and real
couriers. Every line that names a mode shows a [LIVE] badge instead of [TEST].
Reading is free of ceremony (whoami, deliveries get, listen, logs tail).
Writing asks first, and the question says exactly what will happen:
POST /v1/quotes for the same store and
drop-off, and on y it books that quote, so the amount you read is the amount
charged. Any answer but y cancels, and nothing is changed.
deliveries cancel --liveshows the delivery, its status and what the cancel can cost before it asks.trigger --livesays that the event will be recorded on your live account, and with--deliverthat it will reach your live webhook URL.--yesskips the question, for scripts you have decided to trust. For a delivery it still prints the sentence, so the log says what was done.- With no terminal to ask on (a script, a pipe) and no
--yes, the command refuses with exit code 2 and the lineAdd --yes.. It never proceeds on its own.
In scripts
Output is JSON when you add--json and whenever stdout is not a terminal, so
a pipe always gets something a program can read; colour appears only on a
terminal and NO_COLOR turns it off.
listen and logs tail print one JSON object per line. An error goes to stderr
as one JSON object (code, message, and param, doc_url, request_id and
next_step when there are any), and the human lines that say what happened
(Ready, Sent, the confirmation sentence) go to stderr too, so stdout carries
nothing but JSON.
Language
The tool speaks English or Spanish. It uses the language you ask for with--lang es
or --lang en, else LISTO_LANG, else your computer’s (LC_ALL, LC_MESSAGES,
LANG, and on a Mac its system language), and anything that starts with es is
Spanish. Commands, flags and JSON keys stay English. The language also goes to the
API with every call, so an error’s message arrives in it.
Environment variables
Keep it up to date
Once a day, on a terminal,listo prints one line on stderr when a newer
version exists, naming the command that upgrades it the way you installed it. It
is never printed with --json or when the output is piped.
listo update replaces the program itself when you installed it with the install
script, after checking the download against Listo’s release key and its checksum;
nothing is replaced unless both pass. For an npm or Homebrew copy it prints that
tool’s command instead. listo update --check only tells you whether a newer
version exists. It never updates in the background, so the tool you are using
does not change under you mid-session.