API v1 · https://dev.qualidot.ai/api/v1

Qualidot desde tu propio sistema

Compartimos tu Criterio

Registra grabaciones desde tu telefonía, evalúalas con tus rúbricas y recibe el resultado en tu CRM. Lo mismo que haces en la plataforma, con el mismo saldo y las mismas reglas.

Inicio rápido

De cero a la primera evaluación en cuatro llamadas. Guarda tu llave en una variable de entorno; nunca la pongas en código que corra en un navegador.

  1. 1. Crea una llave

    En Configuración → API keys pulsa «Nueva llave»: viene con todos los alcances que tu plan permite, no vence y no tiene tope de gasto (puedes ajustarlo en «Configuración avanzada»). La llave se muestra una sola vez.

    terminal
    export QUALIDOT_API_KEY="ql_live_…"
  2. 2. Registra la grabación

    Qualidot descarga el archivo desde la URL cuando lo evalúa. Usa tu propio id en external_id: registrar dos veces la misma llamada devuelve el mismo archivo.

    curl -X POST "https://dev.qualidot.ai/api/v1/files" \
      -H "Authorization: Bearer $QUALIDOT_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "name": "llamada-00913.mp3",
        "url": "https://grabaciones.tu-telefonia.com/00913.mp3?firma=…",
        "external_id": "CALL-2026-00913",
        "folder_id": 41,
        "duration_seconds": 412
      }'
  3. 3. Pide la evaluación

    rubric_id acepta el group_id de la rúbrica (se usa su versión vigente). La respuesta es 202: la evaluación queda en cola y la procesa el servidor aunque tu sistema no espere.

    curl -X POST "https://dev.qualidot.ai/api/v1/evaluations" \
      -H "Authorization: Bearer $QUALIDOT_API_KEY" \
      -H "Idempotency-Key: $(uuidgen)" \
      -H "Content-Type: application/json" \
      -d '{
        "file_id": 1882,
        "rubric_id": "a41c9e2b-0000-4000-8000-000000000000"
      }'
  4. 4. Recibe el resultado

    Registra un webhook una vez y Qualidot te avisa cuando cada evaluación termina. Con el evaluation_id del evento consultas la nota y los criterios.

    curl -X POST "https://dev.qualidot.ai/api/v1/webhooks" \
      -H "Authorization: Bearer $QUALIDOT_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "url": "https://crm.acme.mx/qualidot",
        "events": [
          "evaluation.completed",
          "evaluation.failed",
          "batch.completed"
        ]
      }'

Autenticación

Todas las rutas usan una API key en la cabecera Authorization: Bearer ql_live_… (también se acepta X-API-Key). La llave actúa en nombre de la cuenta que la creó y nunca puede más que ella: si a la cuenta le quitan un permiso o baja de plan, la llave lo pierde en ese momento.

  • Cada respuesta trae Qualidot-Request-Id; inclúyelo al reportar un problema.
  • Una llave se puede limitar a IPs o rangos y a carpetas (con sus subcarpetas).
  • Rotar una llave es crear otra y revocar la anterior; revocar surte efecto de inmediato.

Alcances y planes

El alcance dice qué puede hacer una llave; el plan de la cuenta dice cuánto de la API está disponible.

AlcanceQué permiteEndpointsPlan
account:readDatos de la cuenta, plan, saldo vigente y consumo de la llave.
  • GET /account
  • GET /credits
  • GET /credits/usage
Lectura o completa
rubrics:readListar tus rúbricas y consultar sus versiones.
  • GET /rubrics
  • GET /rubrics/{group_id}/versions
Lectura o completa
files:readListar archivos y carpetas.
  • GET /folders
  • GET /files
  • GET /files/{id}
Lectura o completa
files:writeRegistrar archivos por URL, crear carpetas y borrar archivos.
  • POST /folders
  • POST /files
  • DELETE /files/{id}
API completa
evaluations:readResultados, criterios y avance de lotes.
  • GET /evaluations
  • GET /evaluations/{id}
  • GET /evaluation-batches/{id}
Lectura o completa
evaluations:runCotizar y encolar evaluaciones. Consume créditos de tu saldo.
  • POST /evaluations/estimate
  • POST /evaluations
  • POST /evaluation-batches
API completa
team:readMiembros (por tu id_miembro), sus datos de contacto y los equipos.
  • GET /teams
  • GET /members
  • GET /members/{ref}
Lectura o completa
team:writeAlta, cambios (correo, WhatsApp, jefe, rúbrica) y baja de miembros con tu id_miembro.
  • POST /members
  • PATCH /members/{ref}
  • DELETE /members/{ref}
  • POST /members/{ref}/whatsapp-verification
API completa
team:accessEnlace de conexión del agente de captura (el Chismoso) de un miembro, y desconectarlo.
    API completa
    automations:readAutomatizaciones, a quién alcanzan y su calendario de reportes.
    • GET /automations
    • GET /automations/{id}
    Lectura o completa
    automations:writeCrear, editar, pausar y borrar automatizaciones; sumar o quitar miembros.
    • POST /automations
    • PATCH /automations/{id}
    • DELETE /automations/{id}
    • POST /automations/{id}/members
    • DELETE /automations/{id}/members/{ref}
    API completa
    webhooks:manageRegistrar y borrar endpoints que reciben los eventos de la cuenta.
    • GET /webhooks
    • POST /webhooks
    • DELETE /webhooks/{id}
    Lectura o completa
    Básico: Sin APIEstándar: Solo lecturaPro: CompletaEnterprise: CompletaPersonalizado: Completa

    Saldo y topes

    Consultar es gratis. Evaluar consume créditos del mismo saldo que la plataforma, y cada petición con costo pasa por estas comprobaciones antes de encolar:

    1. Pago vigente. La cuenta necesita un plan activo o créditos comprados vigentes, y ningún saldo pendiente. Si no: 402 sin_cobertura. Aplica también a registrar archivos. POST /evaluations/estimate lo dice de antemano en can_evaluate.
    2. Tope de la llave (opcional). Una llave nace sin tope; si le pusiste uno diario o mensual, lo que ya comprometió más el anticipo de esta petición no puede pasarlo. Si pasa: 402 tope_de_llave.
    3. Saldo vigente. Créditos no vencidos, del plan y comprados. Deben cubrir el anticipo de todos los archivos. Si no: 402 saldo_insuficiente con requerido y disponible.
    4. Anticipo. Hoy son 30 créditos por archivo, cobrados cuando el archivo se despacha al análisis.
    5. Liquidación. Al terminar se calcula el costo real y se cobra la diferencia o se devuelve el excedente. Si el análisis falla, el anticipo se devuelve completo.

    POST /evaluations/estimate hace todas estas cuentas sin cobrar. En un lote, allow_partial_balance: true encola aunque el saldo no alcance para todo: el lote se pausa al acabarse y se reanuda al recargar. La recarga se hace en la plataforma, no por API.

    Idempotencia

    POST /evaluations y POST /evaluation-batches exigen Idempotency-Key: un valor único por intención (un UUID sirve). Si tu sistema reintenta tras un timeout con la misma clave, recibe la respuesta original con Idempotent-Replayed: true y no se cobra otra vez.

    • La clave vale 24 horas por llave.
    • La misma clave con otro cuerpo responde 409 idempotencia_en_conflicto.
    • Solo se guardan respuestas exitosas: después de un 402 puedes recargar y reintentar con la misma clave.

    Errores

    Todos los errores tienen la misma forma. Programa contra code, que es estable; message es para personas y puede cambiar.

    402 Payment Required
    {
      "error": {
        "code": "saldo_insuficiente",
        "message": "La evaluación requiere un anticipo de 60 créditos y la cuenta tiene 12.",
        "details": {
          "requerido": 60,
          "disponible": 12,
          "anticipo_por_item": 30
        },
        "request_id": "req_4f1c…"
      }
    }
    codeHTTPCuándo
    llave_invalida401Falta la llave, no existe, venció o fue revocada.
    ip_no_permitida403La llave tiene lista de IPs y la petición viene de otra.
    plan_sin_api403El plan no incluye la API, o solo en lectura y el endpoint escribe.
    alcance_insuficiente403La llave no tiene el alcance, o la cuenta ya no tiene ese permiso.
    no_encontrado404No existe o está fuera del alcance de la llave (no se distingue a propósito).
    json_invalido400El cuerpo no es JSON válido.
    falta_idempotency_key400Operación con costo sin cabecera Idempotency-Key.
    validacion422Campos inválidos. details.campos dice cuál y por qué.
    conflicto409external_id ya usado con otra URL.
    idempotencia_en_conflicto409La misma Idempotency-Key con otro cuerpo.
    idempotencia_en_curso409La petición original con esa clave sigue procesándose.
    sin_cobertura402La cuenta no tiene un pago vigente (plan activo o créditos comprados vigentes) o tiene saldo pendiente. Aplica a registrar archivos y evaluar.
    saldo_insuficiente402El saldo vigente no cubre el anticipo. details: requerido, disponible.
    tope_de_llave402La llave llegaría a su tope diario o mensual.
    limite_de_frecuencia429Demasiadas peticiones por minuto. Respeta Retry-After.
    limite_de_webhooks422La cuenta ya tiene 5 webhooks.
    interno500Error nuestro. Comparte el request_id con soporte.

    Paginación y límites

    Las listas responden { data, next_cursor }. Para la siguiente página pasa ?cursor= con ese valor; cuando es null no hay más. limit va de 1 a 100.

    Límite por llave y por minuto: 120 lecturas, 30 escrituras y 20 operaciones con costo. Cada respuesta trae RateLimit-Limit, RateLimit-Remaining y RateLimit-Reset; al pasarte recibes 429 con Retry-After.

    Webhooks

    Qualidot manda un POST JSON a tu URL (https) cuando ocurre un evento. Responde con cualquier 2xx en menos de 10 segundos; si no, se reintenta a los 1, 5 y 30 minutos, 2, 6 y 12 horas y un día. Cada entrega trae Qualidot-Event, Qualidot-Delivery y Qualidot-Signature.

    evaluation.completed

    Una evaluación terminó y se guardó: evaluation_id, file_id, score.

    evaluation.failed

    El análisis falló; el anticipo se devolvió: task_id o batch_item_id y error.

    batch.completed

    Todos los ítems de un lote terminaron: batch_id y conteo por estado.

    member.created

    Se dio de alta un miembro: el miembro completo, con su external_id.

    member.updated

    Cambiaron datos de un miembro: el miembro y la lista changed (p. ej. phone).

    member.deleted

    Se dio de baja un miembro: id y external_id.

    member.whatsapp_verified

    El miembro confirmó su WhatsApp: desde ahora recibe sus reportes por ahí.

    member.agent_connected

    member.agent_disconnected

    Verifica la firma antes de confiar en el evento: es HMAC-SHA256 de <t>.<cuerpo crudo> con el secreto whsec_… que recibiste al registrar el webhook. Un mismo evento puede llegar más de una vez: usa id para no procesarlo dos veces.

    import crypto from "node:crypto";
    
    // En Express: app.post("/qualidot", express.raw({ type: "application/json" }), handler)
    export function verificarFirma(cuerpoCrudo, cabecera, secreto) {
      const t = cabecera.match(/t=(\d+)/)?.[1];
      const v1 = cabecera.match(/v1=([0-9a-f]+)/)?.[1];
      if (!t || !v1) return false;
      // Rechaza entregas de más de 5 minutos (repeticiones).
      if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false;
      const esperada = crypto.createHmac("sha256", secreto).update(`${t}.${cuerpoCrudo}`).digest("hex");
      return crypto.timingSafeEqual(Buffer.from(esperada), Buffer.from(v1));
    }

    Integrar tu ERP

    Tu sistema manda: cada persona se identifica con TU id (external_id) y Qualidot se encarga de que le lleguen sus reportes por correo y WhatsApp. Necesitas los alcances team:write y automations:write; una llave nueva ya los trae. Las llaves creadas antes no los tienen: agrégalos editando la llave en Configuración → API keys, o crea una nueva.

    1. 1. Crea la automatización una vez con POST /automations: rúbricas, calendario de reportes y ventanas de captura. Guarda su id.
    2. 2. Al capturar a alguien en tu ERP, POST /members con su external_id, correo, teléfono y automation_ids. En una llamada queda dado de alta, con su carpeta, dentro de la automatización y avisado por correo.
    3. 3. WhatsApp se confirma una vez. Si mandas teléfono, la respuesta trae whatsapp.verification.url: la persona lo abre y envía el código. Mándaselo por tu propio canal (SMS, portal, correo). Te avisamos con member.whatsapp_verified.
    4. 4. Cuando algo cambie en tu ERP, PATCH /members/{external_id} con solo lo que cambió. Un teléfono nuevo reinicia la verificación y trae un enlace nuevo: nunca se manda un reporte a un número que la persona no confirmó.
    5. 5. Bajas y cambios de grupo: DELETE /members/{external_id} (su historial se conserva) o POST/DELETE /automations/{id}/members.
    curl -X POST "https://dev.qualidot.ai/api/v1/members" \
      -H "Authorization: Bearer $QUALIDOT_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "external_id": "DOC-1042",
        "name": "Laura Méndez",
        "email": "[email protected]",
        "phone": "5512345678",
        "parent_external_id": "COORD-07",
        "team_id": 3,
        "automation_ids": [
          12
        ]
      }'

    Referencia

    URL base: https://dev.qualidot.ai/api/v1

    Cuenta y saldo

    get/account

    Cuenta, plan y llave actual

    Alcance account:read

    Respuestas

    • 200
    • 401
    • 403
    • 429
    Petición
    curl -X GET "https://dev.qualidot.ai/api/v1/account" \
      -H "Authorization: Bearer $QUALIDOT_API_KEY"
    Respuesta
    {
      "id": "232b3fda-…",
      "name": "@acme",
      "email": "[email protected]",
      "plan": {
        "name": "Pro",
        "api_access": "completo"
      },
      "api_key": {
        "id": "9c1e…",
        "name": "Telefonía producción",
        "prefix": "ql_live_4f2a",
        "scopes": [
          "files:write",
          "evaluations:run",
          "evaluations:read"
        ],
        "spend_limits": {
          "daily_credits": 600,
          "monthly_credits": 12000
        }
      }
    }
    get/credits

    Saldo vigente y consumo de la llave

    Alcance account:read

    Respuestas

    • 200
    • 401
    • 403
    • 429
    Petición
    curl -X GET "https://dev.qualidot.ai/api/v1/credits" \
      -H "Authorization: Bearer $QUALIDOT_API_KEY"
    Respuesta
    {
      "available": 9147,
      "by_type": {
        "subscription": 7980,
        "purchased": 1167,
        "bonus": 0
      },
      "has_active_subscription": true,
      "evaluation_deposit": 30,
      "api_key_usage": {
        "committed_today": 90,
        "committed_this_month": 1440,
        "daily_limit": 600,
        "monthly_limit": 12000
      }
    }
    get/credits/usage

    Cargos de créditos

    Alcance account:read

    Parámetros

    limit
    integer · query
    cursor
    string · query — `next_cursor` de la página anterior.

    Respuestas

    • 200
    • 401
    • 403
    • 422
    • 429
    Petición
    curl -X GET "https://dev.qualidot.ai/api/v1/credits/usage?limit=2" \
      -H "Authorization: Bearer $QUALIDOT_API_KEY"
    Respuesta
    {
      "data": [
        {
          "id": 5120,
          "credits": 30,
          "concept": "evaluation_batch:881",
          "evaluation_id": null,
          "created_at": "2026-09-14T15:02:11Z"
        }
      ],
      "next_cursor": "eyJvIjoyfQ"
    }

    Rúbricas

    get/rubrics

    Rúbricas propias (versión vigente)

    Alcance rubrics:read

    Parámetros

    q
    string · query
    limit
    integer · query
    cursor
    string · query — `next_cursor` de la página anterior.

    Respuestas

    • 200
    • 401
    • 403
    • 422
    • 429
    Petición
    curl -X GET "https://dev.qualidot.ai/api/v1/rubrics?q=ventas" \
      -H "Authorization: Bearer $QUALIDOT_API_KEY"
    Respuesta
    {
      "data": [
        {
          "id": "6f0b…",
          "group_id": "a41c…",
          "name": "Proceso de venta consultiva",
          "version": 7,
          "state": "active",
          "visibility": "private"
        }
      ],
      "next_cursor": null
    }
    get/rubrics/{group_id}/versions

    Versiones de un linaje

    Alcance rubrics:read

    Parámetros

    group_id *
    string (uuid) · en la ruta

    Respuestas

    • 200
    • 401
    • 403
    • 404
    • 422
    • 429
    Petición
    curl -X GET "https://dev.qualidot.ai/api/v1/rubrics/a41c9e2b-0000-4000-8000-000000000000/versions" \
      -H "Authorization: Bearer $QUALIDOT_API_KEY"
    Respuesta
    {
      "data": [
        {
          "id": "6f0b…",
          "version": 7,
          "is_current": true,
          "evaluations_count": 214,
          "criteria_primary": 12,
          "criteria_secondary": 4,
          "changes": {
            "added": [
              "Cierre con fecha"
            ],
            "removed": [],
            "modified": []
          }
        }
      ],
      "next_cursor": null
    }

    Archivos y carpetas

    get/folders

    Carpetas visibles

    Alcance files:read

    Parámetros

    parent_id
    string · query — `root` o id.
    limit
    integer · query
    cursor
    string · query — `next_cursor` de la página anterior.

    Respuestas

    • 200
    • 401
    • 403
    • 422
    • 429
    Petición
    curl -X GET "https://dev.qualidot.ai/api/v1/folders?parent_id=root" \
      -H "Authorization: Bearer $QUALIDOT_API_KEY"
    Respuesta
    {
      "data": [
        {
          "id": 41,
          "name": "Equipo Norte",
          "parent_id": null,
          "rubric_id": "6f0b…"
        }
      ],
      "next_cursor": null
    }
    post/folders

    Crear carpeta

    Alcance files:write

    Cuerpo

    name *
    string
    description
    string
    parent_id
    integer
    rubric_id
    string

    Respuestas

    • 201
    • 400
    • 401
    • 403
    • 404
    • 422
    • 429
    Petición
    curl -X POST "https://dev.qualidot.ai/api/v1/folders" \
      -H "Authorization: Bearer $QUALIDOT_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "name": "Equipo Norte",
        "parent_id": null,
        "rubric_id": "a41c9e2b-0000-4000-8000-000000000000"
      }'
    Respuesta
    {
      "id": 41,
      "name": "Equipo Norte",
      "parent_id": null,
      "rubric_id": "6f0b…",
      "created_at": "2026-09-14T15:00:00Z"
    }
    get/files

    Archivos visibles

    Alcance files:read

    Parámetros

    folder_id
    integer · query
    external_id
    string · query
    limit
    integer · query
    cursor
    string · query — `next_cursor` de la página anterior.

    Respuestas

    • 200
    • 401
    • 403
    • 422
    • 429
    Petición
    curl -X GET "https://dev.qualidot.ai/api/v1/files?external_id=CALL-2026-00913" \
      -H "Authorization: Bearer $QUALIDOT_API_KEY"
    Respuesta
    {
      "data": [
        {
          "id": 1882,
          "name": "llamada-00913.mp3",
          "format": "mp3",
          "external_id": "CALL-2026-00913",
          "folder_id": 41
        }
      ],
      "next_cursor": null
    }
    post/files

    Registrar archivo por URL

    Idempotente por `external_id`: misma URL devuelve el existente (200); otra URL, 409. Requiere pago vigente (`402 sin_cobertura`).

    Alcance files:write

    Cuerpo

    name *
    string
    url *
    string (uri)
    format
    string
    folder_id
    integer
    external_id
    string
    duration_seconds
    integer
    size_bytes
    integer

    Respuestas

    • 200
    • 201
    • 400
    • 401
    • 402
    • 403
    • 404
    • 409
    • 422
    • 429
    Petición
    curl -X POST "https://dev.qualidot.ai/api/v1/files" \
      -H "Authorization: Bearer $QUALIDOT_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "name": "llamada-00913.mp3",
        "url": "https://grabaciones.tu-telefonia.com/00913.mp3?firma=…",
        "external_id": "CALL-2026-00913",
        "folder_id": 41,
        "duration_seconds": 412
      }'
    Respuesta
    {
      "id": 1882,
      "name": "llamada-00913.mp3",
      "format": "mp3",
      "url": "https://grabaciones.tu-telefonia.com/00913.mp3?firma=…",
      "folder_id": 41,
      "external_id": "CALL-2026-00913",
      "duration_seconds": 412,
      "created_at": "2026-09-14T15:01:02Z"
    }
    get/files/{id}

    Archivo

    Alcance files:read

    Parámetros

    id *
    integer · en la ruta

    Respuestas

    • 200
    • 401
    • 403
    • 404
    • 429
    Petición
    curl -X GET "https://dev.qualidot.ai/api/v1/files/1882" \
      -H "Authorization: Bearer $QUALIDOT_API_KEY"
    Respuesta
    {
      "id": 1882,
      "name": "llamada-00913.mp3",
      "format": "mp3",
      "external_id": "CALL-2026-00913"
    }
    delete/files/{id}

    Borrar archivo

    Alcance files:write

    Parámetros

    id *
    integer · en la ruta

    Respuestas

    • 200
    • 401
    • 403
    • 404
    • 429
    Petición
    curl -X DELETE "https://dev.qualidot.ai/api/v1/files/1882" \
      -H "Authorization: Bearer $QUALIDOT_API_KEY"
    Respuesta
    {
      "id": 1882,
      "deleted": true
    }

    Evaluaciones

    post/evaluations/estimate

    Cotizar sin cobrar

    Dice si la evaluación procedería: pago vigente, saldo y topes de la llave.

    Alcance evaluations:run

    Cuerpo

    items *
    EvaluationItem[]

    Respuestas

    • 200
    • 400
    • 401
    • 403
    • 422
    • 429
    Petición
    curl -X POST "https://dev.qualidot.ai/api/v1/evaluations/estimate" \
      -H "Authorization: Bearer $QUALIDOT_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "items": [
          {
            "file_id": 1882,
            "rubric_id": "a41c9e2b-0000-4000-8000-000000000000"
          }
        ]
      }'
    Respuesta
    {
      "valid_items": 1,
      "invalid_items": [],
      "deposit_per_item": 30,
      "estimated_credits": 30,
      "available_credits": 9147,
      "sufficient_balance": true,
      "api_key_limits": {
        "daily_remaining": 510,
        "monthly_remaining": 10560,
        "within_limits": true
      }
    }
    get/evaluations

    Evaluaciones terminadas

    Alcance evaluations:read

    Parámetros

    file_id
    integer · query
    external_id
    string · query
    updated_since
    string (date-time) · query
    limit
    integer · query
    cursor
    string · query — `next_cursor` de la página anterior.

    Respuestas

    • 200
    • 401
    • 403
    • 422
    • 429
    Petición
    curl -X GET "https://dev.qualidot.ai/api/v1/evaluations?updated_since=2026-09-14T00:00:00Z" \
      -H "Authorization: Bearer $QUALIDOT_API_KEY"
    Respuesta
    {
      "data": [
        {
          "id": 412,
          "status": "completed",
          "score": 82.5,
          "file": {
            "id": 1882,
            "name": "llamada-00913.mp3",
            "external_id": "CALL-2026-00913"
          },
          "rubric": {
            "id": "6f0b…",
            "group_id": "a41c…",
            "version": 7
          },
          "created_at": "2026-09-14T15:44:10Z"
        }
      ],
      "next_cursor": null
    }
    post/evaluations

    Evaluar un archivo

    Encola la evaluación y responde 202. El resultado llega por `evaluation.completed`. `402`: `sin_cobertura`, `tope_de_llave` o `saldo_insuficiente`.

    Alcance evaluations:run · requiere Idempotency-Key

    Cuerpo

    file_id *
    integer
    rubric_id *
    string (uuid) — Id de versión o group_id (se usa la versión vigente).
    name
    string

    Respuestas

    • 202
    • 400
    • 401
    • 402
    • 403
    • 409
    • 422
    • 429
    Petición
    curl -X POST "https://dev.qualidot.ai/api/v1/evaluations" \
      -H "Authorization: Bearer $QUALIDOT_API_KEY" \
      -H "Idempotency-Key: $(uuidgen)" \
      -H "Content-Type: application/json" \
      -d '{
        "file_id": 1882,
        "rubric_id": "a41c9e2b-0000-4000-8000-000000000000"
      }'
    Respuesta
    {
      "batch_id": 881,
      "item_id": 5120,
      "file_id": 1882,
      "status": "queued",
      "estimated_credits": 30,
      "available_credits": 9147
    }
    get/evaluations/{id}

    Evaluación con criterios

    Alcance evaluations:read

    Parámetros

    id *
    integer · en la ruta
    include
    string · query

    Respuestas

    • 200
    • 401
    • 403
    • 404
    • 422
    • 429
    Petición
    curl -X GET "https://dev.qualidot.ai/api/v1/evaluations/412" \
      -H "Authorization: Bearer $QUALIDOT_API_KEY"
    Respuesta
    {
      "id": 412,
      "status": "completed",
      "score": 82.5,
      "file": {
        "id": 1882,
        "external_id": "CALL-2026-00913"
      },
      "duration_seconds": 412,
      "criteria": [
        {
          "id": 901,
          "parent_id": null,
          "name": "Apertura",
          "type": "primary",
          "weight": 20,
          "score": 18,
          "feedback": "Saluda y se presenta con nombre y empresa."
        }
      ]
    }
    post/evaluation-batches

    Encolar un lote

    `402`: `sin_cobertura`, `tope_de_llave` o `saldo_insuficiente` (este último salvo `allow_partial_balance`).

    Alcance evaluations:run · requiere Idempotency-Key

    Cuerpo

    name
    string
    items *
    EvaluationItem[]
    allow_partial_balance
    boolean

    Respuestas

    • 202
    • 400
    • 401
    • 402
    • 403
    • 409
    • 422
    • 429
    Petición
    curl -X POST "https://dev.qualidot.ai/api/v1/evaluation-batches" \
      -H "Authorization: Bearer $QUALIDOT_API_KEY" \
      -H "Idempotency-Key: $(uuidgen)" \
      -H "Content-Type: application/json" \
      -d '{
        "name": "Llamadas 14 sep",
        "items": [
          {
            "file_id": 1882,
            "rubric_id": "a41c9e2b-0000-4000-8000-000000000000"
          },
          {
            "file_id": 1883,
            "rubric_id": "a41c9e2b-0000-4000-8000-000000000000"
          }
        ]
      }'
    Respuesta
    {
      "id": 881,
      "status": "queued",
      "total_items": 2,
      "items": [
        {
          "id": 5120,
          "file_id": 1882,
          "status": "pending"
        },
        {
          "id": 5121,
          "file_id": 1883,
          "status": "pending"
        }
      ],
      "estimated_credits": 60,
      "available_credits": 9147,
      "balance_covers_all": true
    }
    get/evaluation-batches/{id}

    Avance del lote

    Alcance evaluations:read

    Parámetros

    id *
    integer · en la ruta

    Respuestas

    • 200
    • 401
    • 403
    • 404
    • 422
    • 429
    Petición
    curl -X GET "https://dev.qualidot.ai/api/v1/evaluation-batches/881" \
      -H "Authorization: Bearer $QUALIDOT_API_KEY"
    Respuesta
    {
      "id": 881,
      "status": "running",
      "total_items": 2,
      "counts": {
        "done": 1,
        "dispatched": 1
      },
      "items": [
        {
          "id": 5120,
          "file_id": 1882,
          "status": "done",
          "evaluation_id": 412,
          "error": null
        }
      ]
    }

    Equipo

    get/teams

    Equipos

    Para elegir `team_id` al dar de alta miembros.

    Alcance team:read

    Respuestas

    • 200
    • 401
    • 403
    • 429
    Petición
    curl -X GET "https://dev.qualidot.ai/api/v1/teams" \
      -H "Authorization: Bearer $QUALIDOT_API_KEY"
    Respuesta
    {
      "data": [
        {
          "id": 3,
          "name": "Preparatoria"
        },
        {
          "id": 4,
          "name": "Secundaria"
        }
      ],
      "next_cursor": null
    }
    get/members

    Miembros del equipo

    Alcance team:read

    Parámetros

    external_id
    string · query
    team_id
    integer · query
    limit
    integer · query
    cursor
    string · query — `next_cursor` de la página anterior.

    Respuestas

    • 200
    • 401
    • 403
    • 422
    • 429
    Petición
    curl -X GET "https://dev.qualidot.ai/api/v1/members?external_id=DOC-1042" \
      -H "Authorization: Bearer $QUALIDOT_API_KEY"
    Respuesta
    {
      "data": [
        {
          "id": 318,
          "external_id": "DOC-1042",
          "name": "Laura Méndez",
          "email": "[email protected]",
          "phone": "+525512345678",
          "team_id": 3,
          "parent_id": 301,
          "parent_external_id": "COORD-07",
          "rubric_id": null,
          "status": "invited",
          "folder_id": 977,
          "notifications": {
            "email": true,
            "whatsapp": true
          },
          "whatsapp": {
            "verified": true,
            "verified_at": "2026-10-02T17:12:40Z",
            "verification": null
          }
        }
      ],
      "next_cursor": null
    }
    post/members

    Dar de alta un miembro

    Con tu id_miembro (`external_id`), correo y WhatsApp. Crea su carpeta y, con `automation_ids`, lo suma a esas automatizaciones en la misma llamada. Si mandas teléfono, la respuesta trae el enlace para que confirme su WhatsApp. `409` si el external_id ya existe: usa PATCH.

    Alcance team:write

    Cuerpo

    external_id *
    string
    name *
    string
    email *
    string (email)
    phone
    string | null — 10 dígitos (México) o +52…; null lo quita. Un número NUEVO reinicia la verificación.
    whatsapp
    boolean — Reportes por WhatsApp. Por defecto se enciende al recibir teléfono.
    notify_email
    boolean — Reportes por correo. Por defecto true.
    parent_external_id
    string | null
    parent_id
    integer | null
    team_id
    integer
    rubric_id
    string | null — Versión o group_id.
    automation_ids
    integer[]

    Respuestas

    • 201
    • 400
    • 401
    • 403
    • 404
    • 409
    • 422
    • 429
    Petición
    curl -X POST "https://dev.qualidot.ai/api/v1/members" \
      -H "Authorization: Bearer $QUALIDOT_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "external_id": "DOC-1042",
        "name": "Laura Méndez",
        "email": "[email protected]",
        "phone": "5512345678",
        "parent_external_id": "COORD-07",
        "team_id": 3,
        "automation_ids": [
          12
        ]
      }'
    Respuesta
    {
      "id": 318,
      "external_id": "DOC-1042",
      "name": "Laura Méndez",
      "email": "[email protected]",
      "phone": "+525512345678",
      "team_id": 3,
      "parent_id": 301,
      "parent_external_id": "COORD-07",
      "rubric_id": null,
      "status": "invited",
      "folder_id": 977,
      "notifications": {
        "email": true,
        "whatsapp": true
      },
      "whatsapp": {
        "verified": false,
        "verified_at": null,
        "verification": {
          "code": "QD-9VMC4",
          "url": "https://wa.me/5215500000000?text=QD-9VMC4",
          "business_number": "+5215500000000",
          "expires_at": "2026-10-02T17:30:00Z"
        }
      },
      "automation_ids": [
        12
      ]
    }
    get/members/{ref}

    Miembro

    Alcance team:read

    Parámetros

    ref *
    string · en la ruta — Tu external_id (o el id de Qualidot con `?by=id`).
    by
    string · query — `id` para usar el id de Qualidot en lugar de tu external_id.

    Respuestas

    • 200
    • 401
    • 403
    • 404
    • 429
    Petición
    curl -X GET "https://dev.qualidot.ai/api/v1/members/DOC-1042" \
      -H "Authorization: Bearer $QUALIDOT_API_KEY"
    Respuesta
    {
      "id": 318,
      "external_id": "DOC-1042",
      "name": "Laura Méndez",
      "email": "[email protected]",
      "phone": "+525512345678",
      "team_id": 3,
      "parent_id": 301,
      "parent_external_id": "COORD-07",
      "rubric_id": null,
      "status": "invited",
      "folder_id": 977,
      "notifications": {
        "email": true,
        "whatsapp": true
      },
      "whatsapp": {
        "verified": true,
        "verified_at": "2026-10-02T17:12:40Z",
        "verification": null
      }
    }
    patch/members/{ref}

    Actualizar miembro

    Envía solo lo que cambió en tu sistema (correo, teléfono, canales, jefe, rúbrica, `status`, incluso el `external_id`). Un teléfono nuevo reinicia la verificación de WhatsApp y trae el enlace nuevo. Dispara `member.updated` con `changed`.

    Alcance team:write

    Parámetros

    ref *
    string · en la ruta — Tu external_id (o el id de Qualidot con `?by=id`).
    by
    string · query — `id` para usar el id de Qualidot en lugar de tu external_id.

    Cuerpo

    external_id
    string
    name
    string
    email
    string (email)
    phone
    string | null — 10 dígitos (México) o +52…; null lo quita. Un número NUEVO reinicia la verificación.
    whatsapp
    boolean — Reportes por WhatsApp. Por defecto se enciende al recibir teléfono.
    notify_email
    boolean — Reportes por correo. Por defecto true.
    parent_external_id
    string | null
    parent_id
    integer | null
    team_id
    integer
    rubric_id
    string | null — Versión o group_id.
    status
    string · active, suspended

    Respuestas

    • 200
    • 400
    • 401
    • 403
    • 404
    • 409
    • 422
    • 429
    Petición
    curl -X PATCH "https://dev.qualidot.ai/api/v1/members/DOC-1042" \
      -H "Authorization: Bearer $QUALIDOT_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "phone": "+52 55 8765 4321"
      }'
    Respuesta
    {
      "id": 318,
      "external_id": "DOC-1042",
      "name": "Laura Méndez",
      "email": "[email protected]",
      "phone": "+525587654321",
      "team_id": 3,
      "parent_id": 301,
      "parent_external_id": "COORD-07",
      "rubric_id": null,
      "status": "invited",
      "folder_id": 977,
      "notifications": {
        "email": true,
        "whatsapp": true
      },
      "whatsapp": {
        "verified": false,
        "verified_at": null,
        "verification": {
          "code": "QD-NWYN8",
          "url": "https://wa.me/5215500000000?text=QD-NWYN8",
          "business_number": "+5215500000000",
          "expires_at": "2026-10-02T18:05:00Z"
        }
      },
      "changed": [
        "phone"
      ]
    }
    delete/members/{ref}

    Dar de baja

    Sus carpetas e historial se conservan; sus subordinados suben un nivel.

    Alcance team:write

    Parámetros

    ref *
    string · en la ruta — Tu external_id (o el id de Qualidot con `?by=id`).
    by
    string · query — `id` para usar el id de Qualidot en lugar de tu external_id.

    Respuestas

    • 200
    • 401
    • 403
    • 404
    • 429
    Petición
    curl -X DELETE "https://dev.qualidot.ai/api/v1/members/DOC-1042" \
      -H "Authorization: Bearer $QUALIDOT_API_KEY"
    Respuesta
    {
      "id": 318,
      "external_id": "DOC-1042",
      "deleted": true
    }
    post/members/{ref}/whatsapp-verification

    Enlace para verificar WhatsApp

    Código nuevo (30 min). La persona escribe al número de Qualidot y queda verificada; llega `member.whatsapp_verified`.

    Alcance team:write

    Parámetros

    ref *
    string · en la ruta — Tu external_id (o el id de Qualidot con `?by=id`).
    by
    string · query — `id` para usar el id de Qualidot en lugar de tu external_id.

    Respuestas

    • 201
    • 401
    • 403
    • 404
    • 409
    • 422
    • 429
    Petición
    curl -X POST "https://dev.qualidot.ai/api/v1/members/DOC-1042/whatsapp-verification" \
      -H "Authorization: Bearer $QUALIDOT_API_KEY"
    Respuesta
    {
      "id": 318,
      "external_id": "DOC-1042",
      "phone": "+525512345678",
      "verification": {
        "code": "QD-7KRTA",
        "url": "https://wa.me/5215500000000?text=QD-7KRTA",
        "business_number": "+5215500000000",
        "expires_at": "2026-10-02T18:10:00Z"
      }
    }

    Automatizaciones

    get/automations

    Automatizaciones

    Alcance automations:read

    Respuestas

    • 200
    • 401
    • 403
    • 429
    Petición
    curl -X GET "https://dev.qualidot.ai/api/v1/automations" \
      -H "Authorization: Bearer $QUALIDOT_API_KEY"
    Respuesta
    {
      "data": [
        {
          "id": 12,
          "name": "Observación de clase · Prepa",
          "active": true,
          "reached_members": 24,
          "rubrics": [
            {
              "id": "6f0b…",
              "name": "Proceso académico"
            }
          ],
          "report": {
            "periodicity": "weekly",
            "day_of_week": 1,
            "day_of_month": null,
            "hour": "09:00"
          },
          "files_per_period": 5
        }
      ],
      "next_cursor": null
    }
    post/automations

    Crear automatización

    Rúbricas + miembros (por external_id) + calendario de reportes + ventanas de captura + cuota.

    Alcance automations:write

    Cuerpo

    name *
    string
    description
    string | null
    rubric_ids
    string[] — Versión o group_id.
    default_rubric_id
    string | null
    members
    object[]
    report
    object | null — Calendario de reportes.
    capture_windows
    object[]
    files_per_period
    integer — Archivos a evaluar por periodo, por miembro.
    notify_members
    boolean — Aviso por correo a cada miembro con parámetros y rúbricas en PDF.

    Respuestas

    • 201
    • 400
    • 401
    • 403
    • 404
    • 409
    • 422
    • 429
    Petición
    curl -X POST "https://dev.qualidot.ai/api/v1/automations" \
      -H "Authorization: Bearer $QUALIDOT_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "name": "Observación de clase · Prepa",
        "rubric_ids": [
          "a41c9e2b-0000-4000-8000-000000000000"
        ],
        "members": [
          {
            "external_id": "COORD-07",
            "include_subtree": true
          }
        ],
        "report": {
          "periodicity": "weekly",
          "day_of_week": 1,
          "hour": "09:00",
          "timezone": "America/Mexico_City"
        },
        "capture_windows": [
          {
            "day_of_week": 1,
            "start_time": "07:00",
            "end_time": "14:00",
            "mode": "sesion"
          }
        ],
        "files_per_period": 5
      }'
    Respuesta
    {
      "id": 12,
      "name": "Observación de clase · Prepa",
      "active": true,
      "members": [
        {
          "id": 301,
          "external_id": "COORD-07",
          "include_subtree": true
        }
      ],
      "reached_members": [
        {
          "id": 301,
          "external_id": "COORD-07"
        },
        {
          "id": 318,
          "external_id": "DOC-1042"
        }
      ],
      "rubrics": [
        {
          "id": "6f0b…",
          "name": "Proceso académico"
        }
      ],
      "report": {
        "periodicity": "weekly",
        "day_of_week": 1,
        "day_of_month": null,
        "hour": "09:00",
        "timezone": "America/Mexico_City"
      },
      "files_per_period": 5
    }
    get/automations/{id}

    Automatización

    Alcance automations:read

    Parámetros

    id *
    integer · en la ruta

    Respuestas

    • 200
    • 401
    • 403
    • 404
    • 429
    Petición
    curl -X GET "https://dev.qualidot.ai/api/v1/automations/12" \
      -H "Authorization: Bearer $QUALIDOT_API_KEY"
    Respuesta
    {
      "id": 12,
      "name": "Observación de clase · Prepa",
      "active": true,
      "reached_members": [
        {
          "id": 318,
          "external_id": "DOC-1042"
        }
      ],
      "files_per_period": 5
    }
    patch/automations/{id}

    Editar automatización

    Cambia solo lo que envíes. `active: false` la pausa. `members` reemplaza la lista: para sumar o quitar uno usa `/automations/{id}/members`.

    Alcance automations:write

    Parámetros

    id *
    integer · en la ruta

    Cuerpo

    name
    string
    description
    string | null
    rubric_ids
    string[] — Versión o group_id.
    default_rubric_id
    string | null
    members
    object[]
    report
    object | null — Calendario de reportes.
    capture_windows
    object[]
    files_per_period
    integer — Archivos a evaluar por periodo, por miembro.
    notify_members
    boolean — Aviso por correo a cada miembro con parámetros y rúbricas en PDF.
    active
    boolean

    Respuestas

    • 200
    • 400
    • 401
    • 403
    • 404
    • 409
    • 422
    • 429
    Petición
    curl -X PATCH "https://dev.qualidot.ai/api/v1/automations/12" \
      -H "Authorization: Bearer $QUALIDOT_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "active": false
      }'
    Respuesta
    {
      "id": 12,
      "name": "Observación de clase · Prepa",
      "active": false
    }
    delete/automations/{id}

    Borrar automatización

    Alcance automations:write

    Parámetros

    id *
    integer · en la ruta

    Respuestas

    • 200
    • 401
    • 403
    • 404
    • 429
    Petición
    curl -X DELETE "https://dev.qualidot.ai/api/v1/automations/12" \
      -H "Authorization: Bearer $QUALIDOT_API_KEY"
    Respuesta
    {
      "id": 12,
      "deleted": true
    }
    post/automations/{id}/members

    Sumar miembro a la automatización

    Desde ese momento entra en su calendario de reportes. Idempotente: si ya estaba, 200 con `added: false`.

    Alcance automations:write

    Parámetros

    id *
    integer · en la ruta

    Cuerpo

    external_id
    string
    id
    integer
    include_subtree
    boolean
    notify
    boolean

    Respuestas

    • 200
    • 201
    • 400
    • 401
    • 403
    • 404
    • 422
    • 429
    Petición
    curl -X POST "https://dev.qualidot.ai/api/v1/automations/12/members" \
      -H "Authorization: Bearer $QUALIDOT_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "external_id": "DOC-1042"
      }'
    Respuesta
    {
      "automation_id": 12,
      "member": {
        "id": 318,
        "external_id": "DOC-1042"
      },
      "added": true
    }
    delete/automations/{id}/members/{ref}

    Quitar miembro de la automatización

    Alcance automations:write

    Parámetros

    id *
    integer · en la ruta
    ref *
    string · en la ruta — Tu external_id (o el id de Qualidot con `?by=id`).
    by
    string · query — `id` para usar el id de Qualidot en lugar de tu external_id.

    Respuestas

    • 200
    • 401
    • 403
    • 404
    • 429
    Petición
    curl -X DELETE "https://dev.qualidot.ai/api/v1/automations/12/members/DOC-1042" \
      -H "Authorization: Bearer $QUALIDOT_API_KEY"
    Respuesta
    {
      "automation_id": 12,
      "member": {
        "id": 318,
        "external_id": "DOC-1042"
      },
      "removed": true
    }

    Webhooks

    get/webhooks

    Webhooks de la cuenta

    Alcance webhooks:manage

    Respuestas

    • 200
    • 401
    • 403
    • 429
    Petición
    curl -X GET "https://dev.qualidot.ai/api/v1/webhooks" \
      -H "Authorization: Bearer $QUALIDOT_API_KEY"
    Respuesta
    {
      "data": [
        {
          "id": "0b7d…",
          "url": "https://crm.acme.mx/qualidot",
          "events": [
            "evaluation.completed"
          ],
          "active": true,
          "consecutive_failures": 0
        }
      ],
      "next_cursor": null
    }
    post/webhooks

    Registrar webhook

    El `secret` se devuelve una sola vez.

    Alcance webhooks:manage

    Cuerpo

    url *
    string (uri)
    events *
    string[] · evaluation.completed, evaluation.failed, batch.completed, member.created, member.updated, member.deleted, member.whatsapp_verified, member.agent_connected, member.agent_disconnected

    Respuestas

    • 201
    • 400
    • 401
    • 403
    • 422
    • 429
    Petición
    curl -X POST "https://dev.qualidot.ai/api/v1/webhooks" \
      -H "Authorization: Bearer $QUALIDOT_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "url": "https://crm.acme.mx/qualidot",
        "events": [
          "evaluation.completed",
          "evaluation.failed",
          "batch.completed"
        ]
      }'
    Respuesta
    {
      "id": "0b7d…",
      "url": "https://crm.acme.mx/qualidot",
      "events": [
        "evaluation.completed",
        "evaluation.failed",
        "batch.completed"
      ],
      "active": true,
      "secret": "whsec_…"
    }
    delete/webhooks/{id}

    Borrar webhook

    Alcance webhooks:manage

    Parámetros

    id *
    string (uuid) · en la ruta

    Respuestas

    • 200
    • 401
    • 403
    • 404
    • 422
    • 429
    Petición
    curl -X DELETE "https://dev.qualidot.ai/api/v1/webhooks/0b7d3a10-0000-4000-8000-000000000000" \
      -H "Authorization: Bearer $QUALIDOT_API_KEY"
    Respuesta
    {
      "id": "0b7d…",
      "deleted": true
    }

    ¿Listo para conectar?

    Crea tu llave, prueba con GET /account y registra tu primer webhook. La especificación completa está disponible en OpenAPI para generar clientes.