Skip to content
Avinya Plus logoAvinya Plus

Clinic data security and access control, enforced in the database — not just the UI

Avinya Plus enforces clinic data security with PostgreSQL Row Level Security (enabled and forced) so each clinic sees only its own rows, role-based access control on a module.resource.action permission tree checked server-side, a tenant-scoped audit trail that logs who viewed which patient, including denied attempts, and private document buckets reached only through one-hour signed links.

Everyone has the master key, and nobody knows who opened the file

On paper and in most spreadsheets, access control is a locked cabinet and a trust system. The receptionist can read every patient's history, there's no record of who pulled a chart, and a leaked Excel export is the whole database gone. Old EMRs fake it with checkboxes in the UI that the database happily ignores if someone hits the API directly. You need access rules and an audit trail that live in the data layer, where a busy front desk can't accidentally switch them off.

Built for how clinics actually work.

Per-clinic isolation enforced in PostgreSQL, not app code

Fourteen tenant-scoped tables (patients, appointments, prescriptions, medical_records, invoices, audit_logs, roles and more) have Row Level Security both enabled and forced, so even the table owner can't bypass it. The clinical-data isolate policies require an explicit clinic membership; only the tenant catalog read path widens to an org Owner/Admin. There's no 'WHERE clinic = ?' filter in app code for a developer to forget. One clinic can't read another's clinical rows even by hitting the API directly.

Role-based access on a module.resource.action permission tree

Permissions are dot-path keys grouped by module → resource → action (clinical.patients.view, clinical.medicalRecords.manage, billing.invoices.manage, settings.auditLogs.view) stored as a JSONB tree on each role. The enforcement helper grants access only when a path resolves to boolean true; objects and non-booleans are denied. Roles are unique per clinic, so the same person can hold a different role at each location.

Permissions checked server-side, not just hidden in the menu

The sidebar hides nav items a role can't use, but that's only cosmetic. The real gate is a requirePermission check that runs in the data layer before any query (patients.getById requires clinical.patients.view, editing a record requires clinical.medicalRecords.manage) wired through 25 API modules. Hitting the API without the right permission throws a PERMISSION_DENIED error, so a hidden button is never the security boundary.

A PHI audit trail that records who opened which chart, including denials

Opening a patient or a medical record writes a 'view' row to the tenant-scoped audit log (user, entity, timestamp, and record type), and for records the view is gated by a permission check first. The action vocabulary, enforced by a database CHECK, spans create, update, delete, view, download, print, export, share, login, logout, and denied, plus consent and break-glass events. Every blocked permission check is logged as 'denied', so failed access is as visible as successful access.

Documents behind private buckets and one-hour signed links

Patient files never sit on a public URL. The document bucket is created with public access off, a 10MB cap, and a fixed allow-list (JPEG/PNG/WebP/PDF), and every download mints a fresh signed link that expires in one hour. Upload paths are namespaced per clinic and patient, and an app-layer guard rejects any request for a path outside the caller's own clinic prefix, so even a valid link can't be walked sideways into another clinic's files.

Break-glass emergency access that's never silent

When a patient can't approve access themselves (unconscious, a minor, or an emergency with no patient app), a user with a clinical role (Front Desk is excluded) can break-glass into the record. Every grant lands in a dedicated audit table with a reason of at least 20 characters and a category from a closed set, auto-revokes after 24 hours, and surfaces to the patient as a banner with one-click revoke.

At a glance

  • PostgreSQL Row Level Security is enabled and forced on 14 tenant-scoped tables; isolate policies require explicit clinic membership, so one clinic can't read another's clinical rows, even via the API.
  • Role-based access uses a JSONB permission tree of module.resource.action keys; a key grants access only when it resolves to true, enforced server-side across 25 API modules (PERMISSION_DENIED otherwise).
  • The audit log covers create, update, delete, view, download, print, export, share, login, logout, and denied. Opening a patient or record logs a 'view', and every blocked check logs a 'denied'.
  • Clinical documents live in a private bucket (10MB cap; PDF/JPEG/PNG/WebP only), reachable only via signed URLs that expire in one hour, with paths namespaced per clinic and patient.
  • Break-glass emergency access needs a clinical role and a 20-character reason, is written to a dedicated audit table, auto-revokes after 24 hours, and shows the patient a one-click revoke.

See how it stacks up.

Feature comparison: paper or spreadsheets versus legacy EMR software versus Avinya Plus.
FeaturePaper / ExcelLegacy EMRAvinya Plus
Access control enforced in the database (RLS)
No
Partial
RLS
Role-based permissions (module.resource.action)
No
Partial
Yes
Permissions checked server-side, not just in the menu
No
Partial
Yes
Audit log of who viewed each patient
No
Partial
Yes
Failed / denied access attempts logged
No
No
Yes
Documents behind expiring signed links
No
Partial
Yes
Accountable emergency (break-glass) access
No
No
Yes

Questions, answered.

Does Avinya Plus stop one clinic from seeing another clinic's data?

Yes, for clinical data it's enforced in the database. PostgreSQL Row Level Security is enabled and forced on patients, appointments, prescriptions, medical records, invoices and audit logs, and the isolate policies require an explicit membership in that clinic. There's no clinic-id filter in app code to forget, so one clinic can't read another's clinical rows even via the API.

Can I control what each staff member can see and do?

Yes. Access is role-based on a permission tree of keys like clinical.patients.view and billing.invoices.manage, and a key only grants access when it resolves to true. A receptionist can be allowed to book appointments but not open medical records. Roles are per clinic, so the same person can have different access at each location.

Is there a record of who opened a patient's file?

Yes. Opening a patient or a medical record writes a 'view' entry to a tenant-scoped audit log (user, record, timestamp, and record type) before the data is shown. The audit trail also covers create, update, delete, download, print, export, share, and even denied access attempts, which are logged as 'denied'.

Is hiding a button in the menu the only thing stopping access?

No. Hiding nav items is cosmetic. The real check runs in the data layer before any query across 25 API modules, so calling the API without the right permission is rejected with a PERMISSION_DENIED error, not just an invisible button.

How are lab reports and scanned documents protected?

They live in a private storage bucket (public access off, 10MB cap, PDF/JPEG/PNG/WebP only), never on a public URL. Each access generates a fresh signed link that expires after one hour, and paths are namespaced per clinic and patient, with an app-layer guard rejecting any path outside the caller's clinic prefix.

Is Avinya Plus HIPAA or DPDP certified?

We don't claim any compliance certification. What we can describe precisely is the technical controls actually in the code: per-clinic Row Level Security (enabled and forced), role-based access checked server-side, a PHI audit trail that logs views and denied attempts, private document buckets with one-hour signed links, and accountable break-glass emergency access. India-first today, architecture built to go global, in early access.

Run your clinic on Avinya Plus.

Patient records, billing, and scheduling in one system your team will actually use.