el doc_url de un error no lleva diagonal final, y la documentación pasa a docs.listo.delivery
El
doc_url de un error ya no lleva diagonal final: .../en/errors/not_found en lugar de .../en/errors/not_found/. La documentación responde una página en la dirección sin ella, así que usa el valor tal como llega. La documentación ahora vive en https://docs.listo.delivery, y los enlaces a guías dentro de las descripciones de esta referencia apuntan allí; las direcciones anteriores bajo api.listo.delivery/docs responden con una redirección permanente a la misma página, salvo el documento OpenAPI, que sigue en https://api.listo.delivery/docs/openapi.yaml. La descripción del esquema de autenticación bearer ahora también dice que pruebes las llamadas con una llave de prueba (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), y que una llave real reserva y cobra envíos reales. Nada más cambia.tipos compartidos en cada respuesta, los eventos de webhook como una sola unión, y lo que el servidor exige
La referencia ahora describe cada respuesta y cada cuerpo de petición con un tipo compartido con nombre (
Delivery, Store, Quote, Withdrawal, Error y los demás) en lugar de una copia por endpoint, así que un cliente generado a partir de ella tiene un solo Delivery para manejar en todas partes. Los seis eventos de webhook, y los eventos que lees en GET /v1/events, POST /v1/events/{event_id}/resend y POST /v1/test_webhooks, son una sola unión WebhookEvent que se distingue por type. También dice lo que el servidor ya envía y revisa: un campo que puede ser null se tipa así, las listas cerradas (métodos de pago, formas de entrega, motivos de cancelación, niveles de acceso, tipos de retiro) se publican como enumeraciones, se publican los límites de una petición (una propina de hasta 100000 centavos, un margen de hasta 1000000, un porcentaje de la tarifa de 0 a 100, los rangos de latitud y longitud, la longitud de un external_id, de las notas y del motivo de un reembolso), pickup_ready_at en una creación y en una cotización es un date-time, y cada operación de escritura lista el 403 read_only_key con el que puede responder. Los endpoints que no reciben cuerpo (reintentos, el plan y su cancelación, el alta de tarjeta) ya no listan uno. La latitud y la longitud se publican como números de doble precisión (format: double), que es como el servidor las envía, así que un cliente generado no redondea una coordenada a 32 bits, y la lista de estados de una entrega ahora incluye awaiting_location, el estado de un pedido que tu tienda tomó por la consola de Listo o por el asistente de WhatsApp mientras espera a que el cliente confirme la ubicación de entrega. Nada de lo que la API envía o acepta cambia.cada error enlaza a su página y nombra la petición, encuentra una llamada por su id de petición, y llaves de solo lectura
Toda respuesta de error trae ahora dos campos más en
error: doc_url, la página de esta documentación que explica ese código de error y qué hacer al respecto, en el idioma que pediste con Accept-Language, y request_id, el id que Listo le da a la llamada, el mismo valor del encabezado de respuesta X-Request-Id. Pega el request_id en una solicitud a soporte. También puedes encontrar la llamada tú mismo: GET /v1/request_logs tiene un nuevo filtro request_id (coincidencia exacta), y la página Registro de solicitudes del panel tiene un buscador para él. Ahora una llave se puede emitir como de solo lectura: solo puede llamar GET y HEAD, y cualquier otra llamada con ella se rechaza con el nuevo error 403 read_only_key, así que puedes dársela a un agente de IA o a un panel que no debe mover dinero. Nada de lo que funcionaba antes cambia.ganancias de desarrollador en el libro mayor, y una tienda sin plan
El libro mayor suma el tipo
developer_share: lo que ganas en un viaje entregado de tu propia tienda, abonado a ti cuando el viaje se entrega. Una tienda de un partner con precios sin Plan Listo trae plan en null, porque no necesita plan.ensaya una devolución, la falta de repartidor y una recolección tardía con una llave de prueba
POST /v1/deliveries acepta un simulate opcional con una llave de prueba: returned regresa el pedido a la tienda, no_courier responde el mismo 409 no_couriers_available que una creación real y no crea nada, y delayed_pickup manda a un repartidor que recoge el pedido unos 25 minutos después de crearlo. Una llave real que mande simulate se rechaza con 422 validation_failed.mira tus propias llamadas, eventos e intentos de entrega, y reenvía un evento o envía uno de prueba
Ahora puedes depurar una integración tú mismo.
GET /v1/request_logs y GET /v1/request_logs/{id} listan las llamadas que hicieron tus llaves, con lo que enviaste y lo que respondimos. GET /v1/events y GET /v1/events/{id} listan los eventos que Listo emitió para ti, y GET /v1/webhook_deliveries lista cada intento de entregarlos y lo que contestó tu endpoint. POST /v1/events/{id}/resend envía un evento a tu endpoint otra vez, POST /v1/test_webhooks envía un evento de muestra de cualquier tipo con "test": true, y cuatro códigos de error nuevos (webhook_endpoint_not_configured, event_not_resendable, resend_limit_reached y test_webhook_limit_reached) dicen cuándo se rechaza uno. Un evento reenviado es la instantánea del momento en que ocurrió, así que GET /v1/deliveries sigue siendo la forma de recuperar cómo están las cosas ahora.la descripción del código de devolución, solo redacción
La descripción de
return_verification ahora dice con claridad para qué sirve el código y cuándo viene pincode: una cadena de 4 dígitos mientras el envío está en returning, null en cualquier otro caso. Nada cambió en la API.cada envío trae un código de devolución
Cada envío trae
return_verification, cuyo pincode es el código de devolución de 4 dígitos que la tienda le da al repartidor para completar una devolución. El objeto siempre viene, y pincode es una cadena de 4 dígitos mientras status es returning, y null en cualquier otro caso.los envíos traen sus pruebas de entrega, y los teléfonos se validan como E.164
Cada envío trae
proofs, las pruebas de entrega que dejó el viaje al recoger, al entregar y al devolver, y los enlaces de proof ahora son enlaces firmados de Listo que valen 24 horas y vienen en null durante los pocos segundos antes de que Listo tenga su copia. Los teléfonos se revisan con más rigor como E.164, y una cotización enviada sin payment_method se cotiza como prepaid (business_fee_share 100), así que solo la puede reservar un envío prepaid.Listo le manda a tu cliente el enlace de pago y los mensajes del repartidor
En un pedido
card, Listo ahora le manda el enlace de pago al cliente por su
cuenta. En cuanto el pedido se crea y queda esperando el pago, sale un WhatsApp
desde el número propio de Listo, en español, con el monto que debe y
payment.url, y si ese WhatsApp no le llega al cliente, Listo le manda el mismo
enlace por SMS. payment.url sigue en la respuesta, así que puedes mostrarlo o
guardarlo, pero ya no necesitas mandarlo, y no debes: el cliente recibiría el
mismo enlace dos veces, de dos remitentes. No cambió nada en el esquema, y
ningún socio había creado un pedido card antes, así que ninguna integración
existente cambia de comportamiento.También documentamos algo que ya era cierto y que la documentación negaba. Listo
ya le mandaba al cliente de cada pedido de la API sus mensajes del
repartidor, desde el mismo número: repartidor asignado, en camino, llegando y
entregado, con el enlace de seguimiento y el código de entrega cuando lo pediste.
Eso incluye los pedidos prepaid y cash, y la línea que decía que Listo nunca
trata con tu cliente en un pedido prepagado era incorrecta. Ve
lo que Listo le manda a tu cliente.un plan en espera de su primer pago, y un plan agotado rechaza envíos
Una tienda cuyo primer mes del Plan Listo no pudieron cubrir tu saldo y tu tarjeta ahora muestra
plan.status awaiting_payment en lugar de past_due, y Listo se lo cobra solo en cuanto puedan. Un plan que ya usó todos los envíos incluidos del ciclo rechaza los envíos con 402 plan_required.cada restaurante puede pagar su propio Plan Listo
Tu cuenta puede configurarse para que cada restaurante pague su propio Plan Listo, y entonces una tienda real se crea con
plan en null y no puede ser api_only.una cotización fija el cobro del pedido, y las diferencias de reserva caen en tu ledger
GET /v1/ledger tiene dos tipos de fila nuevos: requote_adjustment, que se explica abajo, y listo_charge_correction, un cargo que Listo hizo por error y devolvió.Una cotización se leía como la promesa de que pagarías exactamente lo cotizado,
sin importar cómo se moviera el costo de fondo antes de crear el envío. Esa
promesa se retira. Usar una cotización en POST /v1/deliveries sigue cobrándole
al pedido el fee_cents y el reparto de la cotización, y el total que ve tu
cliente nunca se mueve. Lo que cambia es qué pasa cuando la red de repartidores
reserva el viaje a un precio un poco distinto: la diferencia ya no se absorbe por
ti. Aparece en tu ledger como su propia fila requote_adjustment en
GET /v1/ledger, negativa si el viaje reservado costó más y positiva si costó
menos.Esto debería ser poco común: ahora una creación reserva la misma cotización que
viste siempre que siga vigente, y pide una nueva solo cuando expiró o cuando el
pedido cambió algo de lo que se cotizó. Un pedido en efectivo siempre reserva una
cotización nueva, porque la que viste no lleva el monto que cobra el repartidor.
requote_adjustment es un valor nuevo de type, y los valores de type crecen
en versiones ordinarias, así que sigue contando dentro de tu saldo los que no
reconozcas.los saldos muestran el efectivo que sigue en revisión
GET /v1/balance y GET /v1/stores/{store_id}/balance traen in_review_cents, efectivo que sigue en revisión y del que los envíos no pueden tomar.una red de repartidores ocupada responde 409 no_couriers_available
Cuando todos los repartidores cerca de un viaje están ocupados, la red de
repartidores se niega a cotizarlo por el momento. Ese rechazo te llegaba como
503 provider_unavailable, que dice que nuestro despacho está caído, y tu
monitoreo lo contaba como un error del servidor. Ahora responde
409 no_couriers_available con retryable: true, en POST /v1/quotes y en
POST /v1/deliveries, y no se crea ni se descuenta nada. La misma solicitud,
con la misma Idempotency-Key en una creación, pasa en cuanto haya un
repartidor libre, normalmente en unos minutos.no_couriers_available es la palabra que un envío terminado sin repartidor ya
trae en failure_reason, así que un mismo hecho ahora tiene un solo nombre
dondequiera que lo encuentres. Si reintentas todo 503 de inmediato, este
rechazo ya no entra en ese ciclo: reintenta en unos minutos, un número limitado
de veces, y después avísale a quien espera el pedido. provider_unavailable
sigue significando lo que dice, una caída del despacho.servicio hasta 15 km, y un destino en el límite se rechaza con out_of_service_area
La distancia a la que Listo entrega desde una tienda ahora es de 15 km en línea
recta desde la ubicación de la tienda; antes era de 10 km. Una cotización o un
envío cuya entrega quede más lejos se rechaza con
422 out_of_service_area,
como antes, a la nueva distancia.Una entrega a unas decenas de metros de ese límite también puede ser rechazada
por la red de repartidores, que mide los mismos 15 km desde su propio registro
de la tienda. Ese rechazo te llegaba como 503 provider_unavailable con
retryable: true, que te invitaba a reintentar una distancia que ningún
reintento cambia. Ahora responde 422 out_of_service_area con
retryable: false, en POST /v1/quotes, en POST /v1/deliveries y en
POST /v1/deliveries/{id}/retries, donde el envío se queda en no_courier y
no se cobra nada. Un envío que la red rechaza así después de creado trae
failure_reason: delivery_failed, como cualquier otra falla de nuestro lado.Rara vez el rechazo llega en POST /v1/deliveries mismo, un momento después de
que la red cotizó el viaje. La creación responde el mismo 422, con el mismo
cuerpo, y repetirla con la misma Idempotency-Key lo responde otra vez. Para
entonces el envío ya estaba registrado, así que se queda, terminado en
no_courier con tu external_id, con el cargo de su tarifa devuelto completo y
sin ningún otro cobro. Para mandar ese pedido a otro lado, créalo con un
external_id nuevo.Repetir una creación con la misma Idempotency-Key mientras la solicitud
original todavía se está procesando, su despacho incluido, responde
409 idempotency_conflict (en proceso) hasta que la original responda, y de ahí
en adelante la repetición devuelve la respuesta de la original, ese 422
incluido. Una repetición que llegaba mientras el despacho seguía en curso se
respondía con 201 a partir del envío, aunque la original terminara
respondiendo 422.Si ya manejas out_of_service_area, no tienes que cambiar nada. Si
reintentas todo 503, una entrega en el límite ahora se detiene en la primera
respuesta en lugar de en tu límite de reintentos.la hora de llegada a la entrega viene en null cuando falla una entrega
dropoff_eta_at es la hora a la que se espera que el repartidor llegue con el
cliente. Después de una entrega fallida traía otra hora: desde que un envío
entraba en returning guardaba cuándo se esperaba al repartidor de regreso en
la tienda, se movía mientras manejaba hacia allá y seguía ahí cuando el envío
quedaba returned. Ahora viene en null en un envío returning y en uno
returned, en toda respuesta y en todo webhook que lleve un envío, porque el
cliente no va a recibir nada y ya no queda una llegada a su puerta que
describir. La hora en que el pedido vuelve a la tienda no se publica en su
lugar.Si les muestras dropoff_eta_at a tus clientes o a tus comercios como hora de
llegada, un pedido que regresa ahora no muestra ninguna, cuando antes mostraba
la hora en que la comida volvía a la cocina como si llegara con el cliente.
returned sigue marcando el momento en que el pedido ya regresó.Ni la petición ni la forma de la respuesta cambiaron: el campo sigue viniendo
siempre, y null ya era uno de sus valores antes de que el repartidor tuviera
una estimación.cada tienda real lleva un Plan Listo que se te cobra a ti
Cada tienda que creas con una llave real lleva ahora un Plan Listo, y ese plan se
te cobra a ti: MX$1,990 más IVA al mes por tienda a precio de lista, o el precio
que acordaste con Listo.
POST /v1/stores con una llave real cobra el primer mes
de la tienda nueva en la misma petición, de tu saldo real o de la tarjeta
guardada en tu cuenta, así que fondea uno de los dos antes de crear una tienda y
manda un Idempotency-Key en cada creación real. Un mes pagado con el saldo
aparece en GET /v1/ledger como una fila plan_charge, y cada fila del
ledger trae ahora store_id, la tienda a la que pertenece, o null cuando no
pertenece a ninguna.A una tienda real cuyo plan no está pagado o ya terminó se le rechazan las
cotizaciones, los envíos y los reintentos con 402 plan_required, tenga lo que
tenga tu saldo. Cada objeto de tienda trae un bloque plan nuevo, que viene en
null en una tienda de prueba y en una tienda real creada antes de que existiera
el Plan Listo: esas siguen pidiendo repartidores como antes. Tres endpoints nuevos
manejan un plan: POST /v1/stores/{id}/plan_cancellation lo termina cuando se
acaba el periodo ya pagado, DELETE en la misma ruta lo deshace, y
POST /v1/stores/{id}/plan reactiva uno que terminó. Un restaurante que conecta
una cuenta propia y ya paga su propio plan lo sigue pagando hasta su siguiente
renovación, y de ahí en adelante se te cobra a ti. El objeto de tienda en
store.updated y store.connected ahora documenta plan, access_tier,
billing y markup_cents como siempre presentes; los últimos tres ya venían en
cada evento, y la referencia ahora lo dice. Cada bloque plan trae además
courtesy, que viene siempre: es true mientras el plan de la tienda es una
cortesía de Listo, cuando no se cobra nada y next_charge_on es null.Todo está en plan y cobro.envíos en coche, y se rechaza un pedido demasiado grande para un viaje
Una cotización y un envío aceptan
vehicle (moto o car), y manifest.items[].size (small, medium, large o xlarge) decide cuándo un viaje necesita coche; un pedido demasiado grande para un solo viaje se rechaza con 422 order_too_large.las tiendas de un restaurante conectado llevan el nombre del negocio
El
name de una tienda es el nombre que tú le diste, y en toda tienda que
creaste eso no cambia. Un restaurante que conecta una cuenta con varias
sucursales ahora llega con cada tienda bajo el nombre del negocio y de la
sucursal, Tortería La Mera Mera, Zapopan, como la leen sus propios clientes,
cuando antes llegaba solo con el nombre de la sucursal (Zapopan). Una
sucursal cuyo nombre ya empieza con el del negocio, como
Tortería La Mera Mera Centro, se publica tal cual. El mismo nombre regresa en
el canje, en store.connected y store.updated, y en GET /v1/stores.
Cuando un restaurante conectado cambia el nombre de su negocio, recibes un
store.updated por cada una de sus tiendas, con el nombre nuevo.Renombrar una tienda con PATCH /v1/stores/{id} ahora renombra también su
negocio cuando los dos llevan un solo nombre, como toda tienda que creaste.
Antes solo cambiaba el nombre de la tienda, así que lo que veían los clientes
del restaurante seguía empezando con el nombre anterior. Una sucursal de un
restaurante con varias sucursales, o una tienda cuyo nombre ya es distinto del
de su negocio, se renombra sola y se lee como el negocio seguido del nombre
nuevo. Mandar de regreso el nombre que leíste es seguro: un nombre que empieza
con el nombre del negocio y una coma se lee como el negocio y la sucursal, así
que no renombra nada, y Tortería La Mera Mera, Centro solo renombra la
sucursal.Ya no se manda un store.updated cuando el objeto de tienda que llevaría es
exactamente el último que recibiste para esa tienda, así que un guardado que no
cambia nada de lo que ves no manda nada, y un cambio es un solo evento. El
evento de una tienda cuya capacidad de cobrar con tarjeta cambió se manda igual
que antes, aunque su objeto no cambie.Ni la petición ni la forma de la respuesta cambiaron.retiros el mismo día, y un rechazo mientras se confirma el punto de recolección
Los retiros pueden ser el mismo día:
POST /v1/withdrawals acepta kind, GET /v1/balance trae same_day, y un retiro trae kind, fee_cents y due_at. Una tienda cuyo punto de recolección aún se está confirmando se rechaza con un 409 pickup_point_pending que se puede reintentar.la zona horaria de una tienda sigue a su ubicación, y un envío puede originarse en WhatsApp
El
origin de un envío tiene un tercer valor, whatsapp: un envío creado a partir de un pedido que un cliente hizo por WhatsApp y que la tienda aceptó.POST /v1/stores y PATCH /v1/stores/{id} aceptan una timezone, y antes se
guardaba tal cual la mandabas. Ahora es el respaldo: la ubicación de la tienda
decide su zona horaria, y lo que mandas se usa solo cuando la ubicación no
puede responder, es decir una tienda sin coordenadas o una posición que ninguna
zona reclama.Mandar una zona que no coincide con la ubicación no cambia nada y no es un
error. La tienda se crea o se actualiza, se aplica todo lo demás que mandaste, y
la respuesta trae la zona que da la ubicación, así que lo que lees de regreso
siempre es lo que la tienda realmente usa.El cambio de zona horaria no mueve nada en la petición ni en la forma de la
respuesta, y el campo no está obsoleto. Lo que cambió es cuál de las dos respuestas gana. Lo invertimos porque
desde aquí no podemos distinguir los dos casos: una zona que sale por omisión de
un formulario que nunca se la preguntó a nadie llega idéntica a una zona que
alguien eligió, y una tienda con la hora equivocada desplaza sin avisar sus
horarios de atención, las marcas de tiempo de sus envíos y la semana en la que se liquidan
sus ingresos. Si el reloj de una tienda te parece incorrecto, lo que hay que
corregir son sus coordenadas.las tiendas pueden dividir la tarifa de envío por un monto fijo
default_fee_split_mode y default_fee_split_amount_cents de una tienda te dicen cuándo el restaurante divide la tarifa de envío por un monto fijo en pesos en lugar de un porcentaje, así que lee el modo antes que default_business_fee_share.un viaje desperdiciado se cobra a la tarifa de envío
Un
provider_charge en GET /v1/ledger, y la entrada correspondiente en el
arreglo charges de un envío, antes era lo que ese viaje desperdiciado le
costaba a Listo. Ahora es la tarifa de envío por cada tramo que sí se manejó,
menos tu propio margen de tienda: un tramo por una cancelación lo bastante
tardía como para que ya hubiera un repartidor trabajando en el pedido, dos por
un viaje de devolución tras una entrega fallida, y ninguno cuando no se manejó
ningún tramo.La forma de la respuesta no cambió. Regresan los mismos campos y los mismos
tipos, y no hay integración que ajustar. Lo que cambia es el monto: espera valores más altos en los mismos pedidos a partir de esta fecha, y
si le trasladas estos cargos a tus propios comercios, cóbrales lo que dice la
fila y no una cifra que hayas calculado antes.notas de entrega editables en camino, y se rechazan tiendas fuera de México
PATCH /v1/deliveries/{id} ahora también acepta dropoff.notes, para corregir lo que se le dice al repartidor sobre la entrega mientras el pedido va en camino, y responde 409 not_editable cuando ya es tarde. Una tienda fuera de México se rechaza con 422 unsupported_country. La referencia de PATCH también deja de listar cuatro campos de la solicitud que el endpoint nunca leyó.un envío cuya entrega falló ahora está en devolución
Un envío cuya entrega falló ahora tiene el estado
returning mientras el repartidor regresa el pedido a la tienda, hasta que termina en returned. Sus charges ya vienen completos en el primer evento returning.reembolsa el pago con tarjeta de tu cliente
Puedes reembolsar el pago con tarjeta de tu cliente con
POST /v1/deliveries/{delivery_id}/refunds, y cada envío trae refunds, refunded_cents y refundable_cents.los retiros traen clave de rastreo y exigen una cuenta bancaria registrada
Un retiro ahora trae
clave_de_rastreo, la clave de rastreo SPEI que tu banco usa para ubicar el depósito, una vez pagado, y POST /v1/withdrawals se rechaza con 422 payout_account_not_ready mientras no tengas una cuenta bancaria registrada.conecta a un restaurante que ya tiene cuenta de Listo
POST /v1/connect_requests y POST /v1/connections conectan a un restaurante que ya tiene cuenta de Listo, con la aprobación de su dueño, y el nuevo webhook store.connected te avisa cuando ocurre. POST /v1/stores también acepta owner_email.tus ids de tienda, creación idempotente de tiendas y el origen del envío
Una tienda lleva tu propio
external_id, que puedes enviar al crear y al actualizar y usar para filtrar GET /v1/stores, y POST /v1/stores respeta Idempotency-Key y responde 409 si reutilizas una llave con otro cuerpo. Cada envío también dice de dónde vino en origin, por el que puede filtrar GET /v1/deliveries.los envíos aceptan una propina para el repartidor
POST /v1/deliveries acepta tip_cents, una propina para el repartidor que cada envío devuelve. La referencia ahora marca charges, access_tier, billing y markup_cents como siempre presentes, como ya lo eran.un tope a tu parte de la tarifa de envío
POST /v1/quotes y POST /v1/deliveries aceptan business_fee_cap_cents, un tope en centavos a tu parte de la tarifa de envío.las tiendas tienen nivel de acceso y margen, con su propio saldo y ledger
Las tiendas ahora tienen
access_tier (api_only, wallet o full) y markup_cents, y billing dice de qué saldo se pagan sus envíos. El saldo y los movimientos propios de una tienda están en GET /v1/stores/{store_id}/balance y GET /v1/stores/{store_id}/ledger, y POST /v1/stores/{store_id}/owner invita a quien la dirige a su consola de Listo.los envíos traen sus cargos, y el ledger lista los movimientos de tu saldo
Cada envío ahora trae
charges, lo que te costó un viaje cancelado o devuelto, y GET /v1/ledger lista cada movimiento de tu saldo. La referencia también muestra la forma de un reintento que falla.la primera versión publicada
La primera versión publicada de la API de Envíos para Socios.