Inter-app Data Grants
The system is not one program. The CRM, the phone system, the workflows engine and the portal each run on their own, with their own data. Sometimes one of them needs to read something that belongs to another — the workflows engine wants the list of doctors the CRM keeps, so a workflow can be built around it.
Nothing like that happens on its own. One app can read another app's data only when somebody writes it down here, in plain sight, as a grant. Until then the answer is nothing — not an empty list by accident, but a refusal.
Where to find it
| Screen | Where it lives | What you need |
|---|---|---|
Inter-app data grants — /admin/inter-app-grants | Settings → System → Inter-app data grants | Permission to see the grants; changing one needs its own separate permission |
Both permissions are platform-wide, because a grant is not a tenant's row — it is wiring for the whole system. Someone who administers a single space cannot write one, even if they somehow hold the key.
What a grant says
A grant is one sentence with three nouns in it: the calling app may read this data set from the target app.
| The noun | What it means |
|---|---|
| Calling app | the app that wants to read. It is the one doing the asking, and it never owns the data |
| Target app | the app the data belongs to. It owns the records, it decides what it is willing to share at all, and it answers the call |
| Data set | a named thing the target app offers — not a table and not a copy. crm.contacts.doctors is "the contacts of one named list", answered by the CRM from its own live records |
An app can never be both sides of the same grant. Reading your own data is not a crossing, so the system refuses it and the log stays meaningful: every line in it is a genuine crossing between two apps.
Three things must all be true
This is the part worth reading twice. A brokered read comes back only when all three of these hold:
- The calling app is granted — a grant on this page says so, it is ticked Allowed, and it is switched On.
- The target app declares the data set — the owning app says, in its own code, that it is willing to share this one. An operator cannot invent a data set here; see What the apps declare below.
- The signed-in person is permitted — the person whose session the call is made on behalf of holds that data set's own permission.
Any one of them missing and the answer is nothing. There is no combination of grants that lets an app read something on behalf of somebody who could not read it themselves.
Whose records come back
Only the signed-in person's own space's records, narrowed further to what the grant's filter names.
The call carries the person's own identity, forwarded unchanged, so the read runs with the same space fence as every other read in the product: a person signed into one space cannot see another space's contacts through a brokered call any more than they can through the CRM's own screens. The grant does not loosen that and cannot.
On top of that comes the filter. For the doctors data set the filter names a data group — a named list of records — and the read returns the contacts of that list, in that person's space, and nothing else. Which list is an operator's decision written in the grant, not a constant somewhere in the code. A grant that names no list shares nothing: the doctors data set refuses the call outright rather than falling back to every contact, because a field somebody forgot must never read as the widest possible share.
Reading the Grants table
Each row names the Calling app and the Target app (display name, with the system key beneath it), the Data set, then:
- Allowed — Yes or No. No means the grant exists and refuses, which is the same answer as no grant at all. It is a way to write down a decision rather than delete it.
- On — On or Off. Off is the switch; the filter is kept.
- Filter — the narrowing, as
key: valuepairs. An em-dash means the grant pins nothing, and what that does is the data set's own rule. The CRM's doctors list (crm.contacts.doctors) refuses a grant that names no group — it shares nothing and never falls back to every contact — so a doctors grant must name the list, for example{ "group": "Doctors" }. - Description — why this grant exists. Write one. In a year it is the only thing that will explain the row.
- Created — when it was written.
No grants yet says what it means: no app can read another app's data at all.
Adding a grant
The New grant form appears for anyone who may change grants.
- Calling app — pick the app that will do the asking.
- Target app — pick the app that owns the data. Changing it clears whatever data set was chosen for the previous one.
- Data set — the picker offers exactly what the target app declares. Pick one. Something else (type it) exists for the case where the grant has to be written before the app that will expose it ships — the page warns you that it will answer "not found" until one does, and the conformance check keeps reporting the gap for as long as it lasts.
- Allowed — ticked means allowed. Unticked writes the grant as a standing refusal.
- Filter config (JSON) — the narrowing, for example
{ "group": "Doctors" }. Under the box the page lists the filter keys this data set lets a grant pin; a key the target app does not declare is refused by name, because a filter the handler ignores would read like a narrowing that is not there. - Why this grant exists — the description.
Save grant writes it. Editing is the same form: Edit on a row loads it, and saving overwrites that grant rather than making a second one.
The Calling app, Target app and Data set are the grant's identity — the grant store is keyed on those three together — so an edit can only change the grant's other fields: the three pickers are locked while a grant is loaded, and the form says so. To move a grant to a different calling app, target app or data set, write the new grant and then switch off or delete the old one. The old grant keeps working until you do: nothing about saving a new one closes it.
Switching one off, and removing one
Two different actions, for two different situations.
| Action | What happens |
|---|---|
| Switch off | the grant stops working immediately — the calling app is refused exactly as if there were no grant — and its filter is kept. Switch it on again and the narrowing is still there |
| Delete | the row is gone. The calling app stops being able to read that data set at all, and the filter you worked out is gone with it |
Deleting asks you to confirm, and says the same thing: switching off instead keeps the filter for later. Reach for Switch off unless you mean never again.
Every one of these — create, edit, switch, delete — is written to the audit log with the grant's before and after state. A grant is a standing permission for a process rather than for a person, so who opened it and when is the whole question, and the row itself cannot answer it.
What the apps declare
The bottom of the page is not editable, and it is the part to read when a grant does not behave.
- Exposed data sets — what an app is willing to share: every data set every app declares, the permission the person needs for it, and the filters a grant may pin on it. If a data set is not in this table, no grant can make it work.
- Declared needs — what an app says it wants to read: every data set an app says it needs, which app it wants it from, and whether it has been granted. A No here means that call is being refused today. When any exist, the top of the page says so in red.
That red line is usually the answer to "why is this app not seeing anything".
When it breaks, you find out
A broker that refuses fails silently by design — the calling app is simply told nothing. That is the right behaviour and a terrible way to discover a mistake, so a conformance runner checks the whole arrangement on a schedule and notifies operators when it stops working: a grant pointing at a data set nobody declares, a target app with no reachable address, a data set that answers empty when it should not. It appears as the inter-app card on the Conformance page — see Maintenance & Conformance.