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/jsonconContent-TypeyAccept - 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=20con respuesta{"count": N, "next": url, "previous": url, "results": [...]} - Códigos de error: machine-readable (
KYC_NOT_APPROVED) ymessagehuman-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_hashactualizado, todos losapi_tokensanteriores revocados
POST /api/v1/auth/logout¶
Cierra la sesión actual.
- Permiso: autenticado
- Response 204: sin contenido
- Estados afectados: revoca el
api_tokenactual - 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 enaudit_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.status→tracking_actualizado - Eventos:
TRACKING_UPDATED
POST /api/v1/purchase-reports/{id}/cancel¶
Cancela una prealerta.
- Permiso: autenticado, dueño
- Estados afectados:
purchase_reports.status→cancelada_por_usuario(si la llama el usuario) ocancelada_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-datacon archivos - Estados afectados: crea
package_photoscon 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_APPROVED403 PACKAGE_NOT_OWNED403 PACKAGE_NOT_AVAILABLE(uno de los paquetes está en estado inválido)403 PACKAGE_ALREADY_CONSOLIDATED422 BUSINESS_RULE_VIOLATEDconrule: "RN-023"si algún paquete tiene incidencia abierta- Estados afectados: crea
consolidations,consolidation_packages; cambiaphysical_packages.status→reservado_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.status→cancelada;physical_packages.status→esperando_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
quoteen 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:
operatoroadmin - Request:
{ "evidence_file_id": 305, "notes": "Pago recibido por Zelle" } - Estados afectados:
payments.status→completado,invoices.status→pagada,consolidations.status→pagada(RN-072) - Eventos:
PAYMENT_CONFIRMED - Notificaciones: confirmación al usuario
POST /api/v1/payments/{id}/refund (admin)¶
Inicia un reembolso.
- Permiso:
adminosysadmin - Request:
{ "amount": "12.62", "reason": "Incidencia resuelta a favor del usuario" } - Estados afectados:
payments.status→reembolsado - 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:
operatoro 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:
operatoroadmin - Request:
{ "master_guide": "1Z999AA10987654321", "carrier": "agencia_aliada", "shipped_at": "2026-06-10T10:00:00Z" } - Estados afectados:
shipments.status→despachado_desde_almacen,consolidations.status→despachada - 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-datacon: document_type: enumdocument_number: stringdocument_front_file: archivodocument_back_file: archivo (opcional)selfie_file: archivoresidence_address: textstate: stringzip_code: string- Response 201:
{ "id": 77, "status": "enviado", "submitted_at": "2026-06-07T17:00:00Z" } - Estados afectados: crea/actualiza
kyc_records(status:enviado), creafile_attachments - Eventos:
KYC_SUBMITTED - Notificaciones: al equipo de revisión
POST /api/v1/kyc/{id}/review (operador)¶
Revisa un KYC.
- Permiso:
operatoroadmin - Request:
{ "decision": "aprobado" | "rechazado" | "requiere_mas_informacion", "notes": "Documento legible y vigente" } - Estados afectados:
kyc_records.statusactualizado - 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.status→cerradaoresuelta
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.statusactualizado, evento enintegration_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.status→completado,consolidations.status→pagada
Módulo 13: Administración (admin)¶
PATCH /api/v1/admin/storage-policy¶
Configura días gratis y costo de almacenaje.
- Permiso:
adminosysadmin - 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:
adminosysadmin - 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:
adminosysadmin - 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: trueySunset: <fecha RFC 7234>.
Cómo se mantiene este documento¶
- Antes de implementar un nuevo endpoint, agregarlo aquí con la firma completa.
- Cualquier cambio de comportamiento en un endpoint existente requiere actualizar este archivo primero, luego el código.
- La matriz de trazabilidad hace referencia a estos endpoints por su URL.
- El equipo de frontend Android usa este archivo como input para sus
RetrofitInterfaceo equivalente.