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

  1. Crea tu cuenta y verifica tu correo. Tu cuenta trae un saldo inicial para probar; recarga desde el dashboard cuando lo necesites.
  2. 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.
  3. Pruébalo con el simulador de la misma pantalla: es gratis y usa la configuración que estás editando.
  4. En Dashboard → API Keys, crea una API Key y guárdala como secreto en tu backend.
  5. Desde tu backend, cotiza con POST /shipping/quote cuando el cliente confirme la dirección, muestra el total y guarda el id.
  6. Al cerrar la compra, verifica la cotización (GET /shipping/quotes/:id) o crea el cobro Yape/Plin con shippingQuoteId.

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).

CampoTipoDescripción
rateCardIduuidTarifario a usar. Si falta, se usa tu tarifario predeterminado.
originobjeto{ lat, lng, ubigeo }: coordenadas y/o ubigeo INEI (6 dígitos; se aceptan 2 o 4).
destinationobjetoIgual que origin.
stops[]listaParadas intermedias en orden (máx. 20), mismo formato. Suman distancia y el recargo por parada.
routeobjeto{ 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).
vehicletextoClave del vehículo de tu tarifario (ej. moto, auto). Si falta, el primero.
scheduledAtISO 8601Momento del envío (default: ahora). Define recargos por horario, feriado y lluvia. Sin zona = hora de Perú.
orderValuenúmeroValor del pedido: envío gratis desde un monto, tarifa de servicio, pedido pequeño, base de la contraentrega.
declaredValuenúmeroValor declarado de la carga (seguro).
waitingMinutesnúmeroMinutos de espera (recargo por espera).
demandFactornúmeroFactor de demanda que mides tú (ej. 1.3); tu tarifario lo acota.
optionsobjeto{ 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 }
}
CampoSignificado
totalLo que cobras al cliente. Las líneas de breakdown siempre suman este total.
subtotal / taxTotal 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).
serviceablefalse = fuera de cobertura; reasons dice por qué y total es 0.
distanceSourceclient (ruta enviada), routing (ruta real), estimated (coordenadas) o ubigeo (capital del distrito).
zonesZonas de origen y destino, si tu tarifario cobra por zonas.
expiresAtHasta cuándo se respeta el precio (lo defines en el tarifario).
usedByPaymentIdPedido de cobro Yape/Plin que ya usa esta cotización.
usage.costLo 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 distanciaCoordenadas 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.
Pesopackages con weightKg y/o medidas.
SegurodeclaredValue 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

HTTPCuándo¿Se cobra?
200 serviceable: trueCotización calculada.Sí
200 serviceable: falseDatos válidos pero fuera de cobertura (distancia, peso, zona).Sí
400Faltan datos que tu tarifario necesita, vehículo inexistente, datos mal formados.No
400 Saldo insuficienteNo te alcanza el saldo para cotizar.No
401API Key inválida o revocada, cuenta inactiva, correo sin verificar o IP no permitida.No
404Tarifario no encontrado o sin tarifario predeterminado.No
429Superaste 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": "…" }
  • amount es 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 reference y 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:

  1. Precio base: suma de componentes (km y tiempo, tramos de distancia, zonas, tramos de peso).
  2. Vehículo: multiplicador y monto fijo.
  3. 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.
  4. Tus ajustes: % de margen → monto fijo → descuento → mínimo/máximo → envío gratis.
  5. 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 id mientras esté vigente.
  • Llama a la API desde tu backend: nunca expongas tu API Key en el navegador o en la app.
  • Guarda el id con 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 429 con reintentos espaciados.

¿Dudas? Escríbenos a soporte@apivalidape.com.