Endpoint reference

All REST endpoints of API v1

Complete reference for every endpoint in the v1 API. Base URL: https://cbcthub.com/api/v1

GET/api/v1/me

Returns your account info (plan, storage, exam count). Useful to verify the key works.

json
{
  "user_id": "...",
  "email": "soporte@miclinica.com",
  "plan": "pro",
  "scopes": ["exams:read", "exams:write"],
  "storage": { "used_bytes": 41200000000, "limit_bytes": 375809638400, "used_percent": 11 },
  "exams": { "count": 110, "limit": 999999 }
}
POST/api/v1/exams

Creates an exam and returns presigned URLs for direct upload to R2.

FieldTypeRequiredNotes
namestringInternal exam name (max 200 chars).
exam_typestringcbct (default), radiografia, mesh, atm. Radiografia requires Pro.
patient_namestringPatient name. Shown in viewer.
patient_idstringSSN, ID number, medical record number.
birth_datestring ISOYYYY-MM-DD.
reasonstringReason / clinical findings requested.
expiration_daysnumberDays until expiry. null = never.
upload_modestring"files" (default) or "zip". In zip mode you get 1 URL, then call /process-zip.
filesarray[{ name, size }]. Required if upload_mode="files". Min 1, max 5000.
zip_size_bytesnumberRequired if upload_mode="zip". Max 1 GB (1073741824).
GET/api/v1/exams?limit=20&offset=0

List your account exams, paginated. Default limit=20, max 100.

GET/api/v1/exams/{id}

Single exam details. Includes share_url, viewer_url, share_password and public_page_pin.

POST/api/v1/exams/{id}/confirm

Marks exam as ready after files are uploaded (files mode). Idempotent.

POST/api/v1/exams/{id}/process-zip

For exams created with upload_mode="zip": server-side streams the staging ZIP, uploads each DICOM to R2 and marks the exam as ready. Idempotent. Limits: 1 GB ZIP, 5000 files, 5 GB expanded, 1.6 TB per individual file, 300 s timeout.

DELETE/api/v1/exams/{id}

Deletes the exam. R2 files are cleaned up in background. Idempotent: returns already_deleted=true if the exam was already gone. Scope: exams:write.

bash
curl -X DELETE "https://cbcthub.com/api/v1/exams/EXAM_ID" \
  -H "Authorization: Bearer $CBCTHUB_KEY"

# → { "deleted": true, "exam_id": "EXAM_ID" }
PATCH/api/v1/exams/{id}

Partial update — only the fields you include are modified. Allowed: name, patient_name, patient_id, birth_date, reason, expiration_days, radiograph_subtype (radiografia only). Cannot change exam_type, share_token, storage or counters.

bash
curl -X PATCH "https://cbcthub.com/api/v1/exams/EXAM_ID" \
  -H "Authorization: Bearer $CBCTHUB_KEY" \
  -H "Content-Type: application/json" \
  -d '{"patient_name":"Juan Pérez","expiration_days":90}'
POST/api/v1/exams/{id}/share

Email the exam to a recipient. Body: { to, cc?, message?, locale? }. In sandbox no real email is sent (sent=false, reason=test_mode_no_send). For radiographs the images are attached; for CBCT the email contains the viewer link.

bash
curl -X POST "https://cbcthub.com/api/v1/exams/EXAM_ID/share" \
  -H "Authorization: Bearer $CBCTHUB_KEY" \
  -H "Content-Type: application/json" \
  -d '{"to":"paciente@example.com","locale":"es"}'
POST/api/v1/exams/{id}/extras

Start an extra take for CBCT/ATM (multi-take). Returns presigned URLs for each file. Body: { label, files: [{name, size}] }. Max 2 extras per exam (3 takes total). Not for radiographs. Scope: exams:write.

bash
curl -X POST "https://cbcthub.com/api/v1/exams/EXAM_ID/extras" \
  -H "Authorization: Bearer $CBCTHUB_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "label": "Segunda toma post-ortodoncia",
    "files": [{ "name": "IM0001.dcm", "size": 524288 }]
  }'

# → { "index": 1, "upload_urls": [...], "confirm_url": ".../extras/1/confirm" }
POST/api/v1/exams/{id}/extras/{index}/confirm

Confirms the extra take finished uploading. Verifies the files in R2 and updates extra_series + storage. Optional body: { label }. Idempotent. Scope: exams:write.

bash
curl -X POST "https://cbcthub.com/api/v1/exams/EXAM_ID/extras/1/confirm" \
  -H "Authorization: Bearer $CBCTHUB_KEY" \
  -H "Content-Type: application/json" \
  -d '{"label":"Segunda toma post-ortodoncia"}'
GET/api/v1/exams/{id}/access-pin

Returns the current PIN + info about the clinic public page. Useful to show the PIN to the patient without going through the dashboard. Scope: exams:read.

bash
curl "https://cbcthub.com/api/v1/exams/EXAM_ID/access-pin" \
  -H "Authorization: Bearer $CBCTHUB_KEY"

# → { "pin": "482931", "clinic_slug": "miclinica", "public_url": "https://cbcthub.com/centro/miclinica" }
POST/api/v1/exams/{id}/access-pin

Rotates the PIN: generates a new one and immediately invalidates the old one. Empty body. Use this if a patient leaked the PIN or after a security incident. Scope: exams:write.

bash
curl -X POST "https://cbcthub.com/api/v1/exams/EXAM_ID/access-pin" \
  -H "Authorization: Bearer $CBCTHUB_KEY"

# → { "pin": "739184", "rotated_at": "2026-06-27T18:20:11Z" }
GET/api/v1/exams/{id}/embed

Returns the embed state (enabled, token, snippet, view_count). Scope: exams:read.

bash
curl "https://cbcthub.com/api/v1/exams/EXAM_ID/embed" \
  -H "Authorization: Bearer $CBCTHUB_KEY"
POST/api/v1/exams/{id}/embed

Enables the embed (generates a token if missing). Returns embed_url and iframe_snippet ready to paste. Optional body: { title, show_year, show_study_type }.

bash
curl -X POST "https://cbcthub.com/api/v1/exams/EXAM_ID/embed" \
  -H "Authorization: Bearer $CBCTHUB_KEY" \
  -H "Content-Type: application/json" \
  -d '{"title":"Caso 12345","show_year":true}'
DELETE/api/v1/exams/{id}/embed

Revokes the embed: deletes the token and breaks the existing URL. To reactivate, POST again (returns a fresh token). Scope: exams:write.

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

Downloads the exam thumbnail (JPEG/PNG) as binary. Returns 404 with thumbnail_not_ready if not generated yet. Ideal for showing thumbnails in your UI. Scope: exams:read.

bash
curl -o thumb.jpg "https://cbcthub.com/api/v1/exams/EXAM_ID/thumbnail" \
  -H "Authorization: Bearer $CBCTHUB_KEY"
GET/api/v1/exams/{id}/patient-exams

Lists the other exams of the same patient (match by patient_id; fallback to normalized name). Useful to show history or enable comparators. Scope: exams:read.

bash
curl "https://cbcthub.com/api/v1/exams/EXAM_ID/patient-exams" \
  -H "Authorization: Bearer $CBCTHUB_KEY"

# → { "exams": [{ "id": "...", "name": "...", "exam_type": "cbct", "created_at": "..." }] }

Informed consent

Full legal flow: initialization by country/locale, in-person signing (patient present with a tablet) or by email (link sent to the patient), download of the signed PDF with SHA-256 and a public verification QR.

GET/api/v1/exams/{id}/consent

Returns the current consent state (status, signing_method, signed_at, hash). If not yet initialized, returns { consent: null }. Scope: exams:read.

bash
curl "https://cbcthub.com/api/v1/exams/EXAM_ID/consent" \
  -H "Authorization: Bearer $CBCTHUB_KEY"
POST/api/v1/exams/{id}/consent

Initializes (or re-initializes if pending) the consent. Optional body: { country_code, locale }. Returns 409 if already signed. Scope: exams:write.

bash
curl -X POST "https://cbcthub.com/api/v1/exams/EXAM_ID/consent" \
  -H "Authorization: Bearer $CBCTHUB_KEY" \
  -H "Content-Type: application/json" \
  -d '{"country_code":"CL","locale":"es"}'
POST/api/v1/exams/{id}/consent/sign-inperson

In-person signing. Body: { signer_name, signer_rut, patient_birth_date?, signature_image (PNG base64), accepted: true }. The server generates the signed PDF and computes SHA-256. Scope: exams:write.

bash
curl -X POST "https://cbcthub.com/api/v1/exams/EXAM_ID/consent/sign-inperson" \
  -H "Authorization: Bearer $CBCTHUB_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "signer_name": "Juan Perez",
    "signer_rut": "12345678-9",
    "signature_image": "iVBORw0KGgoAAAANSUhEUgAA...",
    "accepted": true
  }'
POST/api/v1/exams/{id}/consent/email

Emails the signing link to the patient. Body: { to, patient_name? }. In sandbox no real email is sent. Returns sign_url so you can also render it as a QR. Scope: exams:write.

bash
curl -X POST "https://cbcthub.com/api/v1/exams/EXAM_ID/consent/email" \
  -H "Authorization: Bearer $CBCTHUB_KEY" \
  -H "Content-Type: application/json" \
  -d '{"to":"paciente@example.com","patient_name":"Juan Perez"}'
GET/api/v1/exams/{id}/consent/pdf?type=blank|signed

Downloads the consent PDF as binary. type=blank returns the unsigned document (useful for printing). type=signed requires the consent to be signed. Scope: exams:read.

bash
curl -o consent.pdf \
  "https://cbcthub.com/api/v1/exams/EXAM_ID/consent/pdf?type=signed" \
  -H "Authorization: Bearer $CBCTHUB_KEY"

Radiology reports

Endpoints to upload, read, delete and electronically sign PDF reports. Full details in the Reports API section.

POST/api/v1/exams/{id}/report

Upload or replace the report PDF. Body: { pdf_base64, filename? }.

GET/api/v1/exams/{id}/report

Returns the PDF presigned URL + signature data if signed.

DELETE/api/v1/exams/{id}/report

Deletes the report. Idempotent: returns 200 even if it does not exist.

POST/api/v1/exams/{id}/report/sign

Electronically signs the report. Returns signature_id and public verification URL.

Webhooks

Endpoints to manage webhook subscriptions. Full details (events, HMAC signature, retries) in the Webhooks section.

POST/api/v1/webhooks

Creates a subscription. Returns the secret ONLY ONCE.

GET/api/v1/webhooks

Lists all account subscriptions.

GET/api/v1/webhooks/{id}

Single subscription details.

PATCH/api/v1/webhooks/{id}

Updates events, description or enabled.

DELETE/api/v1/webhooks/{id}

Deletes the subscription. Cannot be reactivated.

POST/api/v1/webhooks/{id}/test

Sends a test ping to the configured endpoint.

GET/api/v1/webhooks/{id}/deliveries

Audit log of the last 50 deliveries (status, response, retry count).

Referrers

CRUD over the directory of referring dentists. New scopes: referrers:read and referrers:write. Sandbox: reads return an empty list and writes return a mock with id "rfr_test_*" without persisting, so you can test the integration without touching live data.

GET/api/v1/referrers

Lists every referrer of the account, sorted alphabetically by name. Scope: referrers:read.

bash
curl "https://cbcthub.com/api/v1/referrers" \
  -H "Authorization: Bearer $CBCTHUB_KEY"

# → { "referrers": [{ "id": "rfr_...", "name": "Dra. Maria Lopez", "email": "...", "specialty": "..." }] }
POST/api/v1/referrers

Creates a referrer. Body: { name (required), email?, phone?, specialty? }.

bash
curl -X POST "https://cbcthub.com/api/v1/referrers" \
  -H "Authorization: Bearer $CBCTHUB_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"Dra. Maria Lopez","email":"maria@example.com","specialty":"Endodoncia"}'
GET/api/v1/referrers/{id}

Single referrer detail. Scope: referrers:read.

PATCH/api/v1/referrers/{id}

Partial update — only the included fields are touched. Scope: referrers:write.

bash
curl -X PATCH "https://cbcthub.com/api/v1/referrers/rfr_xyz" \
  -H "Authorization: Bearer $CBCTHUB_KEY" \
  -H "Content-Type: application/json" \
  -d '{"phone":"+56 9 1234 5678"}'
DELETE/api/v1/referrers/{id}

Deletes the referrer. Idempotent: returns already_deleted=true if not found. Scope: referrers:write.

bash
curl -X DELETE "https://cbcthub.com/api/v1/referrers/rfr_xyz" \
  -H "Authorization: Bearer $CBCTHUB_KEY"

Report templates

CRUD over plate templates for radiology reports. Templates with is_default=true are system-provided, visible to every account, and cannot be edited or deleted. New scopes: templates:read and templates:write. Sandbox: GET returns only the defaults and writes are no-ops (mock with id "tpl_test_*").

GET/api/v1/templates

Lists every visible template (system defaults + the user's own). Scope: templates:read.

bash
curl "https://cbcthub.com/api/v1/templates" \
  -H "Authorization: Bearer $CBCTHUB_KEY"
POST/api/v1/templates

Creates a user template. Body: { name, description?, pages: PageDef[] }. Each page: { name?, slots: [{ x, y, w, h, label? }] } with 0..1 coordinates. Scope: templates:write.

bash
curl -X POST "https://cbcthub.com/api/v1/templates" \
  -H "Authorization: Bearer $CBCTHUB_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Periapical 14 placas",
    "pages": [{
      "name": "Pagina 1",
      "slots": [
        { "x": 0.05, "y": 0.05, "w": 0.2, "h": 0.3, "label": "11" },
        { "x": 0.30, "y": 0.05, "w": 0.2, "h": 0.3, "label": "12" }
      ]
    }]
  }'
GET/api/v1/templates/{id}

Single template detail (system or own). Scope: templates:read.

bash
curl "https://cbcthub.com/api/v1/templates/tpl_xyz" \
  -H "Authorization: Bearer $CBCTHUB_KEY"
PATCH/api/v1/templates/{id}

Updates an owned template. System defaults return 400. Scope: templates:write.

bash
curl -X PATCH "https://cbcthub.com/api/v1/templates/tpl_xyz" \
  -H "Authorization: Bearer $CBCTHUB_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"Periapical 14 placas v2"}'
DELETE/api/v1/templates/{id}

Deletes an owned template. Idempotent. System defaults return 400. Scope: templates:write.

bash
curl -X DELETE "https://cbcthub.com/api/v1/templates/tpl_xyz" \
  -H "Authorization: Bearer $CBCTHUB_KEY"

Implant catalog

Global implant model catalog (brand, product_line, length and diameter). Read-only. Reference resource, not per-user: uses the exams:read scope. Sandbox: the catalog is identical to live.

GET/api/v1/implants

Without params returns the list of brands with count(*). Filters: ?brand=Neodent (models of that brand), ?search=helix (free text on brand+product_line), ?length=11.5&diameter=4 (inverse lookup by size; also returns near_matches within ±0.5 mm). Supports ?limit= and ?offset=.

bash
curl "https://cbcthub.com/api/v1/implants?brand=Neodent&limit=20" \
  -H "Authorization: Bearer $CBCTHUB_KEY"

# Busqueda por dimension
curl "https://cbcthub.com/api/v1/implants?length=11.5&diameter=4" \
  -H "Authorization: Bearer $CBCTHUB_KEY"

Support tickets

Programmatically create and read support tickets. New scopes: support:read and support:write. Creating a ticket in live sends an email to the support team and a confirmation email to the requester. Sandbox: NO persistence, NO emails, returns a mock with id "tkt_test_*" and test_mode=true.

GET/api/v1/support/tickets

Lists the 50 most recent tickets of the account (configurable via ?limit=). Scope: support:read.

bash
curl "https://cbcthub.com/api/v1/support/tickets?limit=20" \
  -H "Authorization: Bearer $CBCTHUB_KEY"
POST/api/v1/support/tickets

Creates a ticket. Body: { subject, message, category?, exam_id? }. Valid categories: general, bug, billing, feature_request, account, viewer.

bash
curl -X POST "https://cbcthub.com/api/v1/support/tickets" \
  -H "Authorization: Bearer $CBCTHUB_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "subject":"Mi examen no se subio",
    "message":"Subi un ZIP de 800 MB y aparece pending desde hace 2 horas.",
    "category":"bug",
    "exam_id":"b1f7c9c6-8c10-4d27-8d80-93e1f1a5d4af"
  }'
GET/api/v1/support/tickets/{id}

Ticket detail + chronological list of admin replies. Scope: support:read.

bash
curl "https://cbcthub.com/api/v1/support/tickets/tkt_xyz" \
  -H "Authorization: Bearer $CBCTHUB_KEY"

# → { "ticket": {...}, "replies": [{ "body": "...", "created_at": "..." }] }

Activity and notifications

Read the account activity log and generated notifications (support replies, system events). New scopes: account:read and account:write. Sandbox: returns empty lists.

GET/api/v1/activity

Paginated activity log (descending). Query: ?limit=50 (1-100), ?before=ISO_DATE (cursor — use next_cursor from the response), ?action=exam.created (optional filter).

bash
curl "https://cbcthub.com/api/v1/activity?limit=20&action=exam.created" \
  -H "Authorization: Bearer $CBCTHUB_KEY"
GET/api/v1/notifications

Lists generic notifications + unread_count. Query: ?unread_only=true, ?limit=30. Does NOT include exam-view events (those flow through webhooks). Scope: account:read.

bash
curl "https://cbcthub.com/api/v1/notifications?unread_only=true&limit=30" \
  -H "Authorization: Bearer $CBCTHUB_KEY"

# → { "notifications": [...], "unread_count": 4 }
POST/api/v1/notifications/{id}/mark-read

Marks a notification as read. Idempotent. Scope: account:write.

bash
curl -X POST "https://cbcthub.com/api/v1/notifications/ntf_xyz/mark-read" \
  -H "Authorization: Bearer $CBCTHUB_KEY"
POST/api/v1/notifications/mark-all-read

Marks every unread notification as read and returns marked_count. Scope: account:write.

bash
curl -X POST "https://cbcthub.com/api/v1/notifications/mark-all-read" \
  -H "Authorization: Bearer $CBCTHUB_KEY"

# → { "marked_count": 4 }

Try these endpoints live

The playgrounds below execute REAL calls against your account. To test without touching production, use a cbct_test_* key (create one in Settings → API, Sandbox tab).

GET /api/v1/exams

GET/api/v1/examsScope: exams:readTry it now

List your account exams, paginated (newest first).

Paste an API key (live or test) created from Settings → API. Create or manage keys →

Query parameters

Request

GET https://cbcthub.com/api/v1/exams?limit=5&offset=0
Paste your API key above to enable the button.

Your API key is stored only in this browser for the current session. It never goes back to CBCTHub outside the request you fire, and it is dropped when you close the tab.

POST /api/v1/exams

POST/api/v1/examsScope: exams:writeTry it now

Create an exam and get presigned URLs for direct R2 upload.

Paste an API key (live or test) created from Settings → API. Create or manage keys →

JSON body

Request

POST https://cbcthub.com/api/v1/exams
Paste your API key above to enable the button.

Your API key is stored only in this browser for the current session. It never goes back to CBCTHub outside the request you fire, and it is dropped when you close the tab.

GET /api/v1/exams/{id}

GET/api/v1/exams/{id}Scope: exams:readTry it now

Single exam details. You need an id from your account (get one from the list playground above).

Paste an API key (live or test) created from Settings → API. Create or manage keys →

Path parameters

Request

GET https://cbcthub.com/api/v1/exams/{id}
Paste your API key above to enable the button.

Your API key is stored only in this browser for the current session. It never goes back to CBCTHub outside the request you fire, and it is dropped when you close the tab.

POST /api/v1/webhooks

POST/api/v1/webhooksScope: webhooks:writeTry it now

Create a webhook subscription. The response returns the secret ONLY ONCE.

Paste an API key (live or test) created from Settings → API. Create or manage keys →

JSON body

Request

POST https://cbcthub.com/api/v1/webhooks
Paste your API key above to enable the button.

Your API key is stored only in this browser for the current session. It never goes back to CBCTHub outside the request you fire, and it is dropped when you close the tab.

POST /api/v1/webhooks/{id}/test

POST/api/v1/webhooks/{id}/testScope: webhooks:writeTry it now

Sends a test ping (test.ping) to the configured endpoint.

Paste an API key (live or test) created from Settings → API. Create or manage keys →

Path parameters

JSON body

Request

POST https://cbcthub.com/api/v1/webhooks/{id}/test
Paste your API key above to enable the button.

Your API key is stored only in this browser for the current session. It never goes back to CBCTHub outside the request you fire, and it is dropped when you close the tab.