Formatos de Respuesta

Entendiendo las Estructuras de Respuesta de la API

Aprenda sobre los formatos de respuesta de mindzieAPI, códigos de estado, patrones de manejo de errores y estructuras de datos para construir integraciones robustas.

Formato Estándar de Respuesta

Todas las respuestas de mindzieAPI siguen un formato JSON consistente con estructuras predecibles:

Respuesta Exitosa

{
  "data": {
    // Datos principales de la respuesta
  },
  "metadata": {
    "timestamp": "2024-01-15T10:30:00Z",
    "requestId": "req_12345",
    "version": "1.0.0"
  },
  "pagination": {
    // Presente en respuestas paginadas
    "currentPage": 1,
    "totalPages": 5,
    "totalItems": 100,
    "itemsPerPage": 20,
    "hasNext": true,
    "hasPrevious": false
  }
}

Respuesta de Error

{
  "error": {
    "code": "validation_failed",
    "message": "La validación de la solicitud falló",
    "details": {
      "field": "datasetId",
      "reason": "Formato GUID inválido"
    },
    "timestamp": "2024-01-15T10:30:00Z",
    "requestId": "req_12345"
  }
}

Tipos de Respuesta

Respuestas de Éxito

Códigos de estado HTTP 2xx con datos JSON estructurados y metadatos.

Respuestas de Error

Códigos de estado HTTP 4xx/5xx con información detallada del error.

Paginación

Formato consistente de paginación para respuestas con grandes conjuntos de datos.

Códigos de Estado HTTP

Códigos de Éxito (2xx)

Código Estado Descripción Uso
200 OK Solicitud exitosa, datos devueltos Solicitudes GET, operaciones exitosas
201 Created Recurso creado exitosamente Solicitudes POST creando nuevos recursos
202 Accepted Solicitud aceptada para procesamiento asíncrono Operaciones de larga duración, tareas en cola
204 No Content Solicitud exitosa, sin datos devueltos Solicitudes DELETE, actualizaciones sin retorno

Códigos de Error del Cliente (4xx)

Código Estado Descripción Causas Comunes
400 Bad Request Formato o parámetros inválidos en la solicitud Cabeceras faltantes, JSON inválido, datos malformados
401 Unauthorized Autenticación requerida o fallida Token inválido o faltante, credenciales expiradas
403 Forbidden Autenticación válida pero permisos insuficientes Acceso limitado del usuario, proyecto/tenant incorrecto
404 Not Found Recurso solicitado no existe Endpoint inválido, ID de recurso inexistente
422 Unprocessable Entity Formato válido, pero falla en la validación lógica Reglas de negocio inválidas, violaciones de restricciones
429 Too Many Requests Límite de frecuencia excedido Demasiadas llamadas API en la ventana de tiempo

Códigos de Error del Servidor (5xx)

Código Estado Descripción Acción
500 Internal Server Error Error inesperado en el servidor Reintentar con retroceso exponencial
502 Bad Gateway Error en servicio upstream Verificar estado del servicio, reintentar luego
503 Service Unavailable Servicio temporalmente no disponible Reintentar después de un retraso, verificar mantenimiento
504 Gateway Timeout Tiempo de espera agotado en la solicitud Incrementar timeout, optimizar solicitud

Patrones Comunes de Respuesta

Respuesta de Recurso Único

{
  "actionId": "87654321-4321-4321-4321-210987654321",
  "actionType": "analyze",
  "status": "completed",
  "startTime": "2024-01-15T10:30:00Z",
  "endTime": "2024-01-15T10:32:15Z",
  "duration": 135,
  "result": {
    "outputId": "98765432-8765-4321-4321-987654321098",
    "recordsProcessed": 10000
  }
}

Respuesta de Colección con Paginación

{
  "actions": [
    {
      "actionId": "87654321-4321-4321-4321-210987654321",
      "actionType": "analyze",
      "status": "completed"
    },
    {
      "actionId": "11111111-2222-3333-4444-555555555555",
      "actionType": "export",
      "status": "processing"
    }
  ],
  "pagination": {
    "currentPage": 1,
    "totalPages": 5,
    "totalItems": 100,
    "itemsPerPage": 20,
    "hasNext": true,
    "hasPrevious": false,
    "links": {
      "first": "/api/Action/history?page=1&limit=20",
      "next": "/api/Action/history?page=2&limit=20",
      "last": "/api/Action/history?page=5&limit=20"
    }
  }
}

Respuesta de Operación Asíncrona

{
  "operationId": "op_12345678-1234-1234-1234-123456789012",
  "status": "processing",
  "progress": {
    "percentage": 45,
    "currentStep": "data_analysis",
    "totalSteps": 5,
    "estimatedCompletion": "2024-01-15T10:35:00Z"
  },
  "trackingUrl": "/api/Execution/status/op_12345678-1234-1234-1234-123456789012",
  "message": "Procesando análisis del conjunto de datos..."
}

Detalles de la Respuesta de Error

Error de Validación

{
  "error": {
    "code": "validation_failed",
    "message": "La validación de la solicitud falló",
    "details": {
      "errors": [
        {
          "field": "datasetId",
          "code": "invalid_format",
          "message": "Debe ser un GUID válido"
        },
        {
          "field": "parameters.timeout",
          "code": "out_of_range",
          "message": "Debe estar entre 1 y 3600 segundos"
        }
      ]
    },
    "timestamp": "2024-01-15T10:30:00Z",
    "requestId": "req_12345"
  }
}

Error de Autenticación

{
  "error": {
    "code": "invalid_token",
    "message": "El token de acceso proporcionado es inválido o ha expirado",
    "details": {
      "tokenType": "bearer",
      "expiresAt": "2024-01-15T09:00:00Z",
      "suggestion": "Por favor refresque su token de acceso"
    },
    "timestamp": "2024-01-15T10:30:00Z",
    "requestId": "req_12345"
  }
}

Error de Límite de Frecuencia

{
  "error": {
    "code": "rate_limit_exceeded",
    "message": "Límite de frecuencia excedido para este endpoint",
    "details": {
      "limit": 100,
      "remaining": 0,
      "resetTime": "2024-01-15T11:00:00Z",
      "retryAfter": 1800
    },
    "timestamp": "2024-01-15T10:30:00Z",
    "requestId": "req_12345"
  }
}

Encabezados de Respuesta

Encabezados Estándar

Encabezado Descripción Ejemplo
Content-Type Formato de la respuesta application/json; charset=utf-8
X-Request-Id Identificador único de solicitud req_12345678
X-Response-Time Tiempo de procesamiento del servidor 145ms
X-API-Version Versión de la API utilizada 1.0.0

Encabezados de Límite de Frecuencia

Encabezado Descripción Ejemplo
X-RateLimit-Limit Máximo de solicitudes por ventana 100
X-RateLimit-Remaining Solicitudes restantes en la ventana 95
X-RateLimit-Reset Timestamp de reseteo de ventana 1642251600
Retry-After Segundos para esperar antes de reintentar 3600

Mejores Prácticas para Manejo de Errores

Ejemplo en JavaScript

async function handleAPIResponse(response) {
  // Comprobar si la respuesta es ok
  if (!response.ok) {
    const errorData = await response.json();

    switch (response.status) {
      case 400:
        throw new ValidationError(errorData.error.message, errorData.error.details);
      case 401:
        throw new AuthenticationError('Autenticación fallida');
      case 403:
        throw new AuthorizationError('Permisos insuficientes');
      case 404:
        throw new NotFoundError('Recurso no encontrado');
      case 429:
        const retryAfter = response.headers.get('Retry-After');
        throw new RateLimitError(`Límite de frecuencia alcanzado. Reintentar después de ${retryAfter} segundos`);
      case 500:
      case 502:
      case 503:
      case 504:
        throw new ServerError('Ocurrió un error en el servidor. Por favor, reintente.');
      default:
        throw new APIError(`Error inesperado: ${response.status}`);
    }
  }

  return await response.json();
}

// Uso con lógica de reintento
async function apiCallWithRetry(url, options, maxRetries = 3) {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      const response = await fetch(url, options);
      return await handleAPIResponse(response);
    } catch (error) {
      if (error instanceof RateLimitError) {
        const retryAfter = parseInt(error.retryAfter) || Math.pow(2, attempt);
        await new Promise(resolve => setTimeout(resolve, retryAfter * 1000));
        continue;
      }

      if (error instanceof ServerError && attempt < maxRetries) {
        const delay = Math.pow(2, attempt) * 1000; // Retroceso exponencial
        await new Promise(resolve => setTimeout(resolve, delay));
        continue;
      }

      throw error;
    }
  }
}

Ejemplo en Python

import requests
import time
from typing import Dict, Any

class APIError(Exception):
    def __init__(self, message: str, status_code: int = None, details: Dict = None):
        super().__init__(message)
        self.status_code = status_code
        self.details = details

def handle_api_response(response: requests.Response) -> Dict[str, Any]:
    """Manejo de la respuesta API con manejo adecuado de errores"""

    if response.ok:
        return response.json()

    try:
        error_data = response.json()
    except ValueError:
        error_data = {"error": {"message": response.text}}

    error_info = error_data.get("error", {})
    message = error_info.get("message", f"HTTP {response.status_code}")
    details = error_info.get("details", {})

    if response.status_code == 429:
        retry_after = response.headers.get('Retry-After', '60')
        raise APIError(f"Límite de frecuencia alcanzado. Reintentar después de {retry_after} segundos",
                      response.status_code, details)

    elif response.status_code >= 500:
        raise APIError(f"Error del servidor: {message}", response.status_code, details)

    elif response.status_code >= 400:
        raise APIError(f"Error del cliente: {message}", response.status_code, details)

    raise APIError(f"Error inesperado: {message}", response.status_code, details)

def api_call_with_retry(url: str, method: str = 'GET', max_retries: int = 3, **kwargs) -> Dict[str, Any]:
    """Realiza llamada a API con lógica automática de reintentos"""

    for attempt in range(1, max_retries + 1):
        try:
            response = requests.request(method, url, **kwargs)
            return handle_api_response(response)

        except APIError as e:
            if e.status_code == 429:
                retry_after = int(e.details.get('retryAfter', 60))
                time.sleep(retry_after)
                continue

            elif e.status_code >= 500 and attempt < max_retries:
                delay = 2 ** attempt  # Retroceso exponencial
                time.sleep(delay)
                continue

            raise

    raise APIError(f"Máximo de reintentos ({max_retries}) excedido")

Objetos de Transferencia de Datos API (DTOs)

A continuación los DTOs clave que se usan en los endpoints de mindzieAPI.

DTOs de Tenant

TenantListItemDto

{
  "tenantId": "12345678-1234-1234-1234-123456789012",
  "name": "acme-corp",
  "displayName": "Acme Corporation",
  "description": "Tenant principal",
  "caseCount": 50000,
  "maxUserCount": 100,
  "maxAnalystCount": 20,
  "analystCount": 12,
  "userCount": 45,
  "preRelease": false,
  "isAcademic": false,
  "autoload": true,
  "dateCreated": "2024-01-15T10:30:00Z",
  "isDisabled": false
}

TenantDetailDto

{
  "tenantId": "12345678-1234-1234-1234-123456789012",
  "name": "acme-corp",
  "displayName": "Acme Corporation",
  "description": "Tenant principal",
  "isAcademic": false,
  "preRelease": false,
  "maxUserCount": 100,
  "maxAnalystCount": 20,
  "maxCases": 100000,
  "dateCreated": "2024-01-15T10:30:00Z",
  "isDisabled": false
}

TenantUpdatedDto

{
  "tenantId": "12345678-1234-1234-1234-123456789012",
  "name": "acme-corp",
  "displayName": "Acme Corporation Actualizado",
  "message": "Tenant 'acme-corp' actualizado correctamente",
  "isDisabled": false
}

DTOs de Usuario

UserListItemDto

{
  "userId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "email": "john.smith@example.com",
  "displayName": "John Smith",
  "firstName": "John",
  "lastName": "Smith",
  "roleName": "Analista",
  "disabled": false,
  "isServiceAccount": false,
  "homeTenantId": null,
  "homeTenantName": null,
  "lastLogin": "2024-01-15T10:30:00Z",
  "tenantCount": 2,
  "tenantNames": "acme-corp, globex-inc",
  "dateCreated": "2024-01-01T00:00:00Z"
}

UserCreatedDto

{
  "userId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "email": "john.smith@example.com",
  "displayName": "John Smith",
  "message": "Usuario creado correctamente"
}

DTOs de Proyecto

ProjectReturn

{
  "projectId": "87654321-4321-4321-4321-210987654321",
  "tenantId": "12345678-1234-1234-1234-123456789012",
  "projectName": "Análisis de Orden de Compra",
  "projectDescription": "Análisis de minería de proceso del flujo P2P",
  "dateCreated": "2024-01-15T10:30:00Z",
  "dateModified": "2024-01-20T14:45:00Z",
  "createdBy": "user-guid",
  "modifiedBy": "user-guid",
  "isActive": true,
  "datasetCount": 3,
  "investigationCount": 5,
  "dashboardCount": 2,
  "userCount": 8
}

ProjectSummaryReturn

{
  "projectId": "87654321-4321-4321-4321-210987654321",
  "projectName": "Análisis de Orden de Compra",
  "projectDescription": "Análisis de minería de proceso",
  "dateCreated": "2024-01-15T10:30:00Z",
  "dateModified": "2024-01-20T14:45:00Z",
  "statistics": {
    "totalDatasets": 3,
    "totalInvestigations": 5,
    "totalDashboards": 2,
    "totalNotebooks": 12,
    "totalUsers": 8
  }
}

ProjectUserReturn

{
  "permissionId": "11111111-1111-1111-1111-111111111111",
  "userId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "email": "john.smith@example.com",
  "displayName": "John Smith",
  "isOwner": true,
  "dateAssigned": "2024-01-15T10:30:00Z"
}

ProjectThumbnailReturn

{
  "projectId": "87654321-4321-4321-4321-210987654321",
  "hasThumbnail": true,
  "base64Image": "data:image/jpeg;base64,/9j/4AAQ..."
}

ProjectImportReturn

{
  "success": true,
  "projectId": "99999999-9999-9999-9999-999999999999",
  "projectName": "Proyecto Importado",
  "datasetsImported": 2,
  "investigationsImported": 3,
  "dashboardsImported": 1,
  "errorMessage": null,
  "message": "Proyecto importado con éxito"
}

DTOs de Investigación

InvestigationReturn

{
  "investigationId": "11111111-2222-3333-4444-555555555555",
  "projectId": "87654321-4321-4321-4321-210987654321",
  "investigationName": "Análisis de Orden",
  "investigationDescription": "Análisis de minería de proceso del flujo de órdenes",
  "datasetId": "12345678-1234-1234-1234-123456789012",
  "dateCreated": "2024-01-15T10:30:00Z",
  "dateModified": "2024-01-20T14:45:00Z",
  "createdBy": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "modifiedBy": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "investigationOrder": 1.0,
  "isUsedForOperationCenter": false,
  "investigationFolderId": null,
  "notebookCount": 3
}

InvestigationListReturn

{
  "investigations": [
    {
      "investigationId": "11111111-2222-3333-4444-555555555555",
      "projectId": "87654321-4321-4321-4321-210987654321",
      "investigationName": "Análisis de Orden",
      "datasetId": "12345678-1234-1234-1234-123456789012",
      "notebookCount": 3
    }
  ],
  "totalCount": 5,
  "page": 1,
  "pageSize": 50
}

CreateInvestigationRequest

{
  "investigationName": "Análisis de Orden",
  "investigationDescription": "Análisis de minería de proceso",
  "datasetId": "12345678-1234-1234-1234-123456789012",
  "isUsedForOperationCenter": false
}

UpdateInvestigationRequest

{
  "investigationName": "Nombre de análisis actualizado",
  "investigationDescription": "Descripción actualizada",
  "isUsedForOperationCenter": true
}

DTOs de Notebook

NotebookReturn

{
  "notebookId": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
  "investigationId": "11111111-2222-3333-4444-555555555555",
  "name": "Principal",
  "description": "Notebook de análisis principal",
  "dateCreated": "2024-01-15T10:30:00Z",
  "dateModified": "2024-01-20T14:45:00Z",
  "createdBy": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "modifiedBy": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "notebookType": 0,
  "notebookOrder": 1.0,
  "lastExecutionDuration": 2.5,
  "blockCount": 12
}

NotebookListReturn

{
  "notebooks": [
    {
      "notebookId": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
      "investigationId": "11111111-2222-3333-4444-555555555555",
      "name": "Principal",
      "notebookOrder": 1.0,
      "blockCount": 12
    }
  ],
  "totalCount": 3
}

Próximos Pasos

Ahora que entiende los formatos de respuesta, explore secciones específicas de la API como Actions, Blocks o Datasets para ver estos patrones en acción.