Referencia de integración

Lía Partner API

Última actualización: 30 ago 2026

La Partner API permite a un integrador registrar vacantes, analizar CVs contra ellas y leer los resultados del análisis de compatibilidad, todo por HTTP. Es un producto server-to-server: se consume con una API key propia, no con usuarios finales.

Base URL: https://api.liatalentos.com/v1

El acceso a la Partner API se habilita mediante acuerdo comercial.

Solicitar acceso a la API →

Autenticación

Todas las llamadas requieren el header:

Authorization: Bearer lia_live_xxxxxxxx_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Si la key falta, es inválida o está inactiva, la respuesta es 401 con el envelope de error estándar:

{ "error": { "code": "unauthorized", "message": "API key inválida o inactiva", "request_id": "b1f2..." } }

Endpoints

1. Crear vacante — POST /jobs

Registra una vacante y genera su perfil de evaluación con IA. Scope: jobs:write.

Headers: Authorization, Content-Type: application/json

CampoTipoRequeridoDescripción
titlestringTítulo de la vacante.
descriptionstringnoDescripción / responsabilidades.
requirementsobjectnoRequisitos estructurados (formato libre).
external_job_idstringnoId de la vacante en tu sistema, para correlación.

Ejemplo:

curl -X POST "https://api.liatalentos.com/v1/jobs" \
  -H "Authorization: Bearer lia_live_ab12cd34_..." \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Ejecutivo de Ventas Inmobiliarias",
    "description": "Cierre consultivo de propiedades de alto valor.",
    "requirements": { "experiencia_min_anios": 3, "idiomas": ["es"] },
    "external_job_id": "JOB-4471"
  }'

Respuesta 201 Created:

{ "job_id": "a3d9c1e0-...", "status": "active" }

Guarda el job_id: lo necesitas para analizar CVs. La misma vacante (mismo contenido) enviada de nuevo reutiliza el perfil ya generado y responde 200 con el mismo job_id.

1.1 Leer una vacante — GET /jobs/{job_id}

Devuelve lo que tenemos guardado. Scope: jobs:write (el mismo que para crearla; no hay un scope de lectura aparte).

{
  "job_id": "8a2f...",
  "external_job_id": "REQ-4471",
  "title": "Analista de Crédito",
  "description": "...",
  "requirements": [ ... ],
  "status": "active",
  "version": 2,
  "previous_job_id": "3c91...",
  "analyses_count": 3,
  "created_at": "2026-08-30T02:10:00.000Z",
  "updated_at": "2026-08-30T02:10:00.000Z"
}

status es active, archived o error. Una vacante ajena a tu key responde 404.

1.2 Editar una vacante — PATCH /jobs/{job_id}

Scope: jobs:write. Manda solo los campos que cambian (title, description, requirements, external_job_id); lo que no venga conserva su valor.

Editar SIEMPRE crea una versión nueva. La vacante anterior pasa a archived y recibes un job_id nuevo:

{
  "job_id": "9d33...",
  "external_job_id": "REQ-4471",
  "previous_job_id": "8a2f...",
  "status": "active",
  "version": 3,
  "analyses_on_previous": 3
}

Por qué siempre y no solo cuando hay análisis. Si el comportamiento dependiera de si ya subiste CVs, el mismo PATCH te devolvería a veces el mismo job_id y a veces uno nuevo, según un evento que no controlas —que alguien haya subido un candidato justo antes—. Preferimos un contrato predecible: el job_id cambia siempre. Usa tu external_job_id como identificador estable.

Qué pasa con los análisis anteriores. Siguen atados a la versión con la que se evaluaron, que se conserva completa y consultable. analyses_on_previous te dice cuántos son, para que decidas si quieres relanzarlos contra la versión nueva. Los que estuvieran en curso terminan contra la versión anterior: se evaluaron con ese perfil y es el que su resultado refleja.

No se puede editar una versión archivada — responde 409 job_not_active. Edita siempre la activa.

Si el contenido nuevo coincide exactamente con el actual, no se versiona: recibes el mismo job_id con "unchanged": true. Si coincide con otra vacante activa tuya, responde 409 duplicate_job.

Editar no genera cargo. Ver Costos.

2. Analizar un CV — POST /jobs/{job_id}/analyses

Sube un CV en PDF y encola su análisis contra la vacante. Scope: analyses:write.

Headers: Authorization, Idempotency-Key: <único-por-CV> (obligatorio), Content-Type: multipart/form-data

CampoTipoRequeridoDescripción
cvfile (PDF)El CV. Solo PDF, máximo 10 MB.
external_candidate_idstringnoId del candidato en tu sistema (recomendado).

Ejemplo:

curl -X POST "https://api.liatalentos.com/v1/jobs/a3d9c1e0-.../analyses" \
  -H "Authorization: Bearer lia_live_ab12cd34_..." \
  -H "Idempotency-Key: cand-8891-job-4471" \
  -F "cv=@/ruta/al/cv.pdf;type=application/pdf" \
  -F "external_candidate_id=CAND-8891"

Respuesta 202 Accepted:

{ "analysis_id": "7f4c...", "status": "pending" }

El análisis es asíncrono: consulta el resultado con el endpoint 3.

3. Leer un análisis — GET /analyses/{analysis_id}

Devuelve el estado y, si terminó, el resultado. Scope: analyses:read.

Headers: Authorization

curl "https://api.liatalentos.com/v1/analyses/7f4c..." \
  -H "Authorization: Bearer lia_live_ab12cd34_..."

Mientras procesa (200):

{ "analysis_id": "7f4c...", "status": "processing", "external_candidate_id": "CAND-8891" }

Completado (200):

{
  "analysis_id": "7f4c...",
  "status": "completed",
  "external_candidate_id": "CAND-8891",
  "candidate": {
    "full_name": "Mariana López",
    "emails": ["mariana@ejemplo.com"],
    "phones": ["+52 55 1234 5678"],
    "location": "Ciudad de México"
  },
  "completed_at": "2026-07-22T00:12:41.220Z",
  "model_version": "lia-match-v3",
  "result": {
    "score": 82,
    "recommendation": "recommended",
    "summary": "Perfil con cierre consultivo sólido en alto valor...",
    "strengths": ["Cierre de alto ticket", "Prospección consultiva"],
    "gaps": ["Sin experiencia inmobiliaria directa"],
    "seniority": {
      "level": "senior",
      "label": "Senior",
      "years_estimated": 7,
      "confidence": "high",
      "evidence": "7 años cerrando operaciones de alto valor en banca patrimonial"
    },
    "skills": {
      "identified": ["Cierre consultivo", "Prospección en frío", "CRM (Salesforce)", "Negociación"],
      "relevant_to_job": ["Cierre consultivo", "Negociación", "CRM (Salesforce)"],
      "missing_preferred": ["Experiencia en el sector inmobiliario"],
      "nice_to_have_found": []
    },
    "experience": {
      "highlights": [
        {
          "title": "Ejecutivo Senior de Banca Patrimonial",
          "company": "Banco del Norte",
          "period": "2021 – 2026",
          "relevance": "high",
          "summary": "Cartera de clientes de alto patrimonio y cierre consultivo de productos de inversión."
        },
        {
          "title": "Asesor Comercial",
          "company": "Grupo Solaris",
          "period": "2018 – 2021",
          "relevance": "medium",
          "summary": "Venta consultiva B2B con ciclo largo."
        }
      ]
    },
    "requirements": [
      {
        "requirement": "3 años de experiencia en venta consultiva de alto valor",
        "priority": "required",
        "status": "met",
        "evidence": "7 años cerrando operaciones de alto valor en banca patrimonial",
        "note": null
      },
      {
        "requirement": "Experiencia en el sector inmobiliario",
        "priority": "preferred",
        "status": "not_found",
        "evidence": null,
        "note": "El CV no menciona operaciones inmobiliarias; su experiencia de alto valor es financiera."
      },
      {
        "requirement": "Inglés conversacional",
        "priority": "nice_to_have",
        "status": "partial",
        "evidence": "Inglés B2 (certificado TOEFL 2023)",
        "note": null
      }
    ]
  }
}

recommendation es uno de: recommended, review, not_recommended. score es un entero de 0 a 100. model_version (lia-match-v<N>) identifica la versión del motor que produjo ese análisis.

seniority.level es uno de: junior, mid, senior; seniority.confidence, uno de low, medium, high. seniority.label es la etiqueta legible de ese nivel —Junior, Semi / mid o Senior— y se deriva de level, así que nunca lo contradice: no es el puesto del candidato.

requirements trae todos los requisitos de la vacante, con la priority (required, preferred, nice_to_have) que quedó fijada al crearla: es la misma lista para todos los candidatos de esa vacante. status es met, partial o not_found.

evidence es una cita textual del CV, verificable. Si no hay evidencia en el CV, evidence es null y el status es not_found. La lectura interpretada va en note, que puede ser null.

Los arrays de skills se derivan de requirements y no lo contradicen: missing_preferred lista requisitos required o preferred que el CV no cubre, y nice_to_have_found solo requisitos de prioridad nice_to_have que el CV demuestra. En el ejemplo va vacío a propósito: el único nice_to_have es «Inglés conversacional» y quedó en partial —el CV lo menciona pero no alcanza el nivel—, así que no es algo encontrado. Un array vacío es una respuesta correcta.

El bloque candidate

Va al mismo nivel que analysis_id y status, fuera de result: result es el juicio de compatibilidad y candidate es identificación. Por eso está disponible aunque el análisis falle, que es cuando más ayuda saber de quién era el CV.

Todo lo que contiene es literal del CV. El motor no infiere ni completa: si un dato no está escrito en el currículum, no se devuelve. No hay campo de confianza porque no hay nada que estimar. El nombre se devuelve tal como aparece, sin normalizar — si el CV lo escribe en mayúsculas, así llega.

Cuando un dato no aparece en el CV: [] (array vacío) para emails y phones; null para full_name y location. Un array vacío es una respuesta correcta, no un error.

Lo que nunca se extrae, aunque el CV lo traiga: fecha de nacimiento, edad, género, fotografía, CURP, RFC y estado civil. Es una decisión deliberada: son datos que sesgan la contratación y no necesitamos tocarlos para evaluar una candidatura.

En análisis anteriores a lia-match-v3 la clave candidate no aparece. No se devuelve vacía ni en null: simplemente no está. Los análisis existentes no se recalculan.

Qué cambia en lia-match-v3

Además del bloque candidate, cambia algo que no se ve en la respuesta y conviene que sepas: a partir de v3 el motor puntúa sobre un CV del que se han retirado el nombre, los correos y los teléfonos. La ubicación se conserva, porque un requisito geográfico es legítimo y evaluarlo exige saber dónde está la persona.

Es la razón de subir de versión. Si comparas resultados de v2 y v3 sobre el mismo CV y ves diferencias, la explicación es ésa: la entrada del evaluador no es la misma.

Análisis anteriores a la v2 conservan su model_version y su estructura v1.

Fallido (200):

{
  "analysis_id": "7f4c...",
  "status": "failed",
  "external_candidate_id": "CAND-8891",
  "error": { "code": "pdf_no_text", "message": "El PDF no contiene texto extraíble (posible escaneo o imagen)." }
}

Un análisis ajeno a tu key o inexistente responde 404.

Idempotencia

El header Idempotency-Key es obligatorio en POST /analyses. Reintentar con la misma key (por ejemplo, tras un timeout de red) devuelve el mismo analysis_id sin crear un segundo análisis ni generar un cargo doble. Usa una key única y estable por CV (por ejemplo candidato-job).

Flujo asíncrono (polling)

  1. POST /analyses202 con status: "pending".
  2. Haz polling de GET /analyses/{id} hasta que status sea completed o failed.
  3. Cadencia recomendada: cada 10–15 segundos. El análisis típico completa en menos de 60 segundos.

Códigos de error

Todos los errores HTTP usan el envelope:

{ "error": { "code": "<code>", "message": "<humano>", "request_id": "<id>" } }

Incluye siempre el request_id al reportar un problema a soporte.

HTTPcodeCuándoQué hacer
400idempotency_key_requiredFalta el header Idempotency-Key en POST /analyses.Agrega el header.
400validation_errorFalta un campo requerido (title, cv).Corrige el body/multipart.
400invalid_json / invalid_multipartEl body no es JSON / multipart válido.Corrige el Content-Type y el cuerpo.
401unauthorizedKey ausente, inválida o inactiva.Revisa el header Authorization.
403forbiddenLa key no tiene el scope del endpoint.Solicita el scope por contrato.
404not_foundVacante/análisis inexistente o de otra key.Verifica el id.
409job_not_analyzableAl analizar un CV: la vacante está en estado error o archived.Usa la versión active de la vacante.
409job_not_activeAl editar (PATCH /jobs/{job_id}): la vacante no está active, así que es una versión anterior.Edita la versión active.
409duplicate_jobEl contenido tras el PATCH coincide con otra vacante tuya ya active.Usa esa vacante, o cambia el contenido.
422profile_generation_failedNo se pudo regenerar el perfil de evaluación al editar.Reintenta el PATCH.
413file_too_largeEl PDF excede 10 MB.Reduce el archivo.
415unsupported_media_typeEl archivo no es un PDF válido.Envía un PDF.
429rate_limitedSe excedió el límite de requests por minuto.Reintenta con backoff.
500internal_error / persist_error / storage_upload_failedError del servidor.Reintenta; si persiste, reporta con el request_id.

error_codes de un análisis fallido (campo error.code en el GET cuando status: "failed"):

codeSignificadoQué hacer
pdf_no_textEl PDF no tiene texto extraíble (escaneo o imagen).Reenvía el CV como PDF con texto seleccionable.
score_malformedEl modelo devolvió un resultado inválido.Reintenta el análisis (nueva Idempotency-Key).
job_profile_missingLa vacante no tiene un perfil válido para evaluar.Verifica que la vacante se creó correctamente.
file_missingEl CV ya no está disponible (venció su retención de 24h).Vuelve a subir el CV en un nuevo análisis.
scoring_unavailableEl servicio de análisis rechazó la solicitud.Reintenta con una nueva Idempotency-Key. Si persiste, es una condición de nuestro lado: repórtalo con el request_id.
scoring_provider_errorEl motor de análisis no estuvo disponible tras varios intentos.Reintenta con una nueva Idempotency-Key. Si se repite, repórtalo con el request_id.
scoring_abortedEl análisis se detuvo sin completar.Reenvía el CV. Si vuelve a fallar con el mismo archivo, el problema está en ese PDF.

Límites

Costos

Solo se facturan los análisis de CV completados, contados por su completed_at.

Retención de datos

Qué significa esto en la práctica. A los 30 días desaparecen los datos con los que identificamos a la persona —nombre, correos, teléfonos, ubicación y tu propio identificador de candidato—, pero el análisis permanece, y su contenido procede del CV: las citas de evidence y el bloque experience pueden nombrar empleadores, puestos y periodos tomados textualmente del currículum.

Es una eliminación de identificadores directos, no una anonimización irreversible: la combinación de empleador, periodo y ubicación puede bastar para reidentificar a una persona. Lo decimos así de claro para que puedas decidir qué conservas tú y bajo qué base legal.

external_candidate_id

Campo opcional pero recomendado en POST /analyses. Lía lo guarda y lo devuelve tal cual en el GET, para que puedas correlacionar cada análisis con el candidato en tu propio sistema sin mantener un mapeo aparte.

¿Listo para integrar? El acceso se habilita mediante acuerdo comercial.

Solicitar acceso a la API →