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
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/
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
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 -X GET https://api.zeolos.es/api/expeditions/ \
-H "Authorization: Token abc123def456ghi789jkl012mno345pqr678stu901vwx234yz" \
-H "Content-Type: application/json"
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())
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 |
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/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)
/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 |
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
{
"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)
{
"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
/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 |
- 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 -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
# 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
# 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
# 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
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)
{
"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
/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 -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)
/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.
- 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
{
"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)
{
"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
{
"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)
/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.
- 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)
{
"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
/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 -X GET "https://api.zeolos.es/api/expeditions/batches/" \
-H "Authorization: Token tu_token_aqui"
Filtrar por fecha y estado
# 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
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)
{
"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
/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.
/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 -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
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)
{
"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
/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 -X GET "https://api.zeolos.es/api/expeditions/batch/BATCH-ASYNC-001/status/" \
-H "Authorization: Token tu_token_aqui"
Respuesta - En Proceso (200 OK)
{
"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)
{
"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 |
Sí | HGV-3.5-7.5t, HGV-7.5-17t, HGV-17-40t, HGV-40-60t, etc. |
fuel_type |
Sí | diesel, gasoline, cng, lng, electric, hybrid |
load_level |
Sí | 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:
{
"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
{
"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
{
"detail": "Invalid token."
}
429 Too Many Requests
{
"detail": "Request was throttled. Expected available in 45 seconds."
}
503 Service Unavailable
{
"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:
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:
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:
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:
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:
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:
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