API de laudos

Criar, ler, assinar laudos programaticamente

A API de laudos permite enviar PDFs de laudos radiológicos a cada exame, ler o laudo vigente, excluí-lo e assiná-lo eletronicamente — tudo do seu sistema. A assinatura gera um signature_id com hash SHA-256 do PDF e uma URL pública de verificação (com QR opcional) para que qualquer um valide a autenticidade.

Enviar ou substituir um laudo

Envie o PDF como base64 (sem o prefixo data:application/pdf;base64,). Se já existe um laudo, substituímos. Esta operação dispara o 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"
  }
}

Ler o laudo vigente

Retorna URL pré-assinada do PDF (válida 15 minutos) e, se já estiver assinado, os dados da assinatura: signature_id, hash SHA-256, data, URL pública de verificação.

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"
    }
  }
}

Excluir o laudo

Remove o laudo vigente do exame. Idempotente: retorna 200 mesmo se não havia laudo. Qualquer assinatura prévia continua no audit log mas sua URL pública para 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 }

Assinar eletronicamente

Calcula o hash SHA-256 do PDF, registra na tabela de assinaturas e gera URL pública de verificação. A assinatura dispara o webhook report.signed. A URL é do 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"
}

Validações

  • O arquivo DEVE ser um PDF válido: validamos magic bytes (%PDF-). Se enviar outra coisa, retornamos 400 invalid_request.
  • Tamanho máximo: 20 MB por laudo. Se precisar mais, comprima imagens ou divida o documento.
  • POST /report é idempotente por conteúdo: enviar o mesmo PDF duas vezes não duplica nem dispara o webhook duas vezes.
  • Para assinar, o exame precisa já ter um laudo. Caso contrário, recebe 400 invalid_request.

Exemplo end-to-end (Node.js)

Envia um PDF gerado por puppeteer, assina eletronicamente e obtém a URL de verificação 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

Se você tem webhooks configurados, essas operações disparam eventos automaticamente:

  • report.publishedao enviar ou substituir o PDF.
  • report.signedao assinar eletronicamente.

Verificação pública

A URL https://cbcthub.com/v/{signature_id} é pública: qualquer um com o link (paciente, médico solicitante, advogado) pode ver o hash SHA-256 assinado, a data e os dados do assinante. Se o PDF mudar nem um byte, o hash não bate e a verificação falha.