Envíos CaimánEnvíos Caimán

API de Envíos Caimán

Domicilio como servicio para Cuba. Tu tienda, restaurante o sistema mantiene su catálogo y sus pedidos; nosotros resolvemos el transporte completo: matching de repartidor, GPS en vivo, tracking público y prueba de entrega.

Introducción

La API es REST + JSON sobre HTTPS. Todas las rutas cuelgan de /v1. Los importes van siempre en CUP, las coordenadas en grados decimales (WGS84) y las fechas en ISO 8601 UTC (2026-07-22T18:30:00Z).

Tu sistema (tienda) Envíos Caimán ────────────────── ───────────── POST /v1/quotes ─────────────► cotiza (precio, distancia, días) POST /v1/deliveries ─────────────► crea el envío y busca repartidor ◄───────────── webhooks firmados en cada estado GET /v1/deliveries/{id} ─────────────► estado + GPS del repartidor enlace /track/{token} ─────────────► tracking público para tu cliente
Pensada para Cuba: rutas por carretera con OSRM auto-hospedado (sin servicios geobloqueados), pagos en efectivo entre cliente y repartidor (la plataforma no intermedia el dinero del envío) y tolerancia total a cortes: si algún componente cae, la API responde igual con datos de respaldo.

Autenticación

Cada negocio recibe una API key (pk_…) y un secret (sk_…) para verificar webhooks. La key va en cada petición, en una de estas dos cabeceras:

X-Api-Key: pk_demo_123
# o equivalente:
Authorization: Bearer pk_demo_123
Nunca pongas la API key en el front-end público de tu web o app: las llamadas deben salir de tu servidor. El secret HMAC no viaja nunca por la red — solo se usa para verificar firmas.

Quickstart — tu primer envío en 5 minutos

1. Cotiza (opcional, para mostrar el precio antes de confirmar):

curl -X POST https://api.envioscaiman.com/v1/quotes \
  -H "Content-Type: application/json" -H "X-Api-Key: pk_demo_123" \
  -d '{
    "vertical": "comida",
    "cargo": { "tipo": "comidaCaliente", "peso_kg": 1.5,
               "dimensiones_cm": { "largo": 35, "ancho": 35, "alto": 8 } },
    "recogida": { "lat": 23.1367, "lon": -82.3674,
                  "municipio": "Centro Habana", "provincia": "La Habana" },
    "entrega":  { "lat": 23.1069, "lon": -82.3819,
                  "municipio": "Cerro", "provincia": "La Habana" }
  }'

2. Crea el envío (mismo cuerpo + direcciones y contacto):

curl -X POST https://api.envioscaiman.com/v1/deliveries \
  -H "Content-Type: application/json" -H "X-Api-Key: pk_demo_123" \
  -d '{
    "external_id": "PEDIDO-123",
    "vertical": "comida",
    "cargo": { "tipo": "comidaCaliente", "peso_kg": 1.5,
               "dimensiones_cm": { "largo": 35, "ancho": 35, "alto": 8 } },
    "recogida": { "lat": 23.1367, "lon": -82.3674,
      "municipio": "Centro Habana", "provincia": "La Habana",
      "direccion": "Calle 23 #456", "referencia": "La pizzería" },
    "entrega": { "lat": 23.1069, "lon": -82.3819,
      "municipio": "Cerro", "provincia": "La Habana",
      "direccion": "Calle 10 #12", "referencia": "Edificio azul, apto 3B",
      "contacto": { "nombre": "Cliente", "telefono": "+53 5XXXXXXX" } },
    "webhook_url": "https://tu-tienda.cu/hooks/domicilio"
  }'

3. Guarda el delivery_id y el share_token de la respuesta. Comparte /track/{share_token} con tu cliente y escucha los webhooks. Listo.

Errores

Los errores devuelven un JSON con un mensaje en español listo para registrar o mostrar:

{ "error": "API key inválida o ausente" }
CódigoSignificado
400JSON inválido o falta un campo requerido
401API key/sesión inválida o ausente
404Recurso inexistente o de otro socio (no se filtra la existencia)
409Conflicto de estado (p. ej. cancelar un envío ya entregado)
429Demasiados intentos (p. ej. login)

Idempotencia: reintenta POST /v1/deliveries con la cabecera Idempotency-Key: <clave-única> y el mismo cuerpo: si ya se creó, devolvemos el envío existente en vez de duplicarlo.

Cotizar sin crear

POST /v1/quotesAPI key

Mismo cuerpo que crear (sin direcciones ni contacto). Responde:

{
  "tarifa_estimada_cup": 197.0,       // precio final sugerido (con surge si aplica)
  "tarifa_base_cup": 197.0,           // sin surge
  "desglose": {
    "base_cup": 80.0,                 // arranque del viaje
    "distancia_cup": 117.0,           // por tramos de km (ver "Tarifa")
    "peso_cup": 0.0                   // solo en vertical "logistica"
  },
  "surge": 1.0,                       // multiplicador por demanda (urbano)
  "precio_alto": false,
  "nacional": false,                  // true si cruza de provincia
  "entrega_dias_min": 2,              // solo en nacionales
  "entrega_dias_max": 3,
  "hay_repartidores": true,
  "repartidores_elegibles": 4,
  "distancia_viaje_km": 4.7,          // por carretera real (OSRM)
  "duracion_min": 8,                  // solo si OSRM respondió
  "ruta_real": true
}

Crear un envío

POST /v1/deliveriesAPI key
CampoDescripción
external_idId del pedido en TU sistema; vuelve en cada webhook
verticalreqcomida (mensajería urbana) o logistica (carga)
cargo.tiporeqcomidaCaliente · documento · paquete · fragil · voluminoso · refrigerado
cargo.peso_kgreqPeso en kg
cargo.dimensiones_cm{largo, ancho, alto} del bulto
recogida / entregareq{lat, lon, municipio, provincia, direccion, referencia, contacto{nombre, telefono}}. La referencia es clave en Cuba ("edificio azul, al lado de la farmacia")
paradasLista de puntos intermedios [{lat, lon, municipio, direccion}]
programado_paraISO 8601: reserva a futuro en vez de buscar ahora
ida_y_vueltatrue cobra la distancia doble (llevar y traer)
paga_envioremitente (default) o destinatario
notasComentarios para el repartidor
webhook_urlURL para los eventos de ESTE envío (si no, la global del socio)

Respuesta 201 (resumen):

{
  "delivery_id": "dlv_abc123",
  "external_id": "PEDIDO-123",
  "estado": "buscando_repartidor",
  "tarifa_cup": 197.0,
  "comision_pct": 0.15,               // comisión de plataforma (informativa)
  "comision_cup": 30.0,
  "ganancia_repartidor_cup": 167.0,
  "codigo_entrega": "4821",           // OTP que cierra la entrega
  "share_token": "trk_xxx",           // para /track/{token} público
  "eta_recogida_min": 7,
  "duracion_viaje_min": 8,
  "recogida": { … }, "entrega": { … }
}
El código de entrega lo da quien recibe al repartidor: es la prueba de que el paquete llegó a las manos correctas. Con sin_telefono_dest: true el cierre pasa a ser foto + confirmación remota del remitente.

Listar tus envíos

GET /v1/deliveriesAPI key

Todos los envíos de tu negocio, del más nuevo al más viejo: { "deliveries": [ … ] } con el mismo formato del detalle.

Consultar un envío

GET /v1/deliveries/{id}API key

Estado actual + posición GPS del repartidor (ubicacion: {lat, lon}), distancia a la entrega, prueba de entrega, calificación y propina.

Geometría de la ruta

GET /v1/deliveries/{id}/routeAPI key

Puntos [lat, lon] de la ruta por calles (OSRM), listos para pintar en un mapa: { "puntos": [[23.137, -82.362], …] }. Lista vacía si el motor de rutas no está disponible.

Cancelar

POST /v1/deliveries/{id}/cancelAPI key

Cancela un envío no entregado. Si ya se entregó responde 409.

Zonas cubiertas

GET /v1/coverage?municipio=Cerropúblico

Consulta si operamos en un municipio y con qué verticales, para validar el checkout de tu tienda antes de cotizar.

Webhooks

En cada cambio de estado hacemos POST a tu webhook_url con reintentos. El cuerpo:

{
  "delivery_id": "dlv_abc123",
  "external_id": "PEDIDO-123",
  "estado": "recogido",
  "repartidor": "Ana",
  "timestamp": "2026-07-22T14:32:10Z"
}

Verificar la firma HMAC

Cada webhook lleva X-Signature: sha256=<hex> — el HMAC-SHA256 del cuerpo crudo con tu secret. Verifícalo SIEMPRE:

# Python
import hmac, hashlib

def firma_valida(cuerpo_crudo: bytes, cabecera: str, secret: str) -> bool:
    esperada = 'sha256=' + hmac.new(
        secret.encode(), cuerpo_crudo, hashlib.sha256).hexdigest()
    return hmac.compare_digest(esperada, cabecera)
// Node.js
const crypto = require('crypto');
function firmaValida(cuerpoCrudo, cabecera, secret) {
  const esperada = 'sha256=' + crypto.createHmac('sha256', secret)
    .update(cuerpoCrudo).digest('hex');
  return crypto.timingSafeEqual(Buffer.from(esperada), Buffer.from(cabecera));
}
Responde 2xx rápido (< 5 s) y procesa en segundo plano. Un webhook puede llegar más de una vez: usa delivery_id + estado como clave de deduplicación.

WebSocket de seguimiento

WS /v1/ws/deliveries/{id}?token=…sesión

Push en tiempo real del envío completo (estado + GPS) en cada cambio. Lo usa la app del cliente; disponible también para integraciones que prefieran socket en vez de polling.

Página pública de tracking

GET /track/{share_token}público

Página web ligera para el destinatario final: ve al repartidor en vivo sin cuenta ni app. Compártela por WhatsApp/SMS desde tu sistema. La versión JSON está en /v1/track/{share_token} (vista limitada, sin datos sensibles).

Ciclo de vida de estados

programado ──► buscando_repartidor ──► asignado ──► en_camino_a_recogida │ entregado ◄── en_camino_a_entrega ◄── recogido ◄──┘ en cualquier punto antes de entregar: cancelado · fallido
EstadoQué significa
programadoReservado a futuro; se ofrecerá cerca de la fecha
buscando_repartidorOfertado a los repartidores elegibles (caduca a las 24 h)
asignadoUn repartidor lo aceptó
en_camino_a_recogidaVa hacia tu local
recogidoTiene el paquete (con foto de recogida)
en_camino_a_entregaVa hacia el destinatario, GPS en vivo
entregadoCerrado con código OTP (o confirmación remota) + foto
cancelado / fallidoNo se completó

Cómo se calcula la tarifa

La distancia se cobra por tramos, como el transporte real (el km urbano vale más que el km de carretera). Valores por defecto — el operador puede ajustarlos desde su panel:

ComponenteValor por defecto
Base80 CUP por viaje
Km 0–15 (urbano)25 CUP/km
Km 15–100 (interurbano)15 CUP/km
Km 100+ (carretera)6 CUP/km
Peso (solo logistica)3 CUP/kg — ×2 si el envío es nacional
Surge (solo urbano)×1.0 a ×2.0 según la demanda de la zona (por municipio de recogida); nunca aplica a nacionales

Un envío nacional (recogida y entrega en provincias distintas) exige un transportista con viajes nacionales, no lleva surge, y la cotización incluye entrega_dias_min/max (~600 km/día + margen de coordinación). El pago es en efectivo directo cliente→repartidor; la plataforma cobra su comisión (15% por defecto) al repartidor al entregar.

Envíos Caimán · API v1