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 catalogPartner, managed teamsNo 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.

Team discovery and portfolio inventory responses that clients use to route subsequent team requests include dataRegion. This covers GET /me/teams, GET /partner, GET /partner/teams, partner billing, connections, owner statements, and listings, plus internal team billing. Region-selected issue, membership, feature-assignment, and audit event responses do not duplicate the selected region on team summaries.

Discovering Teams Before Selection

An interactive bearer user can call list teams without x-team-id. The route filters the regional result to teams the user can access. It does not make other reads or mutations team-optional.

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

GET /teams?search=GPAZ%20LLC

Set excludeTestTeams=true for a production-facing regional directory. This excludes teams marked for testing and teams assigned to the Demo and Test Console as their accounting or billing partner. VRPlatform and its direct customers remain eligible.

The call still requires authentication. For a portfolio spanning regions, select one regional slice with x-data-region as described below.

A Partner API key can instead call GET /partner/teams without x-team-id or x-data-region. It returns only that Partner's managed teams from the global control-plane catalog, so search, filters, pagination, and totals span all data regions. Use each result's id and apiBaseUrl for subsequent team-scoped requests.

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. Regional team and listing eligibility treats an absent local partner row as a cross-region relationship, not as an inactive partner. A partner row that is present in the selected region must still be active.

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.

Portal bootstrap can send allowTeamFallback=true to GET /me. When the requested team is inaccessible or deleted, including a recently deleted team still held in a short-lived client or server cache, the response resolves the user's oldest active membership instead. Treat the returned team.id as the selected context before loading team-scoped resources.

Creating a Team in a Region

See Team Setup for the canonical creation and placement rules. The x-team-id header identifies the creating team for authorization; it does not select where the new team's data lives.

Team Migration Freeze

An internal region migration temporarily freezes the selected team before its application data is copied. While the freeze is active, authenticated team-scoped POST, PUT, PATCH, and DELETE requests return 423 with code TEAM_MIGRATION_FROZEN. The response context contains the team ID and migrationStatus: "frozen".

{
  "teamId": "11111111-2222-4333-8444-555555555555",
  "migrationStatus": "frozen"
}

Reads remain available from the source region during the freeze. Clients must not redirect writes themselves or retry them against another regional host. After the verified placement flip, team discovery and bootstrap responses advertise the new regional API base URL; clear team-scoped caches and resume writes there.

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.

Use GET /partner/teams when the integration needs one cross-region managed team catalog. Use the regional portfolio reads below when the response includes regional application data; those reads still select one partition per request.

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