Saltar a contenido

Contrato de la API

Documento técnico — 2026-06-08

Especificación formal de los endpoints HTTP del backend IslaBox. Es el contrato entre el frontend (app Android) y el backend (FastAPI). Cada endpoint declara: método, URL, permiso requerido, request, response, errores posibles, estados afectados, eventos generados y notificaciones disparadas.

Este documento es la fuente de verdad para integrar app y backend. Si hay conflicto entre este archivo y el código, gana este archivo.


Etiquetado de fase

Cada endpoint lleva una etiqueta que indica en qué fase se implementa:

Etiqueta Significado
🟢 MVP Implementado y disponible en el MVP operativo
🟡 MVP-extendido Planeado para MVP-beta pero puede entregarse después si se atrasa
🔵 Fase 2 No se implementa en MVP; planeado para Fase 2+
Futuro Idea a evaluar; sin fecha de implementación

Por qué importa

El MVP usa pasarela de pago manual (registrada por el operador). Stripe entra en Fase 2. Los webhooks de agencia pueden entrar en MVP-extendido si el contrato con la agencia se firma a tiempo; en caso contrario, el operador actualiza manualmente. El OCR y WhatsApp son Fase 2+.

Esta etiquetado ayuda a:

  • El equipo de mobile a saber qué endpoints puede consumir.
  • El equipo de backend a saber qué endpoints debe implementar primero.
  • El PM a priorizar el scope del MVP.

Convenciones

  • Base URL: https://api.islabox.com (producción) / http://localhost:8000 (desarrollo)
  • Prefijo de versión: /api/v1
  • Autenticación: Bearer Token (JWT) en el header Authorization: Bearer <token>, excepto en endpoints públicos marcados con 🔓
  • Formato de intercambio: application/json con Content-Type y Accept
  • Fechas: ISO 8601 UTC (2026-06-07T14:30:00Z)
  • Montos: string decimal con 2 decimales ("49.50"), moneda separada
  • Idioma de mensajes de error: español
  • Paginación: ?page=1&page_size=20 con respuesta {"count": N, "next": url, "previous": url, "results": [...]}
  • Códigos de error: machine-readable (KYC_NOT_APPROVED) y message human-readable

Códigos de error estándar

HTTP Code Significado
400 VALIDATION_ERROR Error de validación en el request
400 INVALID_PHONE_FORMAT Teléfono no cumple E.164 con +1
400 PHONE_COUNTRY_NOT_SUPPORTED País distinto a EE.UU.
401 UNAUTHENTICATED Token inválido o expirado
403 FORBIDDEN Permiso insuficiente (ver matriz-roles-permisos)
403 KYC_NOT_APPROVED KYC no está en aprobado
403 PACKAGE_NOT_OWNED El paquete no pertenece al usuario
403 PACKAGE_NOT_AVAILABLE El paquete está en un estado que no permite la acción
403 PACKAGE_ALREADY_CONSOLIDATED El paquete ya está en una consolidación
403 CONSOLIDATION_LOCKED La consolidación está pagada o despachada
404 NOT_FOUND Recurso no existe
409 CONFLICT Conflicto con el estado actual
409 LOCKED Recurso bloqueado por otro proceso (lock distribuido)
422 BUSINESS_RULE_VIOLATED Viola una regla RN-XXX (campo rule: "RN-024")
429 RATE_LIMITED Demasiadas requests, ver header Retry-After
500 INTERNAL_ERROR Error del servidor
503 SERVICE_UNAVAILABLE Servicio externo (agencia, Stripe) no disponible

Módulo 1: Autenticación

POST /api/v1/auth/register 🔓

Crea una cuenta nueva. Solo se permiten registros de personas en EE.UU. (RN-001 a RN-004).

  • Permiso: público
  • Request:
    {
      "email": "kleudy@example.com",
      "phone": "+15551234567",
      "first_name": "Kleudy",
      "last_name": "Torno",
      "password": "IslaBox2026!",
      "referral_code": null
    }
    
  • Response 201:
    {
      "user_id": 42,
      "locker_code": "CP-001042-8",
      "email_verification_required": true,
      "phone_verification_required": true,
      "auth_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
    }
    
  • Errores: 400 INVALID_PHONE_FORMAT, 400 PHONE_COUNTRY_NOT_SUPPORTED, 409 EMAIL_ALREADY_EXISTS, 409 PHONE_ALREADY_EXISTS
  • Estados afectados: crea users (status: pending_verification), locker_codes, user_referrals (si hay código)
  • Eventos: USER_REGISTERED
  • Notificaciones: email de verificación + SMS de OTP

POST /api/v1/auth/verify-email 🔓

Verifica el email mediante token enviado al correo.

  • Permiso: público (con token)
  • Request:
    { "token": "abc123..." }
    
  • Response 200:
    { "verified": true, "user_id": 42 }
    
  • Errores: 400 INVALID_TOKEN, 410 TOKEN_EXPIRED
  • Estados afectados: users.email_verified = true
  • Eventos: EMAIL_VERIFIED

POST /api/v1/auth/verify-phone 🔓

Verifica el teléfono con código OTP.

  • Permiso: público
  • Request:
    { "phone": "+15551234567", "otp_code": "847293" }
    
  • Response 200:
    { "verified": true, "user_id": 42 }
    
  • Errores: 400 INVALID_OTP, 410 OTP_EXPIRED, 429 RATE_LIMITED (5 intentos / 15 min)
  • Estados afectados: users.phone_verified = true
  • Eventos: PHONE_VERIFIED

POST /api/v1/auth/login 🔓

Inicia sesión.

  • Permiso: público
  • Request:
    { "email": "kleudy@example.com", "password": "IslaBox2026!" }
    
  • Response 200:
    {
      "auth_token": "eyJ...",
      "refresh_token": "rt_...",
      "expires_in": 3600,
      "user": {
        "id": 42,
        "email": "kleudy@example.com",
        "full_name": "Kleudy Torno",
        "locker_code": "CP-001042-8",
        "kyc_status": "aprobado",
        "role": "user"
      }
    }
    
  • Errores: 401 INVALID_CREDENTIALS, 423 ACCOUNT_LOCKED (5 intentos fallidos)
  • Estados afectados: crea api_tokens
  • Eventos: USER_LOGIN
  • Notificaciones: email de "Nuevo inicio de sesión" si es desde IP/dispositivo nuevo

POST /api/v1/auth/forgot-password 🔓

Solicita recuperación de contraseña.

  • Request:
    { "email": "kleudy@example.com" }
    
  • Response 200:
    { "message": "Si el email existe, recibirás un enlace de recuperación" }
    
  • Eventos: PASSWORD_RESET_REQUESTED (solo si el email existe)
  • Notificaciones: email con enlace válido 30 minutos

POST /api/v1/auth/reset-password 🔓

Confirma el reset con el token recibido.

  • Request:
    { "token": "abc...", "new_password": "NuevaPass2026!" }
    
  • Response 200:
    { "message": "Contraseña actualizada" }
    
  • Estados afectados: users.password_hash actualizado, todos los api_tokens anteriores revocados

POST /api/v1/auth/logout

Cierra la sesión actual.

  • Permiso: autenticado
  • Response 204: sin contenido
  • Estados afectados: revoca el api_token actual
  • Eventos: USER_LOGOUT

Módulo 2: Perfil y casillero

GET /api/v1/users/me

Obtiene el perfil del usuario autenticado.

  • Permiso: autenticado (cualquier rol)
  • Response 200:
    {
      "id": 42,
      "email": "kleudy@example.com",
      "email_verified": true,
      "phone": "+15551234567",
      "phone_verified": true,
      "full_name": "Kleudy Torno",
      "locker_code": "CP-001042-8",
      "country_code": "US",
      "role": "user",
      "level": 1,
      "referral_code": "KLEUDY42",
      "kyc_status": "aprobado",
      "created_at": "2026-05-12T08:00:00Z"
    }
    

PATCH /api/v1/users/me

Actualiza el perfil.

  • Permiso: autenticado
  • Request:
    { "full_name": "Kleudy Victor Torno Peña" }
    
  • Estados afectados: users.full_name (u otros campos permitidos)
  • Eventos: USER_PROFILE_UPDATED, registro en audit_logs

GET /api/v1/users/me/locker-address

Devuelve la dirección completa del casillero para copiarla.

  • Permiso: autenticado
  • Response 200:
    {
      "line1": "ISLABOX CP-001042-8",
      "line2": "1234 NW 24th Ave",
      "city": "Miami",
      "state": "FL",
      "zip_code": "33125",
      "country": "US",
      "full_address": "ISLABOX CP-001042-8\n1234 NW 24th Ave\nMiami, FL 33125\nUnited States"
    }
    

GET /api/v1/users/me/kyc

Devuelve el estado del KYC del usuario.

  • Permiso: autenticado
  • Response 200:
    {
      "status": "aprobado",
      "document_type": "passport",
      "submitted_at": "2026-05-20T10:00:00Z",
      "reviewed_at": "2026-05-22T14:30:00Z",
      "expires_at": "2036-05-22T14:30:00Z",
      "rejection_reason": null
    }
    

Módulo 3: Prealertas (reportes de compra)

POST /api/v1/purchase-reports

Crea una nueva prealerta.

  • Permiso: autenticado (cualquier rol)
  • Request:
    {
      "marketplace": "amazon",
      "order_number": "111-2222222-3333333",
      "purchase_date": "2026-06-01",
      "total_declared_value": "49.50",
      "currency": "USD",
      "tracking_number": "1Z999AA10123456784",
      "items": [
        {
          "description": "Auriculares Bluetooth",
          "quantity": 1,
          "unit_value": "49.50",
          "category_id": 8,
          "product_url": "https://amazon.com/dp/B0XXXX",
          "seller": "Sony"
        }
      ],
      "recipient_id": 12,
      "sender_id": null,
      "consolidate": true,
      "additional_services": ["inspection"],
      "attachment_file_ids": [101]
    }
    
  • Response 201:
    {
      "id": 555,
      "status": "reportada",
      "user_id": 42,
      "created_at": "2026-06-07T15:00:00Z",
      "items_count": 1
    }
    
  • Errores: 400 VALIDATION_ERROR, 404 RECIPIENT_NOT_FOUND
  • Estados afectados: crea purchase_reports, purchase_items, tracking_numbers
  • Eventos: PURCHASE_REPORT_CREATED
  • Notificaciones: confirmación al usuario

GET /api/v1/purchase-reports?status=...

Lista prealertas del usuario autenticado, con filtros.

  • Permiso: autenticado
  • Query params: status (enum oficial), page, page_size
  • Response 200 (paginado):
    {
      "count": 12,
      "next": null,
      "previous": null,
      "results": [
        {
          "id": 555,
          "marketplace": "amazon",
          "order_number": "111-2222222-3333333",
          "status": "reportada_sin_tracking",
          "created_at": "2026-06-07T15:00:00Z"
        }
      ]
    }
    

PATCH /api/v1/purchase-reports/{id}/tracking

Actualiza el tracking de una prealerta.

  • Permiso: autenticado, dueño de la prealerta
  • Request:
    { "tracking_number": "1Z999AA10123456784", "carrier": "ups" }
    
  • Estados afectados: crea/actualiza tracking_numbers, purchase_reports.statustracking_actualizado
  • Eventos: TRACKING_UPDATED

POST /api/v1/purchase-reports/{id}/cancel

Cancela una prealerta.

  • Permiso: autenticado, dueño
  • Estados afectados: purchase_reports.statuscancelada_por_usuario (si la llama el usuario) o cancelada_por_operador (si la llama un operador)
  • Eventos: PURCHASE_REPORT_CANCELLED

Módulo 4: Paquetes

GET /api/v1/packages?status=...

Lista paquetes del usuario.

  • Permiso: autenticado
  • Response 200: igual formato que purchase-reports

GET /api/v1/packages/{id}

Detalle de un paquete.

  • Permiso: autenticado, dueño
  • Response 200:
    {
      "id": 888,
      "status": "en_almacen",
      "tracking_number": "1Z999AA10123456784",
      "carrier": "ups",
      "weight_kg": "0.450",
      "length_cm": "20.0",
      "width_cm": "15.0",
      "height_cm": "10.0",
      "received_at": "2026-06-05T10:30:00Z",
      "storage_days": 2,
      "free_days_remaining": 13,
      "storage_charge_estimated": "0.00",
      "locker_code": "CP-001042-8",
      "purchase_report_id": 555,
      "consolidation_id": null,
      "shipment_id": null,
      "photos": ["/api/v1/files/201/url", "/api/v1/files/202/url"]
    }
    
  • Errores: 404 NOT_FOUND, 403 PACKAGE_NOT_OWNED

GET /api/v1/packages/{id}/events

Timeline de eventos del paquete.

  • Response 200:
    {
      "events": [
        { "status": "prealertado", "timestamp": "2026-06-01T10:00:00Z", "note": "Prealerta creada" },
        { "status": "en_camino_al_almacen", "timestamp": "2026-06-03T08:00:00Z", "note": "Tracking actualizado" },
        { "status": "recibido_sin_identificar", "timestamp": "2026-06-05T10:25:00Z" },
        { "status": "recibido_identificado", "timestamp": "2026-06-05T10:35:00Z" },
        { "status": "en_almacen", "timestamp": "2026-06-05T10:40:00Z" }
      ]
    }
    

GET /api/v1/packages/summary

Resumen agregado para el dashboard (4 secciones).

  • Response 200:
    {
      "in_warehouse": 3,
      "in_transit": 1,
      "shipped": 5,
      "delivered": 12,
      "attention_count": 2
    }
    

POST /api/v1/packages/{id}/photos

Adjunta fotos a un paquete (evidencia de recepción, daño, etc.).

  • Permiso: autenticado, dueño o operator
  • Request: multipart/form-data con archivos
  • Estados afectados: crea package_photos con URL prefirmada en MinIO
  • Eventos: PACKAGE_PHOTO_ADDED

Módulo 5: Consolidación

POST /api/v1/consolidations

Crea una consolidación.

  • Permiso: autenticado, KYC aprobado (RN-024)
  • Request:
    {
      "package_ids": [888, 889, 890],
      "recipient_id": 12,
      "shipping_method": "air",
      "notes": "Por favor consolidar en una caja"
    }
    
  • Response 201:
    {
      "id": 99,
      "status": "pendiente_cotizacion",
      "package_count": 3,
      "total_weight_kg": "1.350",
      "estimated_cost": "12.62",
      "quote_id": null,
      "created_at": "2026-06-07T16:00:00Z"
    }
    
  • Errores:
  • 403 KYC_NOT_APPROVED
  • 403 PACKAGE_NOT_OWNED
  • 403 PACKAGE_NOT_AVAILABLE (uno de los paquetes está en estado inválido)
  • 403 PACKAGE_ALREADY_CONSOLIDATED
  • 422 BUSINESS_RULE_VIOLATED con rule: "RN-023" si algún paquete tiene incidencia abierta
  • Estados afectados: crea consolidations, consolidation_packages; cambia physical_packages.statusreservado_para_consolidacion
  • Eventos: CONSOLIDATION_CREATED, PACKAGE_RESERVED
  • Notificaciones: confirmación al usuario

GET /api/v1/consolidations

Lista consolidaciones del usuario.


GET /api/v1/consolidations/{id}

Detalle de una consolidación, con sus paquetes.

  • Response 200:
    {
      "id": 99,
      "status": "cotizada",
      "package_count": 3,
      "total_weight_kg": "1.350",
      "total_value_usd": "120.00",
      "shipping_method": "air",
      "quote": {
        "id": 50,
        "expires_at": "2026-06-14T16:00:00Z",
        "subtotal": "9.07",
        "consolidation_fee": "1.50",
        "customs_fee": "2.05",
        "total": "12.62",
        "items": [
          { "concept": "Servicio de casillero", "amount": "0.00" },
          { "concept": "Envío internacional (aéreo)", "amount": "4.52" },
          { "concept": "Consolidación", "amount": "1.50" },
          { "concept": "Aduana", "amount": "2.05" }
        ]
      },
      "packages": [888, 889, 890],
      "tracking_number": null,
      "shipped_at": null
    }
    

POST /api/v1/consolidations/{id}/cancel

Cancela una consolidación.

  • Estados afectados: consolidations.statuscancelada; physical_packages.statusesperando_instrucciones (RN-026)
  • Eventos: CONSOLIDATION_CANCELLED

Módulo 6: Cotizaciones

POST /api/v1/quotes

Genera (o regenera) una cotización para una consolidación.

  • Permiso: autenticado, dueño de la consolidación
  • Request:
    { "consolidation_id": 99 }
    
  • Response 201: igual estructura que quote en consolidación
  • Estados afectados: crea/actualiza quotes, quote_items
  • Eventos: QUOTE_GENERATED
  • Reglas: RN-060 a RN-065 (vigencia 7 días, 9 conceptos, desglose obligatorio)

Módulo 7: Pagos

POST /api/v1/payments

Registra un pago (MVP: pago manual; Fase 2: Stripe).

  • Permiso: autenticado, dueño de la consolidación
  • Request:
    {
      "quote_id": 50,
      "payment_method_mix": [
        { "method": "real", "amount": "12.62" }
      ]
    }
    
  • Response 201:
    {
      "id": 200,
      "status": "pendiente",
      "amount": "12.62",
      "invoice_id": 150,
      "provider_reference": null
    }
    
  • Estados afectados: crea payments, invoices, charges
  • Eventos: PAYMENT_INITIATED
  • Reglas: RN-070 a RN-075

POST /api/v1/payments/{id}/confirm (operador)

Confirma un pago manual con comprobante.

  • Permiso: operator o admin
  • Request:
    { "evidence_file_id": 305, "notes": "Pago recibido por Zelle" }
    
  • Estados afectados: payments.statuscompletado, invoices.statuspagada, consolidations.statuspagada (RN-072)
  • Eventos: PAYMENT_CONFIRMED
  • Notificaciones: confirmación al usuario

POST /api/v1/payments/{id}/refund (admin)

Inicia un reembolso.

  • Permiso: admin o sysadmin
  • Request:
    { "amount": "12.62", "reason": "Incidencia resuelta a favor del usuario" }
    
  • Estados afectados: payments.statusreembolsado
  • Eventos: PAYMENT_REFUNDED
  • Auditoría: requerido (RN-100, RN-101)

Módulo 8: Envíos (despacho)

POST /api/v1/shipments

Crea el envío (registro) a partir de una consolidación pagada.

  • Permiso: operator o sistema
  • Request:
    { "consolidation_id": 99 }
    
  • Estados afectados: crea shipments (status: borrador)

POST /api/v1/shipments/{id}/dispatch

Confirma el despacho físico del envío.

  • Permiso: operator o admin
  • Request:
    {
      "master_guide": "1Z999AA10987654321",
      "carrier": "agencia_aliada",
      "shipped_at": "2026-06-10T10:00:00Z"
    }
    
  • Estados afectados: shipments.statusdespachado_desde_almacen, consolidations.statusdespachada
  • Eventos: SHIPMENT_DISPATCHED
  • Notificaciones: "Tu envío ha sido despachado. Guía: {master_guide}"

GET /api/v1/shipments/{id}

Detalle de un envío + timeline.


Módulo 9: KYC

POST /api/v1/kyc/documents

Carga los documentos del KYC.

  • Permiso: autenticado
  • Request: multipart/form-data con:
  • document_type: enum
  • document_number: string
  • document_front_file: archivo
  • document_back_file: archivo (opcional)
  • selfie_file: archivo
  • residence_address: text
  • state: string
  • zip_code: string
  • Response 201:
    {
      "id": 77,
      "status": "enviado",
      "submitted_at": "2026-06-07T17:00:00Z"
    }
    
  • Estados afectados: crea/actualiza kyc_records (status: enviado), crea file_attachments
  • Eventos: KYC_SUBMITTED
  • Notificaciones: al equipo de revisión

POST /api/v1/kyc/{id}/review (operador)

Revisa un KYC.

  • Permiso: operator o admin
  • Request:
    {
      "decision": "aprobado" | "rechazado" | "requiere_mas_informacion",
      "notes": "Documento legible y vigente"
    }
    
  • Estados afectados: kyc_records.status actualizado
  • Eventos: KYC_REVIEWED
  • Notificaciones: al usuario con el resultado

Módulo 10: Servicios adicionales

POST /api/v1/service-requests

Solicita un servicio adicional.

  • Request:
    {
      "package_id": 888,
      "service_type": "inspection" | "repack" | "return",
      "description": "Verificar que el producto no tenga daños"
    }
    

POST /api/v1/service-requests/{id}/approve-cost

Aprueba el costo cotizado por el operador.


POST /api/v1/service-requests/{id}/evidence (operador)

Carga evidencia del servicio ejecutado.


Módulo 11: Incidencias

POST /api/v1/incidents

Abre una incidencia.

  • Request:
    {
      "package_id": 888,
      "type": "paquete_danado" | "paquete_perdido" | "sin_prealerta" | "tracking_incorrecto" | "articulo_prohibido" | "pago_duplicado" | "producto_faltante" | "retenido_aduana" | "otro",
      "description": "El paquete llegó con la caja rota",
      "attachment_file_ids": [403, 404]
    }
    

GET /api/v1/incidents

Lista incidencias del usuario autenticado (o de cualquier usuario si es operador).


POST /api/v1/incidents/{id}/respond

Añade un mensaje a la incidencia.


POST /api/v1/incidents/{id}/close (operador)

Cierra la incidencia con resolución.

  • Estados afectados: incidents.statuscerrada o resuelta

Módulo 12: Webhooks externos

POST /api/v1/webhooks/agency

Recibe actualizaciones de estado de envío desde la agencia aliada.

  • Permiso: autenticación por firma HMAC en header X-Agency-Signature
  • Request:
    {
      "shipment_id": 300,
      "new_status": "en_aduana",
      "carrier_tracking": "1Z999AA...",
      "event_timestamp": "2026-06-12T08:00:00Z",
      "notes": "Ingreso a aduana de Cuba"
    }
    
  • Estados afectados: shipments.status actualizado, evento en integration_events
  • Eventos: SHIPMENT_STATUS_UPDATED_BY_AGENCY
  • Notificaciones: push al usuario

POST /api/v1/webhooks/stripe (Fase 2)

Recibe confirmación de pago desde Stripe.

  • Permiso: firma de Stripe
  • Estados afectados: payments.statuscompletado, consolidations.statuspagada

Módulo 13: Administración (admin)

PATCH /api/v1/admin/storage-policy

Configura días gratis y costo de almacenaje.

  • Permiso: admin o sysadmin
  • Request:
    { "free_days": 15, "daily_cost_usd": "0.50", "warning_days_before": 3 }
    

PATCH /api/v1/admin/shipping-rates

Configura tarifas de envío.

  • Permiso: admin o sysadmin
  • Request:
    { "air_rate_per_lb": "3.35", "sea_rate_per_lb": "2.40" }
    

GET /api/v1/admin/audit-logs?actor_id=...&from=...&to=...

Consulta logs de auditoría con filtros.

  • Permiso: admin o sysadmin
  • Auditoría: cada consulta a este endpoint genera un evento AUDIT_LOG_ACCESSED (RN-101)

Versionado

  • Versión actual: v1 (en prefijo de URL)
  • Política de deprecación: una versión se mantiene por al menos 12 meses después de marcar como deprecated.
  • Header de deprecación: cuando aplica, se incluye Deprecation: true y Sunset: <fecha RFC 7234>.

Cómo se mantiene este documento

  1. Antes de implementar un nuevo endpoint, agregarlo aquí con la firma completa.
  2. Cualquier cambio de comportamiento en un endpoint existente requiere actualizar este archivo primero, luego el código.
  3. La matriz de trazabilidad hace referencia a estos endpoints por su URL.
  4. El equipo de frontend Android usa este archivo como input para sus RetrofitInterface o equivalente.