VRPlatformVRPlatform
Getting Started

Team Context

Know when a request selects one team and when it is partner scoped

Most accounting resources belong to one team. Select that team with:

x-team-id: <team-id>

Route Shapes

ShapeExampleSelection
Regional portfolioTeams, audit eventsx-data-region
Partner/catalogGET /partner, GET /appsNo managed-team selector
Team singletonGET /teamx-team-id for multi-team credentials
Team resourceGET /transactionsx-team-id for multi-team credentials
User bootstrapGET /meOptional; response identifies resolved team
User teamsGET /me/teamsNo team selector; reads the control plane
Embedded callAllowed classified read with session bearerSession's team; header optional

Do not send two competing team selectors. An embedded session is already bound to the team it was issued for: x-team-id is unnecessary there, and sending an x-team-id that names a different team is rejected with 403. The team ID in the response is the selected context; store it with any team-scoped client cache. Embedded sessions cannot use allowTeamFallback; losing access to the signed team ends that session instead of selecting another membership.

Discovering Teams Before Selection

An interactive bearer user can call list teams or look up teams without x-team-id. These discovery routes filter the regional result to teams the user can access. They do not make other reads or mutations team-optional.

Use the collection's search parameter for a general filtered list:

GET /teams?search=GPAZ%20LLC

Use the dedicated lookup when the client needs only name-based discovery:

GET /teams/lookup?name=GPAZ%20LLC

Both calls still require authentication. For a portfolio spanning regions, select one regional slice with x-data-region as described below.

Switching Teams

Load GET /me/teams for the team switcher. It reads the control plane and can therefore return teams from every region in one paginated response. VRPlatform admins receive all teams regardless of membership. Other users receive their direct teams and child teams of partners they belong to.

Use the comma-separated ids query parameter to hydrate selected or recent teams. IDs only narrow the caller's existing access, and they compose with search, status, type, sorting, and pagination. pagination.total reflects the fully filtered accessible set.

Each row includes the team's regional API base URL, isGeneralLedger, and the nullable effective billingPartnerId. The effective billing partner prefers an explicit billing partner and otherwise uses the accounting partner.

GET /team and GET /teams/resolve return effective partner identity even when the selected team and its partner are stored in different regions. The selected team's application data still comes only from its own regional API.

User membership provisioning follows the selected team's dataRegion, even when the provisioning request is authenticated through a partner or platform team in another region. The control plane returns the membership immediately, and its regional projection is sent only to the cell that owns the selected team. Continue team-scoped work through that team's advertised API base URL.

When the user selects a team:

  1. cancel or ignore in-flight requests for the old team;
  2. replace the request header;
  3. clear team-scoped entities, issues, cursors, and snapshots;
  4. load the new team bootstrap state; and
  5. establish new webhook or polling state for that team.

Resource IDs do not grant cross-team access. The API verifies both the credential's access scope and the selected team's ownership of referenced resources.

Creating a Team in a Region

Send POST /teams to the generic API gateway and set the new team's dataRegion in the request body. The gateway forwards the request once to the matching regional cell. The x-team-id header still identifies the creating team for authorization; it does not select where the new team's data lives.

If dataRegion is omitted, creation uses the receiving cell. A request sent directly to a different strict regional cell returns 421 with the selected region and its API base URL.

Frontend Context

GET /me returns selected-team setup signals under frontendContext. hasOwnerBlockingConnection is true when the team has an active connection to a property-management-system app whose declared actions include blockCalendar. The capability does not depend on the team's stored owner portal setting or the connection's accounting window.

hasAchConnection is true when an AccountConnection links an active Ramp Connection to an active Account for the selected team. Inactive Connections, Connections for other apps, and inactive linked Accounts do not count. This is a setup-presence signal; use Account and AccountConnection ACH capabilities when payment readiness is required.

The response evaluates team feature assignments and user approvals from the global control plane. Notification preferences and owner access remain scoped to the selected team's regional database.

Switching Partner Portfolio Regions

Interactive partner applications discover regions from GET /me when it resolves a partner team. partnerContext.managedTeamRegions lists every region containing active or inactive managed teams. Each entry has the region's API base URL and active/inactive counts. Backend partner integrations use the regional API endpoints supplied in their integration configuration. Do not hardcode a customer- or partner-specific hostname.

A partner API key is global: use the same credential for every regional API endpoint. The partner itself does not need an application row in each region. The selected managed team must still belong to the serving region; a strict regional host returns 421 when x-team-id points elsewhere.

For a supported portfolio read, send:

x-data-region: us

x-team-id and x-data-region have different meanings: the first is the authorization/current-team context, while the second selects one regional data partition. The API reads one region per request. Call the advertised regional endpoints independently when the UI needs more than one region, and keep page/cursor state per region. Do not send x-data-region as a substitute for x-team-id on team-scoped routes.

Supported Partner Regional Reads

The following partner-facing reads currently declare x-data-region:

The generated operation page is authoritative. If an operation does not declare the header, x-data-region does not make it a regional portfolio read.

Fetch More Than One Region

Use the configured endpoint for each region and send its matching region identifier. Fetch regions independently; a slow or unavailable partition should not corrupt another partition's pagination state.

const resultsByRegion = await Promise.allSettled(
  regionalApiEndpoints.map(async ({ dataRegion, apiBaseUrl }) => {
    const url = new URL('/partner/audit-events', apiBaseUrl);
    url.searchParams.set('order', 'asc');
    url.searchParams.set('limit', '100');

    const response = await fetch(url, {
      headers: {
        'x-api-key': partnerApiKey,
        'x-data-region': dataRegion,
      },
    });
    if (!response.ok) {
      throw new Error(`${dataRegion} request failed: ${response.status}`);
    }

    return { dataRegion, page: await response.json() };
  })
);

Store page numbers or cursors by region, endpoint, and filter set. Tag every record with its region before combining results in a UI or warehouse. There is no cross-region cursor or guaranteed global ordering, so sort merged display data only after the regional responses arrive.

On this page