๐Ÿ“Ž File & attachment storage

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 gap nobody mentions

Clinical data isn't only rows. It's blobs.

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.

The documents

Scanned intake forms, signed consents, referral letters, lab PDFs โ€” the paper that still runs healthcare.

The images

Wound photos, skin findings, exported tracings. Clinical images that belong to a patient, not a bucket.

The audio

Session recordings the AI scribe transcribes โ€” large, sensitive, and pointless if they're orphaned from the note.

The link

Every blob points at a patient, a note, an encounter. A file with no clinical anchor is a liability, not a record.

One upload call. A DocumentReference comes back.

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.

  • โœ“ Bytes land in your AWS account โ€” encrypted at rest, server-side, with your KMS key.
  • โœ“ The FHIR DocumentReference / Binary is generated and kept consistent for you.
  • โœ“ The upload is patient- and tenant-scoped by the same access policy as every other write.
  • โœ“ An AuditEvent is emitted automatically โ€” who uploaded what, for which patient.

Object storage in your S3. FHIR shadow underneath. One call.

upload.ts
// 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
the worldview

A FHIR server stores the description, not the bytes

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.

Raw FHIR server + a side bucket

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.

bonfireDB

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.

Downloads are access-scoped, expiring, signed URLs

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.

  • โœ“ The signed URL is issued only if the caller can read that patient โ€” checked at request time, not assumed.
  • โœ“ TTL is short and configurable; a leaked link goes dead on its own.
  • โœ“ Every download mints an AuditEvent โ€” you can answer "who pulled this file."
  • โœ“ No public-read buckets, no app server hairpinning gigabytes of audio.

Pairs with clinical authorization & audit โ€” the file path runs through the same gate as structured reads, with no side door.

download.ts
// 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

Attach the scribe audio to the note it produced

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.

  • โœ“ Audio is uploaded as a FHIR Media resource and linked to the note's DocumentReference.
  • โœ“ Provenance records that the draft came from this recording โ€” auditable, not folklore.
  • โœ“ The clinician still signs the note; the AI drafts, a human decides. The recording is evidence, not authority.
  • โœ“ Same ABAC: the audio is only retrievable by callers entitled to that patient.

See the scribe flow for how audio โ†’ draft โ†’ signed note hangs together.

scribe-attach.ts
// 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
how files map to FHIR

Every attachment has a FHIR shadow

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 uploadFHIR R4 resource (underneath)Bytes live in
Scanned intake form / consentDocumentReference + BinaryYour S3
Lab PDF / referral letterDocumentReference + BinaryYour S3
Wound photo / clinical imageMediaYour S3
Scribe session audioMediaYour S3
Generic raw blobBinaryYour 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.

governed like everything else

Files get the same treatment as structured data

โœ“ Encrypted at rest with your KMS key
โœ“ Encrypted in transit, always
โœ“ ABAC enforced on upload + download
โœ“ AuditEvent on every read & write
โœ“ Provenance back to the source
โœ“ Short-lived signed URLs, no public buckets
โœ“ Patient- & tenant-scoped, no orphan blobs
โœ“ FHIR shadow generated underneath
โœ“ Bytes stay in your AWS account
honest scope

What ships, and what's on the roadmap

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.

What storage is designed to do

  • Object storage in your own S3, encrypted with your KMS key
  • FHIR DocumentReference / Media / Binary generated and kept in sync underneath
  • Access-scoped, expiring signed URLs gated by the same ABAC as structured reads
  • Automatic AuditEvent + provenance on uploads and downloads
  • Scribe audio attached to the note it produced, with a provenance chain

What it is not (yet)

  • A DICOM imaging archive / PACS โ€” clinical images are Media, not full DICOM study management
  • A document-management UI โ€” bonfire is the backend; the viewer is yours to build
  • On-blob OCR / virus scanning out of the box โ€” bring your own pipeline, hooked on upload
  • SOC 2 / HITRUST certified โ€” those are on the compliance roadmap, not claims today

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.

where this fits

Storage is wired into the rest of the backend

Authorization & audit

The same ABAC gate and auto-AuditEvent that govern reads govern files.

Explore โ†’

FHIR underneath

Files export in the same clean R4 Bundle as every other resource.

Explore โ†’

App-native primitives

Typed clinical functions โ€” notes, media, files โ€” over one Postgres.

Explore โ†’

Security & your AWS

Encryption, KMS, and the boundary where your bytes live.

Explore โ†’

You build the app. Bonfire is the clinical data layer underneath.

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.

FAQ

Frequently asked questions

Where do my clinical files actually get stored with bonfireDB?

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.

How do I store AI medical scribe audio and attach it to the note?

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.

Does bonfireDB wire file uploads to FHIR DocumentReference and Binary automatically?

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.

How are file downloads secured if the bucket is in my own S3?

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.

Can bonfireDB replace a PACS/DICOM archive or scan files for viruses?

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.