Skip to content
AI Building

Build an Atrium app with AI

One minimised file with everything an AI tool needs: how to work, the hard rules, the full app contract and the design system. Upload it to any LLM, then describe your app.

ATRIUM-AI-BUILD.mdMarkdown
  1. Upload the file to ChatGPT, Claude, Cursor or any AI tool (or paste its contents).
  2. Describe the app you want in plain English.
  3. Let the AI ask you for the API key when it needs one.
View the file
Auto

Section 8 of 11

Permissions (high level)

View .md

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 requestedCapabilities as 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 403 until 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).

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.

  • Create mediums: COHO_MESSENGER (default), EMAIL, SMS, WHATSAPP. Email and SMS are broadcasts (AllowReplies false). 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-quote with telephone numbers and body (COHO returns numSmsSegments and creditsRequired).
  • Tenant messagingRestricted is exposed on tenants and conversation recipients. Create and sent replies refuse restricted housemates (maintenance threads stay writable).
  • Drafts: POST .../messages with status: draft returns 201. List with includeDrafts=true. Delete with DELETE .../messages/{messageGuid} (drafts only). Promote/send a draft with PATCH .../messages/{messageGuid} (status: sent, optional body update). Drafts do not bump thread activity until promoted and stay out of the default Sent-only list.