Real clinical apps store scanned intake forms, wound photos, lab PDFs, and scribe audio. A raw FHIR server gives you none of that. bonfire gives you object storage in your own S3 โ wired to FHIR DocumentReference / Media / Binary, under the same ABAC, audit, and provenance as structured data.
The moment you build a real outpatient workflow, someone uploads a faxed referral, snaps a photo of a rash, drags in a PDF lab result, or records a session for the scribe. A FHIR data model describes those artifacts โ it doesn't store the bytes. So teams bolt on a second storage layer with its own auth, its own audit, and its own way to leak.
Scanned intake forms, signed consents, referral letters, lab PDFs โ the paper that still runs healthcare.
Wound photos, skin findings, exported tracings. Clinical images that belong to a patient, not a bucket.
Session recordings the AI scribe transcribes โ large, sensitive, and pointless if they're orphaned from the note.
Every blob points at a patient, a note, an encounter. A file with no clinical anchor is a liability, not a record.
You don't pre-sign a URL, push to a bucket, store a key in your own table, and hope the FHIR record stays in sync. You call clinical.files.upload(...). bonfire writes the bytes to your S3, generates the FHIR resource underneath, anchors it to the patient, and returns a typed DocumentReference โ fresh on commit.
Object storage in your S3. FHIR shadow underneath. One call.
// store the bytes in your S3, get a FHIR DocumentReference back const doc = await clinical.files.upload({ patientId, file, // File | Blob | ReadableStream kind: "intake-form", // your own taxonomy category: "clinical-note", // โ FHIR category coding }); doc.resourceType; // "DocumentReference" doc.subject; // โ Patient/{patientId} doc.content[0].attachment.contentType; // "application/pdf" doc.id; // stable handle for later reads + signed URLs
FHIR has DocumentReference, Media, and Binary โ but the spec is a data model, not a blob store. Most FHIR servers either make you operate the binary plumbing yourself or shove the file inline as base64, which bloats every read. bonfire treats files as first-class clinical objects: stored efficiently in your object store, referenced by FHIR, governed like everything else.
You stand up your own S3, write your own pre-signing, keep a key column in a separate table, and pray the FHIR record and the bytes never drift. The bucket has its own IAM โ disconnected from patient scoping. Nothing audits a download.
One upload writes bytes to your S3 and the FHIR shadow, anchored to the patient. Access runs through the same ABAC policy, every read and download is audited, and provenance ties the file back to where it came from.
A file isn't served from a public bucket and it isn't streamed through your app server. bonfire mints a short-lived signed URL only after the caller passes the access policy for that patient โ so the same minimum-necessary rules that gate a note read gate a wound photo. The URL expires; the bytes never leave your account un-governed.
Pairs with clinical authorization & audit โ the file path runs through the same gate as structured reads, with no side door.
// access checked first โ then a short-lived signed URL const link = await clinical.files.signedUrl({ documentId: doc.id, expiresIn: 300, // seconds โ keep it short }); // caller must satisfy access.policy for doc.subject, // or this throws โ no row, no URL, no bytes link.url; // https://your-bucketโฆ presigned, expiring link.expiresAt; // ISO timestamp // the download is audited like any other read
The scribe records a session, the recording transcribes into a draft note โ and the audio should live attached to that note, not floating in a folder. Upload the audio, link it to the note, and the provenance chain is intact: this note was drafted from this recording, for this encounter, for this patient.
See the scribe flow for how audio โ draft โ signed note hangs together.
// 1. store the session audio in your S3 as FHIR Media const audio = await clinical.files.upload({ patientId, file: recording, kind: "session-audio", category: "media", // โ FHIR Media }); // 2. create the draft note, linked to the recording const note = await clinical.notes.create({ patientId, encounterId, draft: transcript, derivedFrom: [audio.id], // provenance: drafted from this audio }); // clinician reviews + signs โ AI drafts, human decides
You think in intake forms, wound photos, and recordings. bonfire keeps the corresponding FHIR R4 resource in sync underneath, so a clean Bundle โ files included โ is always one call away. This is the same generated-underneath approach as the rest of the store.
| What you upload | FHIR R4 resource (underneath) | Bytes live in |
|---|---|---|
| Scanned intake form / consent | DocumentReference + Binary | Your S3 |
| Lab PDF / referral letter | DocumentReference + Binary | Your S3 |
| Wound photo / clinical image | Media | Your S3 |
| Scribe session audio | Media | Your S3 |
| Generic raw blob | Binary | Your S3 |
Your data never round-trips through us. The bytes are written to and served from your own AWS account; bonfire orchestrates the FHIR record, the access check, and the signed URL โ the object store stays inside your boundary.
bonfireDB is pre-launch / early access. The storage layer is built around the primitives outpatient apps reach for first โ and we'd rather name the edges than imply more than is true.
bonfireDB is early-stage; this page describes product design and positioning, not a benchmark. "FHIR" is descriptive of the HL7ยฎ standard. Encryption, audit, and access controls are design properties of the storage layer โ verify your own compliance posture before handling PHI.
The same ABAC gate and auto-AuditEvent that govern reads govern files.
Explore โTyped clinical functions โ notes, media, files โ over one Postgres.
Explore โBuilt-in object storage in your S3, wired to FHIR and governed by the same ABAC, audit, and provenance as your structured data. Start building on the open-source core.
The bytes are written to and served from your own AWS S3 bucket, encrypted at rest with your KMS key. bonfireDB orchestrates the FHIR record, the access check, and the signed URL, but the object store stays inside your boundary and never round-trips through us.
You call clinical.files.upload() to store the session audio in your S3 as a FHIR Media resource, then link it to the draft note via derivedFrom. This is the design intent in early access: the provenance chain records that the note was drafted from that recording, for that encounter and patient.
Yes. One upload call writes the bytes to your S3 and generates the FHIR DocumentReference, Media, or Binary underneath, kept in sync. Scanned forms and PDFs map to DocumentReference plus Binary, images and scribe audio map to Media, and raw blobs map to Binary.
There are no public-read buckets. bonfireDB mints a short-lived, configurable-TTL signed URL only after the caller passes the ABAC policy for that patient, and every download emits an AuditEvent. A leaked link expires on its own and bytes never stream through your app server.
Not yet. As of 2026 early access, clinical images are stored as FHIR Media, not full DICOM study management, and on-blob OCR or virus scanning is bring-your-own-pipeline hooked on upload. bonfireDB is the backend, so the document viewer is yours to build.