CazVid
Documentación de la API

CRM API

CRM API v1

Gestiona tu CRM de CazVid desde código o desde un agente de IA: lee y busca contactos, créalos y actualízalos, registra interacciones y adjunta currículums. Es la misma base de datos de contactos que tu equipo usa en la app - la API es una superficie con alcance y cuota sobre ella.

URL base

https://aio-backend-prod.cazvid.app/api/v3.0/crm

Descripción general

Esta referencia cubre veintidós endpoints sobre cuatro objetos: contactos, su línea de tiempo de interacciones, currículums y los pipelines de contratación (listas) donde tus contactos aparecen como tarjetas. Los endpoints de lectura usan el alcance crm:read; toda escritura, incluidas las rutas de pipelines, usa crm:write. Usa /public/usage para consultar tu cuota restante sin gastarla.

GET/public/contacts
Lista o busca tus contactos. Devuelve una página; oculta los canales de contacto (ver la nota de privacidad).
GET/public/contacts/{id}
Lee un contacto completo, incluidos sus correos y teléfonos.
POST/public/contacts
Crea un contacto. Idempotente: deduplica por correo o teléfono dentro del workspace.
PATCH/public/contacts/{id}
Actualiza campos de un contacto. Envía solo los campos que quieras cambiar.
DELETE/public/contacts/{id}
Archiva un contacto (borrado suave). También archiva sus interacciones.
POST/public/contacts/{id}/resume
Adjunta un currículum (PDF, DOC o DOCX en base64) y encola un análisis asíncrono.
POST/public/contacts/{id}/interactions
Registra una interacción (una entrada de la línea de tiempo, como una llamada, entrevista o nota).
GET/public/contacts/{id}/interactions
Lee la línea de tiempo de interacciones de un contacto, de la más reciente a la más antigua.
GET/public/interaction-types
Lista los nombres de tipo de interacción del catálogo global fijo, agrupados por categoría.
GET/public/lists
Lista tus pipelines, del más reciente al más antiguo, cada uno con su número de tarjetas activas. Pagina con hasMore.
POST/public/lists
Crea un pipeline. Define tus propias stages o copia el esqueleto de etapas de otro pipeline. Acepta un encabezado Idempotency-Key.
GET/public/lists/{id}
Lee un pipeline: sus etapas en orden de tablero, cada una con su número de tarjetas y una página de tarjetas.
PATCH/public/lists/{id}
Renombra o cambia el color de un pipeline. Las etapas se editan con las rutas de etapas, no aquí.
DELETE/public/lists/{id}
Borra de forma suave un pipeline, sus etapas y sus tarjetas, e informa qué eliminó la cascada.
POST/public/lists/{id}/stages
Añade una etapa al final del tablero. Devuelve el arreglo completo de etapas del pipeline en orden de tablero.
PATCH/public/lists/{id}/stages/{stageId}
Renombra o cambia el color de una etapa. El orden del tablero no se toca.
PUT/public/lists/{id}/stages/order
Reordena el tablero. Envía stageIds con el orden completo deseado.
DELETE/public/lists/{id}/stages/{stageId}
Elimina una etapa, moviendo antes sus tarjetas a otra etapa del mismo pipeline. Devuelve las etapas restantes como { stagesRemaining }.
POST/public/lists/{id}/items
Añade un contacto existente a un pipeline como tarjeta.
PATCH/public/lists/{id}/items/{itemId}
Mueve una tarjeta a otra etapa del mismo pipeline.
DELETE/public/lists/{id}/items/{itemId}
Quita una tarjeta de un pipeline. El contacto en sí se conserva.
GET/public/usage
Lee tu cuota diaria. Esta llamada no cuenta para la cuota.

La lista oculta correos y teléfonos

GET /public/contacts devuelve filas compactas para que un agente pueda navegar y luego abrir un contacto. Para proteger contra la extracción masiva, la proyección de la lista omite emails y phones (regresan como arreglos vacíos) y devuelve companyId, locationGeoNameId y updatedAt como null. Usa GET /public/contacts/{id} para el detalle completo de un contacto, incluidos sus canales. Un contacto que pertenece a otro workspace devuelve un 404 indistinguible, nunca un 403, así que la API no puede sondearse para saber si un contacto existe en otra parte.

Autenticación

Envía tu clave de API con el encabezado x-api-key o como un token Authorization: Bearer. Es la misma clave de CazVid Developer API que las demás familias de la API - una sola clave sirve para todos los productos, controlada por alcance. Las claves pertenecen a una organización; los propietarios y administradores las gestionan en la app en Perfil > Developer API. Las claves en texto plano se muestran una sola vez al crearlas y CazVid las almacena solo como hash. Trata las claves como secretos: no las pongas en código de frontend, apps móviles, capturas de pantalla, registros ni mensajes de soporte.

x-api-key: cazvid_jb_...
Authorization: Bearer cazvid_jb_...
Content-Type: application/json
La CRM API requiere un plan Platinum, Diamond o Enterprise. Una clave en un plan inferior recibe un 403 PLAN_REQUIRED; una clave sin el alcance de CRM recibe un 403 SCOPE_FORBIDDEN. Los propietarios y administradores gestionan las claves desde la sección Developer API en CazVid.

Leer contactos

GET /public/contacts recibe un search opcional (de 1 a 200 caracteres) que coincide con nombre, empresa, puesto, habilidades, industria, educación, correo y teléfono, además de page (base 1) y pageSize (hasta 50, predeterminado 10). Pagina solo con hasMore - no hay conteo total. jobTitle es un arreglo: lleva todos los puestos del currículum del contacto, así que un contacto puede mostrar varios títulos. Las cadenas largas se truncan a 150 caracteres con un marcador ... [truncated].

GET/public/contacts
Lista o busca tus contactos. Devuelve una página; oculta los canales de contacto (ver la nota de privacidad).
curl "https://aio-backend-prod.cazvid.app/api/v3.0/crm/public/contacts?search=logistics%20manager&pageSize=10" \
  -H "x-api-key: cazvid_jb_..."
{
  "items": [
    {
      "id": "6a57e240f55eb331f6e396c8",
      "name": "Sidney G.",
      "firstName": "Sidney",
      "lastName": "G.",
      "jobTitle": ["Logistics Manager", "Operations Coordinator"],
      "companyName": "Acme Freight",
      "companyId": null,
      "emails": [],
      "phones": [],
      "linkedinUrl": "https://www.linkedin.com/in/example",
      "locationName": "Bogota, Colombia",
      "locationGeoNameId": null,
      "tags": [{ "id": "6a1f0b2c9c7c630206ba1e77", "name": "priority" }],
      "hasResume": true,
      "resumeCount": 1,
      "cazvidUserId": null,
      "linkedCazvidUser": false,
      "createdAt": "2026-07-01T12:30:00.000Z",
      "updatedAt": null
    }
  ],
  "page": 1,
  "pageSize": 10,
  "hasMore": true
}

GET /public/contacts/{id} devuelve el contacto completo. En el endpoint de lista, los campos marcados abajo como ocultos-en-lista están vacíos o en null por diseño.

GET/public/contacts/{id}
Lee un contacto completo, incluidos sus correos y teléfonos.
CampoTipoDescripción
idstringId del contacto. Úsalo con los endpoints de contacto individual, interacciones, currículum, actualización y eliminación.
namestring | nullNombre para mostrar.
firstNamestring | nullNombre de pila.
lastNamestring | nullApellido.
jobTitlestring[]Arreglo de puestos del currículum del contacto; un contacto puede tener varios.
companyNamestring | nullNombre de la empresa o empleador.
companyIdstring | nullId de la empresa vinculada. Oculto-en-lista: siempre null en el endpoint de lista.
emails{ email, type }[]Correos con etiquetas. Oculto-en-lista: vacío en el endpoint de lista; poblado en el de contacto individual.
phones{ phone, type }[]Teléfonos con etiquetas. Oculto-en-lista: vacío en el endpoint de lista; poblado en el de contacto individual.
linkedinUrlstring | nullURL del perfil de LinkedIn, cuando se conoce.
locationNamestring | nullUbicación en formato legible.
locationGeoNameIdnumber | nullId de GeoNames. Oculto-en-lista: siempre null en el endpoint de lista.
tags{ id, name }[]Etiquetas del workspace en el contacto, cada una devuelta como un objeto { id, name }.
hasResumebooleanSi el contacto tiene al menos un currículum adjunto.
resumeCountnumberNúmero de currículums adjuntos.
cazvidUserIdstring | nullId del usuario de la plataforma CazVid vinculado, cuando el contacto es un usuario de CazVid.
linkedCazvidUserbooleanSi el contacto está vinculado a un usuario de la plataforma CazVid.
createdAtstring | nullMarca de tiempo de creación ISO 8601.
updatedAtstring | nullMarca de tiempo de última actualización ISO 8601. Oculto-en-lista: siempre null en el endpoint de lista.
{
  "id": "6a57e240f55eb331f6e396c8",
  "name": "Sidney G.",
  "firstName": "Sidney",
  "lastName": "G.",
  "jobTitle": ["Logistics Manager", "Operations Coordinator"],
  "companyName": "Acme Freight",
  "companyId": "6a1f0b2c9c7c630206ba1e44",
  "emails": [{ "email": "sidney@example.com", "type": "work" }],
  "phones": [{ "phone": "+57 300 000 0000", "type": "mobile" }],
  "linkedinUrl": "https://www.linkedin.com/in/example",
  "locationName": "Bogota, Colombia",
  "locationGeoNameId": 3688689,
  "tags": [{ "id": "6a1f0b2c9c7c630206ba1e77", "name": "priority" }],
  "hasResume": true,
  "resumeCount": 1,
  "cazvidUserId": null,
  "linkedCazvidUser": false,
  "createdAt": "2026-07-01T12:30:00.000Z",
  "updatedAt": "2026-07-03T09:15:00.000Z"
}

Crear, actualizar y archivar contactos

Crea con POST /public/contacts (solo name es obligatorio), actualiza con PATCH /public/contacts/{id} (envía solo los campos que quieras cambiar) y archiva con DELETE /public/contacts/{id}. Los tres usan el alcance crm:write.

Crear es idempotente

Crear deduplica dentro del workspace por vínculo con usuario de CazVid, luego por correo y luego por teléfono, así que reenviar un contacto que lleve alguno de esos devuelve el existente (con deduplicated: true) en vez de crear un duplicado - no se necesita clave de idempotencia y reintentar tras un timeout es seguro. **Envía un correo o un teléfono si piensas reintentar**: solo name es obligatorio, pero un contacto creado solo con nombre no tiene clave de deduplicación, así que reintentarlo tras un timeout PUEDE crear un segundo contacto. deduplicated es orientativo (una heurística de marca de tiempo de creación que puede informar mal cuando el createdAt de un contacto existente cae en el mismo milisegundo que la solicitud, o en registros heredados con createdAt ausente o con desfase de reloj). Si un correo o teléfono coincide con más de un contacto existente, la API se niega con 409 CONTACT_CONFLICT en vez de adivinar cuál querías; reconcilia los duplicados y reintenta.

El cuerpo de creación acepta estos campos. La actualización acepta el mismo conjunto, todos opcionales.

CampoTipoRequeridoDescripción
namestringRequeridoNombre para mostrar. El único campo obligatorio al crear.
firstNamestringOpcionalNombre de pila.
lastNamestringOpcionalApellido.
jobTitlestring[]OpcionalUno o más puestos (hasta 10). Se guardan como el arreglo jobTitle del contacto.
companyNamestringOpcionalNombre de la empresa o empleador.
linkedinUrlstringOpcionalURL del perfil de LinkedIn.
locationNamestringOpcionalUbicación en texto libre (ciudad, región o país).
locationGeoNameIdintegerOpcionalId de GeoNames para una ubicación normalizada, cuando se conoce.
emails{ email, type? }[]OpcionalCorreos como objetos { email, type? } (type es work, personal u other; hasta 10 al crear). En la actualización, como mucho uno - reemplaza el correo principal.
phones{ phone, type? }[]OpcionalTeléfonos como objetos { phone, type? } (type es mobile, desk, home u other; hasta 10 al crear). En la actualización, como mucho uno - reemplaza el teléfono principal.
tagsstring[]OpcionalNombres de etiqueta del workspace (hasta 50). Un nombre de etiqueta desconocido crea esa etiqueta en el workspace.
curl -X POST https://aio-backend-prod.cazvid.app/api/v3.0/crm/public/contacts \
  -H "x-api-key: cazvid_jb_..." \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Sidney G.",
    "companyName": "Acme Freight",
    "jobTitle": ["Logistics Manager"],
    "emails": [{ "email": "sidney@example.com", "type": "work" }],
    "phones": [{ "phone": "+57 300 000 0000", "type": "mobile" }]
  }'
{
  "id": "6a57e240f55eb331f6e396c8",
  "deduplicated": false,
  "name": "Sidney G.",
  "firstName": "Sidney",
  "lastName": "G.",
  "jobTitle": ["Logistics Manager"],
  "companyName": "Acme Freight",
  "companyId": null,
  "emails": [{ "email": "sidney@example.com", "type": "work" }],
  "phones": [{ "phone": "+57 300 000 0000", "type": "mobile" }],
  "linkedinUrl": null,
  "locationName": null,
  "locationGeoNameId": null,
  "tags": [],
  "hasResume": false,
  "resumeCount": 0,
  "cazvidUserId": null,
  "linkedCazvidUser": false,
  "createdAt": "2026-07-25T12:30:00.000Z",
  "updatedAt": "2026-07-25T12:30:00.000Z"
}
En la actualización, emails y phones se limitan a una sola entrada cada uno: un PATCH reemplaza el correo o teléfono principal del contacto en vez de añadir, así que envía como mucho uno de cada. deduplicated solo está presente en la respuesta de creación; se omite en la actualización.
Eliminar es un archivado suave, no un borrado permanente: el contacto se conserva y es recuperable, pero desaparece de la búsqueda y sus interacciones y pertenencias a listas se archivan con él.

Currículums

POST /public/contacts/{id}/resume adjunta un currículum a un contacto como un archivo base64 en línea. fileName, mimeType y contentBase64 son todos obligatorios. Devuelve 202 Accepted y consume una llamada diaria de CRM más una de una cuota de subida de currículums separada por clave (predeterminado 50 por día UTC; ver el bloque resumeUpload en /public/usage).

POST/public/contacts/{id}/resume
Adjunta un currículum (PDF, DOC o DOCX en base64) y encola un análisis asíncrono.
  • mimeType debe ser uno de application/pdf, application/msword o application/vnd.openxmlformats-officedocument.wordprocessingml.document (PDF, DOC, DOCX).
  • El archivo decodificado debe pesar como mucho 10 MB, y su tipo real se verifica a partir de sus magic bytes contra mimeType; una discrepancia devuelve 400 MIME_MISMATCH, un archivo vacío 400 EMPTY_FILE, y una entrada no decodificable 400 INVALID_BASE64.
  • El análisis es asíncrono: el 202 confirma que el currículum se encoló. Los campos analizados (nombre, correo, teléfono, ubicación, puesto, habilidades, LinkedIn) se escriben en el contacto después, llenando solo sus campos vacíos. Consulta GET /public/contacts/{id} para ver actualizarse hasResume y resumeCount.
curl -X POST https://aio-backend-prod.cazvid.app/api/v3.0/crm/public/contacts/6a57e240f55eb331f6e396c8/resume \
  -H "x-api-key: cazvid_jb_..." \
  -H "Content-Type: application/json" \
  -d '{
    "fileName": "sidney-resume.pdf",
    "mimeType": "application/pdf",
    "contentBase64": "JVBERi0xLjc...=="
  }'

Interacciones

Una interacción es una entrada de la línea de tiempo de un contacto - una llamada, correo, entrevista, oferta, nota, etc. Registra una con POST /public/contacts/{id}/interactions (type es obligatorio), lee la línea de tiempo de la más reciente a la más antigua con GET /public/contacts/{id}/interactions, y descubre los nombres de tipo válidos con GET /public/interaction-types. El registro nunca se deduplica: cada llamada crea una entrada nueva, así que no reintentes a ciegas una llamada que quizá ya tuvo éxito. Puedes pasar una date futura para registrar un seguimiento programado.

POST/public/contacts/{id}/interactions
Registra una interacción (una entrada de la línea de tiempo, como una llamada, entrevista o nota).
GET/public/contacts/{id}/interactions
Lee la línea de tiempo de interacciones de un contacto, de la más reciente a la más antigua.
GET/public/interaction-types
Lista los nombres de tipo de interacción del catálogo global fijo, agrupados por categoría.

El catálogo de interacciones es fijo y global

type debe ser un nombre de un catálogo global fijo que es el mismo para cada workspace; un nombre desconocido se rechaza con 400 UNKNOWN_INTERACTION_TYPE, cuyo details lista todos los nombres válidos. Llama a GET /public/interaction-types para la lista autoritativa y actual en vez de codificarlos. Los nombres se agrupan en siete categorías:

communicationassessmentinterviewsoffernextstepsotherrejected
curl -X POST https://aio-backend-prod.cazvid.app/api/v3.0/crm/public/contacts/6a57e240f55eb331f6e396c8/interactions \
  -H "x-api-key: cazvid_jb_..." \
  -H "Content-Type: application/json" \
  -d '{
    "type": "phonecall",
    "notes": "Left a voicemail about the operations role.",
    "date": "2026-07-04T16:00:00.000Z"
  }'

Listas y pipelines

Una lista es un pipeline de contratación: un tablero con nombre y etapas ordenadas que contiene una tarjeta por candidato. Es el mismo objeto que la app de CazVid llama lista, así que un pipeline que construyas aquí aparece en el tablero que usan tus reclutadores, y los movimientos que ellos hagan arrastrando tarjetas se ven a través de estas rutas. Doce endpoints cubren los tres niveles: el pipeline, sus etapas y sus tarjetas. Las lecturas usan crm:read; toda escritura usa crm:write.

GET/public/lists
Lista tus pipelines, del más reciente al más antiguo, cada uno con su número de tarjetas activas. Pagina con hasMore.
POST/public/lists
Crea un pipeline. Define tus propias stages o copia el esqueleto de etapas de otro pipeline. Acepta un encabezado Idempotency-Key.
GET/public/lists/{id}
Lee un pipeline: sus etapas en orden de tablero, cada una con su número de tarjetas y una página de tarjetas.
PATCH/public/lists/{id}
Renombra o cambia el color de un pipeline. Las etapas se editan con las rutas de etapas, no aquí.
DELETE/public/lists/{id}
Borra de forma suave un pipeline, sus etapas y sus tarjetas, e informa qué eliminó la cascada.
POST/public/lists/{id}/stages
Añade una etapa al final del tablero. Devuelve el arreglo completo de etapas del pipeline en orden de tablero.
PATCH/public/lists/{id}/stages/{stageId}
Renombra o cambia el color de una etapa. El orden del tablero no se toca.
PUT/public/lists/{id}/stages/order
Reordena el tablero. Envía stageIds con el orden completo deseado.
DELETE/public/lists/{id}/stages/{stageId}
Elimina una etapa, moviendo antes sus tarjetas a otra etapa del mismo pipeline. Devuelve las etapas restantes como { stagesRemaining }.
POST/public/lists/{id}/items
Añade un contacto existente a un pipeline como tarjeta.
PATCH/public/lists/{id}/items/{itemId}
Mueve una tarjeta a otra etapa del mismo pipeline.
DELETE/public/lists/{id}/items/{itemId}
Quita una tarjeta de un pipeline. El contacto en sí se conserva.

Las escrituras de pipelines son a nivel de workspace

Cualquier clave con alcance sobre un workspace puede gestionar cualquier pipeline dentro de él, sin importar qué usuario de la app lo creó: en estas rutas no hay propiedad por usuario. El límite del workspace sí es absoluto: un pipeline, etapa, tarjeta o contacto de otro workspace devuelve el mismo 404 NOT_FOUND que un id inexistente, así que la API no puede sondearse para saber qué existe en otra parte. La única regla más estrecha es copyFromListId al crear, que en la v1 solo resuelve pipelines creados por el usuario de tu propia clave; el pipeline de otro miembro devuelve 404 incluso dentro de tu workspace.

curl "https://aio-backend-prod.cazvid.app/api/v3.0/crm/public/lists?pageSize=10" \
  -H "x-api-key: cazvid_jb_..."

Crear un pipeline acepta un Idempotency-Key

POST /public/lists es la única escritura del CRM sin deduplicación natural - dos llamadas idénticas crean dos pipelines - así que acepta un encabezado opcional Idempotency-Key con cualquier cadena opaca que puedas reproducir al reintentar (un id de ejecución, una marca de semana). Una repetición con la misma clave Y el mismo cuerpo repite el primer resultado durante 24 horas, devolviendo el pipeline original con su id original y un encabezado de respuesta Idempotency-Replayed: true; una respuesta sin ese encabezado fue creada por esa solicitud. La misma clave con un cuerpo distinto devuelve 422 IDEMPOTENCY_KEY_REUSED y seguirá fallando, así que usa una clave nueva. Una repetición enviada mientras la primera creación sigue en curso devuelve 409 IDEMPOTENCY_IN_PROGRESS: no se duplicó nada, reintenta en un momento para obtener la repetición. Esa reserva en curso dura poco (unos 2 minutos, no 24 horas): si la solicitud original murió a mitad de la creación, la reserva expira sola y el siguiente reintento crea el pipeline. Una creación que FALLA nunca se recuerda, y la clave se recuerda por organización y por workspace resuelto.

curl -X POST https://aio-backend-prod.cazvid.app/api/v3.0/crm/public/lists \
  -H "x-api-key: cazvid_jb_..." \
  -H "Idempotency-Key: weekly-pipeline-2026-W32" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Warehouse Hiring 2026",
    "color": "#0EA5E9",
    "stages": [
      { "name": "Sourced", "color": "#64748B" },
      { "name": "Phone Screen", "color": "#4F46E5" },
      { "name": "Offer", "color": "#16A34A" }
    ]
  }'

GET /public/lists devuelve todos los campos de abajo excepto updatedAt y stages. GET /public/lists/{id} añade esos dos y aplica la misma ventana page / pageSize a CADA etapa, devolviéndolos junto con hasMore en el nivel superior. pageSize llega hasta 50 y su valor predeterminado es 10; page es de base 1 y su máximo es 10000. hasMore es true cuando CUALQUIER etapa tiene más tarjetas, no solo la más grande.

CampoTipoDescripción
idstringId del pipeline. Úsalo en cada ruta /public/lists/{id}.
namestring | nullNombre del pipeline tal como se muestra en el tablero.
colorstring | nullColor del pipeline, tal como está guardado: un código hex o un token de color simple.
templateTypestring | nullLa marca de plantilla de etapas. TODO pipeline creado con esta API se marca como CANDIDATE_PIPELINE_EN, incluso uno creado con tus propias etapas o copiado de otro tablero, así que no indica cuáles son realmente las etapas; los creados automáticamente para una publicación de empleo llevan un valor JOBSEARCH_PIPELINE_. Es solo informativo y nunca cambia después de la creación.
associatedPostsstring[]Ids de las publicaciones de empleo vinculadas a este pipeline. Vacío para un pipeline creado con esta API: los vínculos con publicaciones se hacen en la app de CazVid.
itemCountnumberNúmero de tarjetas activas en el pipeline, en todas sus etapas.
createdAtstring | nullMarca de tiempo de creación ISO 8601.
updatedAtstring | nullMarca de tiempo del último cambio ISO 8601. Solo en la lectura de un pipeline individual.
stagesStage[]Las etapas del tablero en orden de tablero (zorder ascendente), cada una con id, name, color, zorder, su itemCount total y una página de items. Solo en la lectura de un pipeline individual.
{
  "id": "6a3ff55406ae2ed06ddb5fd2",
  "name": "Warehouse Hiring 2026",
  "color": "#0EA5E9",
  "templateType": "CANDIDATE_PIPELINE_EN",
  "associatedPosts": [],
  "itemCount": 1,
  "createdAt": "2026-08-01T10:00:00.000Z",
  "updatedAt": "2026-08-02T09:12:00.000Z",
  "page": 1,
  "pageSize": 10,
  "hasMore": false,
  "stages": [
    {
      "id": "6a3ff55406ae2ed06ddb5fe1",
      "name": "Sourced",
      "color": "#64748B",
      "zorder": 1,
      "itemCount": 1,
      "items": [
        {
          "id": "6a3ff55406ae2ed06ddb5fd1",
          "listId": "6a3ff55406ae2ed06ddb5fd2",
          "stageId": "6a3ff55406ae2ed06ddb5fe1",
          "contactId": "6a57e240f55eb331f6e396c8",
          "authorId": null,
          "rating": null,
          "applicationStatus": null,
          "disqualified": false,
          "disqualifiedReason": null,
          "source": "contact",
          "createdAt": "2026-08-01T10:05:00.000Z",
          "updatedAt": "2026-08-01T10:05:00.000Z"
        }
      ]
    }
  ]
}

Una tarjeta lleva ids, nunca datos personales

Los doce campos de abajo son todo lo que expone una tarjeta: ids y estado del pipeline. Por diseño no hay nombre, correo, teléfono ni currículum en una tarjeta. Combina el contactId de la tarjeta con GET /public/contacts/{id} para esos datos: esa ruta es la única lectura que aplica la verificación de privacidad del propio candidato vinculado, y la lectura del tablero deliberadamente no repite nada de eso. authorId se establece cuando el postulante es un usuario de la plataforma CazVid, quien puede no tener ningún registro de contacto en el CRM, en cuyo caso contactId es null.

Cada tarjeta del arreglo items de una etapa lleva exactamente estos doce campos.

CampoTipoDescripción
idstringId de la tarjeta. Úsalo en las rutas /items/{itemId}.
listIdstringId del pipeline al que pertenece esta tarjeta.
stageIdstring | nullId de la etapa en la que está la tarjeta actualmente.
contactIdstring | nullId del contacto del CRM detrás de esta tarjeta. Combínalo con GET /public/contacts/{id} para el nombre, correo y teléfono de la persona.
authorIdstring | nullId del usuario de la plataforma CazVid detrás de esta tarjeta, presente cuando el postulante es usuario de CazVid. Ese usuario puede no tener registro de contacto en el CRM, en cuyo caso contactId es null.
rating"good_fit" | "maybe" | "not_a_fit" | nullCalificación del reclutador en la tarjeta, cuando se estableció en la app. No es escribible con esta API en la v1.
applicationStatus"invited" | "in_progress" | "completed" | "disqualified" | nullEstado de la postulación de la tarjeta, cuando se estableció. No es escribible con esta API en la v1.
disqualifiedbooleanSi la tarjeta fue descalificada.
disqualifiedReasonstring | nullMotivo en texto libre registrado cuando se descalificó la tarjeta.
sourcestring | nullCómo llegó la tarjeta al pipeline: contact para un alta por API o CRM, o el origen de la postulación cuando es un postulante a un empleo.
createdAtstring | nullMarca de tiempo ISO 8601 de cuando se añadió la tarjeta al pipeline.
updatedAtstring | nullMarca de tiempo ISO 8601 del último cambio de la tarjeta.
curl -X POST https://aio-backend-prod.cazvid.app/api/v3.0/crm/public/lists/6a3ff55406ae2ed06ddb5fd2/items \
  -H "x-api-key: cazvid_jb_..." \
  -H "Content-Type: application/json" \
  -d '{
    "contactId": "6a57e240f55eb331f6e396c8",
    "stageId": "6a3ff55406ae2ed06ddb5fe1"
  }'
  • zorder es de base 1 y contiguo. Cada cambio en las etapas renumera todo el tablero, así que zorder: 1 es siempre la etapa donde caen los candidatos nuevos.
  • Un pipeline admite como máximo 30 etapas activas cuando envías tus propias stages o añades una con POST /public/lists/{id}/stages. copyFromListId deliberadamente NO aplica ese límite, así que copiar un tablero más largo te deja un pipeline por encima del máximo, y uno que ya no se puede reordenar con esta API, porque stageIds también está limitado a 30.
  • stages y copyFromListId son mutuamente excluyentes al crear (400 VALIDATION_ERROR si envías ambos). Si no envías ninguno obtienes el pipeline de candidatos predeterminado. No hay selector de idioma: crea un tablero en español enviando tus propias stages o copiando uno existente en español.
  • Las reglas de nombre son distintas para un pipeline y para sus etapas. El name de un pipeline solo acepta palabras alfanuméricas separadas por un solo espacio, así que un acento, guion, apóstrofo, ampersand, punto o espacio doble devuelve 400 VALIDATION_ERROR. Los nombres de etapa no tienen esa regla: aceptan caracteres acentuados y barras, que es como se nombran los tableros en español predefinidos. Así que Contratación Almacén se rechaza como nombre de pipeline pero se acepta como nombre de etapa.
  • Añadir una tarjeta requiere un contacto existente. POST /public/lists/{id}/items recibe un contactId, no los datos de la persona, así que crea primero el contacto. Añadir el mismo contacto dos veces nunca duplica la tarjeta, pero una repetición con un stageId distinto la mueve: trata la ubicación en la etapa como una escritura intencional, no como una operación sin efecto.
  • stageId debe pertenecer al pipeline de la ruta. La etapa de otro pipeline devuelve 404 NOT_FOUND con details[0].field igual a stageId; una tarjeta nunca puede moverse a un tablero ajeno. Añadir una tarjeta a un pipeline sin etapas devuelve 409 LIST_HAS_NO_STAGES: el id es válido, así que añade una etapa en vez de buscar otro pipeline.
  • Las tarjetas nunca se pierden en silencio. Eliminar una etapa que contiene tarjetas exige ?moveToStageId, otra etapa activa del mismo pipeline; sin él la llamada devuelve 400 MOVE_TO_STAGE_REQUIRED, y un moveToStageId que no sea una etapa activa de ese pipeline devuelve 404 NOT_FOUND nombrando moveToStageId. La última etapa que le queda a un pipeline no puede eliminarse (400 LAST_STAGE_UNDELETABLE), y una etapa con una cantidad extrema de tarjetas devuelve 400 TOO_MANY_ITEMS_TO_REASSIGN.
  • PUT /public/lists/{id}/stages/order recibe el orden completo: stageIds debe contener exactamente los ids de las etapas activas actuales del pipeline, sin faltantes, sobrantes, ajenos ni repetidos.
  • Cada ruta de pipelines consume exactamente una llamada de la misma cuota diaria, el mismo límite por minuto y el mismo límite por IP que las rutas de contactos e interacciones. No hay un pool aparte ni un multiplicador por ruta.
  • Eliminar un pipeline informa su alcance. La respuesta lleva effects con stagesDeleted e itemsDeleted, contados antes de ejecutar la cascada. La cascada borra de forma suave el pipeline, sus etapas y sus tarjetas, y quita el vínculo del pipeline en la publicación de empleo que apuntaba a él: como mucho una, y no se cuenta en effects, así que vuelve a leer la publicación si necesitas confirmarlo. Los contactos en sí no se tocan.
{
  "id": "6a3ff55406ae2ed06ddb5fd2",
  "deleted": true,
  "effects": {
    "stagesDeleted": 3,
    "itemsDeleted": 12
  }
}

Consultar la cuota restante

GET /public/usage devuelve tu cuota diaria de CRM y la cuota de subida de currículums separada por clave. Usa la misma autenticación que los demás endpoints y no cuenta para la cuota, así que puedes consultarlo antes de un lote para evitar respuestas 429. Las consultas están limitadas a 60 solicitudes por minuto por clave de API.

GET/public/usage
Lee tu cuota diaria. Esta llamada no cuenta para la cuota.
curl https://aio-backend-prod.cazvid.app/api/v3.0/crm/public/usage \
  -H "x-api-key: cazvid_jb_..."
CampoTipoDescripción
dailyQuotanumberTotal de llamadas de CRM exitosas permitidas por organización por día UTC (500).
usednumberLlamadas de CRM que consumen cuota realizadas hoy (UTC) con las claves de esta organización.
remainingnumberLlamadas exitosas restantes antes de que se reinicie la cuota.
resetsAtstring (ISO 8601)Marca de tiempo ISO 8601 de la próxima medianoche UTC, cuando se reinicia la cuota.
period"utc_day"Ventana de la cuota. Siempre utc_day en la v1.
resumeUpload{ dailyLimit, used, remaining }La cuota de subida de currículums separada por clave para hoy: dailyLimit (50), used y remaining.
{
  "dailyQuota": 500,
  "used": 39,
  "remaining": 461,
  "resetsAt": "2026-07-26T00:00:00.000Z",
  "period": "utc_day",
  "resumeUpload": {
    "dailyLimit": 50,
    "used": 0,
    "remaining": 50
  }
}

Errores

Las respuestas de error usan valores errorCode estables para que las integraciones no tengan que analizar mensajes de texto. Cada error devuelve el path de la solicitud; las respuestas 429 añaden retryAfter, los segundos hasta que se reinicia la ventana del límite. Un 429 de la cuota diaria lleva details.period utc_day; un 429 del límite por minuto o del límite por IP lleva minute.

EstadoCódigoSignificado
400VALIDATION_ERRORLa solicitud no pasó la validación (por ejemplo un id mal formado, una página o un tamaño de página fuera de rango, un nombre de pipeline con acento o puntuación, o un campo del cuerpo que no está en la lista permitida). details indica cada campo con problema.
400UNKNOWN_INTERACTION_TYPEEl type de una interacción registrada no está en el catálogo global. details lista todos los nombres válidos.
400MIME_MISMATCHEl tipo real del archivo subido no coincide con el mimeType declarado.
400EMPTY_FILEEl currículum subido se decodificó a un archivo vacío.
400INVALID_BASE64No se pudo decodificar contentBase64.
400MOVE_TO_STAGE_REQUIREDSe envió una eliminación de una etapa que aún contiene tarjetas sin ?moveToStageId. La llamada se rechaza antes de escribir nada: las tarjetas nunca se eliminan en silencio.
400LAST_STAGE_UNDELETABLELa única etapa que le queda al pipeline no se puede eliminar. Un pipeline siempre conserva al menos una.
400TOO_MANY_ITEMS_TO_REASSIGNLa etapa contiene más tarjetas de las que una sola solicitud puede reasignar. Mueve algunas a otra etapa primero.
401API_KEY_MISSINGNo se envió ninguna clave de API.
401API_KEY_INVALIDLa clave no existe o no se puede verificar.
401API_KEY_EXPIREDLa clave superó su fecha de expiración.
401API_KEY_REVOKEDLa clave fue revocada.
403PLAN_REQUIREDLa CRM API requiere un plan Platinum, Diamond o Enterprise.
403SCOPE_FORBIDDENLa clave carece del alcance de CRM requerido (crm:read para lecturas, crm:write para escrituras).
403ORG_ACCESS_DENIEDLa clave no puede acceder a esta organización.
403WORKSPACE_ACCESS_DENIEDEl workspaceId en la solicitud no pertenece a tu clave de API.
404NOT_FOUNDNo existe un contacto, pipeline, etapa o tarjeta con ese id en el workspace resuelto. Uno de otro workspace devuelve este mismo 404. En las rutas de pipelines, details[0].field indica qué id corregir (id, stageId, itemId, contactId o moveToStageId).
409CONTACT_CONFLICTEl correo o teléfono coincide con más de un contacto existente, así que no se puede resolver automáticamente. Reconcilia los duplicados y reintenta.
409LIST_HAS_NO_STAGESSe añadió una tarjeta a un pipeline que no tiene ninguna etapa activa donde ubicarla. Añade una etapa y reintenta.
409IDEMPOTENCY_IN_PROGRESSUn POST /public/lists con este Idempotency-Key sigue en curso. No se duplicó nada; reintenta en un momento para obtener la repetición.
413PAYLOAD_TOO_LARGEEl currículum decodificado supera el límite de 10 MB.
422IDEMPOTENCY_KEY_REUSEDEl Idempotency-Key ya se usó con un cuerpo de solicitud distinto. Usa una clave nueva; reintentar no ayudará.
429RATE_LIMIT_EXCEEDEDSe alcanzó un límite: la cuota diaria (details.period utc_day) o el límite por minuto o por IP (details.period minute).
500CRM_API_ERROROcurrió un error inesperado del servidor, o una dependencia no estaba disponible.
{
  "statusCode": 429,
  "errorCode": "RATE_LIMIT_EXCEEDED",
  "message": "CRM API burst limit exceeded. Slow down and retry shortly.",
  "details": {
    "limit": 30,
    "period": "minute"
  },
  "retryAfter": 43,
  "path": "/api/v3.0/crm/public/contacts"
}

Límites y comportamiento

  • La cuota diaria de CRM es de 500 llamadas exitosas por organización por día UTC. Es una cuota separada de las demás familias de la API. Cada llamada con cuota (lecturas y escrituras) consume una; GET /public/usage es gratis.
  • Un límite por organización de 30 llamadas por minuto, más un límite compartido de 60 por minuto por IP, protegen el backend. Al exceder cualquiera se devuelve 429 con details.period en minute.
  • Las subidas de currículum tienen su propia cuota de 50 por clave de API por día UTC, además de la llamada diaria que cada subida también consume.
  • El endpoint de lista oculta correos, teléfonos, companyId, locationGeoNameId y updatedAt; consulta un contacto individual para esos. jobTitle es un arreglo, y las cadenas largas se truncan a 150 caracteres.
  • Un contacto en otro workspace devuelve un 404 indistinguible. Rota las claves creando una nueva, actualizando tu integración y luego revocando la anterior.
  • Las rutas de pipelines comparten ese mismo pool: cada una consume una sola llamada diaria, y sus escrituras son a nivel de workspace (cualquier clave con alcance sobre el workspace puede gestionar cualquier pipeline dentro de él). Un pipeline, etapa o tarjeta de otro workspace devuelve el mismo 404 indistinguible.

Usar desde un agente (MCP)

¿Prefieres controlar tu CRM desde un agente de IA en vez de HTTP directo? El servidor MCP de CazVid expone las nueve herramientas crm_* (buscar, obtener, crear, actualizar, eliminar contacto, registrar y listar interacciones, listar tipos de interacción y subir currículum) además de list_api_usage a Claude Code, Claude.ai, ChatGPT, Cursor y VS Code, con esta misma clave de API y cuota.

Lee la documentación del servidor MCP