Logo Apisdom
InicioAPIsProyectosServiciosBlog
Icono de documentación de APIs de inteligencia artificial

Documentación de APIs

Todo lo que necesitas para integrar nuestras APIs de Inteligencia Artificial en tus proyectos. Encuentra guías, ejemplos de código y referencias completas.

Logo Apisdom

Potenciando el futuro con APIs de Inteligencia Artificial y desarrollo de software a medida.

  • Términos de Servicio
  • Política de Privacidad
  • Política de Cookies
  • Política de Pagos
  • Aviso Legal
  • APIs y Precios
  • Documentación
  • Blog
  • Proyectos
  • Servicios
  • FAQ
  • Contacto: admin@apisdom.com
Contribuir
Logo Apisdom

Potenciando el futuro con APIs de Inteligencia Artificial y desarrollo de software a medida.

Redes
Políticas
  • Términos de Servicio
  • Política de Privacidad
  • Política de Cookies
  • Política de Pagos
  • Aviso Legal
Enlaces Rápidos
  • APIs y Precios
  • Documentación
  • Blog
  • Proyectos
  • Servicios
  • FAQ
Contacto
  • Email: admin@apisdom.com
  • Juan Luis
Contribuir

Contribuye al desarrollo

© 2026 Apisdom. Todos los derechos reservados.

Desarrollado con Next.js

    Navegación

    • Inicio
    • Sentiment API
    • Prediction API
    • Moderation API
    • Recommendations API
    • Testing y Validación
    • Studio API
    • Soporte y Diagnóstico
    • FAQ
    • Contacto
    • Descargo
    ApisDom | Inteligencia Artificial Transparente
    Cargando documentación...
    💬 Sentiment API (Modelo DistilBERT SST-2)
    Cargando documentación...
    🔭 Prediction API (Forecasting con Chronos-2 Amazon)
    Cargando documentación...
    ⚖️ Moderation API (Detector Toxic-BERT Jigsaw)
    Cargando documentación...
    🧲 Recommendations API (Semantic Search all-MiniLM-L6-v2)
    Cargando documentación...
    🧪 Testing y Validación
    Cargando documentación...
    Studio API
    Cargando documentación...
    Soporte y Diagnóstico
    Cargando documentación...
    Preguntas Frecuentes

    Icono de soporte técnico y contacto con el equipo

    ¿Necesitas más Ayuda?

    Si no encuentras la respuesta en nuestra documentación o FAQs, no dudes en ponerte en contacto con nuestro equipo de soporte.

    Descargo de Responsabilidad Importante

    La información y ejemplos de código proporcionados en esta documentación tienen fines ilustrativos y educativos. Aunque nos esforzamos por mantener la documentación actualizada y precisa, Apisdom no garantiza que el contenido esté libre de errores ni que sea adecuado para todos los casos de uso. El uso de nuestras APIs y la implementación de los ejemplos es responsabilidad del desarrollador. Para información sobre términos de uso, privacidad y condiciones del servicio, consulta nuestra página legal.

    📚 Documentación

    IA Profesional. Integración Simple. Resultados Reales.

    Bienvenido a la documentación oficial de las APIs de ApisDom. Aquí encontrarás todo lo necesario para integrar nuestros servicios de inteligencia artificial en tus aplicaciones.


    Nuestras APIs

    APIDescripciónModelo
    🎭 [Sentiment API]Detecta emociones en texto (positivo/negativo)DistilBERT (SST-2)
    🛡️ [Moderation API]Identifica contenido tóxico e inapropiadoToxic-BERT (Jigsaw)
    📈 [Prediction API]Predicciones de series temporalesChronos-2 (Amazon)
    ⚡ [Recommendations API]Recomendaciones personalizadas por similitud semánticaall-MiniLM-L6-v2

    ⚡ Inicio Rápido

    Paso 1: Obtén tu API Key

    1. Regístrate en apisdom.com y obten 1250 llamadas Gratis
    2. Ve al Dashboard
    3. Copia tu API Key

    Tu panel de usuario:

    ▶ Ver recorrido: Panel de Usuario

    Paso 2: Tu Primera Llamada

    curl -X POST "https://apisdom.com/api/v1/sentiment" \
      -H "X-API-Key: TU_API_KEY_AQUI" \
      -H "Content-Type: application/json" \
      -d '{"text": "This product is amazing!"}'
    

    Respuesta

    La respuesta es ANIDADA con dos objetos principales: _metadata y response.

    {
      "_metadata": {
        "provider": "ApisDom",
        "tagline": "Inteligencia artificial transparente",
        "website": "https://apisdom.com",
        "timestamp": "2026-01-21T10:00:00.000Z",
        "service": "Sentiment Analysis",
        "channel": "ApisDom Platform",
        "request_id": "sent_1768954704623_abc123",
        "documentation": "https://apisdom.com/documentacion"
      },
      "response": {
        "text": "This product is amazing!",
        "sentiment": "positive",
        "score": 0.9721,
        "warning": null,
        "info_message": null
      }
    }
    

    ⚠️ Importante: Todas las APIs devuelven esta estructura anidada. Accede a los datos mediante resultado['response'] o resultado.response.


    🔐 Autenticación

    Todas las APIs requieren tu API Key en el header X-API-Key:

    X-API-Key: tu_api_key_aqui
    

    Obtener tu API Key

    1. Inicia sesión en tu Dashboard
    2. Ve a "API Keys"
    3. Genera o copia tu API Key

    Seguridad

    • Las API Keys son permanentes y están vinculadas a tu cuenta
    • NO compartas tu API Key públicamente
    • NO la incluyas en código público (GitHub, etc.)
    • Guárdala de forma segura (variables de entorno, secrets manager)

    💳 Planes y Precios

    Consulta los planes disponibles y precios actualizados en: apisdom.com/pricing

    Sobre los Créditos

    • Los créditos se descuentan DESPUÉS de ejecutar el modelo IA exitosamente
    • Si hay un error en el procesamiento, el crédito NO se consume
    • Cada llamada a la API consume 1 crédito

    📊 Headers Informativos

    La API devuelve headers que te permiten controlar tu consumo programáticamente:

    HeaderDescripción
    X-RateLimit-LimitTu límite de peticiones por minuto
    X-RateLimit-RemainingPeticiones restantes en la ventana actual
    Retry-AfterSegundos a esperar si recibes 429

    ❄️ Cold Start

    Para optimizar costes, los microservicios de IA escalan a cero cuando no hay actividad.

    💡 Nota: La primera petición puede tardar ~20 segundos mientras el servicio arranca. Las peticiones siguientes serán < 500ms mientras el servicio permanezca activo.


    ⚠️ Códigos de Error

    CódigoEstadoSignificadoSolución
    200✅ ÉxitoTodo correctoProcesar respuesta
    400❌ Request inválidoFormato JSON incorrectoRevisar parámetros enviados
    401🔒 No autorizadoAPI Key inválida o revocadaVerificar API Key en dashboard
    402💳 Sin créditosCréditos agotadosConsultar planes
    422❌ Datos inválidosCampos requeridos faltantesRevisar campos requeridos
    429⏱️ Rate limitLímite de peticiones excedidoEsperar según Retry-After
    500🔥 Error servidorError internoReintentar en 30s

    Ejemplo de Error 402

    {
      "detail": "Créditos insuficientes. Consulta planes en apisdom.com/pricing"
    }
    

    🩺 Health Checks

    Cada API expone un endpoint de health check (sin autenticación):

    APIEndpoint
    SentimentGET https://apisdom.com/api/v1/sentiment
    ModerationGET https://apisdom.com/api/v1/moderacion
    PredictionGET https://apisdom.com/api/v1/predictions
    RecommendationsGET https://apisdom.com/api/v1/recommendations/health
    {
      "status": "healthy",
      "service": "sentiment",
      "valid": true,
      "timestamp": "2026-01-21T10:00:00.000Z",
      "provider": "ApisDom"
    }
    

    ⚡ Límites por Request

    APICampoLímite
    Sentimenttext1-5,000 caracteres
    Moderationtext1-5,000 caracteres
    Predictiondates / values10-5,000 puntos
    Predictionperiods1-365 días
    Recommendationsquery3-500 caracteres
    Recommendationscontents1-100 items (máx 1,000 chars/item)
    Recommendationstop_k1-20 resultados

    Nota: Los modelos BERT procesan máximo 512 tokens. Textos más largos serán truncados y recibirás un warning en la respuesta.


    💻 Ejemplos de Código

    Python - Análisis de Sentimiento

    import requests
    
    API_URL = "https://apisdom.com/api/v1/sentiment"
    API_KEY = "tu_api_key_aqui"
    
    def analizar_sentimiento(texto):
        response = requests.post(
            API_URL,
            headers={
                "X-API-Key": API_KEY,
                "Content-Type": "application/json"
            },
            json={"text": texto}
        )
        response.raise_for_status()
        return response.json()
    
    # Uso
    resultado = analizar_sentimiento("This product is excellent!")
    
    # IMPORTANTE: Acceder al objeto 'response' anidado
    datos = resultado['response']
    print(f"Sentimiento: {datos['sentiment']}")
    print(f"Confianza: {datos['score']:.0%}")
    print(f"Request ID: {resultado['_metadata']['request_id']}")
    
    # Output:
    # Sentimiento: positive
    # Confianza: 97%
    # Request ID: sent_1768954704623_abc123
    

    JavaScript - Moderar Contenido

    const API_URL = 'https://apisdom.com/api/v1/moderacion';
    const API_KEY = 'tu_api_key_aqui';
    
    async function moderarContenido(texto) {
      const response = await fetch(API_URL, {
        method: 'POST',
        headers: {
          'X-API-Key': API_KEY,
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({ text: texto })
      });
      
      if (!response.ok) throw new Error(`HTTP ${response.status}`);
      return response.json();
    }
    
    // Uso
    const resultado = await moderarContenido('Thank you for your help!');
    
    // IMPORTANTE: Acceder al objeto 'response' anidado
    const datos = resultado.response;
    console.log(`Es tóxico: ${datos.is_toxic}`);
    console.log(`Score: ${(datos.toxicity_score * 100).toFixed(0)}%`);
    
    // Output:
    // Es tóxico: false
    // Score: 2%
    

    cURL - Predecir Serie Temporal

    curl -X POST "https://apisdom.com/api/v1/predictions" \
      -H "X-API-Key: tu_api_key_aqui" \
      -H "Content-Type: application/json" \
      -d '{
        "dates": ["2024-01-01","2024-01-02","2024-01-03","2024-01-04","2024-01-05",
                  "2024-01-06","2024-01-07","2024-01-08","2024-01-09","2024-01-10"],
        "values": [100,105,102,108,115,112,120,118,125,130],
        "periods": 5
      }'
    
    # La respuesta incluye _metadata y response anidados
    

    cURL - Obtener Recomendaciones

    curl -X POST "https://apisdom.com/api/v1/recommendations" \
      -H "X-API-Key: tu_api_key_aqui" \
      -H "Content-Type: application/json" \
      -d '{
        "query": "machine learning tutorials",
        "contents": [
          "Python ML basics for beginners",
          "JavaScript introduction and syntax",
          "Deep Learning with PyTorch guide",
          "Data Science with pandas",
          "React frontend development"
        ],
        "top_k": 3
      }'
    
    # Respuesta: Lista de contenidos ordenados por similitud semántica
    

    🏗️ Cliente Robusto para Producción

    Este código maneja TODOS los casos de error reales que puede devolver la API:

    import time
    import requests
    from typing import Any
    
    class ApisDomClient:
        """
        Cliente robusto para APIs de ApisDom.
        Maneja: retry, backoff exponencial, rate limit, sin créditos.
        """
        
        def __init__(self, api_key: str):
            self.api_key = api_key
            self.session = requests.Session()
            self.session.headers.update({
                "X-API-Key": api_key,
                "Content-Type": "application/json"
            })
            self.base_url = "https://apisdom.com/api/v1"
        
        def _request_with_retry(
            self, 
            method: str, 
            endpoint: str, 
            json_data: dict | None = None,
            max_retries: int = 3
        ) -> dict[str, Any]:
            """Ejecuta request con reintentos y manejo de errores."""
            url = f"{self.base_url}/{endpoint}"
            
            for attempt in range(max_retries):
                try:
                    response = self.session.request(
                        method, url, json=json_data, timeout=30
                    )
                    
                    # Éxito
                    if response.status_code == 200:
                        return response.json()
                    
                    # Sin créditos - no reintentar
                    if response.status_code == 402:
                        raise CreditosInsuficientesError(
                            "Sin créditos. Consulta planes en: https://apisdom.com/pricing"
                        )
                    
                    # API Key inválida - no reintentar
                    if response.status_code == 401:
                        raise ApiKeyInvalidaError(
                            "API Key inválida. Verifica en tu Dashboard."
                        )
                    
                    # Rate limit - esperar y reintentar
                    if response.status_code == 429:
                        retry_after = int(response.headers.get("Retry-After", 60))
                        print(f"Rate limit. Esperando {retry_after}s...")
                        time.sleep(retry_after)
                        continue
                    
                    # Error servidor - backoff exponencial
                    if response.status_code >= 500:
                        wait_time = (2 ** attempt) * 1
                        print(f"Error {response.status_code}. Retry en {wait_time}s...")
                        time.sleep(wait_time)
                        continue
                    
                    response.raise_for_status()
                    
                except requests.exceptions.Timeout:
                    if attempt < max_retries - 1:
                        wait_time = (2 ** attempt) * 2
                        print(f"Timeout. Retry en {wait_time}s...")
                        time.sleep(wait_time)
                        continue
                    raise
            
            raise Exception(f"Falló después de {max_retries} intentos")
        
        def analizar_sentimiento(self, texto: str) -> dict:
            """Analiza el sentimiento de un texto."""
            result = self._request_with_retry("POST", "sentiment", {"text": texto})
            return result['response']  # Devuelve solo los datos útiles
        
        def moderar_contenido(self, texto: str) -> dict:
            """Modera contenido para detectar toxicidad."""
            result = self._request_with_retry("POST", "moderacion", {"text": texto})
            return result['response']
        
        def predecir_serie(
            self, 
            dates: list[str], 
            values: list[float], 
            periods: int = 7
        ) -> dict:
            """Predice valores futuros de una serie temporal."""
            result = self._request_with_retry(
                "POST", 
                "predictions", 
                {"dates": dates, "values": values, "periods": periods}
            )
            return result['response']
        
        def obtener_recomendaciones(
            self,
            query: str,
            contents: list[str],
            top_k: int = 5
        ) -> dict:
            """Genera recomendaciones personalizadas por similitud semántica."""
            result = self._request_with_retry(
                "POST",
                "recommendations",
                {"query": query, "contents": contents, "top_k": top_k}
            )
            return result['response']
    
    
    class CreditosInsuficientesError(Exception):
        """Error cuando no hay créditos disponibles."""
        pass
    
    
    class ApiKeyInvalidaError(Exception):
        """Error cuando la API Key es inválida o está revocada."""
        pass
    
    
    # Ejemplo de uso
    if __name__ == "__main__":
        client = ApisDomClient("tu_api_key_aqui")
        
        # Sentiment
        sentiment = client.analizar_sentimiento("This product is great!")
        print(f"Sentimiento: {sentiment['sentiment']} ({sentiment['score']:.0%})")
        
        # Moderation
        moderation = client.moderar_contenido("Thank you for your help!")
        print(f"Es tóxico: {moderation['is_toxic']}")
        
        # Recommendations
        recommendations = client.obtener_recomendaciones(
            query="machine learning tutorials",
            contents=[
                "Python ML basics",
                "JavaScript intro",
                "Deep Learning with PyTorch"
            ],
            top_k=3
        )
        print(f"Top recomendación: {recommendations['results'][0]['item']}")
    

    ✅ Checklist Pre-Producción

    Antes de ir a producción, verifica que tu aplicación:

    ElementoDescripción
    ☐ Maneja 401Redirige a login o muestra error de API Key
    ☐ Maneja 402Muestra mensaje "Sin créditos" en UI
    ☐ Maneja 429Implementa retry con header Retry-After
    ☐ Maneja 500Implementa retry con backoff exponencial
    ☐ Accede a responseUsa resultado['response'] para los datos
    ☐ API Key seguraNo expuesta en código público
    ☐ Cachea resultadosOpcional, ahorra créditos en llamadas idénticas
    ☐ Revisa warningVerifica truncamiento en textos largos

    💡 Buenas Prácticas

    ✅ Recomendado

    • Implementar reintentos con backoff exponencial para errores 429/500
    • Cachear resultados idénticos para ahorrar créditos
    • Validar datos antes de enviarlos a la API
    • Manejar el código 402 en tu UI (mostrar mensaje al usuario)
    • Revisar el campo warning en las respuestas
    • Usar variables de entorno para la API Key

    ❌ Evitar

    • Exponer la API Key en código frontend o repositorios públicos
    • Ignorar los headers de rate limit
    • No manejar errores 402 (sin créditos)
    • Enviar textos sin validar longitud

    📄 Documentación Detallada

    APIDocumentación
    🎭 Sentiment[SENTIMENT] - Guía completa
    🛡️ Moderation[MODERATION]- Guía completa
    📈 Prediction[PREDICTION] - Guía completa
    ⚡ Recommendations[RECOMMENDATIONS] - Guía completa

    💬 ¿Necesitas Ayuda?

    📧 soporte@apisdom.com


    Última actualización de documentación: Abril 2026

    ApisDom · Inteligencia Artificial Transparente

    Detecta automáticamente si un texto expresa emociones positivas o negativas utilizando el modelo DistilBERT (fine-tuned en SST-2).

    ⚠️ Limitaciones Conocidas (Leer antes de usar)

    1. Modelo Binario: El modelo ha sido entrenado con críticas de cine (SST-2). Está diseñado para detectar polaridad fuerte (Positivo vs Negativo). No existe clase "neutral" nativa.
    2. Textos Neutros/Fácticos: Debido a la naturaleza del dataset de entrenamiento, el modelo tiende a forzar una clasificación positiva o negativa incluso en textos puramente informativos (ej: "El paquete llegó hoy"). No se recomienda para clasificar textos fácticos sin carga emocional.
    3. Idioma: Optimizado exclusivamente para Inglés. Funciona con español pero con menor precisión.

    Casos de Uso Recomendados

    ✅ Análisis de Reviews de productos (E-commerce)
    ✅ Feedback de clientes (Quejas vs Felicitaciones)
    ✅ Moderación de comentarios polarizados

    Casos de Uso NO Recomendados

    ❌ Clasificación de noticias o hechos objetivos
    ❌ Detección de sarcasmo sutil
    ❌ Textos que requieran clasificación "neutral"


    📍 Información General

    PropiedadValor
    URL Basehttps://apisdom.com/api/v1
    MétodoPOST
    AutenticaciónAPI Key (Header X-API-Key)
    Tipo de Créditotext
    Coste por llamada1 crédito
    Modelo IADistilBERT (fine-tuned en SST-2 - binario: solo positive/negative)
    Límite de tokens512 tokens (textos largos serán truncados)
    Datos de entrenamientoStanford Sentiment Treebank (SST-2) - Solo reseñas de cine

    📊 Headers Informativos

    La API devuelve headers que te permiten controlar tu consumo:

    HeaderDescripción
    X-RateLimit-LimitTu límite de peticiones por minuto
    X-RateLimit-RemainingPeticiones restantes en la ventana actual
    Retry-AfterSegundos a esperar si recibes 429

    ❄️ Cold Start

    Para optimizar costes, los microservicios de IA escalan a cero cuando no hay actividad.

    💡 Nota: La primera petición puede tardar más de lo normal mientras el servicio arranca. Las peticiones siguientes serán mucho más rápidas mientras el servicio permanezca activo.

    💳 Planes y Precios

    Consulta los planes disponibles y precios actualizados en: apisdom.com/pricing


    🔐 Autenticación

    Todas las peticiones requieren tu API Key en el header X-API-Key:

    X-API-Key: tu_api_key_aqui
    

    Puedes obtener tu API Key desde el panel de usuario en apisdom.com/dashboard.


    📥 Endpoint: Analizar Sentimiento

    POST https://apisdom.com/api/v1/sentiment
    

    Request Body

    CampoTipoRequeridoDescripción
    textstring✅ SíTexto a analizar. Mínimo 1 carácter, máximo 5000.

    Ejemplo de Request

    {
      "text": "Este producto es absolutamente increíble. La calidad supera todas mis expectativas y el envío fue rapidísimo. ¡Muy recomendado!"
    }
    

    Response Exitosa (200 OK)

    La respuesta es ANIDADA con dos objetos principales: _metadata y response.

    {
      "_metadata": {
        "provider": "ApisDom",
        "tagline": "Inteligencia artificial transparente",
        "website": "https://apisdom.com",
        "timestamp": "2026-01-21T10:00:00.000Z",
        "service": "Sentiment Analysis",
        "channel": "ApisDom Platform",
        "request_id": "sent_1768954704623_4cj49l3",
        "documentation": "https://apisdom.com/documentacion"
      },
      "response": {
        "text": "Este producto es absolutamente increíble. La calidad supera todas mis expectativas y el envío fue rapidísimo. ¡Muy recomendado!",
        "sentiment": "positive",
        "score": 0.9847,
        "warning": null,
        "info_message": null
      }
    }
    

    Campos de la Respuesta

    Nivel Raíz:

    CampoTipoDescripción
    _metadataobjectInformación del proveedor y tracking de la petición
    responseobjectEl resultado del análisis de sentimiento

    Dentro de _metadata:

    CampoTipoDescripción
    providerstringSiempre "ApisDom"
    timestampstringTimestamp ISO 8601 de la petición
    request_idstringIdentificador único de esta petición
    servicestringNombre del servicio ("Sentiment Analysis")
    channelstringCanal de distribución ("ApisDom Platform")

    Dentro de response:

    CampoTipoDescripción
    textstringEl texto que fue analizado
    sentimentstringSentimiento detectado: positive o negative únicamente (modelo binario)
    scorefloatConfianza del modelo (0.0 a 1.0). Cuanto más cercano a 1, mayor certeza.
    warningstring | nullAviso si el texto fue truncado (textos muy largos)
    info_messagestring | nullInformación adicional sobre el análisis

    ⚠️ Importante: El campo sentiment SOLO devolverá positive o negative. Este es un modelo de clasificación binaria entrenado con reseñas de cine (SST-2). No existe clase "neutral" nativa.


    💻 Ejemplos de Código

    Python

    import requests
    
    API_URL = "https://apisdom.com/api/v1/sentiment"
    API_KEY = "tu_api_key_aqui"
    
    def analizar_sentimiento(texto):
        """
        Analiza el sentimiento de un texto.
        
        Args:
            texto: String con el texto a analizar (máx 5000 caracteres)
        
        Returns:
            dict con _metadata y response conteniendo sentiment, score y detalles
        """
        response = requests.post(
            API_URL,
            headers={
                "X-API-Key": API_KEY,
                "Content-Type": "application/json"
            },
            json={"text": texto}
        )
        
        if response.status_code == 200:
            return response.json()
        elif response.status_code == 402:
            raise Exception("Sin créditos. Consulta planes en apisdom.com/pricing")
        else:
            raise Exception(f"Error: {response.status_code} - {response.text}")
    
    # Ejemplo de uso
    resultado = analizar_sentimiento("Me encanta este servicio, funciona perfecto!")
    
    # IMPORTANTE: Acceder a los datos dentro del objeto 'response'
    datos = resultado['response']
    print(f"Sentimiento: {datos['sentiment']}")
    print(f"Confianza: {datos['score']:.2%}")
    print(f"Request ID: {resultado['_metadata']['request_id']}")
    
    # Output:
    # Sentimiento: positive
    # Confianza: 97.32%
    # Request ID: sent_1768954704623_4cj49l3
    

    JavaScript / Node.js

    const API_URL = 'https://apisdom.com/api/v1/sentiment';
    const API_KEY = 'tu_api_key_aqui';
    
    async function analizarSentimiento(texto) {
      /**
       * Analiza el sentimiento de un texto.
       * @param {string} texto - Texto a analizar (máx 5000 caracteres)
       * @returns {Promise<Object>} - Resultado con _metadata y response
       */
      const response = await fetch(API_URL, {
        method: 'POST',
        headers: {
          'X-API-Key': API_KEY,
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({ text: texto })
      });
    
      if (response.status === 402) {
        throw new Error('Sin créditos. Consulta planes en apisdom.com/pricing');
      }
    
      if (!response.ok) {
        throw new Error(`Error: ${response.status}`);
      }
    
      return response.json();
    }
    
    // Ejemplo de uso
    analizarSentimiento('El producto llegó roto y nadie me ayuda')
      .then(resultado => {
        // IMPORTANTE: Acceder al objeto 'response' anidado
        const datos = resultado.response;
        console.log(`Sentimiento: ${datos.sentiment}`);
        console.log(`Confianza: ${(datos.score * 100).toFixed(2)}%`);
        console.log(`Request ID: ${resultado._metadata.request_id}`);
        // Output:
        // Sentimiento: negative
        // Confianza: 94.56%
        // Request ID: sent_1768954704623_4cj49l3
      })
      .catch(console.error);
    

    cURL

    curl -X POST "https://apisdom.com/api/v1/sentiment" \
      -H "X-API-Key: tu_api_key_aqui" \
      -H "Content-Type: application/json" \
      -d '{"text": "La atención al cliente fue excelente, resolvieron mi problema en minutos."}'
    
    # La respuesta incluye _metadata y response anidados:
    # {
    #   "_metadata": { "provider": "ApisDom", "request_id": "...", ... },
    #   "response": { "sentiment": "positive", "score": 0.9847, ... }
    # }
    

    PHP

    <?php
    $api_url = 'https://apisdom.com/api/v1/sentiment';
    $api_key = 'tu_api_key_aqui';
    
    function analizarSentimiento($texto) {
        global $api_url, $api_key;
        
        $ch = curl_init($api_url);
        curl_setopt_array($ch, [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_POST => true,
            CURLOPT_HTTPHEADER => [
                'X-API-Key: ' . $api_key,
                'Content-Type: application/json'
            ],
            CURLOPT_POSTFIELDS => json_encode(['text' => $texto])
        ]);
        
        $response = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);
        
        if ($httpCode === 402) {
            throw new Exception('Sin créditos. Consulta planes en apisdom.com/pricing');
        }
        
        return json_decode($response, true);
    }
    
    // Ejemplo de uso
    $resultado = analizarSentimiento('El servicio técnico tardó mucho pero al final lo solucionaron');
    
    // IMPORTANTE: Acceder al objeto 'response' anidado
    $datos = $resultado['response'];
    echo "Sentimiento: " . $datos['sentiment'] . "\n";
    echo "Confianza: " . number_format($datos['score'] * 100, 2) . "%\n";
    echo "Request ID: " . $resultado['_metadata']['request_id'] . "\n";
    
    // Output:
    // Sentimiento: positive
    // Confianza: 62.18%
    // Request ID: sent_1768954704623_4cj49l3
    ?>
    

    C# / .NET

    using System.Net.Http;
    using System.Text;
    using System.Text.Json;
    using System.Text.Json.Serialization;
    
    public class SentimentApiClient
    {
        private readonly HttpClient _client;
        private const string API_URL = "https://apisdom.com/api/v1/sentiment";
    
        public SentimentApiClient(string apiKey)
        {
            _client = new HttpClient();
            _client.DefaultRequestHeaders.Add("X-API-Key", apiKey);
        }
    
        public async Task<SentimentApiResponse> AnalizarSentimientoAsync(string texto)
        {
            var content = new StringContent(
                JsonSerializer.Serialize(new { text = texto }),
                Encoding.UTF8,
                "application/json"
            );
    
            var response = await _client.PostAsync(API_URL, content);
    
            if (response.StatusCode == System.Net.HttpStatusCode.PaymentRequired)
            {
                throw new Exception("Sin créditos. Consulta planes en apisdom.com/pricing");
            }
    
            response.EnsureSuccessStatusCode();
            
            var json = await response.Content.ReadAsStringAsync();
            return JsonSerializer.Deserialize<SentimentApiResponse>(json);
        }
    }
    
    // IMPORTANTE: La respuesta es ANIDADA - necesitas estas clases
    public class SentimentApiResponse
    {
        [JsonPropertyName("_metadata")]
        public SentimentMetadata Metadata { get; set; }
        
        [JsonPropertyName("response")]
        public SentimentResult Response { get; set; }
    }
    
    public class SentimentMetadata
    {
        [JsonPropertyName("provider")]
        public string Provider { get; set; }
        
        [JsonPropertyName("request_id")]
        public string RequestId { get; set; }
        
        [JsonPropertyName("timestamp")]
        public string Timestamp { get; set; }
    }
    
    public class SentimentResult
    {
        [JsonPropertyName("text")]
        public string Text { get; set; }
        
        [JsonPropertyName("sentiment")]
        public string Sentiment { get; set; }
        
        [JsonPropertyName("score")]
        public double Score { get; set; }
        
        [JsonPropertyName("warning")]
        public string? Warning { get; set; }
        
        [JsonPropertyName("info_message")]
        public string? InfoMessage { get; set; }
    }
    
    // Ejemplo de uso
    var client = new SentimentApiClient("tu_api_key_aqui");
    var apiResponse = await client.AnalizarSentimientoAsync("¡Producto de primera calidad!");
    
    // Acceder a los datos dentro del objeto Response
    Console.WriteLine($"Sentimiento: {apiResponse.Response.Sentiment}");
    Console.WriteLine($"Confianza: {apiResponse.Response.Score:P2}");
    Console.WriteLine($"Request ID: {apiResponse.Metadata.RequestId}");
    
    // Output:
    // Sentimiento: positive
    // Confianza: 99.99%
    // Request ID: sent_1768954865380_hh3t7b1
    

    📊 Casos de Uso Prácticos

    1. Análisis de Reseñas de Productos

    reseñas = [
        "Excelente relación calidad-precio",
        "Llegó tarde y con el embalaje dañado",
        "Hace lo que promete, nada más",
    ]
    
    for reseña in reseñas:
        api_result = analizar_sentimiento(reseña)
        # Acceder al objeto 'response' anidado
        datos = api_result['response']
        print(f"'{reseña[:30]}...' → {datos['sentiment']} ({datos['score']:.0%})")
    
    # Output (nota: el modelo tiende a alta confianza):
    # 'Excelente relación calidad-pr...' → positive (99%)
    # 'Llegó tarde y con el embalaje...' → negative (99%)
    # 'Hace lo que promete, nada más...' → positive (87%)
    # ⚠️ Nota: textos "neutros" suelen clasificarse como positive debido al modelo binario
    

    2. Clasificación Automática de Tickets de Soporte

    def priorizar_ticket(mensaje):
        """Asigna prioridad según el sentimiento del cliente."""
        api_result = analizar_sentimiento(mensaje)
        # Acceder al objeto 'response' anidado
        resultado = api_result['response']
        
        if resultado['sentiment'] == 'negative' and resultado['score'] > 0.8:
            return "🔴 URGENTE - Cliente muy insatisfecho"
        elif resultado['sentiment'] == 'negative':
            return "🟡 ALTA - Cliente insatisfecho"
        else:
            return "🟢 NORMAL"
    
    ticket = "Llevo 3 días esperando respuesta y nadie me ayuda. Es inaceptable."
    print(priorizar_ticket(ticket))
    # Output: 🔴 URGENTE - Cliente muy insatisfecho
    

    3. Dashboard de Satisfacción en Tiempo Real

    async function actualizarDashboard(comentarios) {
      const apiResults = await Promise.all(
        comentarios.map(c => analizarSentimiento(c))
      );
      
      // Extraer el objeto 'response' de cada resultado de la API
      const resultados = apiResults.map(r => r.response);
      
      const stats = {
        positivos: resultados.filter(r => r.sentiment === 'positive').length,
        negativos: resultados.filter(r => r.sentiment === 'negative').length,
        promedioConfianza: resultados.reduce((a, b) => a + b.score, 0) / resultados.length
      };
      
      console.log('📊 Resumen de Satisfacción:');
      console.log(`   ✅ Positivos: ${stats.positivos}`);
      console.log(`   ❌ Negativos: ${stats.negativos}`);
      console.log(`   📊 Confianza promedio: ${(stats.promedioConfianza * 100).toFixed(1)}%`);
      
      return stats;
    }
    

    ⚠️ Códigos de Error

    CódigoSignificadoSolución
    400Texto inválido (vacío o muy largo)Asegúrate de enviar entre 1 y 5000 caracteres
    401API Key inválidaVerifica tu API Key en el dashboard
    402Sin créditos disponiblesConsulta planes en apisdom.com/pricing
    429Límite de peticiones excedidoEspera antes de reintentar (ver headers de rate limit)
    500Error interno del servidorReintenta en unos segundos. Si persiste, contacta soporte

    🔬 Transparencia Técnica

    Política de ApisDom: Creemos que los desarrolladores merecen saber exactamente cómo funcionan las APIs que usan. Esta sección documenta los detalles técnicos verificados directamente del código fuente.

    Cómo Funciona Internamente

    Tu texto → Tokenización (DistilBERT) → Inferencia → Normalización → Respuesta
             ↓                           ↓             ↓
             512 tokens máx          CPU-bound      POSITIVE → positive
                                     (threadpool)   NEGATIVE → negative
    

    Detalles Verificados del Código

    AspectoValor RealArchivo Fuente
    Modelodistilbert-base-uncased-finetuned-sst-2-englishsentiment_service.py
    Pipelinesentiment-analysis de HuggingFacesentiment_service.py
    Truncamiento512 tokens (automático)sentiment_service.py línea 47
    Labels originalesPOSITIVE, NEGATIVE → normalizados a minúsculassentiment_service.py líneas 64-67
    Clases de salidaSOLO BINARIO - positive o negative (sin neutral)Arquitectura del modelo
    Ejecuciónrun_in_threadpool (no bloquea async)sentiment_service.py línea 51

    ⚠️ Importante: No Existe Clase Neutral Nativa

    Este modelo es binario (positive/negative únicamente). Fue entrenado con el dataset SST-2 (Stanford Sentiment Treebank) que solo contiene reseñas de películas positivas y negativas. El modelo NO tiene clase "neutral".

    Comportamiento observado con texto neutral:

    • Input: "El clima está agradable hoy" (objetivamente neutral)
    • Output: {"sentiment": "positive", "score": 0.9965}
    • El modelo fuerza una clasificación y tiende a ser muy confiado incluso con texto ambiguo

    Esta es una característica conocida de las redes neuronales llamada "sobreconfianza" (ver: On Calibration of Modern Neural Networks, ICML 2017).


    📝 Notas Importantes

    Sobre el Modelo DistilBERT

    El modelo distilbert-base-uncased-finetuned-sst-2-english está entrenado para textos en inglés y ofrece rendimiento óptimo en este idioma.

    Limitaciones conocidas:

    • ⚠️ Modelo solo binario: Devuelve positive o negative - NO existe clase neutral
    • El modelo fue entrenado con reseñas de cine (dataset SST-2, ~11,855 ejemplos)
    • Puede no generalizar bien a otros dominios (redes sociales, texto técnico, etc.)
    • Sarcasmo e ironía pueden ser mal interpretados
    • Sobreconfianza: Los scores tienden a ser muy altos (>95%) incluso para texto ambiguo
    • Texto neutral/fáctico típicamente se clasifica como "positive" con alta confianza

    Mejores casos de uso:

    • Reseñas de productos (similar a datos de entrenamiento)
    • Feedback de clientes con sentimiento claro
    • Clasificación de tickets de soporte (positivo vs negativo)

    No recomendado para:

    • Detectar declaraciones verdaderamente neutrales/fácticas
    • Sentimiento financiero o de noticias (dominio diferente)
    • Análisis emocional con matices

    Sobre el Truncamiento de Texto

    El modelo BERT tiene un límite de 512 tokens (~400 palabras). Si tu texto es más largo:

    • Se analizarán los primeros 512 tokens
    • Recibirás un warning en la respuesta indicando el truncamiento
    • El análisis seguirá siendo válido pero parcial

    Recomendación: Para textos largos, divídelos en párrafos y analiza cada uno por separado.

    Sobre el Tiempo de Respuesta

    La primera petición tras inactividad puede tardar ~20 segundos (cold start). Una vez activo, las respuestas son < 500ms.


    💬 ¿Necesitas Ayuda?

    📧 soporte@apisdom.com


    ⚖️ Aviso Legal

    Nota de Transparencia: Esta API utiliza el modelo de código abierto distilbert-base-uncased-finetuned-sst-2-english de Hugging Face. Los resultados reflejan las capacidades y limitaciones inherentes del modelo, el cual fue entrenado exclusivamente con el dataset SST-2 (críticas de cine en inglés). ApisDom no modifica ni re-entrena el modelo base; proporcionamos una interfaz optimizada para su consumo vía API.

    Los scores de confianza (0.0-1.0) representan la certeza del modelo, no una medida absoluta de precisión. Para aplicaciones críticas, se recomienda validación humana adicional.

    Última actualización de documentación: Enero 2026

    ApisDom · Inteligencia Artificial Transparente

    Esta API utiliza Chronos-2 (modelo pre-entrenado de Amazon Science para forecasting zero-shot) para generar predicciones precisas de series temporales con intervalos de confianza del 90%.

    ⚠️ Limitaciones Conocidas (Leer antes de usar)

    1. Modelo Pre-entrenado: Chronos-2 es zero-shot (no entrena en cada petición). Los tiempos de respuesta son rápidos (1-5 segundos).
    2. Datos Mínimos Requeridos: Se requieren al menos 10 puntos de datos. Para predicciones confiables, se recomiendan 30+ puntos.
    3. Horizonte Máximo: Predicciones limitadas a 365 períodos en el futuro.
    4. Detección de Estacionalidad: Automáticamente aprendida del contexto histórico (sin configuración manual).
    5. Validación MAPE: Usa el parámetro validate_periods para obtener MAPE real mediante backtesting.

    Casos de Uso Recomendados

    🧩 Producto en Producción (Referencia Real)

    Viral
    Plataforma pública de analítica predictiva construida sobre esta API.
    Muestra resultados reales de forecasting, intervalos de confianza (lower/upper) y ausencia de maquillaje de métricas.

    🔗 https://viral.apisdom.com

    ✅ Pronóstico de ventas e ingresos
    ✅ Predicción de tráfico web
    ✅ Gestión de inventario y planificación de demanda
    ✅ Planificación de recursos y capacidad
    ✅ Proyecciones de presupuesto

    Casos de Uso NO Recomendados

    ❌ Predicción de precios de bolsa o criptomonedas (muy volátiles)
    ❌ Extrapolación de un solo punto de datos
    ❌ Datos sin patrón temporal


    📍 Información General

    PropiedadValor
    URL Basehttps://apisdom.com/api/v1
    MétodoPOST
    AutenticaciónAPI Key (Header X-API-Key)
    Tipo de Créditoprediction
    Coste por llamada1 crédito
    Motor IAChronos-2 (Amazon Science zero-shot forecasting)
    Límites de datosMínimo 10, máximo 5000 puntos de datos
    Horizonte máximo365 períodos
    Intervalos de confianza90% (quantiles 5% y 95%)

    📊 Headers Informativos

    La API devuelve headers que te permiten controlar tu consumo:

    HeaderDescripción
    X-RateLimit-LimitTu límite de peticiones por minuto
    X-RateLimit-RemainingPeticiones restantes en la ventana actual
    Retry-AfterSegundos a esperar si recibes 429

    ❄️ Cold Start

    Para optimizar costes, los microservicios de IA escalan a cero cuando no hay actividad.

    💡 Nota: La primera petición puede tardar más de lo normal (hasta 20-30 segundos) mientras el servicio arranca. Las peticiones siguientes serán más rápidas (2-15 segundos dependiendo del tamaño de datos) mientras el servicio permanezca activo.

    💳 Planes y Precios

    Consulta los planes disponibles y precios actualizados en: apisdom.com/pricing


    🔐 Autenticación

    Todas las peticiones requieren tu API Key en el header X-API-Key:

    X-API-Key: tu_api_key_aqui
    

    Puedes obtener tu API Key desde el panel de usuario en apisdom.com/dashboard.


    🏥 Health Check

    Endpoint para verificar que el servicio está disponible. No requiere autenticación.

    GET https://apisdom.com/api/health/predicciones
    

    Response (200 OK)

    {
      "status": "healthy",
      "model": "chronos-2",
      "service": "prediction"
    }
    

    📥 Endpoint: Generar Predicción

    POST https://apisdom.com/api/v1/predictions
    

    Request Body

    CampoTipoRequeridoDefaultDescripción
    datesstring[]✅ Si-Lista de fechas en formato YYYY-MM-DD. Minimo 10, maximo 5000.
    valuesfloat[]✅ Si-Lista de valores numericos correspondientes. Debe tener la misma longitud que dates.
    periodsint❔ Opcional7Numero de periodos futuros a predecir. Minimo 1, maximo 365.
    validate_periodsint❔ OpcionalnullPeriodos para backtesting MAPE (1-30). Si se proporciona, retorna MAPE real.
    is_openbool[]❔ OpcionalnullIndica si el negocio estaba operativo cada dia. Debe tener la misma longitud que dates. Si se proporciona, los dias con false se tratan como datos ausentes.

    Validaciones Importantes

    • dates y values deben tener exactamente la misma longitud
    • Minimo 10 puntos de datos (para contexto historico suficiente)
    • Maximo 5000 puntos de datos
    • Fechas deben estar en orden cronologico
    • Los valores pueden ser enteros o decimales
    • Si se proporciona is_open, debe tener exactamente la misma longitud que dates

    Representacion de datos operativos (is_open)

    El campo is_open es opcional y permite distinguir entre dias sin actividad (negocio cerrado) y dias con ventas reales de cero.

    Si NO envias is_open: Todo funciona como siempre. Los valores 0 se interpretan como datos reales. No necesitas cambiar nada en tu integracion actual.

    Si envias is_open: Puedes indicar exactamente que dias el negocio estaba operativo.

    is_openvalueInterpretacionTratamiento
    falsecualquier valorNegocio cerradoIgnorado (NaN)
    truenumeroNegocio abiertoSe usa el valor
    no definidonumeroComportamiento por defectoSe usa el valor

    Recomendacion para mayor precision:

    • Usa is_open: false cuando el negocio no podia vender (cerrado, festivo, inventario)
    • Usa value: 0 con is_open: true cuando el negocio estaba abierto pero no hubo ventas
    • Si no necesitas esta distincion, simplemente no envies is_open

    Ejemplo con is_open:

    {
      "dates": ["2026-04-07", "2026-04-08", "2026-04-09", "2026-04-10"],
      "values": [5200, 0, 0, 4800],
      "is_open": [true, false, true, true],
      "periods": 7
    }
    

    En este ejemplo:

    • 7 abril: 5200 ventas (abierto, dato real)
    • 8 abril: 0 (cerrado, se ignora en la prediccion)
    • 9 abril: 0 (abierto, 0 ventas reales, dato real)
    • 10 abril: 4800 ventas (abierto, dato real)

    Error de validacion: Si is_open tiene longitud diferente a dates:

    {
      "detail": "is_open debe tener la misma longitud que dates. dates: 91, is_open: 10"
    }
    

    Codigo HTTP: 422 Unprocessable Entity

    Ejemplo de Request

    {
      "dates": [
        "2026-01-01", "2026-01-02", "2026-01-03", "2026-01-04", "2026-01-05",
        "2026-01-06", "2026-01-07", "2026-01-08", "2026-01-09", "2026-01-10",
        "2026-01-11", "2026-01-12", "2026-01-13", "2026-01-14", "2026-01-15"
      ],
      "values": [
        120.5, 135.2, 128.7, 142.1, 155.3,
        148.9, 160.0, 172.4, 165.8, 180.2,
        175.6, 188.9, 195.3, 201.7, 210.5
      ],
      "periods": 7
    }
    

    Response Exitosa (200 OK)

    La respuesta es ANIDADA con dos objetos principales: _metadata y response.

    {
      "_metadata": {
        "provider": "ApisDom",
        "tagline": "Inteligencia artificial transparente",
        "website": "https://apisdom.com",
        "timestamp": "2026-01-21T12:30:00.000Z",
        "service": "Time Series Prediction",
        "channel": "ApisDom Platform",
        "request_id": "pred_1768954704623_abc123",
        "documentation": "https://apisdom.com/documentacion"
      },
      "response": {
        "predictions": [
          {"date": "2026-01-16", "value": 218.34, "lower": 196.51, "upper": 240.17},
          {"date": "2026-01-17", "value": 225.12, "lower": 202.61, "upper": 247.63},
          {"date": "2026-01-18", "value": 231.89, "lower": 208.70, "upper": 255.08},
          {"date": "2026-01-19", "value": 238.45, "lower": 214.61, "upper": 262.30},
          {"date": "2026-01-20", "value": 245.01, "lower": 220.51, "upper": 269.51},
          {"date": "2026-01-21", "value": 251.78, "lower": 226.60, "upper": 276.96},
          {"date": "2026-01-22", "value": 258.34, "lower": 232.51, "upper": 284.17}
        ],
        "mape": null,
        "quality_warning": null,
        "info_message": null
      }
    }
    

    Campos de la Respuesta

    Nivel Raíz:

    CampoTipoDescripción
    _metadataobjectInformación del proveedor y tracking de la petición
    responseobjectEl resultado de la predicción

    Dentro de _metadata:

    CampoTipoDescripción
    providerstringSiempre "ApisDom"
    timestampstringTimestamp ISO 8601 de la petición
    request_idstringIdentificador único de esta petición
    servicestringNombre del servicio ("Time Series Prediction")
    channelstringCanal de distribución ("ApisDom Platform")

    Dentro de response:

    CampoTipoDescripción
    predictionsarrayLista de predicciones con fecha y valores
    predictions[].datestringFecha predicha en formato YYYY-MM-DD
    predictions[].valuefloatValor predicho (mediana, percentil 50%)
    predictions[].lowerfloatLímite inferior del intervalo de confianza 90% (percentil 5%)
    predictions[].upperfloatLímite superior del intervalo de confianza 90% (percentil 95%)
    mapefloat | nullMean Absolute Percentage Error. Retorna valor REAL solo si se proporcionó validate_periods, de lo contrario null.
    quality_warningstring | nullAviso si hay datos insuficientes para validación MAPE (requiere parámetro validate_periods)
    info_messagestring | nullPara usuarios free tier: aviso de cold start. null para planes de pago.

    Sobre intervalos de confianza: Los campos lower y upper representan intervalos de confianza del 90% calculados mediante regresión cuantílica con Chronos-2 (percentiles 5% y 95%). Esto significa que hay un 90% de probabilidad de que el valor real caiga dentro del rango [lower, upper].

    Interpretando el MAPE

    El MAPE (Mean Absolute Percentage Error) se devuelve como valor decimal (0.0 a 1.0):

    MAPE (valor)Error (%)InterpretaciónConfiabilidad
    < 0.05< 5%Excelente⭐⭐⭐⭐⭐ Muy alta
    0.05-0.105-10%Bueno⭐⭐⭐⭐ Alta
    0.10-0.2010-20%Aceptable⭐⭐⭐ Media
    0.20-0.4020-40%Regular⭐⭐ Baja
    > 0.40> 40%Deficiente⭐ Muy baja - Considera usar más datos

    ⚠️ Importante: El MAPE solo se calcula cuando proporcionas validate_periods. Sin él, mape será null. Para obtener MAPE real, usa backtesting:

    {
      "dates": ["2026-01-01", "2026-01-02", ..., "2026-01-30"],
      "values": [120.5, 135.2, ..., 210.5],
      "periods": 7,
      "validate_periods": 5
    }
    

    💻 Ejemplos de Código

    Python

    import requests
    from typing import List
    
    API_URL = "https://apisdom.com/api/v1/predictions"
    API_KEY = "tu_api_key_aqui"
    
    def predecir(fechas: List[str], valores: List[float], periodos: int = 7) -> dict:
        """
        Genera predicciones para una serie temporal.
        
        Args:
            fechas: Lista de fechas en formato 'YYYY-MM-DD'
            valores: Lista de valores numéricos
            periodos: Número de periodos futuros a predecir (1-365)
        
        Returns:
            dict con _metadata y response conteniendo predictions y mape
        """
        if len(fechas) != len(valores):
            raise ValueError("fechas y valores deben tener la misma longitud")
        if len(fechas) < 10:
            raise ValueError("Se necesitan al menos 10 puntos de datos")
        if not 1 <= periodos <= 365:
            raise ValueError("periodos debe estar entre 1 y 365")
        
        response = requests.post(
            API_URL,
            headers={
                "X-API-Key": API_KEY,
                "Content-Type": "application/json"
            },
            json={
                "dates": fechas,
                "values": valores,
                "periods": periodos
            }
        )
        
        if response.status_code == 200:
            return response.json()
        elif response.status_code == 402:
            raise Exception("Sin créditos. Consulta planes en apisdom.com/pricing")
        else:
            raise Exception(f"Error: {response.status_code} - {response.text}")
    
    # Ejemplo de uso
    fechas = [
        "2026-01-01", "2026-01-02", "2026-01-03", "2026-01-04", "2026-01-05",
        "2026-01-06", "2026-01-07", "2026-01-08", "2026-01-09", "2026-01-10",
        "2026-01-11", "2026-01-12", "2026-01-13", "2026-01-14", "2026-01-15"
    ]
    valores = [120.5, 135.2, 128.7, 142.1, 155.3, 148.9, 160.0, 172.4, 165.8, 180.2, 175.6, 188.9, 195.3, 201.7, 210.5]
    
    resultado = predecir(fechas, valores, periodos=7)
    
    # IMPORTANTE: Acceder a los datos dentro del objeto 'response'
    datos = resultado['response']
    if datos['mape'] is not None:
        print(f"MAPE: {datos['mape']:.1%}")
    else:
        print("MAPE: No calculado (usa validate_periods para obtener MAPE real)")
    print(f"Request ID: {resultado['_metadata']['request_id']}")
    print("\nPredicciones:")
    for pred in datos['predictions']:
        print(f"  {pred['date']}: {pred['value']:.2f} [{pred['lower']:.2f} - {pred['upper']:.2f}]")
    
    # Output (sin validate_periods):
    # MAPE: No calculado (usa validate_periods para obtener MAPE real)
    # Request ID: pred_1768954704623_abc123
    # 
    # Predicciones:
    #   2026-01-16: 218.34 [196.51 - 240.17]
    #   2026-01-17: 225.12 [202.61 - 247.63]
    #   ...
    

    JavaScript / Node.js

    const API_URL = 'https://apisdom.com/api/v1/predictions';
    const API_KEY = 'tu_api_key_aqui';
    
    async function predecir(fechas, valores, periodos = 7) {
      /**
       * Genera predicciones para una serie temporal.
       * @param {string[]} fechas - Lista de fechas 'YYYY-MM-DD'
       * @param {number[]} valores - Lista de valores numéricos
       * @param {number} periodos - Periodos a predecir (1-365)
       * @returns {Promise<Object>} - Resultado con _metadata y response
       */
      if (fechas.length !== valores.length) {
        throw new Error('fechas y valores deben tener la misma longitud');
      }
      if (fechas.length < 10) {
        throw new Error('Se necesitan al menos 10 puntos de datos');
      }
      if (periodos < 1 || periodos > 365) {
        throw new Error('periodos debe estar entre 1 y 365');
      }
    
      const response = await fetch(API_URL, {
        method: 'POST',
        headers: {
          'X-API-Key': API_KEY,
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({ dates: fechas, values: valores, periods: periodos })
      });
    
      if (response.status === 402) {
        throw new Error('Sin créditos. Consulta planes en apisdom.com/pricing');
      }
    
      if (!response.ok) {
        throw new Error(`Error: ${response.status}`);
      }
    
      return response.json();
    }
    
    // Ejemplo de uso
    const fechas = [
      '2026-01-01', '2026-01-02', '2026-01-03', '2026-01-04', '2026-01-05',
      '2026-01-06', '2026-01-07', '2026-01-08', '2026-01-09', '2026-01-10',
      '2026-01-11', '2026-01-12', '2026-01-13', '2026-01-14', '2026-01-15'
    ];
    const valores = [120.5, 135.2, 128.7, 142.1, 155.3, 148.9, 160.0, 172.4, 165.8, 180.2, 175.6, 188.9, 195.3, 201.7, 210.5];
    
    predecir(fechas, valores, 7)
      .then(resultado => {
        // IMPORTANTE: Acceder al objeto 'response' anidado
        const datos = resultado.response;
        if (datos.mape !== null) {
          console.log(`MAPE: ${(datos.mape * 100).toFixed(1)}%`);
        } else {
          console.log('MAPE: No calculado (usa validate_periods para obtener MAPE real)');
        }
        console.log(`Request ID: ${resultado._metadata.request_id}`);
        console.log('\nPredicciones:');
        datos.predictions.forEach(p => {
          console.log(`  ${p.date}: ${p.value.toFixed(2)} [${p.lower.toFixed(2)} - ${p.upper.toFixed(2)}]`);
        });
      })
      .catch(console.error);
    

    cURL

    curl -X POST "https://apisdom.com/api/v1/predictions" \
      -H "X-API-Key: tu_api_key_aqui" \
      -H "Content-Type: application/json" \
      -d '{
        "dates": [
          "2026-01-01", "2026-01-02", "2026-01-03", "2026-01-04", "2026-01-05",
          "2026-01-06", "2026-01-07", "2026-01-08", "2026-01-09", "2026-01-10",
          "2026-01-11", "2026-01-12", "2026-01-13", "2026-01-14", "2026-01-15"
        ],
        "values": [120.5, 135.2, 128.7, 142.1, 155.3, 148.9, 160.0, 172.4, 165.8, 180.2, 175.6, 188.9, 195.3, 201.7, 210.5],
        "periods": 7
      }'
    
    # La respuesta incluye _metadata y response anidados:
    # {
    #   "_metadata": { "provider": "ApisDom", "request_id": "...", ... },
    #   "response": { "predictions": [...], "mape": null, ... }
    # }
    

    PHP

    <?php
    $api_url = 'https://apisdom.com/api/v1/predictions';
    $api_key = 'tu_api_key_aqui';
    
    function predecir($fechas, $valores, $periodos = 7) {
        global $api_url, $api_key;
        
        // Validaciones
        if (count($fechas) !== count($valores)) {
            throw new Exception('fechas y valores deben tener la misma longitud');
        }
        if (count($fechas) < 10) {
            throw new Exception('Se necesitan al menos 10 puntos de datos');
        }
        if ($periodos < 1 || $periodos > 365) {
            throw new Exception('periodos debe estar entre 1 y 365');
        }
        
        $ch = curl_init($api_url);
        curl_setopt_array($ch, [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_POST => true,
            CURLOPT_HTTPHEADER => [
                'X-API-Key: ' . $api_key,
                'Content-Type: application/json'
            ],
            CURLOPT_POSTFIELDS => json_encode([
                'dates' => $fechas,
                'values' => $valores,
                'periods' => $periodos
            ])
        ]);
        
        $response = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);
        
        if ($httpCode === 402) {
            throw new Exception('Sin créditos. Consulta planes en apisdom.com/pricing');
        }
        
        return json_decode($response, true);
    }
    
    // Ejemplo de uso
    $fechas = [
        '2026-01-01', '2026-01-02', '2026-01-03', '2026-01-04', '2026-01-05',
        '2026-01-06', '2026-01-07', '2026-01-08', '2026-01-09', '2026-01-10',
        '2026-01-11', '2026-01-12', '2026-01-13', '2026-01-14', '2026-01-15'
    ];
    $valores = [120.5, 135.2, 128.7, 142.1, 155.3, 148.9, 160.0, 172.4, 165.8, 180.2, 175.6, 188.9, 195.3, 201.7, 210.5];
    
    try {
        $resultado = predecir($fechas, $valores, 7);
        
        // IMPORTANTE: Acceder al objeto 'response' anidado
        $datos = $resultado['response'];
        if ($datos['mape'] !== null) {
            echo "MAPE: " . number_format($datos['mape'] * 100, 1) . "%\n";
        } else {
            echo "MAPE: No calculado (usa validate_periods para obtener MAPE real)\n";
        }
        echo "Request ID: " . $resultado['_metadata']['request_id'] . "\n\n";
        
        echo "Predicciones:\n";
        foreach ($datos['predictions'] as $pred) {
            echo "  {$pred['date']}: " . number_format($pred['value'], 2) . 
                 " [" . number_format($pred['lower'], 2) . " - " . number_format($pred['upper'], 2) . "]\n";
        }
    } catch (Exception $e) {
        echo "Error: " . $e->getMessage() . "\n";
    }
    ?>
    

    C# / .NET

    using System.Net.Http;
    using System.Text;
    using System.Text.Json;
    using System.Text.Json.Serialization;
    
    public class PredictionApiClient
    {
        private readonly HttpClient _client;
        private const string API_URL = "https://apisdom.com/api/v1/predictions";
    
        public PredictionApiClient(string apiKey)
        {
            _client = new HttpClient();
            _client.DefaultRequestHeaders.Add("X-API-Key", apiKey);
        }
    
        public async Task<PredictionApiResponse> PredecirAsync(
            List<string> fechas, 
            List<double> valores, 
            int periodos = 7)
        {
            // Validaciones
            if (fechas.Count != valores.Count)
                throw new ArgumentException("fechas y valores deben tener la misma longitud");
            if (fechas.Count < 10)
                throw new ArgumentException("Se necesitan al menos 10 puntos de datos");
            if (periodos < 1 || periodos > 365)
                throw new ArgumentException("periodos debe estar entre 1 y 365");
    
            var request = new { dates = fechas, values = valores, periods = periodos };
            var content = new StringContent(
                JsonSerializer.Serialize(request),
                Encoding.UTF8,
                "application/json"
            );
    
            var response = await _client.PostAsync(API_URL, content);
    
            if (response.StatusCode == System.Net.HttpStatusCode.PaymentRequired)
            {
                throw new Exception("Sin créditos. Consulta planes en apisdom.com/pricing");
            }
    
            response.EnsureSuccessStatusCode();
            
            var json = await response.Content.ReadAsStringAsync();
            return JsonSerializer.Deserialize<PredictionApiResponse>(json);
        }
    }
    
    // IMPORTANTE: La respuesta es ANIDADA - necesitas estas clases
    public class PredictionApiResponse
    {
        [JsonPropertyName("_metadata")]
        public PredictionMetadata Metadata { get; set; }
        
        [JsonPropertyName("response")]
        public PredictionResult Response { get; set; }
    }
    
    public class PredictionMetadata
    {
        [JsonPropertyName("provider")]
        public string Provider { get; set; }
        
        [JsonPropertyName("request_id")]
        public string RequestId { get; set; }
        
        [JsonPropertyName("timestamp")]
        public string Timestamp { get; set; }
    }
    
    public class PredictionResult
    {
        [JsonPropertyName("predictions")]
        public List<Prediction> Predictions { get; set; }
        
        [JsonPropertyName("mape")]
        public double? Mape { get; set; }  // Nullable - solo se establece si se proporcionó validate_periods
        
        [JsonPropertyName("quality_warning")]
        public string? QualityWarning { get; set; }
        
        [JsonPropertyName("info_message")]
        public string? InfoMessage { get; set; }
    }
    
    public class Prediction
    {
        [JsonPropertyName("date")]
        public string Date { get; set; }
        
        [JsonPropertyName("value")]
        public double Value { get; set; }
        
        [JsonPropertyName("lower")]
        public double Lower { get; set; }
        
        [JsonPropertyName("upper")]
        public double Upper { get; set; }
    }
    
    // Ejemplo de uso
    var client = new PredictionApiClient("tu_api_key_aqui");
    
    var fechas = new List<string> {
        "2026-01-01", "2026-01-02", "2026-01-03", "2026-01-04", "2026-01-05",
        "2026-01-06", "2026-01-07", "2026-01-08", "2026-01-09", "2026-01-10",
        "2026-01-11", "2026-01-12", "2026-01-13", "2026-01-14", "2026-01-15"
    };
    var valores = new List<double> { 120.5, 135.2, 128.7, 142.1, 155.3, 148.9, 160.0, 172.4, 165.8, 180.2, 175.6, 188.9, 195.3, 201.7, 210.5 };
    
    var apiResponse = await client.PredecirAsync(fechas, valores, 7);
    
    // Acceder a los datos dentro del objeto Response
    if (apiResponse.Response.Mape.HasValue)
        Console.WriteLine($"MAPE: {apiResponse.Response.Mape:P1}");
    else
        Console.WriteLine("MAPE: No calculado (usa validate_periods para obtener MAPE real)");
    Console.WriteLine($"Request ID: {apiResponse.Metadata.RequestId}");
    Console.WriteLine("\nPredicciones:");
    foreach (var pred in apiResponse.Response.Predictions)
    {
        Console.WriteLine($"  {pred.Date}: {pred.Value:N2} [{pred.Lower:N2} - {pred.Upper:N2}]");
    }
    

    📊 Casos de Uso Prácticos

    1. Sistema de Pronóstico de Ventas

    def pronosticar_ventas(datos_historicos, dias_adelante=30):
        """
        Genera pronóstico de ventas con análisis de tendencia.
        """
        api_result = predecir(
            datos_historicos['fechas'],
            datos_historicos['valores'],
            dias_adelante
        )
        
        # Acceder al objeto 'response' anidado
        datos = api_result['response']
        
        # Calcular métricas
        valores_historicos = datos_historicos['valores']
        valores_predichos = [p['value'] for p in datos['predictions']]
        
        promedio_historico = sum(valores_historicos) / len(valores_historicos)
        promedio_predicho = sum(valores_predichos) / len(valores_predichos)
        
        cambio_porcentaje = ((promedio_predicho - promedio_historico) / promedio_historico) * 100
        
        # Detectar tendencia
        if cambio_porcentaje > 10:
            tendencia = "📈 CRECIMIENTO FUERTE"
        elif cambio_porcentaje > 3:
            tendencia = "📈 Crecimiento moderado"
        elif cambio_porcentaje > -3:
            tendencia = "➡️ Estable"
        elif cambio_porcentaje > -10:
            tendencia = "📉 Declive moderado"
        else:
            tendencia = "📉 DECLIVE FUERTE"
        
        return {
            'predicciones': datos['predictions'],
            'mape': datos['mape'],
            'analisis': {
                'tendencia': tendencia,
                'cambio_porcentaje': f"{cambio_porcentaje:+.1f}%",
                'total_proyectado': sum(valores_predichos)
            }
        }
    

    2. Predicción de Inventario con Alertas

    async function predecirInventario(productId, stockActual, historialVentas) {
      /**
       * Predice cuándo se agotará el inventario y recomienda reposición.
       */
      const apiResult = await predecir(
        historialVentas.fechas,
        historialVentas.valores,
        30  // Predecir 30 días
      );
      
      // Acceder al objeto 'response' anidado
      const datos = apiResult.response;
      
      // Simular consumo de inventario
      let stockRestante = stockActual;
      let diasHastaAgotarse = null;
      
      for (let i = 0; i < datos.predictions.length; i++) {
        const pred = datos.predictions[i];
        stockRestante -= pred.value;
        
        if (stockRestante <= 0 && diasHastaAgotarse === null) {
          diasHastaAgotarse = i + 1;
        }
      }
      
      // Calcular nivel de alerta
      let alerta;
      if (diasHastaAgotarse !== null && diasHastaAgotarse <= 7) {
        alerta = { nivel: 'CRITICO', emoji: '🔴', mensaje: '¡Reponer inmediatamente!' };
      } else if (diasHastaAgotarse !== null && diasHastaAgotarse <= 14) {
        alerta = { nivel: 'ALTO', emoji: '🟠', mensaje: 'Planificar reposición esta semana' };
      } else if (diasHastaAgotarse !== null && diasHastaAgotarse <= 21) {
        alerta = { nivel: 'MEDIO', emoji: '🟡', mensaje: 'Considerar reposición pronto' };
      } else {
        alerta = { nivel: 'BAJO', emoji: '🟢', mensaje: 'Nivel de stock saludable' };
      }
      
      return { productId, stockActual, diasHastaAgotarse, alerta };
    }
    

    3. Dashboard Multi-Métrica

    def crear_dashboard_metricas(metricas_historicas, nombres_metricas):
        """
        Crea un dashboard con múltiples métricas y sus proyecciones.
        """
        resultados = []
        
        for nombre, datos in zip(nombres_metricas, metricas_historicas.values()):
            try:
                api_result = predecir(datos['fechas'], datos['valores'], 7)
                pred_data = api_result['response']  # Acceder al response anidado
                
                historico = datos['valores']
                proyectado = [p['value'] for p in pred_data['predictions']]
                
                resultados.append({
                    'Métrica': nombre,
                    'Promedio Histórico': f"{sum(historico)/len(historico):,.1f}",
                    'Proyección 7 días': f"{sum(proyectado)/7:,.1f}",
                    'MAPE': f"{pred_data['mape']:.3f}" if pred_data['mape'] is not None else "N/A",
                    'Estado': '⚠️' if pred_data.get('quality_warning') else '✅'
                })
            except Exception as e:
                resultados.append({'Métrica': nombre, 'Error': str(e)})
        
        return resultados
    

    ⚠️ Códigos de Error

    CódigoSignificadoSolución
    422Datos invalidosVerifica: minimo 10 puntos, fechas=valores longitud, periodos 1-365, formato fecha YYYY-MM-DD, is_open misma longitud que dates
    401API Key inválidaVerifica tu API Key en el dashboard
    402Sin créditos disponiblesConsulta planes en apisdom.com/pricing
    429Límite de peticiones excedidoEspera antes de reintentar (ver header Retry-After)
    500Error interno del servidorReintenta en unos segundos. Si persiste, contacta soporte

    🔬 Transparencia Técnica

    Política de ApisDom: Creemos que los desarrolladores merecen saber exactamente cómo funcionan las APIs que usan. Esta sección documenta los detalles técnicos verificados directamente del código fuente.

    Cómo Funciona Internamente

    Tus datos → is_open? → DataFrame + covariables → Inferencia Chronos-2 → Prediccion → MAPE
              ↓            ↓                          ↓                       ↓             ↓
              Si is_open   pandas DataFrame           modelo pre-entrenado    predict_df    backtesting
              false→NaN    + day_of_week              (no entrena)            con           validacion
                           + is_weekend                                       covariables
    

    Detalles Verificados del Código

    AspectoValor RealArchivo Fuente
    MotorChronos-2 (Amazon Science)prediction_service.py
    TipoZero-shot (pre-entrenado, no entrena por petición)prediction_service.py
    Modeloamazon/chronos-2 (120M params)prediction_service.py
    Libreríachronos-forecasting==2.2.2requirements.txt
    Método de inferenciapredict_df (con covariables nativas)prediction_service.py
    Covariablesday_of_week (0-6), is_weekend (0/1) — known future covariatesprediction_service.py
    Intervalos de confianzaquantile_levels=[0.05, 0.5, 0.95] (90% confianza)prediction_service.py
    PostprocesamientoValores negativos clamped a 0 (revenue no puede ser negativo)prediction_service.py
    DispositivoCPU (CUDA si disponible)prediction_service.py
    Cálculo MAPEBacktesting real (via validate_periods)prediction_service.py

    Cálculo del MAPE

    # Código real (prediction_service.py) - Verificado 27 marzo 2026
    def _calculate_mape(self, actual: list[float], predicted: list[float]) -> float | None:
        """
        MAPE = (1/n) × Σ |actual - predicted| / |actual|
        """
        if len(actual) != len(predicted):
            return None
    
        # Filtrar valores donde actual != 0 (evitar división por cero)
        valid_pairs = [(a, p) for a, p in zip(actual, predicted, strict=True) if a != 0]
    
        if not valid_pairs:
            return None
    
        errors = [abs(a - p) / abs(a) for a, p in valid_pairs]
        return round(sum(errors) / len(errors), 4)
    

    Importante: El MAPE se calcula via backtesting cuando se proporciona validate_periods. La API reserva los últimos N valores como ground truth, predice usando el resto, y compara.

    Implicacion: Los valores 0 en el periodo de validacion se excluyen del calculo MAPE (para evitar division por cero). Si TODOS los valores de validacion son 0, el MAPE sera null. Si solo algunos son 0, el MAPE se calcula con los restantes. Si usas is_open: false para dias cerrados, esos dias tambien se excluyen del MAPE.

    Por Qué lower y upper Pueden Ser Diferentes a value

    Los intervalos de confianza están implementados mediante regresión cuantílica:

    # Código real (prediction_service.py) - Verificado 27 marzo 2026
    pred_df = pipeline.predict_df(
        context_df,                              # DataFrame histórico con covariables
        future_df=future_df,                     # DataFrame futuro con covariables
        prediction_length=periods,
        quantile_levels=[0.05, 0.5, 0.95],      # Genera percentiles 5%, 50% y 95%
        id_column="item_id",
        timestamp_column="timestamp",
        target="target",
    )
    
    # Extrae valores del DataFrame de salida:
    value = max(0.0, row["predictions"])  # Mediana (clamped >= 0)
    lower = max(0.0, row["0.05"])        # Percentil 5% (clamped >= 0)
    upper = max(0.0, row["0.95"])        # Percentil 95% (clamped >= 0)
    

    Interpretación de tu resultado:

    {
      "date": "2026-01-18",
      "value": 322.15,  // Valor más probable (mediana)
      "lower": 275.44,  // 5% prob. de estar por debajo
      "upper": 450.71   // 5% prob. de estar por encima
    }
    

    Esto significa: Hay 90% de confianza de que el valor real estará entre 275.44 y 450.71.

    Nota: Los valores negativos se clampean a 0 automáticamente (revenue no puede ser negativo).


    📝 Notas Importantes

    Sobre el MAPE

    El MAPE (Mean Absolute Percentage Error) se devuelve como decimal (0.0 a 1.0):

    • mape: 0.05 = 5% de error (excelente)
    • mape: 0.15 = 15% de error (aceptable)
    • mape: 0.40 = 40% de error (considera usar más datos)
    • mape: null = No se solicitó validación (usa validate_periods para obtener MAPE real)

    Para obtener MAPE REAL: Incluye validate_periods en tu petición (1-30). Esto realiza backtesting reservando los últimos N valores como ground truth.

    Para convertir a porcentaje: mape * 100

    Sobre la Calidad de las Predicciones

    FactorImpactoRecomendación
    Cantidad de datosAltoMínimo 30 puntos para predicciones confiables
    Regularidad temporalAltoUsa frecuencia consistente (diaria, semanal)
    Valores atípicosMedioLimpia outliers antes de enviar
    EstacionalidadMedioIncluye al menos 1 ciclo completo
    Tendencia claraAltoSeries con tendencia tienen mejor MAPE

    Cuando el quality_warning Aparece

    Recibirás quality_warning cuando:

    • validate_periods está especificado pero hay datos insuficientes para backtesting
    • Datos mínimos requeridos: validate_periods + 10 puntos (10 para training, resto para validación)
    • Ejemplo: Si validate_periods: 7, necesitas al menos 17 puntos de datos

    Importante: Sin validate_periods, no se genera ningún quality_warning (y mape es null).

    Tiempo de Respuesta

    Puntos de DatosTiempo Estimado
    10-100< 2 segundos
    100-10002-5 segundos
    1000-50005-15 segundos

    Nota: La primera petición del día puede tener ~20-30s de latencia adicional (cold start).


    💬 ¿Necesitas Ayuda?

    📧 soporte@apisdom.com


    ⚖️ Aviso Legal

    Nota de Transparencia: Esta API utiliza el modelo de código abierto Chronos-2 de Amazon Science para pronósticos de series temporales. Los resultados reflejan las capacidades y limitaciones inherentes del modelo. ApisDom proporciona una interfaz optimizada para consumo vía API.

    Las predicciones son estimaciones estadísticas basadas en patrones históricos. No deben usarse como única base para decisiones de negocio críticas. Para aplicaciones de alto riesgo, se recomienda validación adicional.

    Los intervalos de confianza (lower/upper) representan límites de probabilidad del 90%, no garantías. Los valores reales pueden caer fuera de estos rangos el 10% de las veces.

    Ultima actualizacion de documentacion: 10 de abril de 2026

    *ApisDom · Inteligencia Artificial Transparente

    Esta API utiliza el modelo Toxic-BERT (fine-tuned en el dataset Jigsaw Toxic Comment Classification Challenge) para detectar contenido tóxico, discurso de odio, insultos, amenazas y lenguaje obsceno en texto en inglés.

    ⚠️ Limitaciones Conocidas (Leer antes de usar)

    1. 6 Categorías Fijas: El modelo SIEMPRE devuelve las mismas 6 categorías de toxicidad: toxic, severe_toxic, obscene, threat, insult, identity_hate. Todas las categorías se devuelven independientemente del score.
    2. Datos de Entrenamiento: Dataset Jigsaw (comentarios de Wikipedia en inglés) - puede no generalizar perfectamente a todo tipo de contenido (jerga de redes sociales, chats de gaming, etc.).
    3. Idioma: Optimizado exclusivamente para Inglés.
    4. Umbral: El flag is_toxic se activa cuando toxicity_score > 0.7 (puedes implementar tu propio umbral en código).

    🚨 Declaración Oficial de Unitary (Desarrollador del Modelo)

    LITERAL de la documentación oficial de Hugging Face:

    "If words that are associated with swearing, insults or profanity are present in a comment, it is likely that it will be classified as toxic, regardless of the tone or the intent of the author e.g. humorous/self-deprecating. This could present some biases towards already vulnerable minority groups."

    Traducción: Si hay palabras asociadas a insultos o blasfemias, el modelo las clasificará como tóxicas sin importar el tono o la intención del autor (humor, autodesprecio, citas, etc.).

    📊 Tabla de Comportamiento Real del Modelo

    EscenarioComportamientoEjemplo
    Insulto con palabrotas✅ Detecta correctamente"Eres un idiota" → toxic
    Humor con palabrotas⚠️ Marca como tóxico (falso positivo)"Soy un idiota jajaja" → toxic
    Auto-desprecio humorístico⚠️ Marca como tóxico"Me odio a mí mismo lol" → toxic
    Cita de contenido tóxico⚠️ Marca como tóxico"Él dijo 'eres idiota'" → toxic
    Sarcasmo sin palabrotas❌ NO detecta"Qué inteligente eres..." → not toxic
    Pasivo-agresivo❌ NO detecta"Bonito trabajo, lástima que no sirva" → not toxic

    Importante: El modelo detecta vocabulario, NO intención. El sarcasmo, la ironía y el pasivo-agresivo NO son detectados.

    Casos de Uso Recomendados

    ✅ Moderación de comentarios (foros, blogs, redes sociales)
    ✅ Filtrado de chat (gaming, comunidades)
    ✅ Screening de contenido generado por usuarios
    ✅ Triaje de tickets de soporte (detectar clientes enfadados)

    Casos de Uso NO Recomendados

    ❌ Detectar sarcasmo o negatividad sutil
    ❌ Contenido en idiomas distintos al inglés
    ❌ Moderación dependiente del contexto (puede requerir revisión humana)


    📍 Información General

    PropiedadValor
    URL Basehttps://apisdom.com/api/v1
    MétodoPOST
    AutenticaciónAPI Key (Header X-API-Key)
    Tipo de Créditotext
    Coste por llamada1 crédito
    Modelo IAToxic-BERT (unitary/toxic-bert de HuggingFace)
    Límite de tokens512 tokens (textos largos serán truncados)
    Datos de entrenamientoJigsaw Toxic Comment Classification Challenge (comentarios de Wikipedia)
    Categorías de salida6 fijas: toxic, severe_toxic, obscene, threat, insult, identity_hate

    � Métricas Oficiales de Rendimiento

    MétricaLeaderboardTest Set
    ROC-AUC0.988560.98636

    Fuente: Jigsaw Toxic Comment Classification Challenge (2018)

    ⚡ Velocidad y Latencia

    EscenarioTiempo de Respuesta
    En caliente (servicio activo)30-80 ms
    Cold start (primera petición)~20 segundos

    ¿Por qué es importante la velocidad en moderación de contenido?

    1. Experiencia de usuario en tiempo real: En chats de gaming, foros o redes sociales, los usuarios esperan que sus mensajes aparezcan instantáneamente. Una latencia de 30-80ms es imperceptible para el usuario, permitiendo moderar sin que nadie note el filtro.

    2. Escalabilidad masiva: Con ~35ms por inferencia, puedes procesar ~28 mensajes por segundo por instancia. Para plataformas con millones de usuarios activos, esto marca la diferencia entre un sistema que escala y uno que colapsa.

    3. Prevención proactiva: Cuanto antes detectes contenido tóxico, menos usuarios lo verán. En comunidades virales, un mensaje ofensivo puede ser visto por miles de personas en segundos. La moderación en <100ms permite bloquear ANTES de que se propague.

    4. Ahorro en moderación humana: Con pre-filtrado rápido, los moderadores humanos solo revisan el ~5-10% del contenido (zona amarilla del semáforo), no el 100%. Esto reduce costes de personal y burnout.

    5. Cumplimiento legal: Regulaciones como la DSA (Digital Services Act) de la UE exigen "actuación rápida" contra contenido ilegal. Una API lenta puede significar incumplimiento normativo.

    Nota técnica: La latencia de 30-80ms se logra con el modelo en memoria (GPU/CPU). El cold start de ~20s ocurre solo tras períodos de inactividad (escalado a cero para optimizar costes).

    �📊 Headers Informativos

    La API devuelve headers que te permiten controlar tu consumo:

    HeaderDescripción
    X-RateLimit-LimitTu límite de peticiones por minuto
    X-RateLimit-RemainingPeticiones restantes en la ventana actual
    Retry-AfterSegundos a esperar si recibes 429

    ❄️ Cold Start

    Para optimizar costes, los microservicios de IA escalan a cero cuando no hay actividad.

    💡 Nota: La primera petición puede tardar más de lo normal mientras el servicio arranca. Las peticiones siguientes serán mucho más rápidas mientras el servicio permanezca activo.

    💳 Planes y Precios

    Consulta los planes disponibles y precios actualizados en: apisdom.com/pricing


    🔐 Autenticación

    Todas las peticiones requieren tu API Key en el header X-API-Key:

    X-API-Key: tu_api_key_aqui
    

    Puedes obtener tu API Key desde el panel de usuario en apisdom.com/dashboard.


    🏥 Health Check

    Endpoint para verificar que el servicio está disponible. No requiere autenticación.

    GET https://apisdom.com/api/v1/moderacion
    

    Response (200 OK)

    {
      "status": "healthy",
      "service": "moderation",
      "valid": true,
      "timestamp": "2026-01-21T03:45:00.000Z",
      "provider": "ApisDom"
    }
    

    📥 Endpoint: Moderar Contenido

    POST https://apisdom.com/api/v1/moderacion
    

    Request Body

    CampoTipoRequeridoDescripción
    textstring✅ SíTexto a moderar. Mínimo 1 carácter, máximo 5000.

    Ejemplo de Request

    {
      "text": "You are an idiot and should disappear."
    }
    

    Response Exitosa (200 OK)

    La respuesta es ANIDADA con dos objetos principales: _metadata y response.

    {
      "_metadata": {
        "provider": "ApisDom",
        "tagline": "Inteligencia artificial transparente",
        "website": "https://apisdom.com",
        "timestamp": "2026-01-21T03:45:00.000Z",
        "service": "Content Moderation",
        "channel": "ApisDom Platform",
        "request_id": "mod_1768965900123_abc123",
        "documentation": "https://apisdom.com/documentacion"
      },
      "response": {
        "text": "You are an idiot and should disappear.",
        "is_toxic": true,
        "toxicity_score": 0.923,
        "categories": {
          "toxic": 0.923,
          "severe_toxic": 0.145,
          "obscene": 0.321,
          "threat": 0.087,
          "insult": 0.876,
          "identity_hate": 0.023
        },
        "warning": null,
        "info_message": null
      }
    }
    

    Campos de la Respuesta

    Nivel Raíz:

    CampoTipoDescripción
    _metadataobjectInformación del proveedor y tracking de la petición
    responseobjectEl resultado del análisis de moderación

    Dentro de _metadata:

    CampoTipoDescripción
    providerstringSiempre "ApisDom"
    timestampstringTimestamp ISO 8601 de la petición
    request_idstringIdentificador único de esta petición
    servicestringNombre del servicio ("Content Moderation")
    channelstringCanal de distribución ("ApisDom Platform")

    Dentro de response:

    CampoTipoDescripción
    textstringEl texto que fue analizado
    is_toxicbooleantrue si toxicity_score > 0.7, false en caso contrario
    toxicity_scorefloatPuntuación máxima de toxicidad detectada (0.0 a 1.0)
    categoriesobjectTodas las 6 categorías estándar con sus scores (0.0-1.0)
    warningstring | nullAviso si el texto fue truncado (textos muy largos)
    info_messagestring | nullInformación adicional sobre el análisis

    Categorías de Toxicidad Explicadas

    CategoríaDescripción
    toxicToxicidad general (categoría principal)
    severe_toxicToxicidad extrema/severa
    obsceneLenguaje vulgar u obsceno
    threatAmenazas o intimidación
    insultInsultos directos
    identity_hateDiscurso de odio basado en identidad (raza, religión, orientación, etc.)

    ⚠️ Importante: El objeto categories SIEMPRE incluye las 6 categorías con sus scores, incluso si son bajos (ej: threat: 0.05 significa 5% de probabilidad). Filtra por umbral en tu código si solo quieres mostrar categorías relevantes (ej: score > 0.5).


    💻 Ejemplos de Código

    Python

    import requests
    
    API_URL = "https://apisdom.com/api/v1/moderacion"
    API_KEY = "tu_api_key_aqui"
    
    def moderar_contenido(texto):
        """
        Analiza un texto para detectar contenido tóxico.
        
        Args:
            texto: String con el texto a moderar (máx 5000 caracteres)
        
        Returns:
            dict con _metadata y response conteniendo is_toxic, toxicity_score y categories
        """
        response = requests.post(
            API_URL,
            headers={
                "X-API-Key": API_KEY,
                "Content-Type": "application/json"
            },
            json={"text": texto}
        )
        
        if response.status_code == 200:
            return response.json()
        elif response.status_code == 402:
            raise Exception("Sin créditos. Consulta planes en apisdom.com/pricing")
        else:
            raise Exception(f"Error: {response.status_code} - {response.text}")
    
    def debe_bloquear(resultado, umbral=0.7):
        """
        Determina si un contenido debe ser bloqueado.
        
        Args:
            resultado: Respuesta de moderar_contenido()
            umbral: Puntuación mínima para bloquear (default 0.7)
        
        Returns:
            True si debe bloquearse, False si puede publicarse
        """
        # IMPORTANTE: Acceder al objeto 'response' anidado
        datos = resultado['response']
        
        # Bloquear si toxicidad general supera umbral
        if datos['toxicity_score'] >= umbral:
            return True
        
        # Tolerancia cero para amenazas y ataques de identidad
        categorias = datos['categories']
        if categorias.get('threat', 0) >= 0.5:
            return True
        if categorias.get('identity_hate', 0) >= 0.5:
            return True
        
        return False
    
    # Ejemplo de uso
    resultado = moderar_contenido("Thank you for your help, you are great!")
    
    # IMPORTANTE: Acceder a los datos dentro del objeto 'response'
    datos = resultado['response']
    print(f"Es tóxico: {datos['is_toxic']}")
    print(f"Score toxicidad: {datos['toxicity_score']:.2%}")
    print(f"Request ID: {resultado['_metadata']['request_id']}")
    
    if debe_bloquear(resultado):
        print("❌ BLOQUEADO - Contenido inapropiado")
    else:
        print("✅ APROBADO - Contenido apropiado")
    
    # Output:
    # Es tóxico: False
    # Score toxicidad: 2.00%
    # Request ID: mod_1768965900123_abc123
    # ✅ APROBADO - Contenido apropiado
    

    JavaScript / Node.js

    const API_URL = 'https://apisdom.com/api/v1/moderacion';
    const API_KEY = 'tu_api_key_aqui';
    
    async function moderarContenido(texto) {
      /**
       * Analiza un texto para detectar contenido tóxico.
       * @param {string} texto - Texto a moderar (máx 5000 caracteres)
       * @returns {Promise<Object>} - Resultado con _metadata y response
       */
      const response = await fetch(API_URL, {
        method: 'POST',
        headers: {
          'X-API-Key': API_KEY,
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({ text: texto })
      });
    
      if (response.status === 402) {
        throw new Error('Sin créditos. Consulta planes en apisdom.com/pricing');
      }
    
      if (!response.ok) {
        throw new Error(`Error: ${response.status}`);
      }
    
      return response.json();
    }
    
    function debeBloquear(resultado, umbral = 0.7) {
      // IMPORTANTE: Acceder al objeto 'response' anidado
      const datos = resultado.response;
      if (datos.toxicity_score >= umbral) return true;
      if (datos.categories.threat >= 0.5) return true;
      if (datos.categories.identity_hate >= 0.5) return true;
      return false;
    }
    
    // Ejemplo de uso
    moderarContenido('You are an idiot and should disappear')
      .then(resultado => {
        // IMPORTANTE: Acceder al objeto 'response' anidado
        const datos = resultado.response;
        console.log(`Es tóxico: ${datos.is_toxic}`);
        console.log(`Score toxicidad: ${(datos.toxicity_score * 100).toFixed(2)}%`);
        console.log(`Request ID: ${resultado._metadata.request_id}`);
        
        // Mostrar categorías detectadas
        const categoriasAltas = Object.entries(datos.categories)
          .filter(([_, score]) => score >= 0.5)
          .sort((a, b) => b[1] - a[1])
          .map(([cat, score]) => `${cat}: ${(score * 100).toFixed(0)}%`);
        
        if (categoriasAltas.length > 0) {
          console.log(`Detectado: ${categoriasAltas.join(', ')}`);
        }
        
        // Output:
        // Es tóxico: true
        // Score toxicidad: 92.30%
        // Request ID: mod_1768965900456_def456
        // Detectado: toxic: 92%, insult: 88%
      })
      .catch(console.error);
    

    cURL

    curl -X POST "https://apisdom.com/api/v1/moderacion" \
      -H "X-API-Key: tu_api_key_aqui" \
      -H "Content-Type: application/json" \
      -d '{"text": "Thank you for your help, this tutorial is very useful!"}'
    
    # La respuesta incluye _metadata y response anidados:
    # {
    #   "_metadata": { "provider": "ApisDom", "request_id": "...", ... },
    #   "response": { "text": "...", "is_toxic": false, "toxicity_score": 0.02, "categories": {...} }
    # }
    

    PHP

    <?php
    $api_url = 'https://apisdom.com/api/v1/moderacion';
    $api_key = 'tu_api_key_aqui';
    
    function moderarContenido($texto) {
        global $api_url, $api_key;
        
        $ch = curl_init($api_url);
        curl_setopt_array($ch, [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_POST => true,
            CURLOPT_HTTPHEADER => [
                'X-API-Key: ' . $api_key,
                'Content-Type: application/json'
            ],
            CURLOPT_POSTFIELDS => json_encode(['text' => $texto])
        ]);
        
        $response = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);
        
        if ($httpCode === 402) {
            throw new Exception('Sin créditos. Consulta planes en apisdom.com/pricing');
        }
        
        return json_decode($response, true);
    }
    
    function debeBloquear($resultado, $umbral = 0.7) {
        // IMPORTANTE: Acceder al objeto 'response' anidado
        $datos = $resultado['response'];
        if ($datos['toxicity_score'] >= $umbral) return true;
        if ($datos['categories']['threat'] >= 0.5) return true;
        if ($datos['categories']['identity_hate'] >= 0.5) return true;
        return false;
    }
    
    // Ejemplo de uso
    $resultado = moderarContenido('Great article, very well explained!');
    
    // IMPORTANTE: Acceder al objeto 'response' anidado
    $datos = $resultado['response'];
    echo "Es tóxico: " . ($datos['is_toxic'] ? 'Sí' : 'No') . "\n";
    echo "Score toxicidad: " . number_format($datos['toxicity_score'] * 100, 2) . "%\n";
    echo "Request ID: " . $resultado['_metadata']['request_id'] . "\n";
    
    if (debeBloquear($resultado)) {
        echo "❌ BLOQUEADO - Contenido inapropiado\n";
    } else {
        echo "✅ APROBADO - Contenido apropiado\n";
    }
    
    // Output:
    // Es tóxico: No
    // Score toxicidad: 1.50%
    // Request ID: mod_1768965900789_ghi789
    // ✅ APROBADO - Contenido apropiado
    ?>
    

    C# / .NET

    using System.Net.Http;
    using System.Text;
    using System.Text.Json;
    using System.Text.Json.Serialization;
    
    public class ModerationApiClient
    {
        private readonly HttpClient _client;
        private const string API_URL = "https://apisdom.com/api/v1/moderacion";
    
        public ModerationApiClient(string apiKey)
        {
            _client = new HttpClient();
            _client.DefaultRequestHeaders.Add("X-API-Key", apiKey);
        }
    
        public async Task<ModerationApiResponse> ModerarContenidoAsync(string texto)
        {
            var content = new StringContent(
                JsonSerializer.Serialize(new { text = texto }),
                Encoding.UTF8,
                "application/json"
            );
    
            var response = await _client.PostAsync(API_URL, content);
    
            if (response.StatusCode == System.Net.HttpStatusCode.PaymentRequired)
            {
                throw new Exception("Sin créditos. Consulta planes en apisdom.com/pricing");
            }
    
            response.EnsureSuccessStatusCode();
            
            var json = await response.Content.ReadAsStringAsync();
            return JsonSerializer.Deserialize<ModerationApiResponse>(json);
        }
    }
    
    // IMPORTANTE: La respuesta es ANIDADA - necesitas estas clases
    public class ModerationApiResponse
    {
        [JsonPropertyName("_metadata")]
        public ModerationMetadata Metadata { get; set; }
        
        [JsonPropertyName("response")]
        public ModerationResult Response { get; set; }
    }
    
    public class ModerationMetadata
    {
        [JsonPropertyName("provider")]
        public string Provider { get; set; }
        
        [JsonPropertyName("request_id")]
        public string RequestId { get; set; }
        
        [JsonPropertyName("timestamp")]
        public string Timestamp { get; set; }
    }
    
    public class ModerationResult
    {
        [JsonPropertyName("text")]
        public string Text { get; set; }
        
        [JsonPropertyName("is_toxic")]
        public bool IsToxic { get; set; }
        
        [JsonPropertyName("toxicity_score")]
        public double ToxicityScore { get; set; }
        
        [JsonPropertyName("categories")]
        public ToxicityCategories Categories { get; set; }
        
        [JsonPropertyName("warning")]
        public string? Warning { get; set; }
        
        [JsonPropertyName("info_message")]
        public string? InfoMessage { get; set; }
    }
    
    public class ToxicityCategories
    {
        [JsonPropertyName("toxic")]
        public double Toxic { get; set; }
        
        [JsonPropertyName("severe_toxic")]
        public double SevereToxic { get; set; }
        
        [JsonPropertyName("obscene")]
        public double Obscene { get; set; }
        
        [JsonPropertyName("threat")]
        public double Threat { get; set; }
        
        [JsonPropertyName("insult")]
        public double Insult { get; set; }
        
        [JsonPropertyName("identity_hate")]
        public double IdentityHate { get; set; }
    }
    
    // Ejemplo de uso
    var client = new ModerationApiClient("tu_api_key_aqui");
    var apiResponse = await client.ModerarContenidoAsync("You are amazing, thank you!");
    
    // Acceder a los datos dentro del objeto Response
    Console.WriteLine($"Es tóxico: {apiResponse.Response.IsToxic}");
    Console.WriteLine($"Score toxicidad: {apiResponse.Response.ToxicityScore:P2}");
    Console.WriteLine($"Request ID: {apiResponse.Metadata.RequestId}");
    
    // Output:
    // Es tóxico: False
    // Score toxicidad: 1.20%
    // Request ID: mod_1768965900999_jkl999
    

    ⚠️ Sesgos Documentados y Consideraciones Éticas

    La documentación oficial del modelo cita estos papers sobre sesgos en detección de toxicidad:

    1. "The Risk of Racial Bias in Hate Speech Detection" - Paper
    2. "Automated Hate Speech Detection and the Problem of Offensive Language" - arXiv:1703.04009
    3. "Racial Bias in Hate Speech and Abusive Language Detection Datasets" - arXiv:1905.12516

    Sesgo hacia Grupos Minoritarios

    Advertencia oficial: El modelo puede tener sesgos hacia grupos vulnerables minoritarios porque el contenido que menciona identidades (raza, religión, orientación sexual) puede ser clasificado como tóxico incluso cuando no lo es.

    Uso Recomendado (Declaración Oficial)

    "The intended use of this library is for research purposes, fine-tuning on carefully constructed datasets that reflect real world demographics and/or to aid content moderators in flagging out harmful content quicker."

    UsoRecomendación
    ✅ InvestigaciónSí - uso previsto
    ✅ Pre-filtrado para moderadores humanosSí - uso previsto
    ✅ Sanitización automática a escalaSí - acelera detección
    ⚠️ Decisiones automáticas de baneoCon precaución - revisar falsos positivos
    ❌ Juez único sin revisión humanaNo recomendado
    ❌ Detección de sarcasmo/ironíaNo funciona
    ❌ Análisis de intenciónNo funciona

    📊 Casos de Uso Prácticos

    1. Sistema de Moderación de Comentarios

    comentarios = [
        "Great tutorial, very helpful!",
        "You are an idiot, this is garbage",
        "I disagree with this approach",
    ]
    
    for comentario in comentarios:
        api_result = moderar_contenido(comentario)
        # Acceder al objeto 'response' anidado
        datos = api_result['response']
        estado = "❌ BLOQUEADO" if datos['is_toxic'] else "✅ APROBADO"
        print(f"'{comentario[:30]}...' → {estado} ({datos['toxicity_score']:.0%})")
    
    # Output:
    # 'Great tutorial, very helpful!...' → ✅ APROBADO (2%)
    # 'You are an idiot, this is gar...' → ❌ BLOQUEADO (94%)
    # 'I disagree with this approach...' → ✅ APROBADO (5%)
    

    2. Filtro de Chat en Tiempo Real

    def filtrar_mensaje_chat(mensaje, username):
        """Filtra mensajes de chat en tiempo real."""
        api_result = moderar_contenido(mensaje)
        # Acceder al objeto 'response' anidado
        datos = api_result['response']
        
        if datos['is_toxic']:
            # Registrar el incidente
            categorias = datos['categories']
            detectadas = [cat for cat, score in categorias.items() if score >= 0.5]
            print(f"⚠️ Mensaje bloqueado de {username}: {detectadas}")
            return None  # No mostrar el mensaje
        
        return mensaje  # Seguro para mostrar
    
    # Ejemplo
    msg = filtrar_mensaje_chat("You're the worst player ever, uninstall!", "toxic_user123")
    # Output: ⚠️ Mensaje bloqueado de toxic_user123: ['toxic', 'insult']
    

    3. Dashboard de Moderación de Contenido

    async function moderarLote(contenidos) {
      const apiResults = await Promise.all(
        contenidos.map(c => moderarContenido(c.text))
      );
      
      // Extraer el objeto 'response' de cada resultado de la API
      const resultados = apiResults.map(r => r.response);
      
      const stats = {
        total: resultados.length,
        toxicos: resultados.filter(r => r.is_toxic).length,
        limpios: resultados.filter(r => !r.is_toxic).length,
        promedioToxicidad: resultados.reduce((a, b) => a + b.toxicity_score, 0) / resultados.length
      };
      
      console.log('📊 Resumen de Moderación:');
      console.log(`   Total: ${stats.total}`);
      console.log(`   ❌ Tóxicos: ${stats.toxicos}`);
      console.log(`   ✅ Limpios: ${stats.limpios}`);
      console.log(`   📊 Toxicidad promedio: ${(stats.promedioToxicidad * 100).toFixed(1)}%`);
      
      return stats;
    }
    

    🚦 Patrones de Implementación Avanzados

    Patrón 1: Semáforo de Confianza (Human-in-the-Loop)

    Dado que el modelo no es un juez perfecto, se recomienda una lógica de tres niveles en lugar de una decisión binaria:

    NivelScoreAcciónRazón
    🟢 Verde< 0.60Permitir automáticamenteContenido seguro
    🟡 Amarillo0.60 - 0.90Marcar para revisión humanaZona de riesgo (sarcasmo, falsos positivos)
    🔴 Rojo> 0.90Bloqueo automáticoToxicidad explícita casi segura
    def procesar_comentario(texto_usuario):
        # 1. Llamada a Apisdom
        resultado = moderar_contenido(texto_usuario)
        datos = resultado['response']
        score = datos['toxicity_score']
        
        # 2. Lógica de Semáforo (gestionando la incertidumbre)
        
        # CASO A: Toxicidad extrema (Bloqueo inmediato)
        if score >= 0.90:
            return {
                "accion": "BLOQUEAR",
                "mensaje": "Tu comentario ha sido rechazado por violar las normas.",
                "revision_requerida": False
            }
        
        # CASO B: Zona Gris (Revisión Humana)
        # Aquí caen el sarcasmo, falsos positivos, discusiones acaloradas
        elif score >= 0.60:
            guardar_para_revision(texto_usuario, score)
            return {
                "accion": "FLAG", 
                "mensaje": "Tu comentario está pendiente de aprobación.",
                "revision_requerida": True
            }
        
        # CASO C: Contenido Seguro (Publicación inmediata)
        else:
            return {
                "accion": "PUBLICAR", 
                "mensaje": None, 
                "revision_requerida": False
            }
    

    Patrón 2: Gestión de Falsos Positivos (Usuarios de Confianza)

    Para plataformas donde los usuarios usan jerga agresiva no tóxica (gaming, foros especializados):

    async function checkContent(user, text) {
        const apiResponse = await moderarContenido(text);
        const datos = apiResponse.response;
        
        // Si el usuario es veterano/premium, somos más permisivos
        const threshold = user.isTrusted ? 0.95 : 0.80;
        
        if (datos.toxicity_score > threshold) {
            // Doble chequeo: ¿Es solo "obsceno" pero no "amenaza" ni "odio"?
            const cats = datos.categories;
            if (cats.obscene > 0.9 && cats.identity_hate < 0.1 && cats.threat < 0.1) {
                return "ALLOW_WITH_WARNING"; // Permitir palabrotas si no hay odio
            }
            return "BLOCK";
        }
        return "ALLOW";
    }
    

    Patrón 3: Feedback Transparente al Usuario

    En lugar de un error genérico, usa las categorías para educar al usuario:

    def generar_mensaje_rechazo(datos):
        categorias_detectadas = []
        
        if datos['categories']['insult'] >= 0.7:
            categorias_detectadas.append(f"• Insultos directos (Confianza: {datos['categories']['insult']:.0%})")
        if datos['categories']['obscene'] >= 0.5:
            categorias_detectadas.append(f"• Lenguaje obsceno (Confianza: {datos['categories']['obscene']:.0%})")
        if datos['categories']['threat'] >= 0.5:
            categorias_detectadas.append(f"• Amenazas (Confianza: {datos['categories']['threat']:.0%})")
        if datos['categories']['identity_hate'] >= 0.5:
            categorias_detectadas.append(f"• Discurso de odio (Confianza: {datos['categories']['identity_hate']:.0%})")
        
        if categorias_detectadas:
            return f"""No hemos podido publicar tu comentario. Nuestro sistema ha detectado:
    {chr(10).join(categorias_detectadas)}
    
    Por favor, reformula tu mensaje manteniendo el respeto.
    
    💡 Nota: Si crees que es un error (sarcasmo, cita, etc.), 
    contacta con soporte indicando el ID de petición."""
        
        return "Comentario rechazado por contenido inapropiado."
    

    ¿Por qué esto ayuda? Al exponer la categoría específica, el usuario entiende qué hizo mal. Si fue un falso positivo (sarcasmo), podrá reescribirlo de forma más clara, reduciendo frustración y quejas al soporte.


    ⚠️ Códigos de Error

    CódigoSignificadoSolución
    400Texto inválido (vacío o muy largo)Asegúrate de enviar entre 1 y 5000 caracteres
    401API Key inválidaVerifica tu API Key en el dashboard
    402Sin créditos disponiblesConsulta planes en apisdom.com/pricing
    429Límite de peticiones excedidoEspera antes de reintentar (ver headers de rate limit)
    500Error interno del servidorReintenta en unos segundos. Si persiste, contacta soporte

    🔬 Transparencia Técnica

    Política de ApisDom: Creemos que los desarrolladores merecen saber exactamente cómo funcionan las APIs que usan. Esta sección documenta los detalles técnicos verificados directamente del código fuente.

    Cómo Funciona Internamente

    Tu texto → Tokenización (BERT) → Inferencia → Clasificación Multi-label → Respuesta
             ↓                      ↓                    ↓
             512 tokens máx     CPU-bound          6 categorías con scores
                               (threadpool)        + verificación umbral is_toxic
    

    Detalles Técnicos

    AspectoValor
    Modelounitary/toxic-bert (HuggingFace)
    Pipelinetext-classification de HuggingFace Transformers
    Truncamiento512 tokens (automático)
    Categorías de salida6 fijas: toxic, severe_toxic, obscene, threat, insult, identity_hate
    Umbral is_toxicmax(toxicity_score) > 0.7
    EjecuciónAsync no bloqueante (threadpool)

    ⚠️ Importante: Siempre se Devuelven las 6 Categorías

    El modelo Toxic-BERT es un clasificador multi-label. Fue entrenado con el dataset Jigsaw Toxic Comment Classification Challenge (comentarios de Wikipedia). El modelo SIEMPRE devuelve las 6 categorías con sus scores de confianza, incluso si son bajos.

    Ejemplo con texto no tóxico:

    • Input: "Thank you for your help!"
    • Output: Las 6 categorías con scores muy bajos (< 0.05)
    • El flag is_toxic será false porque toxicity_score < 0.7

    📝 Notas Importantes

    Sobre el Modelo Toxic-BERT

    El modelo unitary/toxic-bert está entrenado para textos en inglés y ofrece rendimiento óptimo en este idioma.

    Limitaciones conocidas:

    • ⚠️ Solo inglés: Optimizado para texto en inglés
    • El modelo fue entrenado con comentarios de Wikipedia (dataset Jigsaw)
    • Puede no detectar todas las formas de toxicidad (lenguaje codificado, jerga nueva)
    • La toxicidad dependiente del contexto puede no ser detectada
    • Falsos positivos: Texto agresivo pero no tóxico puede ser marcado
    • Falsos negativos: Toxicidad sutil o sarcasmo puede no ser detectado

    Mejores casos de uso:

    • Moderación de comentarios (foros, blogs)
    • Filtrado de chat (gaming, comunidades)
    • Screening de contenido generado por usuarios
    • Clasificación de tickets de soporte

    No recomendado para:

    • Contenido en idiomas distintos al inglés
    • Detectar sarcasmo o lenguaje codificado
    • Decisiones legales (siempre añadir revisión humana)

    Umbrales Recomendados por Tipo de Plataforma

    Tipo de PlataformaUmbral ToxicidadUmbral AmenazasUmbral Identidad
    Foro para niños0.30.20.2
    Red social general0.50.40.4
    Foro de adultos0.70.40.5
    Chat privado0.80.50.6

    Sobre el Truncamiento de Texto

    El modelo BERT tiene un límite de 512 tokens (~400 palabras). Si tu texto es más largo:

    • Se analizarán los primeros 512 tokens
    • Recibirás un warning en la respuesta indicando el truncamiento
    • El análisis seguirá siendo válido pero parcial

    Recomendación: Para textos largos, divídelos en párrafos y analiza cada uno por separado.

    Sobre el Tiempo de Respuesta

    Ver sección ⚡ Velocidad y Latencia para detalles completos sobre latencias y por qué la velocidad es crítica en moderación de contenido.


    💬 ¿Necesitas Ayuda?

    📧 soporte@apisdom.com


    ⚖️ Aviso Legal y Transparencia Total

    Nota de Transparencia: Esta API utiliza el modelo de código abierto unitary/toxic-bert de Hugging Face, desarrollado por Laura Hanu @ Unitary. Los resultados reflejan las capacidades y limitaciones inherentes del modelo, el cual fue entrenado exclusivamente con el dataset Jigsaw (comentarios de Wikipedia en inglés). ApisDom no modifica ni re-entrena el modelo base; proporcionamos una interfaz optimizada para su consumo vía API.

    Los scores de confianza (0.0-1.0) representan la certeza del modelo, no una medida absoluta de precisión. Para aplicaciones críticas, se recomienda validación humana adicional.

    ✅ Fortalezas del Modelo

    • Alta precisión en inglés (ROC-AUC 0.986 en benchmark)
    • 6 categorías detalladas de toxicidad
    • Rápido (~35ms por inferencia en caliente)
    • Ligero (~110M parámetros, funciona en CPU)
    • Bien documentado y mantenido activamente

    ❌ Limitaciones Inherentes (Ser Honestos)

    • Solo inglés (otros idiomas funcionan por transferencia, con menor precisión)
    • NO detecta intención (sarcasmo, ironía, pasivo-agresivo pasan desapercibidos)
    • Sesgo por vocabulario (palabrotas = tóxico, sin importar contexto humorístico)
    • Sesgos hacia minorías (mencionar identidades puede activar falsos positivos)

    🎯 Caso de Uso Ideal

    Pre-filtrado rápido de contenido a escala para asistir moderadores humanos, NO como juez único de decisiones automáticas de baneo sin revisión.

    📖 Referencias Oficiales

    RecursoURL
    Hugging Face Modelhttps://huggingface.co/unitary/toxic-bert
    GitHub Repositoryhttps://github.com/unitaryai/detoxify
    Jigsaw Challengehttps://www.kaggle.com/c/jigsaw-toxic-comment-classification-challenge

    📚 Citación

    @misc{Detoxify,
      title={Detoxify},
      author={Hanu, Laura and {Unitary team}},
      howpublished={Github. https://github.com/unitaryai/detoxify},
      year={2020}
    }
    

    Última actualización de documentación: Enero 2026*

    ApisDom · Inteligencia Artificial Transparente

    Genera recomendaciones personalizadas basadas en similitud semántica utilizando sentence-transformers (all-MiniLM-L6-v2), identificando los contenidos más relevantes para una consulta específica.


    ⚠️ Limitaciones Conocidas (Leer antes de usar)

    • Modelo de Embeddings: Basado en sentence-transformers/all-MiniLM-L6-v2, optimizado para similitud semántica en inglés. Funciona con español pero con menor precisión.
    • Límite de Tokens: Cada texto (query y contents) se trunca a 512 tokens máximo (aproximadamente 380 palabras). Textos más largos serán recortados automáticamente.
    • Ranking por Similitud: Los resultados se ordenan por score de cosine similarity (0.0-1.0). Un score de 0.85+ indica alta relevancia semántica.
    • Cantidad de Contenidos: Máximo 100 items por petición. Para datasets grandes, implementa paginación en tu código.
    • Idioma: Optimizado para inglés. Funciona en español, francés, alemán, italiano y otros idiomas latinos pero con menor precisión.

    Casos de Uso Recomendados

    • ✅ Motores de búsqueda semántica (e-commerce, documentación)
    • ✅ Sistemas de recomendación de contenido (artículos, productos, videos)
    • ✅ Autocompletado inteligente basado en contexto
    • ✅ Clasificación y clustering de documentos similares
    • ✅ Búsqueda de duplicados o contenido relacionado

    Casos de Uso NO Recomendados

    • ❌ Búsqueda exacta de palabras clave (usa búsqueda tradicional)
    • ❌ Comparación de código fuente (requiere modelos especializados)
    • ❌ Textos con jerga técnica muy específica (puede no generalizarse bien)

    🎯 Optimización de Relevancia (Semantic Enrichment)

    ⚠️ REQUISITO DE INTEGRACIÓN: Esta sección es obligatoria para obtener resultados precisos en búsquedas complejas. El 90% de los problemas de "baja precisión" se resuelven aplicando estas técnicas.

    El Concepto Clave

    El motor de recomendaciones funciona mediante similitud vectorial. Esto significa que calcula la distancia matemática entre el texto de la consulta (query) y el texto de los elementos (contents).

    Limitación crítica: El motor NO realiza inferencias lógicas ni tiene conocimiento externo.

    Lo que el modelo PUEDE hacerLo que el modelo NO puede hacer
    Comparar palabras y sus sinónimos aprendidos"Saber" que Tesla es ecológico
    Detectar similitud semántica entre frasesInferir atributos no escritos
    Ordenar por relevancia textualRazonar sobre el mundo real

    Ejemplo real:

    • ❌ El motor NO "sabe" que un Tesla es "ecológico"
    • ✅ El motor SÍ sabe que "Tesla" y "electric car eco-friendly" son similares SI tú escribes ambos

    Cómo Estructurar el Payload (contents)

    No envíes solo el nombre del producto. Concatena las características clave, categorías y sinónimos dentro de la misma cadena de texto.

    ❌ Forma Incorrecta (Baja Precisión)

    El modelo solo tiene el nombre para trabajar. Resultados pobres garantizados.

    {
      "query": "laptop económica para estudiante",
      "contents": [
        "HP 15",
        "MacBook Pro",
        "Dell Inspiron"
      ]
    }
    

    Resultado: Scores bajos (~0.30-0.45), el modelo no tiene contexto para decidir.

    ✅ Forma Recomendada (Alta Precisión - Score > 0.70)

    El modelo recibe contexto semántico explícito.

    {
      "query": "laptop económica para estudiante",
      "contents": [
        "HP 15 [económico, estudiante, ofimática, barato, gama entrada, precio bajo]",
        "MacBook Pro [profesional, diseño gráfico, potente, alto rendimiento, caro, premium]",
        "Dell Inspiron [gama media, versátil, oficina, estudiante, buen precio]"
      ]
    }
    

    Resultado: HP 15 obtiene score ~0.75+, MacBook Pro queda abajo (~0.35) porque "caro" es opuesto a "económica".

    Ejemplo Comparativo Real (Probado)

    Búsqueda: "electric car sustainable eco-friendly environment"

    Tipo de Dato EnviadoItemScoreExplicación
    ❌ Solo nombre: "BMW iX"BMW iX0.65El modelo asocia vagamente "BMW" con "car"
    ✅ Enriquecido: "Tesla Model 3 electric car sustainable eco-friendly zero emissions"Tesla Model 30.73Match semántico fuerte con todos los términos

    Diferencia: +12% de precisión solo por añadir descriptores semánticos.

    Fórmula de Enriquecimiento Recomendada

    Procesa tus datos ANTES de enviarlos a la API siguiendo este patrón:

    {Nombre del Producto} + {Categoría} + {Atributos/Adjetivos} + {Sinónimos}
    

    Ejemplos por industria:

    IndustriaSolo Nombre (❌)Enriquecido (✅)
    E-commerce"iPhone 15 Pro""iPhone 15 Pro smartphone premium caro Apple fotografía profesional alta gama flagship"
    Recetas"Hamburguesa""Hamburguesa carne roja grasa comida rápida alto calórico indulgente no saludable"
    Artículos"Guía Docker""Guía Docker contenedores DevOps despliegue infraestructura microservicios tutorial técnico"
    Tickets"Error login""Error login acceso cuenta contraseña autenticación problema técnico usuario bloqueado"

    Caso Especial: Atributos Negativos

    Para búsquedas con criterios de exclusión (ej: "saludable", "barato"), incluye explícitamente los atributos negativos en los items que NO deberían matchear:

    {
      "query": "desayuno saludable energía natural",
      "contents": [
        "Avocado toast proteína huevo omega3 saludable nutritivo energía natural",
        "Hamburguesa queso bacon [GRASA, comida rápida, POCO SALUDABLE, calórico]",
        "Smoothie frutas avena antioxidantes energía natural fibra saludable"
      ]
    }
    

    Resultado: La hamburguesa queda al final porque "poco saludable" aumenta la distancia vectorial con "saludable".

    🌐 Idioma: Mejor Rendimiento en Inglés

    El modelo all-MiniLM-L6-v2 fue entrenado principalmente en inglés. Funciona en español, pero:

    IdiomaPrecisión RelativaRecomendación
    Inglés100% (baseline)✅ Usar siempre que sea posible
    Español~85-90%⚠️ Funciona, pero scores ligeramente menores
    Francés/Alemán~80-85%⚠️ Similar a español
    OtrosVariable❌ No recomendado para producción

    Consejo: Si tu aplicación es multilingüe, considera traducir queries y contents a inglés antes de enviarlos, o usa descriptores bilingües:

    {
      "contents": [
        "Tesla Model 3 electric car coche eléctrico sustainable sostenible eco-friendly ecológico"
      ]
    }
    

    Implementación Práctica: Pre-procesador de Contenidos

    def enriquecer_producto(nombre, categoria, atributos, sinonimos=None):
        """
        Prepara un item para máxima precisión en la API de Recomendaciones.
        
        Args:
            nombre: Nombre del producto ("Tesla Model 3")
            categoria: Categoría principal ("electric car")
            atributos: Lista de características ["sustainable", "eco-friendly", "zero emissions"]
            sinonimos: Términos alternativos opcionales ["green", "clean energy"]
        
        Returns:
            String optimizado para la API
        """
        partes = [nombre, categoria]
        partes.extend(atributos)
        if sinonimos:
            partes.extend(sinonimos)
        return " ".join(partes)
    
    # Ejemplo de uso
    productos_raw = [
        {"nombre": "Tesla Model 3", "categoria": "electric car", 
         "atributos": ["sustainable", "eco-friendly", "zero emissions", "long range"]},
        {"nombre": "BMW X5", "categoria": "SUV gasoline", 
         "atributos": ["powerful", "luxury", "premium", "high consumption"]},
    ]
    
    # Convertir a formato optimizado para la API
    contents_optimizados = [
        enriquecer_producto(**p) for p in productos_raw
    ]
    # Resultado: ["Tesla Model 3 electric car sustainable eco-friendly zero emissions long range", ...]
    

    Resumen: Checklist de Optimización

    Antes de llamar a la API, verifica:

    • ¿Mis contents incluyen más que solo el nombre?
    • ¿He añadido categorías y atributos descriptivos?
    • ¿He incluido sinónimos de términos clave?
    • ¿He marcado atributos negativos explícitamente donde aplique?
    • ¿Estoy usando inglés para máxima precisión? (opcional pero recomendado)

    Conclusión: La "inteligencia" del motor depende de la calidad de tus datos. Datos ricos = resultados precisos. Datos pobres = resultados pobres. No es un fallo de la API, es un requisito de los modelos de embeddings.


    📍 Información General

    PropiedadValor
    URL Basehttps://apisdom.com/api/v1
    MétodoPOST
    AutenticaciónAPI Key (Header X-API-Key)
    Tipo de Créditorecommendation
    Coste por llamada1 crédito
    Modelo IAsentence-transformers/all-MiniLM-L6-v2
    Límite de tokens512 tokens por texto (query y cada content)
    Máximo de items100 contenidos por petición
    Rango de score0.0 (sin relación) a 1.0 (idéntico)

    📊 Headers Informativos

    La API devuelve headers que te permiten controlar tu consumo:

    HeaderDescripción
    X-RateLimit-LimitTu límite de peticiones por minuto
    X-RateLimit-RemainingPeticiones restantes en la ventana actual
    Retry-AfterSegundos a esperar si recibes 429

    ❄️ Cold Start

    Para optimizar costes, los microservicios de IA escalan a cero cuando no hay actividad.

    💡 Nota: La primera petición puede tardar más de lo normal (hasta 20 segundos) mientras el servicio arranca. Las peticiones siguientes serán mucho más rápidas mientras el servicio permanezca activo.

    💳 Planes y Precios

    Consulta los planes disponibles y precios actualizados en: apisdom.com/pricing

    🔐 Autenticación

    Todas las peticiones requieren tu API Key en el header X-API-Key:

    X-API-Key: tu_api_key_aqui
    

    Puedes obtener tu API Key desde el panel de usuario en apisdom.com/dashboard.

    📥 Endpoint: Generar Recomendaciones

    POST https://apisdom.com/api/v1/recommendations
    

    Request Body

    CampoTipoRequeridoDefaultDescripción
    querystring✅ Sí-Texto de búsqueda. Mínimo 3, máximo 500 caracteres.
    contentsstring[]✅ Sí-Lista de contenidos a comparar. Mínimo 1, máximo 100 items. Cada item máximo 1000 caracteres.
    top_kint❔ Opcional5Número de resultados a devolver. Mínimo 1, máximo 20.

    Validaciones Importantes

    • query debe tener entre 3 y 500 caracteres
    • contents debe contener entre 1 y 100 items
    • Cada item en contents no puede exceder 1000 caracteres
    • top_k debe estar entre 1 y 20
    • Los resultados se ordenan por score descendente (mayor similitud primero)

    Ejemplo de Request

    {
      "query": "machine learning tutorials for beginners",
      "contents": [
        "Python ML basics: Linear regression and classification",
        "JavaScript introduction: Variables, functions and loops",
        "Deep Learning with PyTorch: Neural networks from scratch",
        "Data Science with pandas: Data manipulation and analysis",
        "React frontend development: Components and hooks",
        "Natural Language Processing: Text analysis with transformers",
        "SQL databases: Queries and optimization",
        "Docker containerization: Build and deploy applications"
      ],
      "top_k": 5
    }
    

    Response Exitosa (200 OK)

    La respuesta es ANIDADA con dos objetos principales: _metadata y response.

    {
      "_metadata": {
        "provider": "ApisDom",
        "tagline": "Inteligencia artificial transparente",
        "website": "https://apisdom.com",
        "timestamp": "2026-01-26T14:30:00.000Z",
        "service": "Recommendations",
        "channel": "ApisDom Platform",
        "request_id": "rec_1768954704623_xyz789",
        "documentation": "https://apisdom.com/documentacion"
      },
      "response": {
        "results": [
          {
            "item": "Python ML basics: Linear regression and classification",
            "score": 0.8947,
            "rank": 1
          },
          {
            "item": "Deep Learning with PyTorch: Neural networks from scratch",
            "score": 0.8523,
            "rank": 2
          },
          {
            "item": "Natural Language Processing: Text analysis with transformers",
            "score": 0.7891,
            "rank": 3
          },
          {
            "item": "Data Science with pandas: Data manipulation and analysis",
            "score": 0.7234,
            "rank": 4
          },
          {
            "item": "SQL databases: Queries and optimization",
            "score": 0.4512,
            "rank": 5
          }
        ],
        "warning": null,
        "info_message": null
      }
    }
    

    Campos de la Respuesta

    Nivel Raíz:

    CampoTipoDescripción
    _metadataobjectInformación del proveedor y tracking de la petición
    responseobjectEl resultado de las recomendaciones

    Dentro de _metadata:

    CampoTipoDescripción
    providerstringSiempre "ApisDom"
    timestampstringTimestamp ISO 8601 de la petición
    request_idstringIdentificador único de esta petición
    servicestringNombre del servicio ("Recommendations")
    channelstringCanal de distribución ("ApisDom Platform")

    Dentro de response:

    CampoTipoDescripción
    resultsarrayLista de recomendaciones ordenadas por relevancia (score descendente)
    results[].itemstringEl contenido recomendado (uno de los items de contents)
    results[].scorefloatScore de similitud semántica (0.0 a 1.0). Cuanto más cercano a 1, mayor similitud.
    results[].rankintPosición en el ranking (1 = más relevante)
    warningstring | nullAviso si el query fue truncado (textos muy largos)
    info_messagestring | nullInformación adicional sobre el análisis (ej: cold start para free tier)

    Interpretando los Scores

    Los scores representan similitud semántica mediante cosine similarity entre embeddings:

    ScoreInterpretaciónEjemplo
    0.85 - 1.0Alta similitud semánticaTextos casi idénticos o muy relacionados
    0.70 - 0.85Similitud moderada-altaMismo tema con enfoques diferentes
    0.50 - 0.70Similitud moderadaTemas relacionados pero distintos
    0.30 - 0.50Similitud bajaPocas palabras clave en común
    0.0 - 0.30Sin relación claraTemas completamente diferentes

    💻 Ejemplos de Código

    Python

    import requests
    
    API_URL = "https://apisdom.com/api/v1/recommendations"
    API_KEY = "tu_api_key_aqui"
    
    def obtener_recomendaciones(query, contents, top_k=5):
        """
        Genera recomendaciones basadas en similitud semántica.
        
        Args:
            query: Texto de búsqueda (3-500 caracteres)
            contents: Lista de contenidos a comparar (1-100 items, máx 1000 chars/item)
            top_k: Número de resultados (1-20, default: 5)
        
        Returns:
            dict con _metadata y response conteniendo results ordenados por score
        """
        response = requests.post(
            API_URL,
            headers={
                "X-API-Key": API_KEY,
                "Content-Type": "application/json"
            },
            json={
                "query": query,
                "contents": contents,
                "top_k": top_k
            }
        )
        
        if response.status_code == 200:
            return response.json()
        elif response.status_code == 402:
            raise Exception("Sin créditos. Consulta planes en apisdom.com/pricing")
        else:
            raise Exception(f"Error: {response.status_code} - {response.text}")
    
    # Ejemplo de uso: Motor de búsqueda de productos
    productos = [
        "Laptop gaming RGB 16GB RAM NVIDIA RTX 4060",
        "Mouse inalámbrico ergonómico recargable",
        "Teclado mecánico retroiluminado switches azules",
        "Monitor 4K 27 pulgadas HDR 144Hz",
        "Auriculares Bluetooth cancelación de ruido",
        "SSD 1TB NVMe Gen4 alta velocidad",
        "Webcam Full HD 1080p con micrófono",
        "Silla ergonómica para oficina ajustable"
    ]
    
    resultado = obtener_recomendaciones(
        query="laptop para gaming con buena gráfica",
        contents=productos,
        top_k=3
    )
    
    # IMPORTANTE: Acceder a los datos dentro del objeto 'response'
    datos = resultado['response']
    print("🎯 Productos recomendados:\n")
    for rec in datos['results']:
        print(f"{rec['rank']}. {rec['item']}")
        print(f"   Similitud: {rec['score']:.2%}\n")
    
    # Output:
    # 🎯 Productos recomendados:
    # 
    # 1. Laptop gaming RGB 16GB RAM NVIDIA RTX 4060
    #    Similitud: 91.23%
    # 
    # 2. Monitor 4K 27 pulgadas HDR 144Hz
    #    Similitud: 68.45%
    # 
    # 3. SSD 1TB NVMe Gen4 alta velocidad
    #    Similitud: 52.18%
    

    JavaScript / Node.js

    const API_URL = 'https://apisdom.com/api/v1/recommendations';
    const API_KEY = 'tu_api_key_aqui';
    
    async function obtenerRecomendaciones(query, contents, topK = 5) {
      /**
       * Genera recomendaciones basadas en similitud semántica.
       * @param {string} query - Texto de búsqueda (3-500 caracteres)
       * @param {string[]} contents - Lista de contenidos (1-100 items)
       * @param {number} topK - Número de resultados (1-20, default: 5)
       * @returns {Promise<Object>} - Resultado con _metadata y response
       */
      const response = await fetch(API_URL, {
        method: 'POST',
        headers: {
          'X-API-Key': API_KEY,
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({
          query: query,
          contents: contents,
          top_k: topK
        })
      });
    
      if (response.status === 402) {
        throw new Error('Sin créditos. Consulta planes en apisdom.com/pricing');
      }
    
      if (!response.ok) {
        throw new Error(`Error: ${response.status}`);
      }
    
      return response.json();
    }
    
    // Ejemplo de uso: Búsqueda de artículos relacionados
    const articulos = [
      'Introducción a React Hooks y Context API',
      'Python para Data Science: NumPy y pandas',
      'Arquitectura de microservicios con Docker',
      'Machine Learning: Regresión lineal explicada',
      'Vue.js 3 Composition API: Guía completa',
      'Diseño de APIs RESTful con buenas prácticas',
      'TensorFlow vs PyTorch: Comparativa 2026'
    ];
    
    obtenerRecomendaciones('tutorial de react para frontend', articulos, 3)
      .then(resultado => {
        // IMPORTANTE: Acceder al objeto 'response' anidado
        const datos = resultado.response;
        console.log('📚 Artículos recomendados:\n');
        datos.results.forEach(rec => {
          console.log(`${rec.rank}. ${rec.item}`);
          console.log(`   Relevancia: ${(rec.score * 100).toFixed(2)}%\n`);
        });
        
        // Output:
        // 📚 Artículos recomendados:
        // 
        // 1. Introducción a React Hooks y Context API
        //    Relevancia: 88.45%
        // 
        // 2. Vue.js 3 Composition API: Guía completa
        //    Relevancia: 71.23%
        // 
        // 3. Diseño de APIs RESTful con buenas prácticas
        //    Relevancia: 54.67%
      })
      .catch(console.error);
    

    cURL

    curl -X POST "https://apisdom.com/api/v1/recommendations" \
      -H "X-API-Key: tu_api_key_aqui" \
      -H "Content-Type: application/json" \
      -d '{
        "query": "python machine learning",
        "contents": [
          "Python ML tutorial with scikit-learn",
          "JavaScript basics for beginners",
          "Deep Learning with TensorFlow",
          "SQL database optimization"
        ],
        "top_k": 3
      }'
    
    # La respuesta incluye _metadata y response anidados:
    # {
    #   "_metadata": { "provider": "ApisDom", "request_id": "...", ... },
    #   "response": {
    #     "results": [
    #       {"item": "Python ML tutorial with scikit-learn", "score": 0.92, "rank": 1},
    #       {"item": "Deep Learning with TensorFlow", "score": 0.78, "rank": 2},
    #       {"item": "SQL database optimization", "score": 0.35, "rank": 3}
    #     ],
    #     "warning": null,
    #     "info_message": null
    #   }
    # }
    

    PHP

    <?php
    $api_url = 'https://apisdom.com/api/v1/recommendations';
    $api_key = 'tu_api_key_aqui';
    
    function obtenerRecomendaciones($query, $contents, $top_k = 5) {
        global $api_url, $api_key;
        
        $ch = curl_init($api_url);
        curl_setopt_array($ch, [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_POST => true,
            CURLOPT_HTTPHEADER => [
                'X-API-Key: ' . $api_key,
                'Content-Type: application/json'
            ],
            CURLOPT_POSTFIELDS => json_encode([
                'query' => $query,
                'contents' => $contents,
                'top_k' => $top_k
            ])
        ]);
        
        $response = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);
        
        if ($httpCode === 402) {
            throw new Exception('Sin créditos. Consulta planes en apisdom.com/pricing');
        }
        
        return json_decode($response, true);
    }
    
    // Ejemplo de uso: Búsqueda de documentos
    $documentos = [
        'Manual de usuario: Configuración inicial del sistema',
        'Guía de solución de problemas comunes',
        'Tutorial: Integración con APIs externas',
        'Referencia técnica: Comandos de consola',
        'FAQ: Preguntas frecuentes sobre facturación'
    ];
    
    $resultado = obtenerRecomendaciones(
        'cómo configurar el sistema por primera vez',
        $documentos,
        3
    );
    
    // IMPORTANTE: Acceder al objeto 'response' anidado
    $datos = $resultado['response'];
    echo "📖 Documentos relevantes:\n\n";
    foreach ($datos['results'] as $rec) {
        echo $rec['rank'] . ". " . $rec['item'] . "\n";
        echo "   Score: " . number_format($rec['score'] * 100, 2) . "%\n\n";
    }
    
    // Output:
    // 📖 Documentos relevantes:
    // 
    // 1. Manual de usuario: Configuración inicial del sistema
    //    Score: 89.34%
    // 
    // 2. Guía de solución de problemas comunes
    //    Score: 62.18%
    // 
    // 3. Tutorial: Integración con APIs externas
    //    Score: 48.91%
    ?>
    

    C# / .NET

    using System.Net.Http;
    using System.Text;
    using System.Text.Json;
    using System.Text.Json.Serialization;
    
    public class RecommendationsApiClient
    {
        private readonly HttpClient _client;
        private const string API_URL = "https://apisdom.com/api/v1/recommendations";
    
        public RecommendationsApiClient(string apiKey)
        {
            _client = new HttpClient();
            _client.DefaultRequestHeaders.Add("X-API-Key", apiKey);
        }
    
        public async Task<RecommendationsApiResponse> ObtenerRecomendacionesAsync(
            string query, 
            List<string> contents, 
            int topK = 5)
        {
            var content = new StringContent(
                JsonSerializer.Serialize(new { 
                    query = query, 
                    contents = contents, 
                    top_k = topK 
                }),
                Encoding.UTF8,
                "application/json"
            );
    
            var response = await _client.PostAsync(API_URL, content);
    
            if (response.StatusCode == System.Net.HttpStatusCode.PaymentRequired)
            {
                throw new Exception("Sin créditos. Consulta planes en apisdom.com/pricing");
            }
    
            response.EnsureSuccessStatusCode();
            
            var json = await response.Content.ReadAsStringAsync();
            return JsonSerializer.Deserialize<RecommendationsApiResponse>(json);
        }
    }
    
    // IMPORTANTE: La respuesta es ANIDADA - necesitas estas clases
    public class RecommendationsApiResponse
    {
        [JsonPropertyName("_metadata")]
        public RecommendationsMetadata Metadata { get; set; }
        
        [JsonPropertyName("response")]
        public RecommendationsResult Response { get; set; }
    }
    
    public class RecommendationsMetadata
    {
        [JsonPropertyName("provider")]
        public string Provider { get; set; }
        
        [JsonPropertyName("request_id")]
        public string RequestId { get; set; }
        
        [JsonPropertyName("timestamp")]
        public string Timestamp { get; set; }
        
        [JsonPropertyName("service")]
        public string Service { get; set; }
    }
    
    public class RecommendationsResult
    {
        [JsonPropertyName("results")]
        public List<RecommendationItem> Results { get; set; }
        
        [JsonPropertyName("warning")]
        public string? Warning { get; set; }
        
        [JsonPropertyName("info_message")]
        public string? InfoMessage { get; set; }
    }
    
    public class RecommendationItem
    {
        [JsonPropertyName("item")]
        public string Item { get; set; }
        
        [JsonPropertyName("score")]
        public double Score { get; set; }
        
        [JsonPropertyName("rank")]
        public int Rank { get; set; }
    }
    
    // Ejemplo de uso
    var client = new RecommendationsApiClient("tu_api_key_aqui");
    
    var cursos = new List<string>
    {
        "C# avanzado: LINQ y programación asíncrona",
        "Python para principiantes: Sintaxis básica",
        "ASP.NET Core Web API: RESTful services",
        "JavaScript moderno: ES6+ características",
        "Blazor WebAssembly: Frontend con C#",
        "Entity Framework Core: ORM y migraciones"
    };
    
    var apiResponse = await client.ObtenerRecomendacionesAsync(
        "curso de c# para backend",
        cursos,
        3
    );
    
    // Acceder a los datos dentro del objeto Response
    Console.WriteLine("🎓 Cursos recomendados:\n");
    foreach (var rec in apiResponse.Response.Results)
    {
        Console.WriteLine($"{rec.Rank}. {rec.Item}");
        Console.WriteLine($"   Similitud: {rec.Score:P2}\n");
    }
    
    // Output:
    // 🎓 Cursos recomendados:
    // 
    // 1. C# avanzado: LINQ y programación asíncrona
    //    Similitud: 87.65%
    // 
    // 2. ASP.NET Core Web API: RESTful services
    //    Similitud: 82.34%
    // 
    // 3. Blazor WebAssembly: Frontend con C#
    //    Similitud: 71.23%
    

    📊 Casos de Uso Prácticos

    1. Motor de Búsqueda Semántica en E-commerce

    productos = [
        "Smartphone Samsung Galaxy S24 5G 256GB",
        "iPhone 15 Pro Max 512GB Titanio",
        "Laptop Dell XPS 15 Intel i7 16GB",
        "Tablet iPad Pro 12.9 M2 WiFi",
        "Smartwatch Apple Watch Series 9 GPS",
        "Auriculares Sony WH-1000XM5 Bluetooth",
        "Cámara Canon EOS R6 mirrorless",
        "Monitor LG UltraWide 34 pulgadas"
    ]
    
    # Usuario busca con lenguaje natural
    busqueda_usuario = "teléfono móvil apple con mucha memoria"
    
    api_result = obtener_recomendaciones(busqueda_usuario, productos, 3)
    datos = api_result['response']
    
    print("🛍️ Productos que coinciden con tu búsqueda:\n")
    for rec in datos['results']:
        print(f"{rec['rank']}. {rec['item']}")
        print(f"   Relevancia: {rec['score']:.2%}\n")
    
    # Output:
    # 🛍️ Productos que coinciden con tu búsqueda:
    # 
    # 1. iPhone 15 Pro Max 512GB Titanio
    #    Relevancia: 88.92%
    # 
    # 2. Smartwatch Apple Watch Series 9 GPS
    #    Relevancia: 61.45%
    # 
    # 3. Smartphone Samsung Galaxy S24 5G 256GB
    #    Relevancia: 59.23%
    

    2. Sistema de Recomendación de Artículos Relacionados

    async function mostrarArticulosRelacionados(articuloActual, todosLosArticulos) {
      // Filtrar artículo actual para no recomendarlo a sí mismo
      const otrosArticulos = todosLosArticulos.filter(a => a !== articuloActual);
      
      const apiResult = await obtenerRecomendaciones(
        articuloActual,
        otrosArticulos,
        5
      );
      
      // Extraer el objeto 'response' de la API
      const datos = apiResult.response;
      
      // Filtrar solo artículos con score > 0.6 (similitud moderada-alta)
      const relevantes = datos.results.filter(r => r.score > 0.6);
      
      console.log('📰 Artículos relacionados que te pueden interesar:\n');
      relevantes.forEach(rec => {
        console.log(`• ${rec.item}`);
        console.log(`  Similitud: ${(rec.score * 100).toFixed(1)}%\n`);
      });
      
      return relevantes;
    }
    
    // Ejemplo
    const articulos = [
      'Cómo configurar Docker en producción paso a paso',
      'Introducción a Kubernetes para principiantes',
      'Recetas saludables para el desayuno',
      'CI/CD con GitHub Actions y despliegue automático',
      'Arquitectura de microservicios: Patrones y antipatrones',
      'Guía de viajes: Mejores destinos 2026'
    ];
    
    mostrarArticulosRelacionados(
      'Guía completa de contenedores Docker',
      articulos
    );
    
    // Output:
    // 📰 Artículos relacionados que te pueden interesar:
    // 
    // • Cómo configurar Docker en producción paso a paso
    //   Similitud: 89.3%
    // 
    // • Introducción a Kubernetes para principiantes
    //   Similitud: 76.8%
    // 
    // • Arquitectura de microservicios: Patrones y antipatrones
    //   Similitud: 68.2%
    // 
    // • CI/CD con GitHub Actions y despliegue automático
    //   Similitud: 61.5%
    

    3. Clasificación Automática de Tickets de Soporte

    def clasificar_ticket(descripcion, categorias_disponibles):
        """Asigna automáticamente un ticket a la categoría más relevante."""
        api_result = obtener_recomendaciones(
            query=descripcion,
            contents=categorias_disponibles,
            top_k=1
        )
        
        datos = api_result['response']
        mejor_categoria = datos['results'][0]
        
        # Determinar confianza de la clasificación
        if mejor_categoria['score'] > 0.75:
            confianza = "Alta"
        elif mejor_categoria['score'] > 0.50:
            confianza = "Media"
        else:
            confianza = "Baja - Requiere revisión manual"
        
        return {
            'categoria': mejor_categoria['item'],
            'score': mejor_categoria['score'],
            'confianza': confianza
        }
    
    # Categorías del sistema
    categorias = [
        "Facturación y pagos",
        "Problemas técnicos de login",
        "Consultas sobre funcionalidades",
        "Solicitud de cancelación o reembolso",
        "Errores en la aplicación móvil",
        "Integración con APIs"
    ]
    
    # Ticket de usuario
    ticket = "No puedo acceder a mi cuenta, me dice que la contraseña es incorrecta"
    
    resultado = clasificar_ticket(ticket, categorias)
    print(f"📋 Categoría asignada: {resultado['categoria']}")
    print(f"🎯 Confianza: {resultado['confianza']} ({resultado['score']:.0%})")
    
    # Output:
    # 📋 Categoría asignada: Problemas técnicos de login
    # 🎯 Confianza: Alta (91%)
    

    ⚠️ Códigos de Error

    CódigoSignificadoSolución
    400Parámetros inválidos (query muy corto, contents vacío, top_k fuera de rango)Verifica límites: query 3-500 chars, contents 1-100 items, top_k 1-20
    401API Key inválidaVerifica tu API Key en el dashboard
    402Sin créditos disponiblesConsulta planes en apisdom.com/pricing
    429Límite de peticiones excedidoEspera antes de reintentar (ver headers de rate limit)
    500Error interno del servidorReintenta en unos segundos. Si persiste, contacta soporte

    🔬 Transparencia Técnica

    Política de ApisDom: Creemos que los desarrolladores merecen saber exactamente cómo funcionan las APIs que usan. Esta sección documenta los detalles técnicos verificados directamente del código fuente.

    Cómo Funciona Internamente

    Tu query + contents → Sentence Embeddings → Cosine Similarity → Ranking → Response
                         ↓                     ↓                    ↓
                         all-MiniLM-L6-v2     Scores 0.0-1.0      Top-K ordenado
                         (384 dimensiones)    (asyncio)           (descendente)
    

    Detalles Verificados del Código

    AspectoValor RealArchivo Fuente
    Modelosentence-transformers/all-MiniLM-L6-v2ml_service.py
    Dimensiones384 (embedding vector size)Arquitectura del modelo
    SimilitudCosine similarity entre embeddingsml_service.py línea 78
    Límite de tokens512 tokens por texto (query y contents)ml_service.py línea 64
    EjecuciónAsyncio (no bloquea)recommendations.py línea 74
    OrdenamientoScore descendente (mayor a menor)ml_service.py línea 89

    Proceso de Recomendación Paso a Paso

    1. Tokenización: Tu query y cada content se convierten en tokens (máx 512)
    2. Embedding: El modelo genera un vector de 384 dimensiones para cada texto
    3. Similitud: Se calcula cosine similarity entre el embedding de query y cada content
    4. Ranking: Los contenidos se ordenan por score (descendente)
    5. Top-K: Se devuelven los top_k resultados más relevantes

    ⚠️ Sobre Truncamiento

    Si tu query o algún content excede 512 tokens (~380 palabras), será truncado automáticamente. Recibirás un warning en la respuesta indicando que el análisis es parcial.

    Recomendación: Para textos largos, extrae los párrafos o secciones más relevantes antes de enviarlos a la API.


    📝 Notas Importantes

    Sobre el Modelo all-MiniLM-L6-v2

    El modelo sentence-transformers/all-MiniLM-L6-v2 está optimizado para inglés y ofrece un excelente balance entre precisión y velocidad.

    Características:

    • ✅ Compacto: 384 dimensiones (vs 768 en BERT completo)✅ Compacto: 384 dimensiones (vs 768 en BERT completo)
    • ✅ Rápido: ~10-50ms por embedding
    • ✅ Multilingual limitado: Funciona con español, francés, alemán pero con menor precisión que inglés
    • ✅ Pre-entrenado: No requiere fine-tuning adicional

    Limitaciones conocidas:

    • El modelo captura similitud semántica, NO coincidencia exacta de palabras
    • Jerga técnica muy específica puede no generalizarse bien
    • Funciona mejor con oraciones completas que con keywords aisladas
    • El truncamiento a 512 tokens puede afectar textos muy largos

    Mejores casos de uso:

    • Búsqueda semántica (usuarios buscan con lenguaje natural)
    • Sistemas de recomendación de contenido
    • Clasificación de documentos por tema
    • Detección de duplicados o contenido similar

    No recomendado para:

    • Búsqueda exacta de palabras clave (usa búsqueda tradicional)
    • Comparación de código fuente (requiere modelos especializados como CodeBERT)
    • Matching de entidades nombradas exactas (nombres propios, IDs, etc.)

    Sobre los Scores de Similitud

    Los scores representan cosine similarity entre vectores de embedding:

    • 1.0 = Vectores idénticos (textos iguales o casi idénticos)
    • 0.0 = Vectores ortogonales (sin relación semántica)
    • Los scores NO son porcentajes de probabilidad, sino medidas de similitud geométrica
    • Un score de 0.85+ indica alta relevancia semántica
    • Un score de 0.50-0.70 indica temas relacionados pero distintos

    Importante: Los scores son relativos. Un score de 0.60 puede ser muy bueno si los contenidos son diversos, o bajo si todos los contenidos son similares.

    Sobre el Tiempo de Respuesta

    Depende del número de contents:

    • 1-10 items: ~100-300ms
    • 10-50 items: ~300-800ms
    • 50-100 items: ~800-1500ms
    • Primera petición tras inactividad: ~20 segundos (cold start)

    💬 ¿Necesitas Ayuda?

    📧 soporte@apisdom.com


    ⚖️ Aviso Legal

    Nota de Transparencia: Esta API utiliza el modelo de código abierto sentence-transformers/all-MiniLM-L6-v2 de Hugging Face. Los resultados reflejan las capacidades y limitaciones inherentes del modelo, el cual fue optimizado para similitud semántica en inglés. ApisDom no modifica ni re-entrena el modelo base; proporcionamos una infraestructura optimizada para su consumo vía API.

    Los scores de similitud (0.0-1.0) representan distancia geométrica entre embeddings, no precisión absoluta. Para aplicaciones críticas, se recomienda validación manual de los resultados, especialmente cuando los scores sean menores a 0.70.


    Última actualización de documentación: Enero 2026

    ApisDom · Inteligencia Artificial Transparente

    Esta sección documenta las validaciones de calidad ejecutadas en el entorno de producción de ApisDom. Incluye verificación de estado de los microservicios y análisis estático del código backend.


    🩺 Estado de los Servicios en Producción

    El panel de administración de ApisDom incluye monitorización en tiempo real de todos los microservicios de IA.

    Panel de Estado de Servicios ApisDom

    Dashboard de monitorización mostrando los 4 microservicios en estado HEALTHY: Sentiment API (38ms), Moderation API (35ms), Prediction API (39ms) y MaaS (38ms). Cada servicio muestra su endpoint de health check y la respuesta JSON con el estado del modelo cargado.

    Servicios Monitorizados

    ServicioEstadoLatenciaModelo
    Sentiment API🟢 HEALTHY38msDistilBERT (SST-2)
    Moderation API🟢 HEALTHY35msToxic-BERT
    Prediction API🟢 HEALTHY39msChronos-2 (Amazon)
    Recommendations API🟢 HEALTHY42msall-MiniLM-L6-v2
    MaaS🟢 HEALTHY38msv1.0.0 (production)

    Health Check Response

    Cada servicio expone un endpoint de health check que devuelve:

    {
      "status": "healthy",
      "model_loaded": true,
      "service": "sentiment"
    }
    

    🔑 Comprobador de APIs

    El panel de usuario de ApisDom incluye un Comprobador de APIs que permite verificar la conexión y validez de tu API Key con cualquiera de nuestros microservicios antes de integrarlos en tu aplicación.

    Comprobador de API de Predicciones en ApisDom

    Comprobador de APIs de ApisDom verificando el servicio de Predicciones. La interfaz muestra tres paneles: respuesta JSON del microservicio con predicciones de series temporales, validador de API Key confirmando autenticación exitosa, y ejemplo de código Python listo para integración.

    Funcionalidades del Comprobador

    FunciónDescripción
    Comprobar SaludVerifica que el microservicio está operativo (gratuito)
    Validar API KeyConfirma que tu clave es válida y tiene créditos disponibles (consume 1 crédito)
    Ver Respuesta JSONMuestra la respuesta completa del microservicio con metadatos
    Ver Código PythonGenera código de ejemplo listo para copiar e integrar

    🔍 Validación de Calidad del Código

    Para garantizar la estabilidad y seguridad de los microservicios, el código se somete a validaciones automatizadas antes de cada despliegue.

    Terminal mostrando validación MyPy y Ruff

    Terminal de desarrollo ejecutando MyPy (verificación de tipado estricto) y Ruff (linter de calidad y seguridad) sobre todos los paquetes del monorepo. Todos los checks pasan sin errores.

    Herramientas Utilizadas

    HerramientaPropósitoConfiguración
    MyPyVerificación de tipado estático--strict --ignore-missing-imports
    RuffLinter de calidad y seguridad--select=E,W,F,B,C90,I,N,UP,S

    Resultados de Validación

    MyPy - Tipado Estricto

    PaqueteArchivosEstado
    packages/sentiment-api11 source files✅ Success: no issues found
    packages/moderation-api11 source files✅ Success: no issues found
    packages/prediction-api11 source files✅ Success: no issues found
    packages/api-core23 source files✅ Success: no issues found
    packages/maas11 source files✅ Success: no issues found

    Ruff - Calidad y Seguridad

    PaqueteReglas VerificadasEstado
    packages/sentiment-apiE, W, F, B, C90, I, N, UP, S✅ All checks passed
    packages/moderation-apiE, W, F, B, C90, I, N, UP, S✅ All checks passed
    packages/prediction-apiE, W, F, B, C90, I, N, UP, S✅ All checks passed
    packages/api-coreE, W, F, B, C90, I, N, UP, S✅ All checks passed
    packages/maasE, W, F, B, C90, I, N, UP, S✅ All checks passed

    Reglas de Ruff Aplicadas

    CódigoCategoríaDescripción
    Epycodestyle errorsErrores de estilo PEP 8
    Wpycodestyle warningsAdvertencias de estilo
    FPyflakesErrores lógicos y variables no usadas
    Bflake8-bugbearBugs potenciales y malas prácticas
    C90mccabeComplejidad ciclomática
    IisortOrdenación de imports
    Npep8-namingConvenciones de nombres
    UPpyupgradeModernización de sintaxis Python
    Sflake8-banditVulnerabilidades de seguridad

    🔐 Nota de Seguridad

    ⚠️ Importante: En los ejemplos de código de la documentación, sustituya siempre tu_api_key_aqui por su clave privada obtenida en el panel de administración. Nunca comparta su API Key públicamente.


    📊 Métricas de Calidad

    MétricaValorObjetivo
    Cobertura de tipos100%≥ 95%
    Errores de linting00
    Vulnerabilidades de seguridad00
    Complejidad ciclomáticaDentro de límites≤ 10 por función
    Tiempo de respuesta (p95)< 500ms< 1000ms

    Última actualización: Enero 2026

    🎬 Studio — Ingeniería de Contenidos con IA

    Aplicación propia de ApisDom · https://studio.apisdom.com


    ¿Qué es Studio?

    Studio es una aplicación independiente desarrollada por ApisDom para la generación de contenido documentado con inteligencia artificial. A diferencia de las APIs de microservicio de ApisDom (Sentiment, Moderation, Prediction, Recommendations), Studio es una plataforma completa con interfaz web propia y también dispone de API para desarrolladores.

    No es un wrapper de chatbot. Studio utiliza un motor propio con arquitectura de ApisDom y navegación web integrada. La investigación ocurre en tiempo real sobre fuentes abiertas.


    Filosofía: hechos, no alucinaciones

    Sin StudioCon Studio
    Confianza"Creo que este dato es correcto""Sé que es correcto porque tengo el enlace"
    Respaldo"Me suena bien el texto""El texto está respaldado por 3 fuentes"
    Verificación"Voy a buscar en Google si es verdad""Ya viene verificado de serie"
    Publicación"¿Puedo publicar esto sin miedo?""Sabes exactamente el origen de la info"
    Transparencia"¿Se lo habrá inventado la IA?""Ves si es dato real o cita textual"
    Eficiencia"Tardo una hora en corregir""Decides con el borrador ya hecho"

    Características principales

    🔍 Investigación web inteligente

    El algoritmo analiza decenas de fuentes y descarta el ruido. El contenido generado incluye citas de las fuentes reales de donde sale la información. Si la información es contradictoria, lo verás. Si no hay consenso, lo sabrás.

    📝 Más de 15 tipos de contenido

    article · blog_post · tutorial · newsletter · social_post · product_description · press_release · documentation · landing_page · case_study · comparison · listicle · faq · how_to · opinion

    🚦 Semáforo de calidad del briefing

    Antes de generar, el sistema evalúa la calidad de tu entrada con un semáforo visual en tiempo real:

    ColorSignificado
    🟢 VerdeContexto detallado, fuentes específicas — resultado óptimo
    🟡 AmarilloContexto básico — resultados más genéricos
    🔴 RojoContexto insuficiente — generación bloqueada para proteger tus créditos

    🔎 Optimización SEO

    Metaetiquetas, Open Graph, datos estructurados, segmentación por palabras clave y tabla de contenidos navegable. Todo incluido en la generación.

    📱 Adaptación a redes sociales

    Transforma cada artículo en publicaciones optimizadas para 7 plataformas:

    • Facebook
    • Instagram
    • X (Twitter)
    • LinkedIn
    • TikTok
    • YouTube
    • Pinterest

    Cada adaptación respeta los límites de caracteres de la plataforma e incluye hashtags, emojis y llamadas a la acción opcionales.

    📂 Historial completo

    Cada artículo generado queda guardado con fecha, tema y recuento de palabras. Busca, descarga o reutiliza cualquier documento en cualquier momento.

    👤 Perfiles de generación

    Guarda y reutiliza configuraciones personalizadas: tono, idioma, audiencia, guía de estilo, voz de marca, palabras prohibidas y preferencias SEO, para un resultado consistente.

    🌐 Idiomas soportados

    • Español (es)
    • Inglés (en)

    Secciones de la aplicación

    SecciónDescripción
    GenerarMotor principal de creación de contenido con IA y fuentes verificadas
    PreciosPacks de créditos desde 9€ (10 créditos) hasta 499€ (1.000 créditos). IVA incluido
    BlogBlog de la plataforma con artículos sobre ingeniería de contenidos
    HistorialTodos los documentos generados, con búsqueda, descarga y reutilización
    AdminPanel de administración de la cuenta
    PerfilGestión de datos de usuario y configuración

    Modelo de precios

    • Sin cuotas mensuales — Esto no es una suscripción
    • Créditos sin caducidad — Los créditos comprados se quedan en tu monedero y los usas cuando tú decidas
    • 3 generaciones gratis al registrarte (sin tarjeta requerida)
    • Transparencia total — Sin costes ocultos, sin letras pequeñas
    PackCréditosPrecio (IVA incl.)
    Básico109 €
    .........
    Máximo1.000499 €

    Consulta todos los packs en: studio.apisdom.com/pricing


    Privacidad y cumplimiento

    • Servidores en Europa
    • Cumplimiento GDPR
    • Tus textos no se comparten ni se venden
    • Puedes eliminar todo el historial en cualquier momento desde tu cuenta

    Casos de uso reales

    • Agencias que entregan artículos documentados a clientes
    • Freelancers que multiplican su producción con rigor
    • Empresas que mantienen un blog con datos contrastados
    • Profesionales que necesitan datos verificados, no relleno

    API para desarrolladores

    Studio expone una API REST completa para integrar la generación de contenido en tus propios proyectos.

    Base URL

    https://studio.apisdom.com/api
    

    Autenticación

    Todos los endpoints (excepto GET /v1/platforms) requieren un token Bearer:

    Authorization: Bearer <tu-api-key>
    

    Endpoints principales

    MétodoEndpointDescripción
    POST/v1/generateGenera contenido optimizado para SEO. Soporta modo estándar y streaming (SSE)
    GET/v1/platformsLista las plataformas de redes sociales soportadas y sus límites (público, sin auth)
    POST/v1/adaptAdapta contenido a una o más plataformas de redes sociales
    POST/v1/profilesCrea un nuevo perfil de generación
    GET/v1/profilesLista todos los perfiles del usuario
    GET/v1/profiles/{profile_id}Obtiene la configuración completa de un perfil
    PUT/v1/profiles/{profile_id}Actualiza un perfil existente (parcial)
    DELETE/v1/profiles/{profile_id}Elimina un perfil permanentemente

    Ejemplo rápido — Generar un artículo

    curl -X POST "https://studio.apisdom.com/api/v1/generate" \
      -H "Authorization: Bearer TU_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "topic": "Cómo optimizar tu sitio web para Core Web Vitals",
        "language": "es",
        "content_type": "article",
        "parameters": {
          "length": "medium",
          "tone": "professional",
          "audience": "general",
          "include_sources": true,
          "include_meta_seo": true,
          "output_format": "html"
        }
      }'
    

    Ejemplo — Adaptar contenido a redes sociales

    curl -X POST "https://studio.apisdom.com/api/v1/adapt" \
      -H "Authorization: Bearer TU_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "content_id": "id_del_contenido_generado",
        "platforms": ["instagram", "linkedin", "x"],
        "language": "es",
        "include_hashtags": true,
        "max_hashtags": 5,
        "include_emoji": true,
        "include_cta": true
      }'
    

    Parámetros de generación disponibles

    ParámetroTipoDescripciónDefault
    topicstringTema principal del contenido (obligatorio)—
    languagees | enIdioma del contenido generadoen
    content_typestringTipo de contenido (15+ opciones)article
    streambooleanStreaming en tiempo real vía SSEfalse
    profile_idstringID de perfil guardado a aplicarnull
    parameters.lengthshort | medium | long | extra_longLongitud del contenidomedium
    parameters.tonestringTono de escritura (10 opciones)professional
    parameters.audiencestringNivel del público objetivogeneral
    parameters.include_sourcesbooleanIncluir citas de fuentestrue
    parameters.include_qabooleanIncluir sección de preguntas y respuestasfalse
    parameters.include_meta_seobooleanGenerar metadatos SEOtrue
    parameters.include_codebooleanIncluir bloques de códigofalse
    parameters.output_formathtml | markdown | plain_textFormato de salidahtml
    parameters.max_sourcesinteger (1-20)Máximo de fuentes a citar5

    Tonos disponibles

    professional · casual · academic · conversational · persuasive · technical · journalistic · educational · inspirational · authoritative

    Códigos de error

    CódigoSignificado
    400Parámetros no válidos
    401Token de autenticación faltante o no válido
    403Permisos insuficientes
    404Recurso no encontrado
    422Error de validación
    502Error en la generación de IA

    Documentación OpenAPI completa

    La especificación OpenAPI 3.1 está disponible para importar en Postman, Swagger UI u otras herramientas:

    📖 Documentación completa de la API de Studio

    📘 Referencia interactiva (ReDoc)


    Relación con el ecosistema ApisDom

    Studio es una aplicación independiente dentro del ecosistema de ApisDom. Mientras que las APIs de microservicio de ApisDom (Sentiment, Moderation, Prediction, Recommendations) son servicios que se consumen exclusivamente vía API, Studio funciona como:

    1. Aplicación web completa — Interfaz propia con registro, dashboard, historial y generación visual
    2. API para desarrolladores — Endpoints REST documentados para integración programática
    3. Motor propio — Arquitectura propietaria de ApisDom con Claude (Anthropic) y navegación web integrada

    Comparativa con las APIs de ApisDom

    CaracterísticaAPIs ApisDom (Sentiment, Moderation, etc.)Studio
    TipoMicroservicio API puroAplicación web + API
    AccesoSolo vía API (código)Web + API
    Modelo IADistilBERT, Toxic-BERT, Chronos-2, MiniLMClaude (Anthropic)
    FunciónAnálisis y predicciónGeneración de contenido
    AuthHeader X-API-KeyBearer Token
    URLapisdom.com/api/v1/...studio.apisdom.com/api/v1/...
    CréditosSistema unificado ApisDomSistema propio de Studio

    Enlaces

    RecursoURL
    🌐 Aplicaciónstudio.apisdom.com
    📖 Documentación APIstudio.apisdom.com/docs
    💰 Preciosstudio.apisdom.com/pricing
    📰 Blogstudio.apisdom.com/blog
    🏠 ApisDom (plataforma principal)apisdom.com
    📚 Documentación APIs ApisDomapisdom.com/documentacion

    Redes sociales

    • TikTok
    • Facebook
    • Instagram

    📧 Contacto: soporte@apisdom.com


    © 2026 Studio by ApisDom. Todos los derechos reservados. Desarrollado por ApisDom · Ingeniería de Contenidos

    ApisDom · Inteligencia Artificial Transparente

    Esta sección documenta cómo funciona el sistema de soporte integrado en el panel de usuario de ApisDom. Su objetivo es ofrecer un primer diagnóstico claro, reducir incidencias repetidas y permitir que el usuario escale el problema al equipo técnico solo cuando realmente sigue sin resolverse.


    🛟 Sistema de Soporte Integrado

    El panel de usuario de ApisDom incluye un sistema de soporte técnico integrado dentro del dashboard. No funciona como un formulario aislado, sino como un flujo guiado que combina contexto de cuenta, diagnóstico automático y escalado manual cuando el problema persiste.

    Formulario de soporte de ApisDom

    Formulario de soporte integrado en el panel de usuario de ApisDom. El sistema guía al usuario por categorías de incidencia, muestra un diagnóstico inicial y permite escalar el caso al equipo técnico solo si el problema continúa.

    Estructura del Centro de Soporte

    SecciónDescripción
    Nuevo ticketFlujo guiado para abrir una nueva incidencia
    Mis ticketsHistorial de tickets enviados por el usuario
    InformaciónExplicación del funcionamiento del sistema de soporte

    📋 Flujo de Apertura de Ticket

    El sistema de soporte de ApisDom organiza la apertura de incidencias en tres pasos. Esto permite recoger mejor el contexto del problema y evitar tickets innecesarios cuando la incidencia puede resolverse desde el propio panel.

    Paso 1: Selección del motivo

    El usuario debe elegir la categoría que mejor describe el problema. Actualmente el sistema contempla seis motivos de consulta.

    MotivoDescripción
    La API no devuelve respuestaNo recibes ninguna respuesta del endpoint
    Error de autenticación (401 / 403)La clave no está siendo aceptada por la API
    Mis créditos no se descuentan bienEl contador de créditos no refleja lo esperado
    La respuesta no es la esperadaLa API responde, pero el contenido no coincide con lo esperado
    Timeout / lentitudLa llamada tarda demasiado o se interrumpe
    OtroCualquier otro problema no incluido en las categorías anteriores

    Paso 2: Diagnóstico automático

    Una vez elegido el motivo, el sistema muestra una respuesta inicial basada en la categoría seleccionada. Este diagnóstico no sustituye al soporte técnico, pero ayuda a orientar al usuario y a revisar primero las causas más comunes.

    Además, cuando corresponde, se ofrece acceso directo al Comprobador de API Key para verificar autenticación, conexión y respuesta real del microservicio antes de escalar la consulta.

    Paso 3: Escalado manual

    Si el problema sigue sin resolverse, el usuario puede marcar la opción "Lo he intentado y sigue sin funcionar" y describir la incidencia con detalle. En ese punto, el ticket se envía al equipo técnico.

    El formulario permite también adjuntar una captura de pantalla para facilitar el diagnóstico.


    🔎 Diagnóstico con Contexto de Cuenta

    Antes de escalar una incidencia, el sistema muestra información real de la cuenta del usuario dentro del flujo de soporte.

    Datos visibles en el diagnóstico

    DatoFunción
    Estado de la cuentaIndica si la cuenta está operativa
    PlanMuestra el plan activo del usuario
    CréditosPermite comprobar el saldo disponible
    Estado internoRefleja el tipo de cuenta o tier asociado

    Este bloque da contexto inmediato al usuario y reduce incertidumbre. En lugar de abrir un ticket a ciegas, el usuario puede comprobar primero si el problema puede estar relacionado con su plan, sus créditos o el estado de su cuenta.


    🔑 Relación con el Comprobador de APIs

    Dentro del flujo de soporte, ApisDom permite abrir directamente el Comprobador de API Key desde el diagnóstico. Esto forma parte del diseño del sistema: antes de escalar una incidencia, el usuario puede validar por sí mismo que la API responde, que su clave es válida y que la integración funciona correctamente.

    Función dentro del soporte

    AcciónDescripción
    Abrir Comprobador de API KeyPermite verificar autenticación y respuesta real del servicio
    Validar antes de escalarReduce tickets cuando el problema está en la integración del cliente
    Confirmar funcionamiento realAporta tranquilidad al usuario al comprobar que la API responde correctamente

    🧾 Gestión de Tickets

    Cuando el usuario envía un ticket, este queda guardado dentro del sistema de soporte de ApisDom y puede consultarse posteriormente desde la pestaña Mis tickets.

    Estados del ticket

    EstadoSignificado
    Auto-resueltoEl ticket se ha guardado con el diagnóstico automático, sin escalar al equipo
    EscaladoEl ticket ha sido enviado al equipo técnico
    CerradoEl caso ha sido revisado y marcado como resuelto

    Información asociada al ticket

    CampoDescripción
    MotivoCategoría seleccionada por el usuario
    MensajeDescripción detallada de la incidencia
    Diagnóstico automáticoRespuesta inicial generada por el sistema
    AdjuntoCaptura de pantalla opcional
    FechaMomento de creación del ticket

    💬 Historial y Seguimiento

    La pestaña Mis tickets permite al usuario consultar el historial reciente de incidencias enviadas desde su cuenta.

    Desde esta sección puede:

    • ver el estado actual del ticket
    • consultar el contenido enviado
    • revisar si ha habido respuesta
    • eliminar tickets antiguos desde su panel

    El objetivo es que el soporte no dependa de un correo aislado, sino que quede registrado dentro del entorno de usuario, con trazabilidad y seguimiento.


    🧰 Gestión Administrativa del Soporte

    El sistema de soporte de ApisDom también incluye una capa de administración interna. Desde el panel de administración es posible revisar tickets, filtrar incidencias, responderlas y cerrarlas.

    Funciones disponibles en administración

    FunciónDescripción
    Ver ticketsListado completo de incidencias
    Filtrar por estadoPermite revisar solo tickets abiertos, escalados o cerrados
    ResponderPosibilidad de contestar desde el panel de administración
    Cerrar ticketMarcar la incidencia como resuelta
    Eliminar ticketBorrar tickets cuando corresponde
    Añadir crédito de pruebaAcción manual disponible para validar incidencias concretas

    ⚙️ Límites del Sistema

    Para evitar abuso y mantener la calidad del soporte, el sistema aplica una serie de límites operativos.

    ReglaValor
    Máximo de consultas por día3 por usuario
    Reenvío de la misma categoríaEl ticket se guarda, pero no se reenvía al equipo hasta pasadas 6 horas
    Adjuntos permitidosCapturas de pantalla
    Longitud máxima del mensaje2000 caracteres

    🔐 Nota de Privacidad

    Importante: Cuando una incidencia se escala al equipo técnico, el sistema comparte únicamente la información mínima necesaria para diagnosticar el caso: nombre, email, plan activo, créditos disponibles, estado de la suscripción y los primeros 8 caracteres de la API Key. Nunca se comparte la clave completa.


    📊 Valor del Sistema de Soporte

    AspectoValor aportado
    Diagnóstico previoReduce tickets innecesarios
    Contexto real de cuentaEvita consultas a ciegas
    Comprobación directaPermite validar la API antes de escalar
    Historial integradoMantiene trazabilidad dentro del dashboard
    Soporte con contextoEl equipo recibe información útil desde el primer momento

    Última actualización: Marzo 2026