Upload flow

Upload an exam in 3 steps

Uploading an exam is a 3-step flow. Files go directly to Cloudflare R2 with presigned URLs (they do not pass through our servers), so it is fast even with 500 MB+ scans.

Step 1 — Create the exam

Send patient metadata and the file list (name + size in bytes). You receive an exam_id and presigned URLs (valid for 15 minutes).

bash
POST /api/v1/exams
{
  "name": "CBCT mandíbula",
  "patient_name": "Juan Pérez",
  "files": [
    { "name": "ct_001.dcm", "size": 524288 }
  ]
}

Step 2 — Upload files to R2

For each presigned URL, do a PUT with the file binary content. R2 responds 200 OK if the upload succeeded. No additional auth needed: the URL is already signed.

bash
PUT https://r2.cloudflarestorage.com/.../ct_001.dcm
Content-Type: application/dicom

<bytes>

Step 3 — Confirm

Once all files are uploaded, call /confirm to mark the exam as ready. You receive share_url (branded link) and viewer_url (direct 3D viewer).

bash
POST /api/v1/exams/{exam_id}/confirm

→ {
  "ok": true,
  "exam_id": "...",
  "status": "ready",
  "share_url": "https://cbcthub.com/share/...",
  "viewer_url": "https://cbcthub.com/viewer/..."
}

Flow diagram

TU SISTEMA                CBCTHub API             Cloudflare R2
    │                         │                       │
    ├──POST /v1/exams────────►│                       │
    │                         │                       │
    │◄────exam_id, upload_urls┤                       │
    │                                                 │
    ├──PUT ct_001.dcm ────────────────────────────────►│
    ├──PUT ct_002.dcm ────────────────────────────────►│
    │◄───── 200 OK ───────────────────────────────────┤
    │                                                 │
    ├──POST /v1/exams/{id}/confirm ►│                 │
    │◄────share_url, viewer_url─────┤                 │

Idempotency

/confirm is idempotent: calling it multiple times does not duplicate the exam. If your connection dropped right after a successful confirm, you can safely retry.

What if upload takes longer than 15 minutes?

Presigned URLs expire after 15 minutes. If your client did not finish uploading in time, call POST /api/v1/exams again to regenerate URLs. The old exam_id stays in status uploading until you delete it.