At connect, COHO mints a background app token — a machine principal credential (AppConnection) delivered once in lifecycle.setup as apiKey. It is a Public API key backed by a private role holding exactly the requested catalogue scopes, and it is revoked on disconnect. It is not a fake manager user (ATRIUM_USER is not the model). Schedules and other unattended work use this key with no manager signed in.
- Declare
requestedCapabilitiesas catalogue scope ids (e.g.conversations.write,tenants.read). Legacy short names (conversations,settlements, …) still expand. - Connect fails if the connecting person cannot grant every resolved scope. The credential is minted with exactly that set.
- The Store shows requested, granted, pending, and unsupported scopes.
- A manifest update never expands an existing key automatically. Existing grants keep working and newly requested calls return
403until a manager who can grant those scopes reviews and approves the exact additive delta in Store. - Approval updates the existing key’s role in place, is audited, and takes effect on the next API request. It does not rotate credentials or rerun
lifecycle.setup. Disconnect/connect remains the credential-rotation path. - Scope removal is not automatic. A future explicit downgrade/revoke flow owns that case.
Interactive open (quick view / external configure): COHO also mints a short-lived usage JWT (atriumUsageToken) bound to the signed-in manager, organisation, and connection. Public API accepts that Bearer as well as the connection API key Guid. On each usage-token request, effective scopes are recomputed as connection scopes ∩ person scopes (so a restricted manager cannot outrun their role through an org-installed app). While a quick view stays open, COHO remints via the postMessage bridge (atrium.usageToken) so interactive sessions can last as long as the manager’s COHO session — apps must swap the Bearer; they cannot mint. Background triggers keep using the connection apiKey only.
Install scope: Connect can be organisation-wide (default) or personal (installer only). Organisation installs may also set a person allow-list; when the allow-list is empty, any member with Store access can open the app. Personal installs keep the org background apiKey for schedules/webhooks, but only the installing manager appears in My apps and can open the interactive UI. That is the dual-credential model: background org key + interactive person usage JWT.
COHO staff signal: When the opening manager is a COHO super-admin, the usage JWT and Public API principal include claim isCohoStaff=true. This is a staff-only UX signal for in-app admin tools — not a grantable catalogue scope, and never present on the machine apiKey.
You cannot outrun the connecting manager’s organisation permissions on connect, and you cannot outrun the opening manager’s permissions on interactive usage.
Managers can review which Public API routes an install has called with its background connection key under My apps → Data access (last 30 days).
Scope ids for common Public API routes
Section titled “Scope ids for common Public API routes”Missing scope returns 403. Stacked [RequirePublicApiScope] attributes on one action OR. Authoritative per-action attributes live on the V11 controllers; Scalar lists required scopes on each operation (description + x-required-scopes).
| Route | Scope(s) |
|---|---|
GET /conversations, GET /conversations/{reference}, GET /conversations/{reference}/messages, GET /conversations/recipients, GET /conversation-templates, GET /conversation-merge-tags |
conversations.read |
POST /conversations, POST /conversations/{reference}/messages, PATCH /conversations/{reference}/messages/{messageGuid}, POST /conversations/{reference}/read, DELETE /conversations/{reference}/messages/{messageGuid} |
conversations.write |
GET /organisation/credits |
organisation.read or conversations.read |
POST /organisation/sms-credit-quote |
conversations.read |
GET /maintenance, GET /maintenance/categories |
maintenance.read |
POST /maintenance, POST /maintenance/convert-from-conversation |
maintenance.write (convert also requires conversations.write in-action) |
POST /actions |
actions.write |
GET /calendar/items |
calendar.read |
GET /calendar/maintenance-issues |
maintenance.read |
GET /properties, GET /properties/{propertyReference}/info-boxes/overview |
properties.read / info_boxes.read (info-boxes) |
GET /properties/{propertyReference}/tenants |
tenants.read |
GET /tenancies/{guid} |
tenancies.read |
GET /rent-due-records |
(core Public API key; rent payment writes need rent_collection.write) |
METHOD /apps/{appReference}/{operationName} (app operations gateway, §3.5) |
apps.operations |
Living full catalogue: atrium-requested-capabilities.md lists every grantable scope id, group, and label. The Store guided manifest editor uses the same catalogue (Read/Write by area). Prefer catalogue ids over legacy short names. That markdown file is CI-checked against CohoScopes so new scopes show up there when they ship.
Messaging notes for apps
Section titled “Messaging notes for apps”- Create mediums:
COHO_MESSENGER(default),EMAIL,SMS,WHATSAPP. Email and SMS are broadcasts (AllowRepliesfalse). SMS send prices itself (segments + credit debit). WhatsApp requires an active org WhatsApp connection. - WhatsApp is billed by Meta to the landlord (no COHO credit balance). COHO credits (SMS and other types) are on
GET /organisation/credits(optional?type=SMS). Optional SMS cost preview:POST /organisation/sms-credit-quotewith telephone numbers andbody(COHO returnsnumSmsSegmentsandcreditsRequired). - Tenant
messagingRestrictedis exposed on tenants and conversation recipients. Create and sent replies refuse restricted housemates (maintenance threads stay writable). - Drafts:
POST .../messageswithstatus: draftreturns 201. List withincludeDrafts=true. Delete withDELETE .../messages/{messageGuid}(drafts only). Promote/send a draft withPATCH .../messages/{messageGuid}(status: sent, optional body update). Drafts do not bump thread activity until promoted and stay out of the default Sent-only list.