Skip to main content

Platform architecture

Haste Health isolates data on two levels: Tenants are the top-level organizational boundary, and Projects partition resources within a tenant. Every request, token, and resource belongs to exactly one tenant and one project, no exceptions.

Tenants and projects​

Tenantacme-health
Project: production
Project: staging

Every tenant gets exactly one System Project. It holds identity, login config, and the Project record for every project in the tenant; access rules, OAuth clients, and clinical data all live in regular projects like the two on the right.

LevelIsolatesIdentified by
TenantIdentity providers, users, and the Project record of every project underneath itTenant ID, first path segment
ProjectAccess policies, OAuth clients, memberships, FHIR resources, searchProject ID, second path segment

A resource in one project can't reference a resource in another, and a tenant's data is never visible to another tenant. Access tokens are scoped to a tenant, and, once a user's membership is checked, to specific projects within it.

The System Project​

Every tenant gets one reserved project, system, created automatically and impossible to delete or rename. It holds tenant-level resources, including the record of every other project in the tenant:

  • IdentityProvider — external OIDC providers for federated login
  • User — accounts and their tenant-level role (admin, member, or owner)
  • Project — metadata for every project in the tenant, user-created ones included
{
"resourceType": "User",
"email": "bob@myhealth.health",
"role": "admin"
}

Regular projects​

Everything else lives in regular, user-created projects, scoped per project rather than per tenant:

  • AccessPolicyV2 — the access a user or client has within that project. See Access control.
  • ClientApplication — OAuth clients associated with that project
  • Membership — links a system-project User to this project
  • Clinical and application data: Patient, Observation, and every other FHIR resource

How a request resolves​

1Authenticate

User signs in through that project's OAuth client.

2Verify

Credentials and Membership are checked.

3Issue token

Token is scoped to accessible project(s).

4Evaluate

AccessPolicyV2 in that project is applied.

5Respond

FHIR response is returned.

Membership resources (in each project) determine which projects a user can reach; that project's own AccessPolicyV2 then determines what they can do once there. See Identity & access control for the full model.

URL structure​

https://api.haste.health/w/{tenant}/{project}/api/v1/fhir/{resourceType}/{id}
↓ ↓ ↓ ↓
Tenant Project Resource Resource ID
GET /w/acme-health/production/api/v1/fhir/Patient/123 # a project's data
GET /w/acme-health/system/api/v1/fhir/User/user-456 # the System Project

Working with projects​

### Create
POST /w/{tenant}/system/api/v1/fhir/Project
Content-Type: application/fhir+json

{ "resourceType": "Project", "name": "MY CLINIC", "fhirVersion": "r4" }

### List
GET /w/{tenant}/system/api/v1/fhir/Project

### Delete (permanent; the reserved `system` project can't be deleted)
DELETE /w/{tenant}/system/api/v1/fhir/Project/cardiology

Projects are managed through the System Project's API, but each one is a fully separate data container once created.

Choosing project boundaries​

Common ways teams split projects within a tenant:

  • Environment: development, staging, production
  • Department: cardiology, oncology, emergency
  • Application: patient-portal, clinical-emr, analytics
  • Customer: one project per customer, for SaaS deployments

Keep related resources together within one project. References can't cross project boundaries.