VRPlatformVRPlatform
Integrate & Migrate Data

Connections

Discover apps, create source identity, complete OAuth, and monitor sync

A connection relates one team to a PMS, booking channel, bank provider, or other external system. It scopes source identity and synchronization; it does not replace VRPlatform resource IDs.

Unrestricted partner API keys can use the integration routes in this guide for the team selected by x-team-id. Restricted partner keys grant partner:integrations:v1 for app, connection, flow, mapping, and setting reads; connection and workflow configuration writes; sync creation, status, changes, cancellation, and subtasks; booking-channel discovery; source downloads; flow execution; and connection imports. The bundle does not grant connection deletion, which requires the separate integrations:delete scope.

GET /partner/connections is the regional portfolio summary, not a replacement for these selected-team routes. API-key requests cannot manage credentials through /api-tokens; that surface requires a signed-in admin.

Use this identity chain when a canonical resource needs a separate external identity:

App -> Connection -> EntityConnection -> canonical entity

For banking, the EntityConnection is an AccountConnection and the canonical entity is an Account with type=bank. A bank feed is the stream of BankRecords for that Account, including CSV-imported records. CSV records can use accountId directly and do not need an AccountConnection.

Discover The App

Call GET /apps, choose the exact returned id, and inspect its category, flows, extractors, importers, and connection requirements. Do not use the display name as identity.

Set hasConnection=true to return only apps with at least one connection for the team selected by x-team-id. Connection status does not affect this filter. Set hasConnection=false to return only apps without a connection for that team. Omit the parameter to return the full app catalog.

Create With Credentials

Call POST /connections with partner or team API-key authentication and the selected x-team-id. Provide appId, optional name, and the credential shape declared by that app's generated contract.

Creation can queue PMS initialization or provider work. Read the returned connection and sync state instead of assuming all external setup completed in the request.

Browser Extension Credentials

Apps whose authentication.extension is true or an extension settings object use the browser-extension credential flow. A connect request without credentials returns ISSUE_EXTENSION so the client can collect them.

Submit extension credentials through params.credentials together with stable account identity from params.uniqueRef, credential email, or credential username. The API completes these credentials directly; do not forward them through any intermediary OAuth flow or treat the issue response as an OAuth prompt.

OAuth Flow

Use POST /connections/connect as the normal OAuth entry point.

When redirectTo is omitted, the API uses the registered production callback https://portal.vrplatform.app/callback/. The legacy production value without the final slash is canonicalized to that registered callback. Other explicit callback targets are preserved exactly because OAuth providers require an exact redirect URI match.

  1. Send appId, setup params, and your real callback redirectTo.
  2. If the API returns ISSUE_REQUEST_OAUTH, redirect to action.value, the provider authorization URL.
  3. The provider redirects back to redirectTo with state and other params.
  4. Send those callback params unchanged to POST /connections/connect.
  5. Read the success response and resulting connection.

An ISSUE_REQUEST_OAUTH response looks like this:

{
  "type": "issue",
  "code": "ISSUE_REQUEST_OAUTH",
  "message": "Please provide the missing details to connect this app.",
  "state": { "expiresIn": 180000 },
  "action": {
    "type": "oauth",
    "value": "https://provider.example.com/oauth/authorize?client_id=...&state=..."
  }
}

Callback params go into the request body under params — copy everything the provider appended to your callback URL, including state, as-is. The state value resolves the app, so appId may be omitted on the callback request. To reconnect an existing connection, send the same body to POST /connections/{id}/connect; a first-time connect uses POST /connections/connect:

POST /connections/11111111-2222-3333-4444-555555555555/connect
{
  "params": {
    "state": "<state param exactly as received on the callback>",
    "code": "<provider authorization code>"
  }
}

A completed connect returns type: "success" with the connection id and the connection resource.

Provider account identity is case-insensitive during connection completion. Create and reconnect trim boundary whitespace and normalize the returned identity to lowercase, so formatting-only provider changes do not make the same login look like a different account. A genuinely different provider identity still blocks reconnecting the selected connection.

If another connection already owns the returned provider identity, reconnect fails before changing credentials, status, or identity on either connection. The response identifies the condition as an existing provider-account conflict instead of exposing the underlying database constraint.

redirectTo is your callback; action.value is the provider page. Do not redirect to the callback before the provider authorizes.

state is a signed token that binds the app, team, setup params, callback, and optional connection; during the flow it can come back wrapped in an outer envelope that embeds the signed token. The API verifies the signature and the team binding, so pass the callback state back exactly as received — a rebuilt or edited value is rejected. If state is missing, malformed, expired, or completed under another team, restart the flow.

Update Connection Settings

Use PUT /connections/{id} to update a connection's name or active status.

PMS accounting windows (accountingStartAt and accountingEndAt) are a VRPlatform-admin repair surface. Other credentials receive 403 when either field is present; values are never silently ignored. The start is inclusive, the end is exclusive, and the normal non-overlap and date-order rules still apply.

Synchronization

Use GET /connections, GET /connections/{id}, and Sync connection to render status, enabled flows, last fetch, and current failure. Provider sync is an external side effect and has no generic dry-run guarantee.

Manual extract and sync requests against a credential-broken connection (fetchStatus: failed, set when the provider reports a breaking credential error such as a Plaid login-required Item) are rejected with 409 and error code CONNECTION_RECONNECT_REQUIRED instead of queueing a sync that cannot run. The error message is static; context.connectionId and context.connectionName identify the affected connection. Direct the user to the provider reconnect flow; a successful reconnect resets the fetch status and re-enables manual fetches. Initial syncs created by the reconnect flow itself are not blocked.

Reconnect sync dispatch is asynchronous and retryable. If dispatch setup or provider task creation fails, the sync changes to failed with the dispatch error instead of remaining indefinitely queued without a Trigger run. A later effect retry uses the same idempotency key and can return the sync to queued after successful dispatch.

Flow Settings

Read a configured flow setting through GET /flows/{id}/connections/{connectionId}/settings/{settingKey}. A valid setting key that has not been set returns {}; this is not a missing flow or connection. Create or replace its value with POST to the same path, and send null when the generated setting contract permits deletion.

Primitive setting values retain their declared JSON type. Send booleans and numbers as JSON booleans and numbers, and send string settings as plain JSON strings.

For production monitoring and team issues, continue to Operate.

Webhook Reconciliation

Subscribe to connection.changed for committed public connection changes and sync.status.changed for actual sync status transitions. Credential-only updates and sync subtask-only changes do not emit. Compare resourceVersion per resource and fetch the event's href; webhook bodies never contain credentials or provider response data.

API Reference

On this page