Multi-tenant FHIR
Multi-Tenant FHIR for Digital Health SaaS, Clinics, Hospital Networks, and Research Sites
Healthcare multi-tenancy rarely stops at "isolate the customers." Specialists consult across organisations, patients transfer between clinics, support staff need scoped access, and roles inside each tenant differ between doctors, nurses, and IT. Fire Arrow expresses tenant boundaries in standard FHIR resources (Organization, PractitionerRole, managingOrganization) and enforces them on every request, so the same backend serves a digital health SaaS platform, a clinic network, a hospital group, or a research consortium without parallel permission systems.
What you can build
- Independent tenants on shared infrastructure
Clinics, sites, or programs share one Fire Arrow Server while their data stays isolated. New tenant onboarding is a new Organization resource, not a new deployment.
- Role differentiation per tenant
Doctors, nurses, IT staff, support, and patients all in the same backend. PractitionerRole codes select which rules apply to whom; IT staff with no clinical-resource rules simply fall through to `Forbidden`.
- Cross-organisational specialist access scoped to one patient
Add the specialist to the patient's CareTeam. The CareTeam validator grants access to that patient's data, nothing else. Remove the entry to revoke access.
- Hierarchical access through Organization.partOf
Hospital networks, regional health authorities, SaaS vendor support staff at the platform root, and research consortium coordinators all use `role-inheritance-levels` to inherit access downward through the org tree.
- Patient transfers and role changes as data updates
Update one field (`managingOrganization`) and access shifts immediately. Deactivate a PractitionerRole and access is revoked on the next request. No code, no infrastructure changes, no migration.
- One declarative permission model
Auditors and architects review rules in one place instead of tracing scattered conditional logic. The effective access for any role is inspectable, not emergent.
- Cross-app consistency across REST and GraphQL
REST search, GraphQL search, GraphQL read, includes, and reverse-includes all flow through the same rule set. Adding a new API surface does not mean re-implementing tenant checks.
How it works
- 1 Describe the tenant structure in FHIR
Create one Organization for each clinic, site, or program. Use `Organization.partOf` to express hierarchies: a hospital network above its clinics, a SaaS platform above its customer organisations, a study sponsor above its sites. Each clinician has a Practitioner record plus one PractitionerRole entry per organisation they work in, tagged with what they do (doctor, nurse, IT, support). Each patient is linked to the organisation that owns their record. The tenant boundary now lives in the same data that already describes your clinical and organisational world.
- 2 Deny by default
Set the server's default policy to deny. Anything that does not match an explicit rule is rejected, so adding a new resource type, a new endpoint, or a new role can never silently leak data across tenants. Policy becomes an explicit allow-list, not a series of conditional checks scattered across the codebase.
- 3 Write rules per role, per resource, per operation
For each staff role, declare which resources they can read, search, create, or update. Doctors might get read/search/create/update on clinical resources; nurses read/search; IT staff are limited to administrative resources like Practitioner, Organization, and Device. The rule names the role; the server intersects it with the practitioner's organisation, so a doctor at clinic A cannot reach clinic B's patients even if the rule itself never mentions a tenant.
- 4 Allow cross-organisation access through CareTeam
When a specialist outside a clinic needs to see one specific patient, add them to that patient's CareTeam. The server treats the membership as an additional, narrowly scoped grant: the specialist keeps their normal access at their own organisation, and on top of that they see the one patient they were added for. Removing the CareTeam entry takes the access away. No separate sharing system to maintain.
- 5 Cascade access down through the hierarchy
Pick how many levels access should cascade downward through the org tree. A support team placed at the platform root then sees everything below; a regional authority sees its hospitals; a study sponsor sees its sites. Clinic-level staff never see siblings or parents. Inheritance only flows down.
- 6 Inspect why a request was allowed or denied
During development, ask the server for a debug trace on any request. The response then includes which rule matched, why it matched, and (for denied requests) near-miss hints like "the practitioner has no role at this organisation", "the role code does not match this rule", or "this patient has no managing organisation". Use it to verify policy intent against actual behaviour. Turn it off in production because the output exposes your full rule configuration.
- 7 Operate it as data, not code
Patients move between clinics by updating one field on the patient record. A practitioner stops working somewhere by deactivating their role entry. New tenants are new Organization records. Caches that depend on these relationships invalidate themselves when the underlying data changes. No deploy, no service restart, no separate access-management system to keep in sync.
What you get out of the box
Tenant boundaries belong in the domain data
Healthcare multi-tenancy is rarely "one platform, multiple customers, no cross-tenant visibility". A clinic needs different visibility for doctors, nurses, and IT staff. Specialists consult across organisations. Support teams need scoped operational access. New clinics onboard. Patients transfer.
Building all of that as a parallel permission system means custom code that grows with every product change. Modelling it instead as the FHIR resources that already describe the clinical and organisational world (Organization, PractitionerRole, the patient's managing organisation, CareTeam) keeps the boundary in one place. Authorisation rules connect those relationships to the server's checks, and the server enforces them on every request, whether it arrived over REST, GraphQL, or any other supported access path.
Row-level security and SMART scopes do not cover this on their own
Row-level security at the database is close to the data and works well for simple "this row belongs to that customer" filtering. It does not express "this practitioner has a cardiology role at clinic A but not a radiology role at clinic B", or "a CareTeam membership grants this one specific patient even though the practitioner is in another organisation". Those decisions need knowledge of the FHIR data model and of clinical relationships, which the database layer does not have.
SMART on FHIR is the standard way an application gets a token from an identity provider and presents it. Its scope syntax (for example, a scope meaning "this user can read and search Observations") does not say which organisation the practitioner belongs to, where they sit in the hierarchy, or whether a CareTeam membership grants a one-patient exception. The SMART specification itself defers those decisions to a separate layer. Fire Arrow operates at that layer and accepts standard OAuth bearer tokens, so the identity provider does not have to understand FHIR scopes for any of this to work.
Searches must be narrowed inside authorisation, not after it
A missing tenant filter on a search endpoint does not throw an error. It returns results from every tenant. With a single tenant in development this often goes undetected; in production the same unfiltered search exposes cross-tenant data silently. OWASP has ranked broken access control as the top web application security risk for several editions running, and unfiltered search is one of its most common shapes.
Fire Arrow narrows searches inside the authorisation pipeline rather than after it. For REST searches, the server adds the tenant-scoping parameters before the query runs. For GraphQL, the same rules drive the query through an equivalent path, so client code does not need to differ between the two. Reads of a single resource by id (for example, fetching one patient directly) go through the same check, not through a shorter alternate code path that could be forgotten when a new resource type is added.
Multiple scopes add up rather than override
A practitioner who works at two clinics has one role record per clinic, and the server adds the two scopes together rather than asking you to pick one. A clinician with normal access at their organisation and a CareTeam membership for a patient elsewhere keeps both scopes in the same request; neither displaces the other.
This is what makes cross-organisational specialists fit cleanly. The specialist does not need to be granted broad access into another tenant; they get a single-patient exception that simply adds to whatever they already had, and removing the exception leaves their original access untouched.
Runtime changes are data updates, not deploys
A patient transferring between clinics is one field update on the patient record. The next request that hits the server evaluates against the new clinic. A practitioner who has stopped working somewhere is one flag flipped on their role entry; their access through that role is gone on the next request. Reparenting a clinic under a different network is a single change to the org-tree link, and the server's hierarchy cache picks it up automatically.
All cache invalidation is deferred until the database transaction commits, so concurrent requests cannot read a cached value that contradicts data that was just written. From an operations point of view, none of these changes need a deploy, a migration, or a service restart.
Example deployments
- Multi-clinic SaaS platform
Two clinics share one Fire Arrow Server. Each has its own doctors, nurses, IT staff, and patients, none of whom can see the other clinic's data. The SaaS vendor's support team sits at the platform's root organisation, with access cascading two levels down so they can investigate issues in either clinic without parallel admin accounts.
- Hospital network with departmental hierarchy
A regional health authority sits over its hospitals, and each hospital sits over its departments. Authority staff see across the network; hospital staff see their hospital; department staff see their department. Cross-department referrals are recorded as CareTeam memberships, so a referred specialist gets access to that one patient, not to the receiving department as a whole.
- Research consortium with site isolation
Each study site is its own Organization. Site investigators see only the participants enrolled at their site. A study coordinator at the sponsor organisation sees data across every site through the same downward cascade, with no separate "sponsor view" pipeline to maintain.
- Care coordination program with cross-organisational specialists
A multidisciplinary diabetes team (primary care, endocrinologist, dietician, nurse educator) each work at a different organisation. Adding each of them to a patient's CareTeam gives that one team access to that one patient, without granting any of them broader access to the others' clinics.
FAQ
What if a tenant requires fully separated infrastructure?
Run a separate Fire Arrow Server deployment for that tenant. The shared-infrastructure model is for tenants that accept logical separation with enforced boundaries; fully separated infrastructure stays an option when a contract or a regulator requires it.
Can a single Practitioner work at multiple clinics?
Yes. Add one role entry per clinic to the practitioner's record. The server adds the scopes together rather than asking you to pick one, so the same person sees both clinics' data through the same login. No duplicate accounts, no proxy users, no out-of-band reconciliation between systems.
How do I onboard a new tenant?
Create the Organization for the new tenant (placing it correctly in the hierarchy if applicable), add the practitioners and their role entries, and assign their patients to the new organisation. The existing rules cover the new tenant from the next request. No schema migration, no new deployment, no parallel database.
Does the model handle support staff that need cross-tenant access?
Yes. Place the support team's role record at the platform's root organisation and configure how many levels access should cascade downward. Support sees everything below them; clinic staff never see siblings or parents.
What about IoT devices and caregiver apps?
Devices have their own scope, limited to their configuration and to the observations they submit. Caregivers using a patient app have a tighter scope than the patient's own; they see what the patient has chosen to share, no more. Both fit into the same additive model: their grants stack with whatever else applies, never overriding it.
How do I prove the rules do what they claim?
During development, ask the server for a debug trace on a request. The response includes the full rule trace, which rule matched, and (for denied requests) near-miss hints that point at the most likely cause (missing role entry, wrong role code, patient with no managing organisation). Use the trace to verify policy intent against actual behaviour. Turn it off in production because the output exposes your full rule configuration.