Access and Permissions
Assign dashboard roles, partner reach, and API-key capabilities
Last Updated: 2026-09-17
Version: 1.4
VRPlatform uses the same primitive permission vocabulary for interactive users and API keys, but their bundles are separate grants.
| Grant | Stored on | Purpose |
|---|---|---|
Dashboard role (ui:*) | Team membership | Controls a signed-in user's product access |
Partner API bundle (partner:*) | Partner API key | Controls a backend integration |
| Managed-team reach | Partner membership | Controls which managed teams a user can open |
| Partner deny policy | Managed team | Removes selected partner-derived permissions |
Assigning a dashboard role never changes an API key. Granting an API-key bundle never changes a user membership.
Dashboard Roles
Dashboard roles are versioned ui:* bundles. The API expands the selected
membership's bundles and returns the effective union as
GET /me.membership.permissions.
Broad Team Access
| UI choice | Includes | Does not include |
|---|---|---|
| Team Admin | Standard Member, audit, access admin, bank setup, and listing merges | ACH execution |
| Standard Member | Product, settings, GL, and banking | Audit, access, bank setup, ACH execution |
Focused Team Access
Use this matrix for Operations and Read Only. View means read access. Other verbs name the additional actions granted in that area. A dash means the role does not grant that workflow.
| Workflow | Read Only | Operations |
|---|---|---|
| Team settings | View | Basic team view |
| Member roster | View | — |
| Integrations and webhooks | View | — |
| Calendar | View | View |
| Files | View | View and upload |
| Listings and contacts | View and export | View listings and contacts; create and edit vendors |
| Ownerships | View | View |
| Reservations | View and export | View |
| Transactions | View and export | View all; create and edit expenses; record an expense as paid without a funding account |
| General ledger | View | — |
| Accounts | View and export | View |
| Tax rates and mappings | View | View tax rates |
| Owner statements | View and export | View, edit, publish, and export |
| PM statements | View and export | — |
| Statement layouts | View | View |
| Recurring fees and commissions | View | View |
| Other reports and metrics | View and export | — |
| Bank accounts and rules | View | — |
| Bank records | View and export | — |
Read Only includes team discovery so connection details and every listed settings view can resolve the selected team before loading its domain data.
GET /booking-channels supports both Integrations and Recurring Fees. It
accepts either integrations:read or recurring-fees:read, so focused roles
and API keys can resolve fee channel data without receiving integration access.
Transaction writes use one independent permission for each public type:
transactions:expenses:write, transactions:deposits:write,
transactions:transfers:write, and transactions:payouts:write. Operations
receives only the expense permission. A type change requires both type
permissions, and a mixed batch is rejected before any item is written when a
required type permission is absent. Expense payment state still requires a
separate payment permission. ACH Payer supplies transactions:mark-paid.
Operations receives transactions:mark-paid:reimbursement, which records an
expense as paid only without a funding account, so Operations cannot record a
payment from a trust or operating bank account.
Operations receives contacts:vendors:write for vendor creation and editing.
The API checks both the requested type and the stored type on updates and batch
imports. This scope cannot create or edit owners, invite or manage owner viewers,
delete contacts, import vendor CSV files, or save ACH details. Broad contact
writers keep the existing contacts:write behavior.
Operations receives tax-rates:read so expense forms can show tax choices and
calculate totals. It does not grant tax-rate changes, deletion, or reservation
mapping access.
Operations receives team-issues:read so expense forms can read
missing account assignments
from GET /teams/issues. It covers every team issue code for the selected team
and does not grant dashboard, connection, or journal-repair actions.
Additive Privileges
These privileges are independent of team access. They add a sensitive or partner-specific capability and are not substitutes for a team-access role.
| Privilege | Adds | Does not add |
|---|---|---|
| Audit Log | Selected-team public activity history | Other product access |
| Partner Admin | Partner administration and integrations | Team reach or ACH |
| ACH Payer | Record payments and execute ACH | Bank setup or administration |
Partner Admin is available only to partner-team members. It covers partner
members, managed teams, sessions, integrations, webhooks, audit, and API keys.
ACH Payer requires Team Admin or Standard Member in the same bundle array.
Those roles carry transactions:payouts:write, which a payer needs to archive
a failed payment or record a return. POST /team/members/invite and
PUT /team/members/{userId}/permission-bundles reject ACH Payer with
Operations, Read Only, Partner Admin alone, or no team-access role: 400 with
reason: "achPayerBaseRoleRequired".
Global VRPlatform administrators cannot save owner or vendor bank details or execute, cancel, or recover ACH payments. Use a direct customer-team membership with Team Admin and ACH Payer when testing those workflows.
A managed team's deny policy can remove ACH execution from partner-derived
access even when the partner member has ACH Payer. To let a partner member pay
via ACH, assign ACH Payer with Team Admin or Standard Member on their
partner-team membership. Each team in their reach allows it only after it
removes ach-payments:execute from its partner deny policy.
Stable Bundle Names
| UI label | API catalog title | Bundle name |
|---|---|---|
| Team Admin | Team Admin | ui:team-admin:v1 |
| Standard Member | Standard Member | ui:standard-member:v1 |
| Operations | Operations | ui:operations:v1 |
| Read Only | Read Only | ui:read-only:v1 |
| Read activity history | Audit Log | ui:audit:v1 |
| Manage partner organization | Partner Admin | ui:partner-admin:v1 |
| Execute ACH payments | ACH Payer | ui:ach-payer:v1 |
For exact primitive permission strings, use
GET /team/permission-bundles. Each catalog entry returns its complete
permissions array. The tables above group those strings into UI workflows.
Every current dashboard bundle includes operations:read and apps:read.
apps:read covers GET /apps and GET /apps/{id}, the app catalog that is the
same for every team. API keys read the catalog with either apps:read or
integrations:read. The hasConnection filter on GET /apps reveals the team's
connections, so it still requires integrations:read. There are no account-specific role assignments in V1.
Build a Role Picker
The API accepts multiple bundles and combines their permissions. The role picker should present exactly one team-access role: Team Admin, Standard Member, Operations, or Read Only. Do not expose the bundle array as an undifferentiated multi-select.
Then render the independent controls:
| Control | Availability | Effect |
|---|---|---|
| Read activity history | Every team | Add Audit Log |
| Manage partner organization | Partner-team members only | Add Partner Admin |
| Execute ACH payments | With Team Admin or Standard Member | Add ACH Payer |
| Managed-team access | Partner-team members only | Select all or assigned teams |
A partner-team member therefore receives Team access, Partner organization, Sensitive permissions, and Managed-team access sections. A non-partner member receives only Team access and Sensitive permissions.
Partner Admin is independent because it governs the parent partner organization; Team Admin governs the selected team's product and administrative access. ACH Payer remains independent because even Team Admin and Standard Member exclude money movement.
Apart from the ACH Payer rule, this hierarchy is product guidance. Until the API enforces it, clients must still handle existing members with any valid bundle array.
Gate navigation and actions using membership.permissions, never a primary
role, bundle name, or legacy admin/user label. A member with no bundles can
still load GET /me and GET /me/teams, but receives no product permissions.
Role Endpoints
| Task | Endpoint |
|---|---|
| Load assignable roles and expanded permissions | GET /team/permission-bundles |
| Load primitive permission names | GET /team/permissions |
| Replace a member's complete assignment | PUT /team/members/{userId}/permission-bundles |
| Load the selected user's assignment and effective permissions | GET /me |
Team Admins may replace their own assignment, including adding or removing ACH Payer. Removing their own Team Admin access is allowed only when another active direct access manager remains. The same final-manager rule applies to updates of other members.
Grant Notifications
Partner members may invite or update client-team members with any role, including Team Admin and ACH Payer. Every grant is visible to the client team:
- When a member of a team with UI Permission Roles enabled for all users receives Team Admin or ACH Payer they did not hold, every active direct Team Admin of that team gets one email, except the member and the user who granted it. It names the member, the roles, the user who granted them, the managing partner that user acted through, and the time.
- The email is sent whether the granting user works with UI Permission Roles
or legacy roles. The grant applies through
POST /team/members/invite,PUT /team/members/{userId}/permission-bundles, andPUT /team/members/{userId}. Partner staff are never recipients. - No email is sent when the team has no other direct Team Admin, on partner teams, or on teams where UI Permission Roles is off or enabled only for some users. The audit log records every grant.
Invitations and Compatibility Updates
| Action | Bundle behavior |
|---|---|
| Invite in permission mode | Send permissionBundles, including [] when intentional |
| Resend or copy an invitation | Omit permissionBundles to preserve the complete assignment |
| Submit the same legacy role | Preserve the complete bundle assignment |
| Change a legacy role | Replace bundles with that role's compatibility default |
New and legacy non-admin user memberships default to Standard Member. Existing
team admins map to Team Admin, and existing partner-team admins also map to
Partner Admin. Before permission mode is enabled for a user, authorization
continues to follow the legacy role while the API keeps bundle data populated.
An invitation delivery failure does not roll back a committed role change.
Partner-Member Team Reach
A partner member's permissions and team reach are independent. Configure the role picker and managed-team picker as separate controls.
| Reach | Teams available through the partner membership |
|---|---|
all | Every child team managed by the partner |
assigned | Explicitly assigned managed teams only |
assigned with no teams | No managed teams |
Owner membership on a child team remains available independently of partner assignments.
Before permission mode is enabled, users retain legacy broad partner reach.
The stored all or assigned selection takes effect when permission mode is
enabled, without a separate membership migration.
A legacy role change never changes reach. Demoting a partner admin to user
through PUT /team/members/{userId} requires all reach or at least one
managed team; otherwise the API returns 400 with
reason: "managedTeamRequired". Assign teams first, then demote.
Resolve Access
| Selected-team relationship | Permission source | accessSource |
|---|---|---|
| Direct child-team membership | Child membership's bundles | direct |
Partner all reach | Partner membership's bundles | partnerDerived |
| Explicit partner assignment | Partner membership's bundles | partnerDerived |
| Direct membership and partner reach | Partner membership's bundles | partnerDerived |
| No applicable relationship | No access | Not returned |
Partner reach replaces a direct child-team membership: only the partner bundles apply, filtered by the selected team's partner deny policy. Bundles stored on the child team do not apply while the reach lasts.
Partner Members on a Managed Team
The partner manages which of its members reach a managed team and what they
can do there. GET /team/members does not list a member who reaches the team
through its managing partner, also when UI Permission Roles is off. The Members
table shows one read-only Partner row for the managing partner instead. Teams
managed directly by VRPlatform show no Partner row. The client team cannot
remove the partner. The rules below apply with UI Permission Roles.
| Action on the managed team | Partner member |
|---|---|
Change roles through PUT /team/members/{userId}/permission-bundles or PUT /team/members/{userId} | 400 with reason: "partnerManagedMember" |
Invite through POST /team/members/invite | 400 with reason: "partnerManagedMember" |
Remove through DELETE /team/members/{userId} | 400 with reason: "partnerManagedMember" |
| Count as the team's last access manager | Never |
On a client team with UI Permission Roles, members of the managing partner get
team access only through the partner console. The invite returns 400 with
reason: "partnerManagedMember" for any member of the managing partner, also
when their reach excludes the team or their partner invitation is pending.
Members of another partner can be invited as normal members and are listed
with the roles the client team picked.
Creating a team adds it to the assigned reach of every partner member listed
in addMembers. Assigning a team to a partner member who is already a direct
member turns that membership into partner access and replaces its bundles.
Removing the team from their reach deletes the membership. Selecting all
deletes their direct memberships on managed teams with UI Permission Roles.
On teams without UI Permission Roles the direct membership stays, because its
bundles add to the partner bundles there.
Use GET /me/teams as the authoritative team-switcher source. Use
GET /partner/members to render each partner member's roles, reach mode, and
explicit assignments. Replace managed-team reach through
PUT /partner/members/{userId}/memberships.
| Update | Result |
|---|---|
Select assigned | Replace the partner-managed team assignments |
Select assigned with teamIds: [] | Remove all partner-derived child-team reach |
Select all | Clear explicit partner assignments, delete direct memberships on managed teams with UI Permission Roles, and allow every managed team |
| Remove the partner member | Revoke partner-derived assignments |
These changes never delete owner memberships.
Restrict Partner-Derived Access
A directly authorized Team Admin can manage the selected team's partner deny
policy through GET and PUT /team/partner-permission-policy.
| Principal | Policy applies | Can edit policy |
|---|---|---|
| Partner-derived dashboard user | Yes | No |
| Partner API key selecting the team | Yes, after full team enablement | No |
| Embedded session for the team | Yes, after full team enablement | No |
| Direct team member without partner reach | No | Team Admin only |
The policy stores primitive permission names. A denial wins over a granted bundle. Use it to withhold sensitive actions such as ACH execution, exports, or member-role management. The policy applies team-wide rather than to one bank account or one partner user.
New teams deny ach-payments:execute to partner-derived access by default,
and the same denial is backfilled onto existing teams without replacing other
denials. A directly authorized access manager must explicitly remove that
denial before a partner can execute ACH for the client. Partner members cannot
edit the policy, even when they also hold a direct membership.
Partner API-Key Bundles
Partner keys may omit bundles for unrestricted partner access or declare up to the supported reviewed bundles. Restricted keys must have every primitive permission required by an operation.
| Bundle | Domain | Included workflow |
|---|---|---|
partner:accounting-data:v1 | Accounting | Core records, mappings, tax, and fees |
partner:ach-payments:v1 | ACH | Owner payment-method submission and Ramp execution |
partner:audit:v1 | Audit | Redacted public audit events for selected and managed teams |
partner:banking:v1 | Banking | Accounts, connections, Plaid, bank records, rules, and cleanup |
partner:calendar:v1 | Calendar | Read, create, update, and remove blocks |
partner:files:v1 | Files | Read, download, upload, update metadata, and remove files |
partner:general-ledger:v1 | General ledger | Closing, opening balances, and imports |
partner:integrations:v1 | Integrations | Apps, connections, flows, syncs, and imports |
partner:listing-merges:v1 | Listing merges | Merge listings with published statement history into a group |
partner:onboarding:v1 | Onboarding | Team onboarding guide and issue operations |
partner:payments:v1 | Payments | Record expenses and owner or manager payouts |
partner:provisioning:v1 | Provisioning | Discover and configure teams; issue sessions |
partner:recurring-fees:v1 | Recurring fees | Fee and listing-period workflows |
partner:reports:v1 | Reports | Report reads, metrics, and exports |
partner:statement-layouts:v1 | Layouts | Read, create, assign, and remove layouts |
partner:statements:v1 | Statements | Manage, publish, and export statements |
partner:team-deletion:v1 | Team deletion | Delete managed teams and read operations |
partner:team-members:v1 | Members | Read, invite, update, and remove members |
partner:webhooks:v1 | Webhooks | Subscriptions, deliveries, tests, rotation, and replay |
Current early-access domain bundles include destructive operations supported by their domain. Credential management, self-service, internal, demo, and legacy routes remain unavailable to partner API keys.
Authentication expands stored bundle names using the current catalog, so a catalog update applies without replacing an existing key.
GET /api-tokens/permission-bundles returns the assignable API-token catalog.
Each entry contains the stable name, display title, display description,
and expanded permissions. Use this endpoint to render token bundle selectors
instead of keeping a separate client-side list. It uses the same catalog shape
as GET /team/permission-bundles for human roles.
Separate Recording From Money Movement
| Permission or bundle | Effect |
|---|---|
partner:payments:v1 | Records eligible payments and payouts without ACH execution |
partner:ach-payments:v1 | Adds ACH setup and execution capabilities |
transactions:mark-paid | Records an expense payment with provider: null |
transactions:mark-paid:reimbursement | Records an expense payment only when no funding account or bank record is involved |
statements:record-payout | Records an owner or manager payout without dispatch |
ach-payment-methods:write | Submits an owner or vendor ACH payment method |
ach-payments:execute | Executes, cancels, or unlinks a Ramp payment attempt |
A restricted partner key must combine partner:payments:v1 and
partner:ach-payments:v1 to move money. provider: "ramp" requires the
applicable recording permission and ach-payments:execute before VRPlatform
reads provider state or queues work.
Implementation Checklist
- Load role and primitive-permission catalogs from the API.
- Present one of Team Admin, Standard Member, Operations, or Read Only.
- Keep partner administration, ACH, and managed-team reach independent.
- Save complete replacement arrays, including intentional empty arrays.
- Re-read
GET /meafter the selected user's own access changes. - Gate every action from effective primitive permissions.
- Treat the final API response as authoritative under concurrent changes.
