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 entityFor 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.
- Send
appId, setupparams, and your real callbackredirectTo. - If the API returns
oauthRequired, redirect toaction.value, the provider authorization URL. - The provider redirects back to
redirectTowithstateand other params. - Send those callback params unchanged to
POST /connections/connect. - 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
- Apps
- Connections
- Create connection
- Connect an app
- Extract connection data
- Sync connection
GET /syncs,GET /syncs/{id}, andGET /syncs/{id}/changes
