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 follow its connection and authentication 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.

Determine Supported Operations

App category and type can organize a catalog, but they do not establish what a connection can do. Do not enable synchronization, imports, or workflows from those labels.

Use capability metadata only for the operation the client implements:

  • For connection setup, follow the selected app's authentication and connection requirements.
  • For source synchronization, offer only the resource scope, date behavior, and targeted refresh declared for that app.
  • For imports or push/pull workflows, use their metadata only when the client implements the corresponding API operation. Otherwise it can ignore them.
  • For accounting coverage, treat the connection start as inclusive and its end as exclusive.
  • For operations, distinguish connection health from one sync execution and reconcile the sync's resulting resource changes after completion.

The generated OpenAPI defines the exact capability fields and request shapes. Do not infer support from another app or maintain a provider capability list in the client.

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 extensionRequired so the client can collect them.

Teams with the extension-ecosystem-connect feature enabled do not receive extensionRequired. Their connect requests run through the integration's connect task instead and return the standard connect responses, typically oauthRequired with a provider authorization URL. Clients must handle both responses for extension apps and follow the returned action. When redirectTo is omitted, this path still uses the registered production callback and signed state described below.

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 and other legacy callback values are canonicalized to their registered trailing-slash callbacks. Other explicit callback targets are preserved exactly because OAuth providers require an exact redirect URI match. Register the exact callback with the provider before starting OAuth and ensure the client implements that route.

  1. Send appId, setup params, and your real callback redirectTo.
  2. If the API returns oauthRequired, 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 oauthRequired response looks like this:

{
  "type": "issue",
  "code": "oauthRequired",
  "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. A scoped reconnect also resolves the app from the existing team-owned connection when both appId and callback state are omitted. 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.

Recoverable prompts return HTTP 200 with type: "issue". Their finite codes are extensionRequired, formRequired, linkRequired, and oauthRequired. Provider failures use the standard HTTP error envelope: BAD_GATEWAY for a failed or invalid provider response and GATEWAY_TIMEOUT when the awaited provider task exceeds 25 seconds. No HTTP 200 response has type: "error".

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 connection status, current sync state, last fetch, and current failure. Provider sync is an external side effect and has no generic dry-run guarantee.

Use POST /connections/extract only for source data and date behavior supported by the selected app. Use POST /connections/{id}/sync for its generated targeted reservation, listing, or payout contract. The targeted request waits only for its bounded response window; a timeout does not prove that provider work stopped. Track the returned sync through GET /syncs/{id} and inspect GET /syncs/{id}/changes before reconciling canonical resources.

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.

Deleting a connection also removes its queued sync requests. If a dispatch worker starts after that deletion, it discards the stale request and does not contact the provider.

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