VRPlatformVRPlatform
Build a Product UI

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.

GrantStored onPurpose
Dashboard role (ui:*)Team membershipControls a signed-in user's product access
Partner API bundle (partner:*)Partner API keyControls a backend integration
Managed-team reachPartner membershipControls which managed teams a user can open
Partner deny policyManaged teamRemoves 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 choiceIncludesDoes not include
Team AdminStandard Member, audit, access admin, bank setup, and listing mergesACH execution
Standard MemberProduct, settings, GL, and bankingAudit, 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.

WorkflowRead OnlyOperations
Team settingsViewBasic team view
Member rosterView—
Integrations and webhooksView—
CalendarViewView
FilesViewView and upload
Listings and contactsView and exportView listings and contacts; create and edit vendors
OwnershipsViewView
ReservationsView and exportView
TransactionsView and exportView all; create and edit expenses; record an expense as paid without a funding account
General ledgerView—
AccountsView and exportView
Tax rates and mappingsViewView tax rates
Owner statementsView and exportView, edit, publish, and export
PM statementsView and export—
Statement layoutsViewView
Recurring fees and commissionsViewView
Other reports and metricsView and export—
Bank accounts and rulesView—
Bank recordsView 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.

PrivilegeAddsDoes not add
Audit LogSelected-team public activity historyOther product access
Partner AdminPartner administration and integrationsTeam reach or ACH
ACH PayerRecord payments and execute ACHBank 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 labelAPI catalog titleBundle name
Team AdminTeam Adminui:team-admin:v1
Standard MemberStandard Memberui:standard-member:v1
OperationsOperationsui:operations:v1
Read OnlyRead Onlyui:read-only:v1
Read activity historyAudit Logui:audit:v1
Manage partner organizationPartner Adminui:partner-admin:v1
Execute ACH paymentsACH Payerui: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:

ControlAvailabilityEffect
Read activity historyEvery teamAdd Audit Log
Manage partner organizationPartner-team members onlyAdd Partner Admin
Execute ACH paymentsWith Team Admin or Standard MemberAdd ACH Payer
Managed-team accessPartner-team members onlySelect 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

TaskEndpoint
Load assignable roles and expanded permissionsGET /team/permission-bundles
Load primitive permission namesGET /team/permissions
Replace a member's complete assignmentPUT /team/members/{userId}/permission-bundles
Load the selected user's assignment and effective permissionsGET /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, and PUT /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

ActionBundle behavior
Invite in permission modeSend permissionBundles, including [] when intentional
Resend or copy an invitationOmit permissionBundles to preserve the complete assignment
Submit the same legacy rolePreserve the complete bundle assignment
Change a legacy roleReplace 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.

ReachTeams available through the partner membership
allEvery child team managed by the partner
assignedExplicitly assigned managed teams only
assigned with no teamsNo 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 relationshipPermission sourceaccessSource
Direct child-team membershipChild membership's bundlesdirect
Partner all reachPartner membership's bundlespartnerDerived
Explicit partner assignmentPartner membership's bundlespartnerDerived
Direct membership and partner reachPartner membership's bundlespartnerDerived
No applicable relationshipNo accessNot 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 teamPartner member
Change roles through PUT /team/members/{userId}/permission-bundles or PUT /team/members/{userId}400 with reason: "partnerManagedMember"
Invite through POST /team/members/invite400 with reason: "partnerManagedMember"
Remove through DELETE /team/members/{userId}400 with reason: "partnerManagedMember"
Count as the team's last access managerNever

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.

UpdateResult
Select assignedReplace the partner-managed team assignments
Select assigned with teamIds: []Remove all partner-derived child-team reach
Select allClear explicit partner assignments, delete direct memberships on managed teams with UI Permission Roles, and allow every managed team
Remove the partner memberRevoke 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.

PrincipalPolicy appliesCan edit policy
Partner-derived dashboard userYesNo
Partner API key selecting the teamYes, after full team enablementNo
Embedded session for the teamYes, after full team enablementNo
Direct team member without partner reachNoTeam 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.

BundleDomainIncluded workflow
partner:accounting-data:v1AccountingCore records, mappings, tax, and fees
partner:ach-payments:v1ACHOwner payment-method submission and Ramp execution
partner:audit:v1AuditRedacted public audit events for selected and managed teams
partner:banking:v1BankingAccounts, connections, Plaid, bank records, rules, and cleanup
partner:calendar:v1CalendarRead, create, update, and remove blocks
partner:files:v1FilesRead, download, upload, update metadata, and remove files
partner:general-ledger:v1General ledgerClosing, opening balances, and imports
partner:integrations:v1IntegrationsApps, connections, flows, syncs, and imports
partner:listing-merges:v1Listing mergesMerge listings with published statement history into a group
partner:onboarding:v1OnboardingTeam onboarding guide and issue operations
partner:payments:v1PaymentsRecord expenses and owner or manager payouts
partner:provisioning:v1ProvisioningDiscover and configure teams; issue sessions
partner:recurring-fees:v1Recurring feesFee and listing-period workflows
partner:reports:v1ReportsReport reads, metrics, and exports
partner:statement-layouts:v1LayoutsRead, create, assign, and remove layouts
partner:statements:v1StatementsManage, publish, and export statements
partner:team-deletion:v1Team deletionDelete managed teams and read operations
partner:team-members:v1MembersRead, invite, update, and remove members
partner:webhooks:v1WebhooksSubscriptions, 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 bundleEffect
partner:payments:v1Records eligible payments and payouts without ACH execution
partner:ach-payments:v1Adds ACH setup and execution capabilities
transactions:mark-paidRecords an expense payment with provider: null
transactions:mark-paid:reimbursementRecords an expense payment only when no funding account or bank record is involved
statements:record-payoutRecords an owner or manager payout without dispatch
ach-payment-methods:writeSubmits an owner or vendor ACH payment method
ach-payments:executeExecutes, 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

  1. Load role and primitive-permission catalogs from the API.
  2. Present one of Team Admin, Standard Member, Operations, or Read Only.
  3. Keep partner administration, ACH, and managed-team reach independent.
  4. Save complete replacement arrays, including intentional empty arrays.
  5. Re-read GET /me after the selected user's own access changes.
  6. Gate every action from effective primitive permissions.
  7. Treat the final API response as authoritative under concurrent changes.

On this page