Skip to main content
El citations artifact es un JSON que mapea cada field del response de una sección a sus coordenadas exactas (bounding_boxes) dentro del PDF original. Lo usas para construir UIs auditables: al mostrar un dato extraído, resaltas la región del documento de donde Trébol lo extrajo. Trébol genera el artifact cuando termina la verificación y te entrega una URL firmada para descargarlo.

Endpoints con soporte de citations

EndpointSección/TipoDónde aparece citationsPaths de ejemplo
GET /v2/verifications/{id}/peoplepeopledata.citations.url (único artifact)key_people[0].roles[0].role_name, signatory_groups.executives[0].names
GET /v2/companies/{tag}/peoplepeopledata.citations.url (único artifact)ídem
GET /v2/verifications/{id}/shareholdersshareholdersdata.citations.url (único artifact)shareholders[0].name, shareholders[0].id_number, shareholders[0].fixed_value
GET /v2/companies/{tag}/shareholdersshareholdersdata.citations.url (único artifact)ídem
GET /v2/verifications/{id}/sourcessources (acta)data.sources[i].citations.url (por item)notary_info.notaryName, corporate_purpose.object, assembly_minute.resolutions, shareholders[0].name, board_members[0].name
GET /v2/companies/{tag}/sourcessources (acta)data.sources[i].citations.url (por item)ídem
GET /verifications/{id}items de actaitems[i].citations.url (por item de acta)ídem
GET /verification-items/{id}item de actacitations.url (en el objeto raíz)ídem

Cómo activarlo

Agrega el query parameter ?with_citations=true al endpoint de la sección. El response añade un campo data.citations con la URL firmada al JSON. Tienes dos rutas equivalentes: por verification_id o por etiqueta.
curl -X GET "https://api.gotrebol.com/v2/verifications/{verification_id}/people?with_citations=true" \
     -H "x-api-key: {api_key}"
La URL firmada caduca 1 hora después de generarla. Descarga el JSON y persiste su contenido en tu lado. Si la URL caduca, vuelve a llamar al endpoint con el flag para obtener una nueva.
Trébol genera el artifact con el evento verification.v2.finished. Los requests anteriores a ese evento no traen la url de citations.

Estructura del artifact

El artifact es un JSON con un schema versionado. Hoy la versión activa es citations/v1.

Top level

CampoTipoDescripción
schema_versionstringVersión del schema. Actualmente "citations/v1".
verification_idstringID de la verificación a la que pertenece el artifact.
sectionstringSección citada: "people", "shareholders" o "acta". Los artifacts de sources tienen section: "acta" y además incluyen item_id.
fieldsField[]Lista plana de fields citados con sus coordenadas.
unresolvedUnresolvedField[]Fields que Trébol extrajo pero no logró mapear a coordenadas. Vacío en el caso normal. Cada objeto trae path y value, pero sin bounding_boxes.
item_idnumber(Solo artifacts de sources) ID del item al que pertenece el artifact. Corresponde al id del source en el response de la sección.

Objeto Field

CampoTipoDescripción
pathstringRuta del field en el response de la sección, en notación de punto + índices (ej. key_people[3].roles[0].powers[0].power_name, shareholders[0].id_number). El índice refleja la posición en el array del endpoint — misma posición que el UI renderiza.
valuestring | number | booleanValor extraído. Coincide con el valor en el response de /people.
sourceSourceMetadata del documento fuente: item_id, item_type, document_date, document_number.
bounding_boxesBoundingBox[]Una o más coordenadas. Un mismo field puede tener varios bboxes si la información aparece en distintas partes del documento.

Objeto BoundingBox

CampoTipoDescripción
pagenumberPágina del PDF (1-indexed).
vertices[number, number][]4 puntos [x, y] con coordenadas normalizadas en [0, 1] respecto al tamaño de la página renderizada. Origen top-left (y crece hacia abajo).
textstringSnippet de texto OCR del bbox. Útil para mostrar contexto sin renderizar el PDF.
Los 4 vértices no siempre forman un rectángulo aligned con los ejes. Documentos con texto rotado (sellos, firmas) producen polígonos inclinados. Renderiza los 4 vértices como un polígono, no como un rect axis-aligned.

Ejemplos

{
  "schema_version": "citations/v1",
  "verification_id": "79aa3663-ca3f-4aaa-a354-ae4630b96f3b",
  "section": "people",
  "fields": [
    {
      "path": "key_people[3].roles[0].duration",
      "value": -1,
      "source": {
        "item_id": 37257,
        "item_type": "ac_mx",
        "document_date": "2015-08-25",
        "document_number": "10223"
      },
      "bounding_boxes": [
        {
          "page": 6,
          "vertices": [
            [0.1193, 0.1484],
            [0.8623, 0.1554],
            [0.8594, 0.2666],
            [0.1164, 0.2596]
          ],
          "text": "--- VIGÉSIMA SEGUNDA .- La administración de la sociedad estará a cargo de un administrador único..."
        }
      ]
    }
  ],
  "unresolved": []
}
El campo value refleja el valor extraído originalmente del documento. Para people, algunos fields usan valores centinela: duration se expresa en años y -1 significa cargo indefinido.

Cómo manejar unresolved

Cuando Trébol extrae un dato pero no logra ubicarlo en el PDF (texto re-flujado, tablas complejas, OCR de baja confianza), el field cae en unresolved en vez de fields. Estos objetos traen path y value, pero no bounding_boxes. En tu UI, muestra el valor normalmente y omite el botón de “ver cita” para esos fields. No los escondas: el dato sigue siendo válido, solo no tiene coordenadas que resaltar.

Cómo implementarlo en tu UI

Obtener el PDF original

El artifact te da las coordenadas, pero no el PDF. Cada field trae source.item_id: ese es el documento del que salió el dato. Para resaltar la cita necesitas el PDF de ese mismo item_id. La URL del PDF vive en el response de la sección, no en el artifact. Para people, cada objeto source dentro del response incluye item_id y document_url. Para shareholders, la URL del PDF del acta está en data.source.document_url (un único documento fuente para todos los accionistas). Para sources, cada item del array expone document_url directamente — ese es el PDF del acta. El artifact de ese item trae item_id en el nivel raíz: úsalo para identificar el item correcto cuando hay múltiples actas; no cruces por field.source.item_id dentro del artifact (todos los fields de un artifact de acta pertenecen al mismo documento).
// Para people: recorre el response y arma item_id -> document_url
function buildPdfUrlMap(sectionData) {
  const map = new Map();
  JSON.stringify(sectionData, (key, val) => {
    if (val && typeof val === "object" && val.item_id && val.document_url) {
      map.set(val.item_id, val.document_url);
    }
    return val;
  });
  return map;
}
const peoplePdfMap = buildPdfUrlMap(peopleData);
const peoplePdfUrl = peoplePdfMap.get(field.source.item_id);

// Para shareholders: el PDF del acta está directamente en data.source
const shareholdersPdfUrl = shareholdersData.source?.document_url ?? null;

// Para sources: cruza por artifact.item_id (nivel raíz del artifact) para
// obtener el item correcto; su document_url es el PDF a renderizar.
const sourceItem = sourcesData.sources.find(s => s.id === artifact.item_id);
const sourcesPdfUrl = sourceItem?.document_url ?? null;
document_url también es una URL firmada que caduca en 1 hora. Resuélvela en el momento de renderizar, no la persistas. Si está vacía o null, ese item_id no tiene PDF disponible para citar.

Receta de renderizado

La receta que recomendamos: usa el componente PDFViewer de pdfjs-dist (no pdf.js core directo) y monta un <canvas> overlay como hijo del div que el viewer crea para cada página. Dibuja el polígono con moveTo/lineTo sobre los 4 vértices reales. Esto te da text layer, scroll, virtualización y zoom sin escribir código.

Arquitectura mínima

1

Cargar pdfjs-dist y setear el worker

Importa pdfjs-dist y asigna GlobalWorkerOptions.workerSrc para que el worker quede disponible.
2

Instanciar PDFViewer

Crea un EventBus y un PDFViewer apuntando a un contenedor con position: absolute (requerido por el viewer).
3

Cargar el PDF original

Usa pdfjs.getDocument(PDF_URL).promise y pásalo a viewer.setDocument().
4

Al seleccionar un field, dibuja el bbox

Toma el bbox, multiplica cada vértice por pageView.viewport.width/height, dibuja el polígono en un <canvas> agregado como hijo de pageView.div y scrollea con viewer.scrollPageIntoView().
5

Redibuja en zoom

Suscríbete a eventBus.on("scalechanging", ...). El canvas está en píxeles fijos: sin esto, el highlight se desfasa al cambiar el zoom.

Ejemplo funcional

Copia este snippet a un archivo local, reemplaza PDF_URL y ARTIFACT_URL por valores reales, y ábrelo en el navegador.
<!doctype html>
<html>
  <head>
    <link
      rel="stylesheet"
      href="https://cdn.jsdelivr.net/npm/pdfjs-dist@4.7.76/web/pdf_viewer.css"
    />
    <style>
      #viewerContainer {
        position: absolute;
        top: 60px;
        left: 0;
        width: 70%;
        height: 80%;
        overflow: auto;
      }
    </style>
  </head>
  <body>
    <button id="cite">Mostrar cita</button>
    <div id="viewerContainer"><div id="viewer" class="pdfViewer"></div></div>
    <script type="module" src="./viewer.js"></script>
  </body>
</html>

Detalles importantes de integración

  • Worker de pdf.js: con bundlers modernos usa new URL("pdfjs-dist/build/pdf.worker.mjs", import.meta.url).toString(). Sin bundler, sirve pdf.worker.mjs desde tu propio host o desde un CDN.
  • CSS del viewer: importa pdfjs-dist/web/pdf_viewer.css o las páginas se ven sin estilo.
  • Coordenadas normalizadas: multiplica nx * viewport.width y ny * viewport.height. No uses viewport.convertToViewportPoint: asume coordenadas en espacio PDF (bottom-left) y aquí las coordenadas son top-left normalizadas.
  • bbox.page es 1-indexed, pero viewer.getPageView() espera 0-indexed. Resta 1.
  • Zoom: el overlay vive en píxeles fijos. Sin escuchar scalechanging y redibujar, el highlight se desfasa.
  • Polígonos rotados: dibuja siempre como polígono (moveTo + lineTo + closePath), no como rect axis-aligned.
  • Páginas virtualizadas: PDFViewer no renderiza todas las páginas hasta que entran al viewport. Si getPageView() devuelve undefined, llama primero scrollPageIntoView y espera al evento pagerendered.
  • CORS: el bucket del PDF y el del artifact deben permitir el origen de tu app. Si tu backend hace fetch del artifact y lo sirve a tu front, este punto desaparece.

Siguientes pasos

Leer una verificación

Cómo consumir el endpoint /people y otras secciones de la verificación.

Webhooks

Suscríbete a verification.v2.finished para saber cuándo el artifact está listo.

Tipos de item

Qué items alimentan la sección people y, por tanto, generan citas.