Documentación API Zeolos

v1.0

Introducción

Bienvenido a la documentación de la API de Zeolos. Esta API REST permite calcular las emisiones de CO₂ de expediciones de transporte de mercancías, considerando múltiples modos de transporte (carretera, marítimo, aéreo, ferrocarril).

La API utiliza factores de emisión certificados y calcula tres tipos de emisiones:

  • WTW (Well-to-Wheel): Emisiones totales del ciclo completo
  • WTT (Well-to-Tank): Emisiones de producción y distribución del combustible
  • TTW (Tank-to-Wheel): Emisiones directas del vehículo
Nota: Esta API requiere autenticación mediante token. Contacta con el administrador para obtener tus credenciales de acceso.

URL Base

https://api.zeolos.es/api/

Todas las peticiones deben realizarse a esta URL base seguida del endpoint específico. Por ejemplo: https://api.zeolos.es/api/expeditions/

Seguridad: Todas las comunicaciones con la API se realizan exclusivamente por HTTPS (TLS 1.2+). Nunca compartas tu token de acceso ni lo incluyas en URLs públicas o logs.

Autenticación

La API utiliza autenticación basada en tokens. Cada cliente tiene un token único que debe incluirse en el header Authorization de todas las peticiones.

Obtener tu Token

Tu token se genera automáticamente cuando tu cuenta es creada. El token tiene las siguientes características:

  • Longitud: 64 caracteres alfanuméricos
  • Validez: 1 año desde su emisión
  • Revocable manualmente si es necesario
Seguridad: Nunca compartas tu token. Trátalo como una contraseña y guárdalo de forma segura.

Headers Requeridos

Todas las peticiones deben incluir los siguientes headers:

Header Valor Descripción
Authorization Token {tu_token} Tu token de autenticación
Content-Type application/json Formato de los datos enviados

Ejemplo de Autenticación

cURL
curl -X GET https://api.zeolos.es/api/expeditions/ \
  -H "Authorization: Token abc123def456ghi789jkl012mno345pqr678stu901vwx234yz" \
  -H "Content-Type: application/json"
Python
import requests

API_TOKEN = "tu_token_aqui"
BASE_URL = "https://api.zeolos.es/api"

headers = {
    "Authorization": f"Token {API_TOKEN}",
    "Content-Type": "application/json"
}

response = requests.get(f"{BASE_URL}/expeditions/", headers=headers)
print(response.json())
JavaScript
const API_TOKEN = "tu_token_aqui";
const BASE_URL = "https://api.zeolos.es/api";

const headers = {
    "Authorization": `Token ${API_TOKEN}`,
    "Content-Type": "application/json"
};

fetch(`${BASE_URL}/expeditions/`, { headers })
    .then(response => response.json())
    .then(data => console.log(data));

Rate Limiting & Throttling

Para garantizar la calidad del servicio, la API implementa límites de uso (throttling) en tres niveles temporales:

Límite Ventana Máximo Descripción
Por Minuto 60 segundos 30 requests Límite de ráfagas cortas
Por Hora 60 minutos 500 requests Límite sostenido medio
Por Día 24 horas 5,000 requests Límite total diario
Expediciones Diarias 24 horas 1,500 expediciones Límite de expediciones procesadas
Importante: Estos límites son independientes. Si alcanzas cualquiera de ellos, recibirás un error 429.

Headers de Respuesta

Cada respuesta de la API incluye headers que te informan sobre tu uso actual:

Header Descripción Ejemplo
X-RateLimit-Limit-minute Límite por minuto 30
X-RateLimit-Remaining-minute Requests restantes (minuto) 25
X-RateLimit-Limit-hour Límite por hora 500
X-RateLimit-Remaining-hour Requests restantes (hora) 480
X-RateLimit-Limit-day Límite por día 5000
X-RateLimit-Remaining-day Requests restantes (día) 4850
Retry-After Segundos hasta reinicio (solo en 429) 45

Respuesta 429 (Too Many Requests)

Si excedes algún límite, recibirás esta respuesta:

HTTP Response
HTTP/1.1 429 Too Many Requests
Retry-After: 45
X-RateLimit-Limit-minute: 30
X-RateLimit-Remaining-minute: 0

{
  "detail": "Request was throttled. Expected available in 45 seconds."
}

Crear Expedición (Sync)

POST /api/expeditions/

Crea una nueva expedición y calcula sus emisiones de forma síncrona. La respuesta se devuelve cuando el cálculo ha finalizado.

Parámetros del Body (JSON)

Campo Tipo Requerido Descripción
client_name string Requerido Nombre del cliente final
expedition_id string Requerido ID único de la expedición (max 100 caracteres)
expedition_date date Requerido Fecha de la expedición (formato: YYYY-MM-DD)
scope integer Opcional Alcance de emisiones: 1 o 3 (default: 3)
cargo object Requerido Información de la carga
cargo.weight decimal Requerido Peso de la carga (> 0)
cargo.weight_unit string Requerido Unidad de peso: kg, t, lb
cargo.is_refrigerated boolean Requerido ¿La carga requiere refrigeración?
route array Requerido Array de ubicaciones y transportes alternados
Estructura de la Ruta: El array route debe alternar ubicaciones y transportes, empezando y terminando con una ubicación. Mínimo 3 elementos: [ubicación, transporte, ubicación]

Ejemplo de Request

JSON
{
  "client_name": "ACME Corp",
  "expedition_id": "EXP-2025-001",
  "expedition_date": "2025-11-08",
  "scope": 3,
  "cargo": {
    "weight": 1500,
    "weight_unit": "kg",
    "is_refrigerated": false
  },
  "route": [
    {
      "location": {
        "query": "Madrid, España"
      }
    },
    {
      "transport_mode": "road",
      "details": {
        "vehicle_type": "HGV-40-60t",
        "fuel_type": "diesel",
        "load_level": "50-100"
      }
    },
    {
      "location": {
        "query": "Barcelona, España"
      }
    }
  ]
}

Respuesta Exitosa (201 Created)

JSON
{
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "client_name": "ACME Corp",
  "expedition_id": "EXP-2025-001",
  "expedition_date": "2025-11-08",
  "status": "COMPLETED",
  "error_message": null,
  "scope": 3,
  "cargo": {
    "weight": 1500,
    "weight_unit": "kg",
    "is_refrigerated": false
  },
  "route": [
    {
      "location": {
        "query": "Madrid, España"
      }
    },
    {
      "transport_mode": "road",
      "details": {
        "vehicle_type": "HGV-40-60t",
        "fuel_type": "diesel",
        "load_level": "50-100"
      },
      "results": {
        "distance_km": 621.45,
        "emission_wtw": 234.56,
        "emission_wtt": 45.32,
        "emission_ttw": 189.24
      }
    },
    {
      "location": {
        "query": "Barcelona, España"
      }
    }
  ],
  "results": {
    "total_distance_km": 621.45,
    "total_emission_wtw": 234.56,
    "total_emission_wtt": 45.32,
    "total_emission_ttw": 189.24
  },
  "created_at": "2025-11-08T10:30:00Z",
  "updated_at": "2025-11-08T10:30:05Z"
}

Errores Posibles

Código Descripción
400 Datos de entrada inválidos (campos faltantes, formato incorrecto, etc.)
401 Token de autenticación inválido o ausente
403 Cliente inactivo o sin permisos
429 Límite de requests excedido (throttling)
500 Error interno del servidor
503 Servicio externo no disponible (geocodificación, etc.)

Listar Expediciones

GET /api/expeditions/

Obtiene una lista paginada de todas las expediciones del cliente autenticado. Soporta múltiples filtros para búsquedas específicas por fecha y estado.

Parámetros Query (Opcionales)

Parámetro Tipo Descripción Ejemplo
page integer Número de página (default: 1) 1
page_size integer Items por página (default: 50, max: 100) 20
expedition_date_from date Expediciones desde esta fecha de expedición 2025-11-01
expedition_date_to date Expediciones hasta esta fecha de expedición 2025-11-30
created_from datetime Creadas en BD desde esta fecha/hora 2025-11-01T00:00:00Z
created_to datetime Creadas en BD hasta esta fecha/hora 2025-11-30T23:59:59Z
status string Filtrar por estado COMPLETED, ERROR
Diferencia entre filtros de fecha:
  • expedition_date_from/to: Filtra por la fecha real del envío (campo expedition_date)
  • created_from/to: Filtra por cuándo se registró en la base de datos (campo created_at)
  • Puedes combinar múltiples filtros en una sola query
  • Formato de fecha: YYYY-MM-DD o ISO 8601 (YYYY-MM-DDTHH:MM:SSZ)

Ejemplos de Request

1. Paginación básica

cURL
curl -X GET "https://api.zeolos.es/api/expeditions/?page=1&page_size=20" \
  -H "Authorization: Token tu_token_aqui"

2. Filtrar por rango de fechas de expedición

cURL
# Expediciones enviadas en noviembre 2025
curl -X GET "https://api.zeolos.es/api/expeditions/?expedition_date_from=2025-11-01&expedition_date_to=2025-11-30" \
  -H "Authorization: Token tu_token_aqui"

3. Filtrar por fecha de creación en BD

cURL
# Expediciones registradas hoy
curl -X GET "https://api.zeolos.es/api/expeditions/?created_from=2025-11-12T00:00:00Z" \
  -H "Authorization: Token tu_token_aqui"

4. Combinar múltiples filtros

cURL
# Expediciones completadas de noviembre, registradas esta semana
curl -X GET "https://api.zeolos.es/api/expeditions/?expedition_date_from=2025-11-01&expedition_date_to=2025-11-30&status=COMPLETED&created_from=2025-11-11" \
  -H "Authorization: Token tu_token_aqui"

5. Ejemplo en Python con filtros

Python
import requests
from datetime import datetime, timedelta

API_TOKEN = "tu_token_aqui"
BASE_URL = "https://api.zeolos.es/api"

headers = {"Authorization": f"Token {API_TOKEN}"}

# Obtener expediciones del último mes
today = datetime.now()
last_month = today - timedelta(days=30)

params = {
    "expedition_date_from": last_month.strftime("%Y-%m-%d"),
    "expedition_date_to": today.strftime("%Y-%m-%d"),
    "status": "COMPLETED",
    "page_size": 50
}

response = requests.get(
    f"{BASE_URL}/expeditions/",
    headers=headers,
    params=params
)

if response.status_code == 200:
    data = response.json()
    print(f"Total: {data['count']} expediciones")
    
    for exp in data['results']:
        print(f"- {exp['expedition_id']}: {exp['expedition_date']} - {exp['results']['total_emission_wtw']} kg CO₂")
    
    # Navegar a la siguiente página si existe
    if data['next']:
        next_response = requests.get(data['next'], headers=headers)
        # ... procesar siguiente página
else:
    print(f"Error: {response.status_code}")

Respuesta Exitosa (200 OK)

JSON
{
  "count": 150,
  "next": "https://api.zeolos.es/api/expeditions/?page=2&expedition_date_from=2025-11-01",
  "previous": null,
  "results": [
    {
      "client_name": "ACME Corp",
      "expedition_id": "EXP-2025-001",
      "expedition_date": "2025-11-08",
      "status": "COMPLETED",
      "error_message": "",
      "scope": 3,
      "cargo": {
        "weight": 1500.0,
        "weight_unit": "kg",
        "is_refrigerated": false
      },
      "route": [...],
      "results": {
        "total_distance_km": 621.45,
        "total_emission_wtw": 234.56,
        "total_emission_wtt": 45.32,
        "total_emission_ttw": 189.24
      },
      "created_at": "2025-11-08T10:30:00Z",
      "updated_at": "2025-11-08T10:30:05Z"
    }
  ]
}

Casos de Uso Comunes

Caso de Uso Filtros a Usar
Expediciones enviadas en un mes específico expedition_date_from=2025-11-01&expedition_date_to=2025-11-30
Expediciones registradas hoy created_from=2025-11-12T00:00:00Z
Solo expediciones completadas status=COMPLETED
Expediciones con errores de esta semana status=ERROR&created_from=2025-11-11
Reporte mensual (expediciones + creación) expedition_date_from=2025-11-01&expedition_date_to=2025-11-30&created_from=2025-11-01&created_to=2025-11-30

Obtener Expedición

GET /api/expeditions/{id}/

Obtiene los detalles completos de una expedición específica por su ID.

Parámetros URL

Parámetro Tipo Descripción
id UUID ID único de la expedición (generado por el sistema)

Ejemplo de Request

cURL
curl -X GET "https://api.zeolos.es/api/expeditions/3fa85f64-5717-4562-b3fc-2c963f66afa6/" \
  -H "Authorization: Token tu_token_aqui"

Respuesta Exitosa (200 OK)

Devuelve el mismo formato que la respuesta de crear expedición, con todos los detalles.

Errores Posibles

Código Descripción
404 Expedición no encontrada o no pertenece al cliente autenticado

Batch Sync (hasta 10 expediciones)

POST /api/expeditions/batch/

Procesa múltiples expediciones en un solo request (hasta 10). El procesamiento es síncrono y la respuesta incluye el resultado de cada expedición.

Límites: - Mínimo: 1 expedición
- Máximo: 10 expediciones
- Todos los expedition_id deben ser únicos dentro del batch

Parámetros del Body (JSON)

Campo Tipo Requerido Descripción
batch_id string Requerido ID único del lote (max 100 caracteres)
expeditions array Requerido Array de 1-10 expediciones (mismo formato que POST individual)

Ejemplo de Request

JSON
{
  "batch_id": "BATCH-2025-001",
  "expeditions": [
    {
      "client_name": "ACME Corp",
      "expedition_id": "EXP-001",
      "expedition_date": "2025-11-08",
      "cargo": {
        "weight": 1500,
        "weight_unit": "kg",
        "is_refrigerated": false
      },
      "route": [...]
    },
    {
      "client_name": "ACME Corp",
      "expedition_id": "EXP-002",
      "expedition_date": "2025-11-08",
      "cargo": {
        "weight": 2000,
        "weight_unit": "kg",
        "is_refrigerated": true
      },
      "route": [...]
    }
  ]
}

Respuesta Exitosa (200 OK)

JSON
{
  "batch_id": "BATCH-2025-001",
  "total": 2,
  "successful": 2,
  "failed": 0,
  "processing_time_seconds": 3.45,
  "results": [
    {
      "expedition_id": "EXP-001",
      "status": "success",
      "data": {
        "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
        "status": "COMPLETED",
        "results": {...}
      }
    },
    {
      "expedition_id": "EXP-002",
      "status": "success",
      "data": {
        "id": "7bc96e85-6828-5673-c4gd-3d074g77bgb7",
        "status": "COMPLETED",
        "results": {...}
      }
    }
  ]
}

Respuesta con Errores Parciales

JSON
{
  "batch_id": "BATCH-2025-001",
  "total": 2,
  "successful": 1,
  "failed": 1,
  "processing_time_seconds": 2.15,
  "results": [
    {
      "expedition_id": "EXP-001",
      "status": "success",
      "data": {...}
    },
    {
      "expedition_id": "EXP-002",
      "status": "error",
      "error": "Invalid weight: must be greater than 0",
      "error_type": "validation_error"
    }
  ]
}

Batch Async (hasta 500 expediciones)

POST /api/expeditions/batch/async/

Procesa grandes lotes de expediciones (hasta 500) de forma asíncrona. La respuesta es inmediata (202 Accepted) y el procesamiento se realiza en segundo plano.

Características: - Máximo: 500 expediciones
- Respuesta inmediata con status URL
- Tiempo estimado: ~1.2 segundos por expedición

Parámetros del Body (JSON)

Mismo formato que batch sync, pero acepta hasta 500 expediciones.

Respuesta Inmediata (202 Accepted)

JSON
{
  "batch_id": "BATCH-ASYNC-001",
  "status": "QUEUED",
  "message": "Batch accepted for processing",
  "total_expeditions": 250,
  "estimated_time_minutes": 5.0,
  "status_url": "/api/expeditions/batch/BATCH-ASYNC-001/status/",
  "task_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}

Listar Batches

GET /api/expeditions/batches/

Obtiene una lista de todos los batches del cliente autenticado con filtros opcionales. No incluye las expediciones individuales (solo metadatos).

Parámetros Query (Opcionales)

Parámetro Tipo Descripción Ejemplo
created_from date Batches creados desde esta fecha 2025-11-01
created_to date Batches creados hasta esta fecha 2025-11-30
status string Filtrar por estado del batch COMPLETED, PROCESSING, ERROR

Ejemplos de Request

Listar todos los batches

cURL
curl -X GET "https://api.zeolos.es/api/expeditions/batches/" \
  -H "Authorization: Token tu_token_aqui"

Filtrar por fecha y estado

cURL
# Batches completados de noviembre
curl -X GET "https://api.zeolos.es/api/expeditions/batches/?created_from=2025-11-01&created_to=2025-11-30&status=COMPLETED" \
  -H "Authorization: Token tu_token_aqui"

Ejemplo en Python

Python
import requests

API_TOKEN = "tu_token_aqui"
BASE_URL = "https://api.zeolos.es/api"

headers = {"Authorization": f"Token {API_TOKEN}"}

# Obtener batches completados de esta semana
response = requests.get(
    f"{BASE_URL}/expeditions/batches/",
    headers=headers,
    params={
        "status": "COMPLETED",
        "created_from": "2025-11-11"
    }
)

if response.status_code == 200:
    data = response.json()
    print(f"Total: {data['count']} batches")
    
    for batch in data['results']:
        success_rate = (batch['successful'] / batch['total'] * 100) if batch['total'] > 0 else 0
        print(f"- {batch['batch_id']}: {batch['successful']}/{batch['total']} ({success_rate:.1f}% éxito)")

Respuesta Exitosa (200 OK)

JSON
{
  "count": 15,
  "results": [
    {
      "batch_id": "BATCH-2025-001",
      "status": "COMPLETED",
      "total": 10,
      "processed": 10,
      "successful": 10,
      "failed": 0,
      "progress_percent": 100.0,
      "created_at": "2025-11-08T10:00:00+00:00",
      "updated_at": "2025-11-08T10:05:30+00:00",
      "completed_at": "2025-11-08T10:05:30+00:00"
    },
    {
      "batch_id": "BATCH-ASYNC-001",
      "status": "PROCESSING",
      "total": 250,
      "processed": 150,
      "successful": 148,
      "failed": 2,
      "progress_percent": 60.0,
      "created_at": "2025-11-08T09:00:00+00:00",
      "updated_at": "2025-11-08T09:03:00+00:00",
      "completed_at": null
    }
  ]
}

Obtener Batch Completo

GET /api/expeditions/batch/{batch_id}/

Obtiene el batch completo con **todas** sus expediciones y detalles. Funciona para batches sync y async. Útil para análisis detallado o exports.

Rendimiento: Para batches de 500 expediciones, la respuesta puede ser de 5-10 MB. Si solo necesitas el progreso, usa /batch/{batch_id}/status/ que es más ligero (< 1 KB).

Parámetros URL

Parámetro Tipo Descripción
batch_id string ID del batch a obtener

Ejemplo de Request

cURL
curl -X GET "https://api.zeolos.es/api/expeditions/batch/BATCH-2025-001/" \
  -H "Authorization: Token tu_token_aqui"

Ejemplo en Python - Análisis de Batch

Python
import requests

API_TOKEN = "tu_token_aqui"
BASE_URL = "https://api.zeolos.es/api"

headers = {"Authorization": f"Token {API_TOKEN}"}

# Obtener batch completo
response = requests.get(
    f"{BASE_URL}/expeditions/batch/BATCH-2025-001/",
    headers=headers
)

if response.status_code == 200:
    batch = response.json()
    print(f"Batch: {batch['batch_id']}")
    print(f"Estado: {batch['status']}")
    print(f"Total expediciones: {len(batch['expeditions'])}")
    
    # Calcular estadísticas
    completed = [exp for exp in batch['expeditions'] if exp['status'] == 'COMPLETED']
    
    if completed:
        total_emissions = sum(exp['results']['total_emission_wtw'] for exp in completed)
        total_distance = sum(exp['results']['total_distance_km'] for exp in completed)
        avg_emissions = total_emissions / len(completed)
        
        print(f"\n📊 Estadísticas:")
        print(f"- Emisiones totales: {total_emissions:.2f} kg CO₂")
        print(f"- Distancia total: {total_distance:.2f} km")
        print(f"- Promedio por expedición: {avg_emissions:.2f} kg CO₂")
        print(f"- Intensidad: {(total_emissions/total_distance):.3f} kg CO₂/km")

Respuesta Exitosa (200 OK)

JSON
{
  "batch_id": "BATCH-2025-001",
  "status": "COMPLETED",
  "total": 2,
  "processed": 2,
  "successful": 2,
  "failed": 0,
  "progress_percent": 100.0,
  "created_at": "2025-11-08T10:00:00+00:00",
  "updated_at": "2025-11-08T10:05:30+00:00",
  "completed_at": "2025-11-08T10:05:30+00:00",
  "expeditions": [
    {
      "client_name": "ACME Corp",
      "expedition_id": "EXP-001",
      "expedition_date": "2025-11-08",
      "status": "COMPLETED",
      "error_message": "",
      "scope": 3,
      "cargo": {
        "weight": 1500.0,
        "weight_unit": "kg",
        "is_refrigerated": false
      },
      "route": [...],
      "results": {
        "total_distance_km": 621.45,
        "total_emission_wtw": 234.56,
        "total_emission_wtt": 45.32,
        "total_emission_ttw": 189.24
      },
      "created_at": "2025-11-08T10:00:05+00:00",
      "updated_at": "2025-11-08T10:00:10+00:00"
    }
  ]
}

Comparación de Endpoints

Endpoint Expediciones Tamaño Cuándo Usar
/batches/ ❌ No Pequeño Listar todos los batches (resumen)
/batch/{id}/status/ ❌ No Muy pequeño (< 1 KB) Monitorear progreso rápido
/batch/{id}/ ✅ Sí (todas) Grande (hasta 10 MB) Análisis completo, export, reportes

Errores Posibles

Código Descripción
404 Batch no encontrado o no pertenece al cliente autenticado

Consultar Estado del Batch

GET /api/expeditions/batch/{batch_id}/status/

Consulta el estado de procesamiento de un batch (sync o async). Devuelve el progreso actual sin incluir las expediciones completas.

Parámetros URL

Parámetro Tipo Descripción
batch_id string ID del batch a consultar

Ejemplo de Request

cURL
curl -X GET "https://api.zeolos.es/api/expeditions/batch/BATCH-ASYNC-001/status/" \
  -H "Authorization: Token tu_token_aqui"

Respuesta - En Proceso (200 OK)

JSON
{
  "batch_id": "BATCH-ASYNC-001",
  "status": "PROCESSING",
  "is_async": true,
  "total": 250,
  "processed": 150,
  "successful": 148,
  "failed": 2,
  "progress_percent": 60.0,
  "created_at": "2025-11-08T10:00:00+00:00",
  "updated_at": "2025-11-08T10:03:00+00:00",
  "celery": {
    "task_id": "a1b2c3d4...",
    "state": "PROGRESS",
    "current": 150,
    "total": 250
  }
}

Respuesta - Completado (200 OK)

JSON
{
  "batch_id": "BATCH-ASYNC-001",
  "status": "COMPLETED",
  "is_async": true,
  "total": 250,
  "processed": 250,
  "successful": 248,
  "failed": 2,
  "progress_percent": 100.0,
  "created_at": "2025-11-08T10:00:00+00:00",
  "updated_at": "2025-11-08T10:05:00+00:00",
  "completed_at": "2025-11-08T10:05:00+00:00"
}

Estados Posibles

Estado Descripción
QUEUED Batch en cola, aún no ha empezado
PROCESSING Batch en procesamiento activo
COMPLETED Batch completado (todas procesadas)
PARTIAL Batch completado con algunos errores
ERROR Batch falló completamente
CANCELLED Batch cancelado manualmente

Modelos de Datos

Expedición

Estructura completa de una expedición:

Campo Tipo Descripción
id UUID ID único generado por el sistema
client_name string Nombre del cliente final
expedition_id string ID proporcionado por el cliente
expedition_date date Fecha de la expedición
status string PENDING, CALCULATING, COMPLETED, ERROR
error_message string|null Mensaje de error si status=ERROR
scope integer Alcance de emisiones (1 o 3)
cargo object Información de la carga
route array Array de ubicaciones y transportes
results object Resultados totales de emisiones
created_at datetime Fecha de creación
updated_at datetime Última actualización

Cargo

Campo Tipo Descripción
weight decimal Peso de la carga
weight_unit string Unidad: kg, t, lb
is_refrigerated boolean ¿Requiere refrigeración?

Results (Resultados de Emisiones)

Campo Tipo Descripción
total_distance_km decimal Distancia total en kilómetros
total_emission_wtw decimal Emisiones WTW totales (kg CO₂)
total_emission_wtt decimal Emisiones WTT totales (kg CO₂)
total_emission_ttw decimal Emisiones TTW totales (kg CO₂)

Modos de Transporte

Códigos de modos de transporte disponibles y sus parámetros requeridos:

Road (Carretera)

Parámetro Requerido Valores Posibles
vehicle_type HGV-3.5-7.5t, HGV-7.5-17t, HGV-17-40t, HGV-40-60t, etc.
fuel_type diesel, gasoline, cng, lng, electric, hybrid
load_level 0-50, 50-100
distance_km Opcional Distancia en km (si no se proporciona, se calcula automáticamente)

Sea (Marítimo)

Parámetro Requerido Descripción
distance_km Opcional Distancia en km

Air (Aéreo)

Parámetro Requerido Descripción
distance_km Opcional Distancia en km

Rail (Ferrocarril)

Parámetro Requerido Descripción
distance_km Opcional Distancia en km

Códigos de Error

La API utiliza códigos HTTP estándar para indicar el resultado de las peticiones:

Código Nombre Descripción
200 OK Petición exitosa
201 Created Recurso creado exitosamente
202 Accepted Petición aceptada para procesamiento async
400 Bad Request Datos de entrada inválidos
401 Unauthorized Token inválido o ausente
403 Forbidden Cliente inactivo o sin permisos
404 Not Found Recurso no encontrado
429 Too Many Requests Límite de throttling excedido
500 Internal Server Error Error interno del servidor
503 Service Unavailable Servicio externo no disponible

Formato de Respuestas de Error

Todas las respuestas de error siguen este formato JSON estándar:

JSON
{
  "error": "Mensaje descriptivo del error",
  "error_type": "validation_error",
  "details": {
    "field_name": ["Error específico del campo"]
  }
}

Tipos de Error

error_type Descripción
validation_error Datos de entrada inválidos
authentication_error Token inválido o expirado
permission_error Sin permisos para este recurso
limit_exceeded Límite de expediciones agotado
throttle_error Demasiadas peticiones
geocoding_error Error al geocodificar ubicación
routing_error Error al calcular ruta
factor_error Factor de emisión no encontrado
internal_error Error interno del sistema
external_service_error Servicio externo no disponible

Ejemplos de Errores

400 Bad Request - Validación

JSON
{
  "error": "Invalid input data",
  "error_type": "validation_error",
  "details": {
    "cargo": {
      "weight": ["Weight must be greater than 0"]
    },
    "route": ["Route must have at least 3 elements"]
  }
}

401 Unauthorized

JSON
{
  "detail": "Invalid token."
}

429 Too Many Requests

JSON
{
  "detail": "Request was throttled. Expected available in 45 seconds."
}

503 Service Unavailable

JSON
{
  "error": "Geocoding service temporarily unavailable",
  "error_type": "external_service_error"
}

Ejemplos Prácticos

Ejemplo 1: Expedición Simple (Madrid → Barcelona)

Expedición por carretera con un solo tramo:

Python
import requests

API_TOKEN = "tu_token_aqui"
BASE_URL = "https://api.zeolos.es/api"

headers = {
    "Authorization": f"Token {API_TOKEN}",
    "Content-Type": "application/json"
}

# Crear expedición simple
payload = {
    "client_name": "ACME Corporation",
    "expedition_id": "EXP-2025-001",
    "expedition_date": "2025-11-08",
    "scope": 3,
    "cargo": {
        "weight": 1500,
        "weight_unit": "kg",
        "is_refrigerated": False
    },
    "route": [
        {
            "location": {
                "query": "Madrid, España"
            }
        },
        {
            "transport_mode": "road",
            "details": {
                "vehicle_type": "HGV-40-60t",
                "fuel_type": "diesel",
                "load_level": "50-100"
            }
        },
        {
            "location": {
                "query": "Barcelona, España"
            }
        }
    ]
}

response = requests.post(
    f"{BASE_URL}/expeditions/",
    headers=headers,
    json=payload
)

if response.status_code == 201:
    data = response.json()
    print(f"✅ Expedición creada: {data['id']}")
    print(f"📊 Distancia total: {data['results']['total_distance_km']} km")
    print(f"🌍 Emisiones WTW: {data['results']['total_emission_wtw']} kg CO₂")
else:
    print(f"❌ Error {response.status_code}: {response.json()}")

Ejemplo 2: Expedición Multimodal

Expedición combinando carretera + marítimo + carretera:

JavaScript
const API_TOKEN = "tu_token_aqui";
const BASE_URL = "https://api.zeolos.es/api";

const payload = {
    "client_name": "Global Logistics Inc",
    "expedition_id": "MULTI-2025-042",
    "expedition_date": "2025-11-08",
    "cargo": {
        "weight": 5000,
        "weight_unit": "kg",
        "is_refrigerated": true
    },
    "route": [
        { "location": { "query": "Madrid, España" } },
        {
            "transport_mode": "road",
            "details": {
                "vehicle_type": "HGV-17-40t",
                "fuel_type": "diesel",
                "load_level": "50-100"
            }
        },
        { "location": { "query": "Valencia, España" } },
        {
            "transport_mode": "sea",
            "details": {}
        },
        { "location": { "query": "Génova, Italia" } },
        {
            "transport_mode": "road",
            "details": {
                "vehicle_type": "HGV-17-40t",
                "fuel_type": "diesel",
                "load_level": "50-100"
            }
        },
        { "location": { "query": "Milán, Italia" } }
    ]
};

fetch(`${BASE_URL}/expeditions/`, {
    method: 'POST',
    headers: {
        'Authorization': `Token ${API_TOKEN}`,
        'Content-Type': 'application/json'
    },
    body: JSON.stringify(payload)
})
.then(response => response.json())
.then(data => {
    console.log("✅ Expedición creada:", data.id);
    console.log("🚛 Tramos:", data.route.filter(r => r.transport_mode).length);
    console.log("📊 Total CO₂:", data.results.total_emission_wtw, "kg");
})
.catch(error => console.error("❌ Error:", error));

Ejemplo 3: Batch de 5 Expediciones

Procesar múltiples expediciones en un solo request:

Python
import requests

API_TOKEN = "tu_token_aqui"
BASE_URL = "https://api.zeolos.es/api"

headers = {
    "Authorization": f"Token {API_TOKEN}",
    "Content-Type": "application/json"
}

# Crear lista de expediciones
expeditions = []
for i in range(1, 6):
    expeditions.append({
        "client_name": "ACME Corp",
        "expedition_id": f"BATCH-EXP-{i:03d}",
        "expedition_date": "2025-11-08",
        "cargo": {
            "weight": 1000 + (i * 100),
            "weight_unit": "kg",
            "is_refrigerated": False
        },
        "route": [
            {"location": {"query": "Madrid, España"}},
            {
                "transport_mode": "road",
                "details": {
                    "vehicle_type": "HGV-17-40t",
                    "fuel_type": "diesel",
                    "load_level": "50-100"
                }
            },
            {"location": {"query": "Barcelona, España"}}
        ]
    })

# Enviar batch
batch_payload = {
    "batch_id": "BATCH-2025-001",
    "expeditions": expeditions
}

response = requests.post(
    f"{BASE_URL}/expeditions/batch/",
    headers=headers,
    json=batch_payload
)

if response.status_code == 200:
    result = response.json()
    print(f"✅ Batch procesado en {result['processing_time_seconds']:.2f} segundos")
    print(f"📦 Total: {result['total']}")
    print(f"✅ Exitosas: {result['successful']}")
    print(f"❌ Fallidas: {result['failed']}")
    
    # Mostrar resultados
    for item in result['results']:
        if item['status'] == 'success':
            print(f"   ✅ {item['expedition_id']}: OK")
        else:
            print(f"   ❌ {item['expedition_id']}: {item['error']}")
else:
    print(f"❌ Error: {response.json()}")

Ejemplo 4: Batch Async + Polling de Estado

Procesar 100 expediciones de forma asíncrona y consultar el progreso:

Python
import requests
import time

API_TOKEN = "tu_token_aqui"
BASE_URL = "https://api.zeolos.es/api"

headers = {
    "Authorization": f"Token {API_TOKEN}",
    "Content-Type": "application/json"
}

# Generar 100 expediciones
expeditions = []
for i in range(1, 101):
    expeditions.append({
        "client_name": "Big Client",
        "expedition_id": f"ASYNC-{i:04d}",
        "expedition_date": "2025-11-08",
        "cargo": {
            "weight": 2000,
            "weight_unit": "kg",
            "is_refrigerated": False
        },
        "route": [
            {"location": {"query": "Madrid, España"}},
            {
                "transport_mode": "road",
                "details": {
                    "vehicle_type": "HGV-17-40t",
                    "fuel_type": "diesel",
                    "load_level": "50-100"
                }
            },
            {"location": {"query": "París, Francia"}}
        ]
    })

# Enviar batch async
print("📤 Enviando batch asíncrono...")
response = requests.post(
    f"{BASE_URL}/expeditions/batch/async/",
    headers=headers,
    json={
        "batch_id": "BATCH-ASYNC-2025",
        "expeditions": expeditions
    }
)

if response.status_code == 202:
    data = response.json()
    batch_id = data['batch_id']
    status_url = data['status_url']
    
    print(f"✅ Batch aceptado: {batch_id}")
    print(f"⏱️  Tiempo estimado: {data['estimated_time_minutes']:.1f} minutos")
    print(f"🔍 Consultando estado cada 10 segundos...\n")
    
    # Polling del estado
    while True:
        status_response = requests.get(
            f"{BASE_URL}{status_url}",
            headers=headers
        )
        
        if status_response.status_code == 200:
            status_data = status_response.json()
            status = status_data['status']
            progress = status_data['progress_percent']
            processed = status_data['processed']
            total = status_data['total']
            
            print(f"📊 {status}: {processed}/{total} ({progress:.1f}%)")
            
            if status in ['COMPLETED', 'PARTIAL', 'ERROR', 'CANCELLED']:
                print(f"\n✅ Batch finalizado!")
                print(f"   ✅ Exitosas: {status_data['successful']}")
                print(f"   ❌ Fallidas: {status_data['failed']}")
                break
            
            time.sleep(10)  # Esperar 10 segundos antes de consultar de nuevo
        else:
            print(f"❌ Error consultando estado: {status_response.status_code}")
            break
else:
    print(f"❌ Error: {response.json()}")

Ejemplo 5: Manejo de Errores

Ejemplo robusto con manejo completo de errores:

Python
import requests
from requests.exceptions import RequestException
import time

API_TOKEN = "tu_token_aqui"
BASE_URL = "https://api.zeolos.es/api"

def create_expedition_with_retry(payload, max_retries=3):
    """Crear expedición con reintentos en caso de error."""
    headers = {
        "Authorization": f"Token {API_TOKEN}",
        "Content-Type": "application/json"
    }
    
    for attempt in range(max_retries):
        try:
            response = requests.post(
                f"{BASE_URL}/expeditions/",
                headers=headers,
                json=payload,
                timeout=30
            )
            
            # Éxito
            if response.status_code == 201:
                return {'success': True, 'data': response.json()}
            
            # Error de validación
            elif response.status_code == 400:
                error_data = response.json()
                return {
                    'success': False,
                    'error': 'validation_error',
                    'message': error_data.get('error'),
                    'details': error_data.get('details')
                }
            
            # Error de autenticación
            elif response.status_code == 401:
                return {
                    'success': False,
                    'error': 'auth_error',
                    'message': 'Token inválido o expirado'
                }
            
            # Throttling (429) - esperar y reintentar
            elif response.status_code == 429:
                retry_after = int(response.headers.get('Retry-After', 60))
                print(f"⏳ Throttle detectado. Esperando {retry_after} segundos...")
                
                if attempt < max_retries - 1:
                    time.sleep(retry_after)
                    continue
                else:
                    return {
                        'success': False,
                        'error': 'throttle_error',
                        'message': f'Demasiadas peticiones. Reintentar en {retry_after}s'
                    }
            
            # Error del servidor (500/503) - reintentar
            elif response.status_code >= 500:
                error_data = response.json()
                
                if attempt < max_retries - 1:
                    wait_time = 2 ** attempt  # Backoff exponencial
                    print(f"⏳ Error del servidor. Reintentando en {wait_time}s...")
                    time.sleep(wait_time)
                    continue
                else:
                    return {
                        'success': False,
                        'error': 'server_error',
                        'message': error_data.get('error', 'Error del servidor')
                    }
            
            # Otro error
            else:
                return {
                    'success': False,
                    'error': 'unknown_error',
                    'message': f'Error HTTP {response.status_code}'
                }
        
        except RequestException as e:
            if attempt < max_retries - 1:
                print(f"⏳ Error de conexión. Reintentando...")
                time.sleep(2 ** attempt)
                continue
            else:
                return {
                    'success': False,
                    'error': 'connection_error',
                    'message': str(e)
                }
    
    return {
        'success': False,
        'error': 'max_retries_exceeded',
        'message': 'Se alcanzó el máximo de reintentos'
    }

# Usar la función
payload = {
    "client_name": "Test Client",
    "expedition_id": "TEST-001",
    "expedition_date": "2025-11-08",
    "cargo": {
        "weight": 1000,
        "weight_unit": "kg",
        "is_refrigerated": False
    },
    "route": [
        {"location": {"query": "Madrid, España"}},
        {
            "transport_mode": "road",
            "details": {
                "vehicle_type": "HGV-17-40t",
                "fuel_type": "diesel",
                "load_level": "50-100"
            }
        },
        {"location": {"query": "Barcelona, España"}}
    ]
}

result = create_expedition_with_retry(payload)

if result['success']:
    data = result['data']
    print(f"✅ Expedición creada exitosamente!")
    print(f"   ID: {data['id']}")
    print(f"   CO₂: {data['results']['total_emission_wtw']} kg")
else:
    print(f"❌ Error: {result['error']}")
    print(f"   Mensaje: {result['message']}")
    if 'details' in result:
        print(f"   Detalles: {result['details']}")

Ejemplo 6: Filtrar Expediciones y Batches por Fecha

Ejemplo completo de reporting mensual con filtrado por fechas:

Python
import requests
from datetime import datetime, timedelta

API_TOKEN = "tu_token_aqui"
BASE_URL = "https://api.zeolos.es/api"

headers = {"Authorization": f"Token {API_TOKEN}"}

def generate_monthly_report(year, month):
    """Genera reporte mensual de expediciones y batches"""
    
    # Calcular rango de fechas del mes
    first_day = datetime(year, month, 1)
    if month == 12:
        last_day = datetime(year + 1, 1, 1) - timedelta(days=1)
    else:
        last_day = datetime(year, month + 1, 1) - timedelta(days=1)
    
    print(f"📊 Reporte: {first_day.strftime('%B %Y')}")
    print("=" * 60)
    
    # 1. Obtener todas las expediciones del mes
    print("\n📦 Expediciones del mes:")
    
    all_expeditions = []
    page = 1
    
    while True:
        response = requests.get(
            f"{BASE_URL}/expeditions/",
            headers=headers,
            params={
                "expedition_date_from": first_day.strftime("%Y-%m-%d"),
                "expedition_date_to": last_day.strftime("%Y-%m-%d"),
                "page": page,
                "page_size": 100
            }
        )
        
        if response.status_code != 200:
            print(f"Error: {response.status_code}")
            break
        
        data = response.json()
        all_expeditions.extend(data['results'])
        
        if not data['next']:
            break
        page += 1
    
    # Estadísticas de expediciones
    completed = [exp for exp in all_expeditions if exp['status'] == 'COMPLETED']
    errors = [exp for exp in all_expeditions if exp['status'] == 'ERROR']
    
    if completed:
        total_co2 = sum(exp['results']['total_emission_wtw'] for exp in completed)
        total_km = sum(exp['results']['total_distance_km'] for exp in completed)
        avg_co2 = total_co2 / len(completed)
        
        print(f"Total expediciones: {len(all_expeditions)}")
        print(f"  ✅ Completadas: {len(completed)} ({len(completed)/len(all_expeditions)*100:.1f}%)")
        print(f"  ❌ Con error: {len(errors)} ({len(errors)/len(all_expeditions)*100:.1f}%)")
        print(f"\nEmisiones totales: {total_co2:.2f} kg CO₂")
        print(f"Distancia total: {total_km:.2f} km")
        print(f"Promedio: {avg_co2:.2f} kg CO₂/expedición")
        print(f"Intensidad: {(total_co2/total_km):.3f} kg CO₂/km")
    
    # 2. Obtener todos los batches del mes
    print(f"\n\n📦 Batches del mes:")
    
    response = requests.get(
        f"{BASE_URL}/expeditions/batches/",
        headers=headers,
        params={
            "created_from": first_day.strftime("%Y-%m-%d"),
            "created_to": last_day.strftime("%Y-%m-%d")
        }
    )
    
    if response.status_code == 200:
        batches = response.json()['results']
        print(f"Total batches: {len(batches)}")
        
        for batch in batches:
            success_rate = (batch['successful'] / batch['total'] * 100) if batch['total'] > 0 else 0
            status_icon = "✅" if batch['status'] == "COMPLETED" else "⏳"
            print(f"{status_icon} {batch['batch_id']}: {batch['successful']}/{batch['total']} ({success_rate:.1f}%)")
    
    # 3. Top 10 expediciones con más emisiones
    print(f"\n\n🔝 Top 10 expediciones con más emisiones:")
    top_emissions = sorted(completed, key=lambda x: x['results']['total_emission_wtw'], reverse=True)[:10]
    
    for i, exp in enumerate(top_emissions, 1):
        print(f"{i}. {exp['expedition_id']}: {exp['results']['total_emission_wtw']:.2f} kg CO₂ ({exp['results']['total_distance_km']:.0f} km)")

# Generar reporte de noviembre 2025
generate_monthly_report(2025, 11)

¿Necesitas ayuda?

Contacta con soporte técnico: soporte@zeolos.com

Documentación actualizada: Noviembre 2025