API de informes

Crear, leer, firmar informes programáticamente

La API de informes te permite subir PDFs de informes radiológicos a cada examen, leer el informe vigente, eliminarlo y firmarlo electrónicamente — todo desde tu sistema. La firma genera un signature_id con hash SHA-256 del PDF y una URL pública de verificación (con QR opcional) para que cualquiera pueda validar la autenticidad del documento.

Subir o reemplazar un informe

Envía el PDF como base64 (sin prefijo data:application/pdf;base64,). Si ya hay un informe en el examen, lo reemplazamos. Esta operación dispara el webhook report.published.

POST/api/v1/exams/{id}/report
bash
curl -X POST "https://cbcthub.com/api/v1/exams/$EXAM_ID/report" \
  -H "Authorization: Bearer $CBCTHUB_API_KEY" \
  -H "Content-Type: application/json" \
  -d "{
    \"pdf_base64\": \"JVBERi0xLjQKJeLjz9MKMyA...\",
    \"filename\": \"informe_juan_perez.pdf\"
  }"

→ {
  "ok": true,
  "exam_id": "exm_abc123",
  "report": {
    "filename": "informe_juan_perez.pdf",
    "size_bytes": 412300,
    "created_at": "2026-06-23T14:35:01Z"
  }
}

Leer el informe vigente

Devuelve una URL presignada del PDF (válida 15 minutos) y, si ya está firmado, los datos de la firma: signature_id, hash SHA-256, fecha, URL pública de verificación.

GET/api/v1/exams/{id}/report
bash
curl "https://cbcthub.com/api/v1/exams/$EXAM_ID/report" \
  -H "Authorization: Bearer $CBCTHUB_API_KEY"

→ {
  "ok": true,
  "exam_id": "exm_abc123",
  "report": {
    "filename": "informe_juan_perez.pdf",
    "size_bytes": 412300,
    "url": "https://r2.cloudflarestorage.com/...?signed",
    "created_at": "2026-06-23T14:35:01Z",
    "signature": {
      "signature_id": "sig_9z8y7x6w5v",
      "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
      "signed_at": "2026-06-23T14:36:10Z",
      "verification_url": "https://cbcthub.com/v/sig_9z8y7x6w5v"
    }
  }
}

Eliminar el informe

Elimina el informe vigente del examen. Es idempotente: si no había informe devolvemos 200 igual. La firma previa (si existía) queda registrada en el audit log pero su URL pública deja de funcionar.

DELETE/api/v1/exams/{id}/report
bash
curl -X DELETE "https://cbcthub.com/api/v1/exams/$EXAM_ID/report" \
  -H "Authorization: Bearer $CBCTHUB_API_KEY"

→ { "ok": true }

Firmar electrónicamente

Calcula el hash SHA-256 del PDF, lo registra en la tabla de firmas y genera una URL pública de verificación. La firma dispara el webhook report.signed. La URL de verificación es del tipo https://cbcthub.com/v/{signature_id}.

POST/api/v1/exams/{id}/report/sign
bash
curl -X POST "https://cbcthub.com/api/v1/exams/$EXAM_ID/report/sign" \
  -H "Authorization: Bearer $CBCTHUB_API_KEY"

→ {
  "ok": true,
  "signature_id": "sig_9z8y7x6w5v",
  "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
  "signed_at": "2026-06-23T14:36:10Z",
  "verification_url": "https://cbcthub.com/v/sig_9z8y7x6w5v"
}

Validaciones

  • El archivo DEBE ser un PDF válido: validamos magic bytes (%PDF-). Si subes otra cosa, devolvemos 400 invalid_request.
  • Tamaño máximo: 20 MB por informe. Si necesitas más, comprime imágenes o divide el documento.
  • POST /report es idempotente por contenido: subir el mismo PDF dos veces no genera duplicados ni dispara el webhook dos veces.
  • Para firmar, el examen tiene que tener un informe subido. Si no hay informe, recibes 400 invalid_request.

Ejemplo end-to-end (Node.js)

Sube un PDF generado por puppeteer, firma electrónicamente y obtiene la URL de verificación pública:

typescript
import { readFile } from 'node:fs/promises';

const API = 'https://cbcthub.com/api/v1';
const KEY = process.env.CBCTHUB_API_KEY!;
const examId = 'exm_abc123';

async function uploadAndSignReport(pdfPath: string) {
  // 1. Leer el PDF y codificar en base64
  const pdf = await readFile(pdfPath);
  const pdf_base64 = pdf.toString('base64');

  // 2. Subir el informe (dispara webhook report.published)
  const upRes = await fetch(`${API}/exams/${examId}/report`, {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      pdf_base64,
      filename: 'informe.pdf',
    }),
  });
  if (!upRes.ok) throw new Error(`upload failed: ${upRes.status}`);

  // 3. Firmar electrónicamente (dispara webhook report.signed)
  const signRes = await fetch(`${API}/exams/${examId}/report/sign`, {
    method: 'POST',
    headers: { 'Authorization': `Bearer ${KEY}` },
  });
  const { signature_id, verification_url, sha256 } = await signRes.json();

  console.log('Informe firmado.');
  console.log('URL pública:', verification_url);
  console.log('Hash SHA-256:', sha256);
  // verification_url se puede convertir en QR client-side con cualquier
  // librería (qrcode, qrcode-generator) y agregar al PDF si lo deseas.

  return { signature_id, verification_url };
}

uploadAndSignReport('./informe_juan_perez.pdf').catch(console.error);

Webhooks relacionados

Si tienes webhooks configurados, estas operaciones disparan eventos automáticamente:

  • report.publishedal subir o reemplazar el PDF.
  • report.signedal firmar electrónicamente.

Verificación pública

La URL https://cbcthub.com/v/{signature_id} es pública: cualquiera con el link (paciente, médico derivador, abogado) puede ver el hash SHA-256 firmado, la fecha y los datos del firmante. Si el PDF cambió aunque sea un byte, el hash no coincide y la verificación falla.