Appearance
Crear un pedido
POST /v1/orders con scope write:orders.
Campos
| Campo | Tipo | Obligatorio | Notas |
|---|---|---|---|
recipient_name | string | sí | Nombre del destinatario |
recipient_phone | string | sí | Celular del destinatario. Se guarda en formato E.164 (+51987654321); si mandas 987654321 se canonicaliza con el país del delivery |
delivery_address | string | sí | Dirección tal como se la dirías al motorizado |
location_id | uuid | sí | Distrito o zona del catálogo. Ver cómo obtenerlo |
product | string | sí | Qué va en el paquete, en una línea |
delivery_reference | string | no | "Frente al parque, portón negro" |
delivery_lat, delivery_lng | number | no | El punto GPS del cliente |
delivery_maps_url | string | no | En vez de lat/lng: el enlace de Maps que el cliente compartió por WhatsApp, acortado o largo |
amount_to_collect | number | no | Cuánto cobra el motorizado al entregar. 0 o ausente = solo entrega |
payment_type | string | no | cash_on_delivery | delivery_only. Ver reglas |
recipient_doc_type | string | no | dni | ce | passport | other |
recipient_doc_number | string | no | Solo el DNI peruano se verifica contra RENIEC; los demás quedan como dato declarado |
delivery_type | string | no | home (default) | agency |
size | string | no | Código de tamaño del delivery. Si no lo mandas o no existe, se usa el tamaño por defecto del operador |
store_id | uuid | no | Solo con llave sin tienda. Con llave atada a una tienda se ignora |
branch_id | uuid | no | Sucursal del delivery que recibe. Si no lo mandas, la principal |
notes | string | no | Indicaciones para el despacho |
El punto GPS
Sin punto el motorizado no puede navegar. Manda uno de los dos:
json
{ "delivery_lat": -12.1219, "delivery_lng": -77.0297 }json
{ "delivery_maps_url": "https://maps.app.goo.gl/AbCdEfGh" }El enlace acortado (maps.app.goo.gl) no trae las coordenadas dentro: el servidor sigue el redirect para sacarlas. Si no puede, el pedido se crea igual y el punto se completa después desde el panel. No lo intentes resolver tú en el navegador: el redirect no se puede seguir por CORS.
El location_id
Es el distrito (o la zona dentro del distrito) del catálogo compartido de Motochaski. Es lo que decide la tarifa y si el delivery cubre el destino.
Sin autenticación:
bash
# Distritos de una ciudad con sus zonas, ensamblado
curl "https://api.motochaski.com/public/locations/tree?country_code=PE&city=Lima"
# Si tu sistema ya trabaja con ubigeo (INEI)
curl "https://api.motochaski.com/public/locations/ubigeo?country_code=PE&codes=150122,150131"
# Búsqueda por texto
curl "https://api.motochaski.com/public/locations?country_code=PE&q=miraflores"Cachea el catálogo: cambia poco y no cuenta para tu límite de peticiones, pero tampoco hace falta pedirlo en cada pedido.
Cobro contra entrega
payment_type se deriva del monto en una sola dirección:
amount_to_collect > 0→ siemprecash_on_delivery, mandes lo que mandes.- Sin monto → se respeta lo que declares; si no declaras nada,
delivery_only.
cash_on_delivery con monto 0 es válido: significa "hay que cobrar pero la tienda aún no fija el precio". No lo degrades a delivery_only desde tu lado.
Destino sin cobertura
Por API el destino que el delivery no atiende se rechaza con 422:
json
{ "error": "El operador no atiende ese destino: no está en su cobertura ni tiene tarifa configurada" }En el panel es solo un aviso porque hay una persona que puede cubrir el distrito en el momento; del otro lado de la API no hay nadie, y un pedido sin precio se descubriría recién en la liquidación.
Zona de riesgo
Si la dirección coincide con una zona marcada como de riesgo por el delivery, el pedido se crea igual y la respuesta trae warning. El despachador lo ve con un aviso.
Respuesta
201 con el pedido en data. Los campos que vas a querer guardar:
| Campo | Para qué |
|---|---|
id | Consultar y cancelar (GET/PATCH /v1/orders/:id) |
guide_code | Lo que va en el sticker y lo que usa el cliente para el seguimiento |
shipping_cost | Lo que el delivery le cobra a la tienda por este envío |
status | registered hasta que el paquete llegue físicamente al delivery |
Cancelar
PATCH /v1/orders/:id/cancel con scope write:orders.
json
{ "reason": "customer_cancelled", "note": "El cliente ya no lo quiere" }Solo mientras el pedido está en registered: después el paquete ya salió del mostrador de la tienda y la cancelación la hace el delivery desde su panel. Si ya lo recibieron, 400.
Auto-cancelación
Un pedido que sigue en registered 24 horas después de creado —el paquete nunca llegó al delivery— se cancela solo y dispara order.cancelled. Si la tienda lo lleva tarde, el delivery puede recibirlo igual desde su panel y el pedido revive con el mismo id y el mismo guide_code. No hay evento de recepción: el siguiente que te llega es order.assigned. Así que no borres el pedido de tu lado al recibir order.cancelled; márcalo y espera.
Consultar
bash
# Uno
curl https://api.motochaski.com/v1/orders/b1c2d3… -H "X-API-Key: …"
# Lista, con filtros opcionales
curl "https://api.motochaski.com/v1/orders?status=delivered&from_date=2026-09-01&to_date=2026-09-18&page=1&limit=50" \
-H "X-API-Key: …"La lista devuelve { "orders": [...], "total": n, "page": p, "limit": l }. Con llave atada a una tienda, solo sus pedidos.
Reintentar sin duplicar: Idempotency-Key
Si tu request de creación falla por red después de que el servidor lo procesó, de tu lado solo hay un error, y reintentarlo a secas crea un segundo pedido: dos guías, dos stickers, el motorizado dos veces a la misma casa.
Manda en cada POST /v1/orders una cabecera con un identificador que tú inventas, único por pedido — lo natural es el id de tu venta:
bash
curl -X POST https://api.motochaski.com/v1/orders \
-H "X-API-Key: mck_live_…" \
-H "Idempotency-Key: venta-48213" \
-H "Content-Type: application/json" \
-d '{ ... }'Con eso, reintentar es seguro:
| Situación | Respuesta |
|---|---|
| Primera vez | Se crea el pedido. 201 |
| Misma clave, mismo body, dentro de 24 h | No se crea nada. La misma respuesta de la primera vez, byte a byte, con la cabecera Idempotent-Replayed: true |
| Misma clave, otro body | 422 — reusaste el id de una venta para otra. Usa una clave nueva |
| Misma clave mientras la primera todavía se procesa | 409 — espera unos segundos y reintenta |
| Misma clave después de 24 h | Se trata como nueva |
Solo se guarda la respuesta cuando el pedido se creó (2xx). Si el primer intento falló —422 por cobertura, 400 por un campo—, la clave queda libre y el reintento con el body corregido entra normal.
Reglas:
- Hasta 255 caracteres. Cualquier texto; lo importante es que sea único por pedido en tu sistema.
- La clave se acota a tu llave de API: otra tienda con
venta-48213no choca contigo. - Sin cabecera, cada request es un pedido nuevo. Es opcional, pero no hay motivo para no mandarla.