Clinical Integration Recipes
Short, practical recipes for the Wave-2 clinical surfaces. All snippets use @dentolize/plugin-sdk ≥ 0.3.0 (const client = new Dentolize({ token })).
Dental lab network (lab orders + webhooks)
Scopes: laborders:read, laborders:write, webhooks:manage. Events: laborder.created, laborder.updated.
Sync open orders into the lab's queue, then push status back when work completes:
// Ingest: subscribe to laborder.created, then fetch details on delivery
const order = await client.labOrders.get(event.data.id);
const items = await client.labItems.list({ labId: order.labId ?? undefined });
// Poll fallback / backfill (delta sync)
for await (const o of client.labOrders.iterate({ status: 'pending', updatedAfter: lastSync })) {
enqueue(o);
}
// Push back: mark received when the case returns from the lab
await client.labOrders.update(order.id, { received: true, shade: 'A2', details: 'Zirconia crown, glazed' });
PATCH /lab-orders/{id} accepts only received/delivered/shade/details — a lab integration can never touch pricing. The clinic sees laborder.received / laborder.delivered fire for its own automations.
Imaging device / PACS bridge (file upload flow)
Scopes: files:read, files:write. Events fired: file.created, xray.created.
Push captured images straight onto the patient chart with the two-step upload (presign → S3 POST → confirm) — upload() does all three:
import { readFile } from 'node:fs/promises';
const file = await client.patientFiles.upload({
patientId,
buffer: await readFile('capture.dcm'),
fileName: 'capture.dcm',
contentType: 'application/dicom',
kind: 'xray', // also creates the x-ray record on the chart
operationId: treatmentId // optional: attach to the treatment being imaged
});
console.log(file.xrayId, file.downloadUrl); // downloadUrl = 1h presigned GET
Allowed types: jpeg/png/webp/tiff/bmp/pdf/dicom, ≤ 100 MB. To pull existing imaging instead, use client.patientFiles.xrays(patientId) / .list(patientId).
Intake forms (external form submissions)
Scopes: forms:read, forms:write. Event fired: form.submitted.
Render the clinic's own form schema in your kiosk/portal, then submit on the patient's behalf:
// 1. Fetch the form definition (current version schema = form-builder field array)
const forms = await client.forms.list();
const intake = forms.data.find(f => f.title === 'New Patient Intake');
renderFields(intake.currentVersion?.schema ?? []);
// 2. Submit answers keyed by field key (Idempotency-Key is automatic)
const submission = await client.formSubmissions.create({
formId: intake.id,
patientId,
ref: `kiosk-${sessionId}`, // your own external reference
answers: { allergies: 'Penicillin', smoker: false }
});
Signature-typed answers are stripped from reads (formSubmissions.get()), and consent forms (client.consentForms.list()) expose signed state only — never signature images.
Vitals device (measurements on encounters)
Scopes: encounters:read, encounters:write. Event fired: measurement.recorded.
Push device readings onto the patient's open encounter:
// Find the latest encounter for the patient
const encounters = await client.encounters.list({ patientId, limit: 1 });
const encounter = encounters.data[0];
await client.encounters.addMeasurement(encounter.id, { name: 'HEART_RATE', value: 72 });
await client.encounters.addMeasurement(encounter.id, { name: 'OXYGEN_SATURATION', value: 98 });
The platform computes trend fields (improvement, difference, normal-range status) automatically — read them back via client.encounters.get(encounter.id). Note: the measurement.recorded webhook payload carries the vital under measurementName (not name), and this event fires only for measurements recorded through this REST endpoint.
Treatment-plan follow-ups (quotations)
Scope: quotations:read (financial tier). Events: quotation.created, quotation.signed (quotation.viewed is reserved — not yet emitted).
// Unsigned estimates from the last 30 days — candidates for a follow-up nudge
const pending = await client.quotations.list({ from, to, signed: false });
for (const quote of pending.data) {
schedule(quote.patientId, { total: quote.total, quotedTreatments: quote.operationIds.length });
}
Treatments now expose plan linkage too: Treatment.quotationId, steps[], approvalRequired/approved and preAuth (via treatments:read).