curl --request GET \
--url https://api.listo.delivery/v1/ledger \
--header 'Authorization: Bearer <token>'import requests
url = "https://api.listo.delivery/v1/ledger"
headers = {"Authorization": "Bearer <token>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
fetch('https://api.listo.delivery/v1/ledger', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.listo.delivery/v1/ledger",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.listo.delivery/v1/ledger"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://api.listo.delivery/v1/ledger")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.listo.delivery/v1/ledger")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Bearer <token>'
response = http.request(request)
puts response.read_body{
"data": [
{
"id": "le_3kR7pM1xV9tB4nQ6cW2sZdYe",
"type": "provider_charge",
"amount_cents": -5800,
"delivery_id": "dlv_7hK2mQ9xR4tV6bN1cZ8sLwYe",
"store_id": "st_4bR8kM2wQ7xT1nV5cZ9pLdYe",
"test": false,
"occurred_at": "2026-08-01T12:00:00Z"
}
],
"has_more": false,
"cursor": null
}{
"error": {
"code": "invalid_api_key",
"message": "The API key is missing, revoked, or not valid for this mode.",
"param": null,
"retryable": false,
"doc_url": "https://docs.listo.delivery/en/errors/invalid_api_key",
"request_id": "0b1f6c9a-7e2d-4c53-9a41-5d8e3f2a6b10"
}
}{
"error": {
"code": "not_found",
"message": "No resource with that id exists for this API key.",
"param": null,
"retryable": false,
"doc_url": "https://docs.listo.delivery/en/errors/not_found",
"request_id": "0b1f6c9a-7e2d-4c53-9a41-5d8e3f2a6b10"
}
}List your ledger entries
Every movement on your balance, newest first, keyset-paginated. This is the reconciliation surface: close a period by walking it rather than by adding up webhook events, so an event you missed, or one you had resent, changes nothing about what you can rebuild.
amount_cents is signed, credits positive and debits negative, which is
what lets a page sum to the movement in your balance across it. Read the
direction off that sign and never off type.
type says where a movement came from. The pair worth knowing is the
one a wasted trip produces:
provider_chargeis the delivery fee billed for a late cancellation or a return trip, one fee per leg that was driven, less your own store markup. Negative, and tied to the order that caused it. This amount changed on 2026-08-29: it used to be what the wasted trip cost Listo, so expect larger values on the same orders from that date onward. The row itself is unchanged.provider_charge_reversalis that charge credited back in full, when the cost turns out not to have been owed. Positive, and posted as its own row against the samedelivery_idrather than as a reduction of the original charge, so what you were billed and what came back are both still readable. It can arrive weeks after the charge. Pair the two bydelivery_idand expect equal and opposite amounts.
A third value can appear on the same order and is a different amount under a different heading:
order_value_reimbursementis the value of an order a courier collected that reached nobody, credited after we claim it. Positive, tied to the order it belongs to, and NOT paired with any debit on this ledger: the order’s value never moved through your balance, so there is nothing to net it against. It is decided case by case and is not guaranteed, so never derive it from the order total or expect one per incident.listo_charge_correctionis a charge Listo made in error and gave back: aprovider_chargethat turned out not to be yours to pay. Positive, tied to the order it corrects, and posted as its own row beside the original charge, never as a reduction of it. Pair it with the charge bydelivery_id; it can be less than the charge, and an order can carry more than one. It is decided case by case, so never derive it from a charge or expect one per charge.requote_adjustmentis the difference between the fee your order was charged and the fee the courier network booked for its trip, when the two are not the same. Signed: negative when the booked trip cost more than you were charged, positive when it cost less. Tied to the order, posted as its own row beside the order’s own charge and never as a rewrite of it, and a retry of the same order trues it up to the trip finally booked rather than adding another. It is not paired with any other row, and it is uncommon.
One more is money you earn, and it is a credit:
developer_shareis what you earn on a delivered trip of your own store, credited to you when the trip is delivered. Positive, and tied to the order it belongs to. A trip that is canceled or returned earns none.
Two more are a store’s Plan Listo, paid from your balance:
plan_chargeis one month of a store’s plan, IVA included. Negative. A month paid by card writes no row, because it never touched the balance.plan_extra_chargeis the WhatsApp assistant’s conversations past what afullstore’s plan includes, charged to you with that store’s plan. Negative.
store_id names the store a movement belongs to, on every row: the
store of the order, or the store whose plan was paid. It is null when
a movement belongs to no single store, such as a top-up or a withdrawal.
This vocabulary GROWS. New values can appear in any release, because v1
changes additively and a movement you cannot yet name is still money
that moved. So branch on the values you know and let the rest fall
through: count an unfamiliar type toward the balance, show it, and do
not drop it or reject it. Rows you discard are the difference between
your books and GET /v1/balance.
curl --request GET \
--url https://api.listo.delivery/v1/ledger \
--header 'Authorization: Bearer <token>'import requests
url = "https://api.listo.delivery/v1/ledger"
headers = {"Authorization": "Bearer <token>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
fetch('https://api.listo.delivery/v1/ledger', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.listo.delivery/v1/ledger",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.listo.delivery/v1/ledger"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://api.listo.delivery/v1/ledger")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.listo.delivery/v1/ledger")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Bearer <token>'
response = http.request(request)
puts response.read_body{
"data": [
{
"id": "le_3kR7pM1xV9tB4nQ6cW2sZdYe",
"type": "provider_charge",
"amount_cents": -5800,
"delivery_id": "dlv_7hK2mQ9xR4tV6bN1cZ8sLwYe",
"store_id": "st_4bR8kM2wQ7xT1nV5cZ9pLdYe",
"test": false,
"occurred_at": "2026-08-01T12:00:00Z"
}
],
"has_more": false,
"cursor": null
}{
"error": {
"code": "invalid_api_key",
"message": "The API key is missing, revoked, or not valid for this mode.",
"param": null,
"retryable": false,
"doc_url": "https://docs.listo.delivery/en/errors/invalid_api_key",
"request_id": "0b1f6c9a-7e2d-4c53-9a41-5d8e3f2a6b10"
}
}{
"error": {
"code": "not_found",
"message": "No resource with that id exists for this API key.",
"param": null,
"retryable": false,
"doc_url": "https://docs.listo.delivery/en/errors/not_found",
"request_id": "0b1f6c9a-7e2d-4c53-9a41-5d8e3f2a6b10"
}
}Authorizations
Your API key, sent as Authorization: Bearer <key>. Live and test keys are separate; the key you send decides which world the call acts in. To try calls, use a test key (it starts with lst_test_): no real courier is sent and no real money moves, but webhooks fire and the dropoff.phone you give can receive WhatsApp messages, so use your own number. A live key (it starts with lst_live_) books and charges real deliveries.