curl --request POST \
--url https://api.listo.delivery/v1/withdrawals \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"amount_cents": 250000,
"kind": "standard"
}
'import requests
url = "https://api.listo.delivery/v1/withdrawals"
payload = {
"amount_cents": 250000,
"kind": "standard"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({amount_cents: 250000, kind: 'standard'})
};
fetch('https://api.listo.delivery/v1/withdrawals', 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/withdrawals",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'amount_cents' => 250000,
'kind' => 'standard'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.listo.delivery/v1/withdrawals"
payload := strings.NewReader("{\n \"amount_cents\": 250000,\n \"kind\": \"standard\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.listo.delivery/v1/withdrawals")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"amount_cents\": 250000,\n \"kind\": \"standard\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.listo.delivery/v1/withdrawals")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"amount_cents\": 250000,\n \"kind\": \"standard\"\n}"
response = http.request(request)
puts response.read_body{
"id": "wd_2xN6bK9mR4tQ7vC1sZ8pLwYe",
"amount_cents": 250000,
"currency": "MXN",
"status": "pending",
"requested_at": "2026-07-30T18:30:00Z",
"resolved_at": null,
"test": false,
"clave_de_rastreo": null,
"fee_cents": 0,
"kind": "standard",
"due_at": 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": "insufficient_balance",
"message": "The account balance does not cover this request.",
"param": null,
"retryable": false,
"doc_url": "https://docs.listo.delivery/en/errors/insufficient_balance",
"request_id": "0b1f6c9a-7e2d-4c53-9a41-5d8e3f2a6b10"
}
}{
"error": {
"code": "read_only_key",
"message": "This API key is read-only. Use a key without the read-only setting to create, change or cancel anything.",
"param": null,
"retryable": false,
"doc_url": "https://docs.listo.delivery/en/errors/read_only_key",
"request_id": "0b1f6c9a-7e2d-4c53-9a41-5d8e3f2a6b10"
}
}{
"error": {
"code": "validation_failed",
"message": "Amount cents must be an integer",
"param": "amount_cents",
"retryable": false,
"doc_url": "https://docs.listo.delivery/en/errors/validation_failed",
"request_id": "0b1f6c9a-7e2d-4c53-9a41-5d8e3f2a6b10"
}
}Solicita un retiro
Solicita una transferencia de amount_cents desde tu saldo a tu cuenta bancaria registrada. El dinero se reserva en el momento en que se acepta la solicitud, así que una segunda solicitud no puede gastarlo dos veces.
La solicitud la liquida una persona, normalmente dentro de un día hábil, y el resultado llega como un webhook withdrawal.updated y no en esta respuesta. Pedir más que available_cents es un 402.
Necesitas una cuenta bancaria registrada antes de tu primer retiro, desde el panel y no desde esta API. Sin una, esta llamada responde 422 payout_account_not_ready y no se reserva nada. El mismo rechazo aplica al día después de que CAMBIAS la cuenta: un destino recién cambiado no puede recibir dinero durante 24 horas, y el message indica el momento en que vuelve a poder hacerlo.
Retiros el mismo día
kind es opcional y es standard si lo omites, que es el camino gratuito de arriba y no cambia. Envía "kind": "same_day" para que se envíe HOY:
- Cuesta una tarifa fija, con IVA incluido, que la respuesta reporta como
fee_cents. La tarifa es un SEGUNDO cargo y nunca una deducción, así que tu banco recibe todo elamount_centsy tu saldo pierde los dos juntos. - Puede usar efectivo que todavía está retenido, algo que el camino gratuito no puede, así que
available_centsno es el límite aquí. - Las solicitudes se aceptan a cualquier hora. Una hecha entre las 8 am y las 8 pm de la Ciudad de México se envía dentro de 3 horas; una hecha fuera de ese horario sale a la mañana siguiente.
due_aten la respuesta es la fecha límite que tu solicitud lleva realmente, fijada en el momento en que la hiciste. - Hay un mínimo por solicitud y un máximo diario por cuenta.
Tres rechazos son específicos de este camino y cada uno nombra el límite en message: 402 insufficient_balance cuando el monto más la tarifa es más de lo que se puede retirar hoy, 422 same_day_cap_exceeded cuando el máximo del día no tiene lugar para él, y 422 same_day_below_minimum cuando el monto está por debajo del mínimo. Un kind que no es ninguna de las dos palabras es 422 validation_failed con param: "kind", nunca un retorno silencioso al camino gratuito.
curl --request POST \
--url https://api.listo.delivery/v1/withdrawals \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"amount_cents": 250000,
"kind": "standard"
}
'import requests
url = "https://api.listo.delivery/v1/withdrawals"
payload = {
"amount_cents": 250000,
"kind": "standard"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({amount_cents: 250000, kind: 'standard'})
};
fetch('https://api.listo.delivery/v1/withdrawals', 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/withdrawals",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'amount_cents' => 250000,
'kind' => 'standard'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.listo.delivery/v1/withdrawals"
payload := strings.NewReader("{\n \"amount_cents\": 250000,\n \"kind\": \"standard\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.listo.delivery/v1/withdrawals")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"amount_cents\": 250000,\n \"kind\": \"standard\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.listo.delivery/v1/withdrawals")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"amount_cents\": 250000,\n \"kind\": \"standard\"\n}"
response = http.request(request)
puts response.read_body{
"id": "wd_2xN6bK9mR4tQ7vC1sZ8pLwYe",
"amount_cents": 250000,
"currency": "MXN",
"status": "pending",
"requested_at": "2026-07-30T18:30:00Z",
"resolved_at": null,
"test": false,
"clave_de_rastreo": null,
"fee_cents": 0,
"kind": "standard",
"due_at": 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": "insufficient_balance",
"message": "The account balance does not cover this request.",
"param": null,
"retryable": false,
"doc_url": "https://docs.listo.delivery/en/errors/insufficient_balance",
"request_id": "0b1f6c9a-7e2d-4c53-9a41-5d8e3f2a6b10"
}
}{
"error": {
"code": "read_only_key",
"message": "This API key is read-only. Use a key without the read-only setting to create, change or cancel anything.",
"param": null,
"retryable": false,
"doc_url": "https://docs.listo.delivery/en/errors/read_only_key",
"request_id": "0b1f6c9a-7e2d-4c53-9a41-5d8e3f2a6b10"
}
}{
"error": {
"code": "validation_failed",
"message": "Amount cents must be an integer",
"param": "amount_cents",
"retryable": false,
"doc_url": "https://docs.listo.delivery/en/errors/validation_failed",
"request_id": "0b1f6c9a-7e2d-4c53-9a41-5d8e3f2a6b10"
}
}Autorizaciones
Tu llave de API, enviada como Authorization: Bearer <key>. Las llaves reales y de prueba son distintas; la llave que envías decide en qué modo actúa la llamada. Para probar llamadas, usa una llave de prueba (empieza con lst_test_): no se envía ningún repartidor real y no se mueve dinero real, pero los webhooks se disparan y el dropoff.phone que des puede recibir mensajes de WhatsApp, así que usa tu propio número. Una llave real (empieza con lst_live_) reserva y cobra envíos reales.
Cuerpo
Respuesta
Creado.
Una solicitud para mover dinero de tu saldo a tu cuenta bancaria.
El id del retiro, con prefijo wd_.
"wd_2xN6bK9mR4tQ7vC1sZ8pLwYe"
Lo que recibe tu banco. En un retiro same_day la tarifa es un SEGUNDO cargo en lugar de una deducción, así que esta cifra es lo que llega en cualquiera de los dos casos, y fee_cents es lo que tu saldo pierde encima de ella.
Cuánto costó el retiro, con IVA incluido. Siempre 0 en un retiro standard. Siempre está presente.
MXN standard es la vía gratuita: solo tu saldo liberado, enviado en 1 a 3 días hábiles. same_day cuesta fee_cents, puede alcanzar efectivo que todavía está retenido, y se envía el mismo día dentro de los horarios indicados en POST /v1/withdrawals.
standard, same_day pending hasta que una persona lo liquide. El paso a paid o failed es lo que anuncia withdrawal.updated.
pending, paid, failed Cuándo se espera que se haya enviado un retiro same_day, sellado cuando lo solicitaste y nunca recalculado después. Es null en cada retiro standard: la vía gratuita promete un rango de días hábiles en lugar de un instante.
Null mientras siga pendiente.
La clave de rastreo SPEI de la transferencia: el identificador que tu propio banco usa para localizar el depósito. Es con lo que concilias un pago contra un estado de cuenta bancario, y es la misma clave que ves en el panel de este retiro.
Siempre está presente, y es null hasta que se envía la transferencia. Se queda en null
para siempre en un retiro failed, porque nunca hubo transferencia.
"MBAN01002608120001234567"