Documentación / Cotización de envíos
Cotización de envíos
Calcula cuánto cobrar por un delivery, un courier o un traslado con tu propio tarifario, y devuelve el precio a tu app con su desglose. Base URL: https://apivalidape.com/api/v1.
1. Cómo funciona
- Tarifario: lo configuras tú en el dashboard (Envíos) a partir de una plantilla. Hay cinco: delivery por km, delivery por distritos de Lima, delivery por tramos, courier nacional (zonas × peso) y movilidad. Defines precios, vehículos, recargos (nocturno, feriados, lluvia, paradas, espera, contraentrega, seguro…) y tus ajustes (margen %, monto fijo, mínimo, envío gratis, redondeo).
- Cotización: tu backend envía origen, destino y los datos del envío; ValidApi aplica tu tarifario y responde el total con su desglose y un
id(quo_…) con el precio fijado por unos minutos. - Ubicación: coordenadas (
lat/lng) o solo el ubigeo INEI de 6 dígitos (se usa la capital del distrito). Si ya calculas la ruta con tu proveedor de mapas, envía su distancia. - Costo: S/ 0.05 por cada cotización, descontado de tu saldo prepago. Consultar una cotización, listar tarifarios y el simulador del dashboard son gratis.
- Hora: todo se calcula en hora de Perú (UTC−5). Las fechas sin zona horaria se toman como hora de Perú.
2. Inicio rápido
- Crea tu cuenta y verifica tu correo. Tu cuenta trae un saldo inicial para probar; recarga desde el dashboard cuando lo necesites.
- En Dashboard → Envíos, crea tu tarifario desde la plantilla que más se parece a tu operación y ajusta los montos. El primero queda como predeterminado.
- Pruébalo con el simulador de la misma pantalla: es gratis y usa la configuración que estás editando.
- En Dashboard → API Keys, crea una API Key y guárdala como secreto en tu backend.
- Desde tu backend, cotiza con
POST /shipping/quotecuando el cliente confirme la dirección, muestra el total y guarda elid. - Al cerrar la compra, verifica la cotización (
GET /shipping/quotes/:id) o crea el cobro Yape/Plin conshippingQuoteId.
3. Cómo probar
Sin costo: el simulador del dashboard (Envíos → Editar y simular) calcula exactamente lo mismo que la API, incluidas la ruta real y la lluvia, sin cobrar ni guardar (hasta 20 por minuto).
Por API (cada cotización cuesta S/ 0.05 y se descuenta de tu saldo):
# 1) ¿Qué tarifarios tengo y qué datos piden? (gratis)
curl https://apivalidape.com/api/v1/shipping/rate-cards -H "X-API-Key: vapi_xxx"
# 2) Cotizar solo con ubigeo: Miraflores (150122) → Cercado de Lima (150101)
curl -X POST https://apivalidape.com/api/v1/shipping/quote \
-H "X-API-Key: vapi_xxx" -H "Content-Type: application/json" \
-d '{ "origin": { "ubigeo": "150122" }, "destination": { "ubigeo": "150101" } }'
# 3) Con coordenadas, vehículo y contraentrega
curl -X POST https://apivalidape.com/api/v1/shipping/quote \
-H "X-API-Key: vapi_xxx" -H "Content-Type: application/json" \
-d '{
"origin": { "lat": -12.1211, "lng": -77.0297 },
"destination": { "lat": -12.0464, "lng": -77.0428 },
"vehicle": "moto",
"orderValue": 80,
"options": { "cod": true }
}'
# 4) Verificar la cotización (gratis)
curl https://apivalidape.com/api/v1/shipping/quotes/quo_… -H "X-API-Key: vapi_xxx"Para probar el cobro completo, crea el pedido de Yape/Plin con "mode": "test" y shippingQuoteId (ver sección 9): en modo prueba no se cobra la conciliación y puedes simular el aviso del banco.
4. POST /shipping/quote
Autenticación con X-API-Key. Todos los campos son opcionales salvo origin y destination; lo que haga falta depende de tu tarifario (sección 6).
| Campo | Tipo | Descripción |
|---|---|---|
rateCardId | uuid | Tarifario a usar. Si falta, se usa tu tarifario predeterminado. |
origin | objeto | { lat, lng, ubigeo }: coordenadas y/o ubigeo INEI (6 dígitos; se aceptan 2 o 4). |
destination | objeto | Igual que origin. |
stops[] | lista | Paradas intermedias en orden (máx. 20), mismo formato. Suman distancia y el recargo por parada. |
route | objeto | { distanceKm, durationMin? }: ruta que ya calculaste. Tiene prioridad sobre la estimación. |
packages[] | lista | { weightKg, lengthCm, widthCm, heightCm, quantity }. Se cobra el mayor entre peso real y volumétrico (L×A×H/5000). |
vehicle | texto | Clave del vehículo de tu tarifario (ej. moto, auto). Si falta, el primero. |
scheduledAt | ISO 8601 | Momento del envío (default: ahora). Define recargos por horario, feriado y lluvia. Sin zona = hora de Perú. |
orderValue | número | Valor del pedido: envío gratis desde un monto, tarifa de servicio, pedido pequeño, base de la contraentrega. |
declaredValue | número | Valor declarado de la carga (seguro). |
waitingMinutes | número | Minutos de espera (recargo por espera). |
demandFactor | número | Factor de demanda que mides tú (ej. 1.3); tu tarifario lo acota. |
options | objeto | { cod, codAmount, insurance, fragile, returnTrip }: contraentrega, seguro, frágil, retorno. |
5. Respuesta
{
"success": true,
"data": {
"id": "quo_50c7c7b2bbce2a4ca80af261",
"rateCard": { "id": "231c45c6-…", "name": "Delivery Lima" },
"serviceable": true,
"reasons": [],
"currency": "PEN",
"total": 17.00,
"subtotal": 14.41,
"tax": 2.59,
"breakdown": [
{ "code": "base.distance_time", "label": "Tarifa por recorrido (11.38 km)", "amount": 15.38 },
{ "code": "surcharge.cod", "label": "Contraentrega", "amount": 1.60 },
{ "code": "rounding", "label": "Redondeo", "amount": 0.02 }
],
"distanceKm": 11.38,
"durationMin": 31.04,
"distanceSource": "estimated",
"billableWeightKg": null,
"vehicle": "moto",
"zones": null,
"freeShipping": false,
"locatedByUbigeo": false,
"expiresAt": "2026-09-24T16:16:10.309Z",
"expired": false,
"usedByPaymentId": null,
"createdAt": "2026-09-24T16:01:10.315Z"
},
"usage": { "cost": 0.05, "balanceRemaining": 4.95 }
}| Campo | Significado |
|---|---|
total | Lo que cobras al cliente. Las líneas de breakdown siempre suman este total. |
subtotal / tax | Total sin IGV e IGV (según tu tarifario: incluido o adicional). |
breakdown[] | Desglose para mostrar o auditar. code es estable (base.*, vehicle, surcharge.*, adjust.*, free_shipping, rounding, tax). |
serviceable | false = fuera de cobertura; reasons dice por qué y total es 0. |
distanceSource | client (ruta enviada), routing (ruta real), estimated (coordenadas) o ubigeo (capital del distrito). |
zones | Zonas de origen y destino, si tu tarifario cobra por zonas. |
expiresAt | Hasta cuándo se respeta el precio (lo defines en el tarifario). |
usedByPaymentId | Pedido de cobro Yape/Plin que ya usa esta cotización. |
usage.cost | Lo que se descontó de tu saldo por esta cotización. |
6. Qué datos exige cada tarifario
| Tu tarifario cobra por… | Envía como mínimo |
|---|---|
| Km / tiempo o tramos de distancia | Coordenadas o ubigeo de origen y destino, o route.distanceKm. |
| Zonas (distritos, regiones) | Ubigeo de origen y destino. Si tus zonas son polígonos dibujados en el mapa, coordenadas. |
| Peso | packages con weightKg y/o medidas. |
| Seguro | declaredValue y options.insurance: true. |
GET /shipping/rate-cards te dice qué exige cada uno (requires.distance, requires.zones, requires.packages) y sus vehículos.
7. Errores y cobro
| HTTP | Cuándo | ¿Se cobra? |
|---|---|---|
| 200 serviceable: true | Cotización calculada. | Sí |
| 200 serviceable: false | Datos válidos pero fuera de cobertura (distancia, peso, zona). | Sí |
| 400 | Faltan datos que tu tarifario necesita, vehículo inexistente, datos mal formados. | No |
| 400 Saldo insuficiente | No te alcanza el saldo para cotizar. | No |
| 401 | API Key inválida o revocada, cuenta inactiva, correo sin verificar o IP no permitida. | No |
| 404 | Tarifario no encontrado o sin tarifario predeterminado. | No |
| 429 | Superaste el límite de peticiones por minuto. | No |
Los errores responden { "success": false, "error": { "code", "message" } }. El mensaje está en español y es apto para mostrarlo en tu panel.
8. Verificar el precio al cerrar la compra
El navegador de tu cliente puede alterar el monto. Guarda el id de la cotización y, al confirmar la compra, valídalo desde tu backend (gratis):
GET /api/v1/shipping/quotes/quo_… → { "total": 17.00, "expired": false, "serviceable": true, ... }Acepta el pedido solo si existe, expired es false y el total coincide. Si venció, vuelve a cotizar.
9. Cobrar el envío con Yape/Plin
Si usas el servicio de cobros, envía la cotización al crear el pedido: ValidApi suma el envío al monto de los productos y el checkout muestra cuánto corresponde al envío.
curl -X POST https://apivalidape.com/api/v1/payments \
-H "X-API-Key: vapi_xxx" -H "Content-Type: application/json" \
-d '{ "amount": 50.00, "shippingQuoteId": "quo_…", "reference": "PED-10025", "mode": "test" }'
# → { "amount": 67.00, "shipping": { "quoteId": "quo_…", "amount": 17.00 }, "checkoutUrl": "…" }amountes el monto de los productos (puede ser 0 si solo cobras el envío).- La cotización debe estar vigente, con cobertura y en soles.
- Una cotización sirve para un solo pedido. Si otro pedido que aún puede pagarse la usa, responde 409. Se libera si ese pedido se cancela, se rechaza o vence sin pago.
- Reintentar el mismo pedido (misma
referencey monto) devuelve el pedido existente.
10. Listar tarifarios
GET /api/v1/shipping/rate-cards
[{
"id": "231c45c6-…",
"name": "Delivery Lima",
"isDefault": true,
"currency": "PEN",
"pricingModel": ["distance_time"],
"vehicles": [{ "key": "moto", "label": "Moto", "maxWeightKg": 15 }, { "key": "auto", "label": "Auto", "maxWeightKg": 150 }],
"requires": { "distance": true, "zones": false, "packages": false },
"distanceMethod": "estimated",
"limits": { "maxStops": 5, "maxDistanceKm": 30 },
"quoteTtlMinutes": 15
}]11. Ejemplos por lenguaje
Node.js / TypeScript
async function cotizarEnvio(destino) {
const res = await fetch('https://apivalidape.com/api/v1/shipping/quote', {
method: 'POST',
headers: { 'X-API-Key': process.env.VALIDAPI_KEY, 'Content-Type': 'application/json' },
body: JSON.stringify({
origin: { lat: -12.1211, lng: -77.0297 }, // tu almacén o local
destination: destino, // { lat, lng } o { ubigeo }
}),
});
const body = await res.json();
if (!body.success) throw new Error(body.error.message);
const q = body.data;
if (!q.serviceable) return { disponible: false, motivo: q.reasons.join('. ') };
return { disponible: true, id: q.id, total: q.total, detalle: q.breakdown };
}PHP
$ch = curl_init("https://apivalidape.com/api/v1/shipping/quote");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => ["X-API-Key: " . getenv("VALIDAPI_KEY"), "Content-Type: application/json"],
CURLOPT_POSTFIELDS => json_encode([
"origin" => ["ubigeo" => "150122"],
"destination" => ["ubigeo" => $ubigeoCliente],
]),
]);
$res = json_decode(curl_exec($ch), true);
$total = $res["success"] && $res["data"]["serviceable"] ? $res["data"]["total"] : null;Python
import os, requests
r = requests.post(
"https://apivalidape.com/api/v1/shipping/quote",
headers={"X-API-Key": os.environ["VALIDAPI_KEY"]},
json={"origin": {"ubigeo": "150122"}, "destination": {"ubigeo": "040101"},
"packages": [{"weightKg": 3}]},
timeout=10,
)
data = r.json()
if data["success"] and data["data"]["serviceable"]:
print(data["data"]["total"], data["data"]["id"])12. Configurar el tarifario
Se hace en el dashboard; la API solo lo usa. El cálculo sigue este orden:
- Precio base: suma de componentes (km y tiempo, tramos de distancia, zonas, tramos de peso).
- Vehículo: multiplicador y monto fijo.
- Recargos: horario, feriados de Perú (incluye Semana Santa) y fechas propias, lluvia pronosticada, paradas, espera, seguro, contraentrega, frágil, retorno, zona de difícil acceso, pedido pequeño, tarifa de servicio y demanda.
- Tus ajustes: % de margen → monto fijo → descuento → mínimo/máximo → envío gratis.
- IGV (incluido o adicional) y redondeo (0.10, 0.50 o 1.00).
Las zonas se definen por departamento, provincia o distrito, o dibujando polígonos (GeoJSON). La distancia puede ser estimada o la ruta real por calles, si activas esa opción.
13. Buenas prácticas
- Cada petición se cobra, aunque repitas la misma dirección. Cotiza cuando el cliente confirme sus datos de envío (no en cada tecla ni al mover el mapa) y reutiliza el
idmientras esté vigente. - Llama a la API desde tu backend: nunca expongas tu API Key en el navegador o en la app.
- Guarda el
idcon tu pedido y verifica el total antes de cobrar. - Si ya tienes la distancia de tu proveedor de mapas, envíala en
route: es más precisa que la estimación. - Revisa tu saldo en el dashboard; sin saldo la API responde «Saldo insuficiente».
- Maneja el
429con reintentos espaciados.
¿Dudas? Escríbenos a soporte@apivalidape.com.