Saltar al contenido principal
Todos los cambios al contrato v1, del más reciente al más antiguo. Cada versión dice qué cambió, algunas con detalle cuando cambió el comportamiento. Los cambios al esquema de cada versión van plegados y se generan comparando su documento OpenAPI contra el de la versión anterior, así que dicen exactamente lo que dice la API y nada más. Los números de versión nombran una versión publicada del esquema. No puedes elegir una: todos los clientes reciben siempre la más reciente. Los cambios a v1 son aditivos, salvo un cambio marcado como Cambio incompatible, que te avisamos antes de publicarlo. Pueden aparecer campos nuevos y valores nuevos en los enums en cualquier momento, así que ignora lo que no reconozcas en lugar de fallar.
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.