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.
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. 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. 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.
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.
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.
Alcance
Qué permite
Endpoints
Plan
account:read
Datos de la cuenta, plan, saldo vigente y consumo de la llave.
GET /account
GET /credits
GET /credits/usage
Lectura o completa
rubrics:read
Listar tus rúbricas y consultar sus versiones.
GET /rubrics
GET /rubrics/{group_id}/versions
Lectura o completa
files:read
Listar archivos y carpetas.
GET /folders
GET /files
GET /files/{id}
Lectura o completa
files:write
Registrar archivos por URL, crear carpetas y borrar archivos.
POST /folders
POST /files
DELETE /files/{id}
API completa
evaluations:read
Resultados, criterios y avance de lotes.
GET /evaluations
GET /evaluations/{id}
GET /evaluation-batches/{id}
Lectura o completa
evaluations:run
Cotizar y encolar evaluaciones. Consume créditos de tu saldo.
POST /evaluations/estimate
POST /evaluations
POST /evaluation-batches
API completa
team:read
Miembros (por tu id_miembro), sus datos de contacto y los equipos.
GET /teams
GET /members
GET /members/{ref}
Lectura o completa
team:write
Alta, 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:access
Enlace de conexión del agente de captura (el Chismoso) de un miembro, y desconectarlo.
API completa
automations:read
Automatizaciones, a quién alcanzan y su calendario de reportes.
GET /automations
GET /automations/{id}
Lectura o completa
automations:write
Crear, 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:manage
Registrar 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:
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.
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.
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.
Anticipo. Hoy son 30 créditos por archivo, cobrados cuando el archivo se despacha al análisis.
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…"
}
}
code
HTTP
Cuándo
llave_invalida
401
Falta la llave, no existe, venció o fue revocada.
ip_no_permitida
403
La llave tiene lista de IPs y la petición viene de otra.
plan_sin_api
403
El plan no incluye la API, o solo en lectura y el endpoint escribe.
alcance_insuficiente
403
La llave no tiene el alcance, o la cuenta ya no tiene ese permiso.
no_encontrado
404
No existe o está fuera del alcance de la llave (no se distingue a propósito).
json_invalido
400
El cuerpo no es JSON válido.
falta_idempotency_key
400
Operación con costo sin cabecera Idempotency-Key.
validacion
422
Campos inválidos. details.campos dice cuál y por qué.
conflicto
409
external_id ya usado con otra URL.
idempotencia_en_conflicto
409
La misma Idempotency-Key con otro cuerpo.
idempotencia_en_curso
409
La petición original con esa clave sigue procesándose.
sin_cobertura
402
La cuenta no tiene un pago vigente (plan activo o créditos comprados vigentes) o tiene saldo pendiente. Aplica a registrar archivos y evaluar.
saldo_insuficiente
402
El saldo vigente no cubre el anticipo. details: requerido, disponible.
tope_de_llave
402
La llave llegaría a su tope diario o mensual.
limite_de_frecuencia
429
Demasiadas peticiones por minuto. Respeta Retry-After.
limite_de_webhooks
422
La cuenta ya tiene 5 webhooks.
interno
500
Error 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. Crea la automatización una vez con POST /automations: rúbricas, calendario de reportes y ventanas de captura. Guarda su id.
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. 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. 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. Bajas y cambios de grupo:DELETE /members/{external_id} (su historial se conserva) o POST/DELETE /automations/{id}/members.
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.
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.