WorkOS Docs Homepage
Pipes
API referenceDashboardSign In
Getting StartedOverviewOverviewConnection sharingConnection sharingMultiple connectionsMultiple connectionsCustom providersCustom providersOrganization-scoped providersOrganization-scoped providersAPI key providersAPI key providersRelayRelayProvidersProviders
API Reference
API Reference
Events
Events
Integrations
Integrations
Migrate to WorkOS
Migrate to WorkOS
SDKs
SDKs

Multiple connections

Let a user or an organization connect more than one account for the same Pipes provider.

On this page

  • Overview
  • Availability
  • Labels and account identifiers
  • Adding a connection
    • OAuth providers
    • API key and client credentials providers
    • Imported tokens
    • Limits
  • Retrying an add safely
  • Listing connections
  • Using a specific connection
  • Updating, reauthorizing, and deleting
  • Webhooks
  • In the Pipes widgets
  • Errors
  • Existing integrations
    • What requests without the new fields do
    • An empty compatibility slot
    • Webhooks
    • Migrating

Overview

Multiple connections let the same owner connect several accounts for one provider: a personal and a work GitHub account, or one API key per project. “Owner” means the same connection owner as everywhere else in Pipes: a user, or an organization for shared connections. Each owner’s connections to a provider are counted and selected separately.

An integration built for multiple connections follows three rules:

  1. Create with connection_intent: "add".
  2. Read with supports_multiple_connections: true.
  3. Act on a connection by passing its connected_account_id.

Store the connected_account_id from each new connection against your own record of what it is for. It is the only handle for a connection.

Integrations that already use Pipes keep their current behavior until they adopt these rules. See existing integrations for how requests without them behave and how to migrate.

Availability

Creating an additional connection is unavailable until WorkOS enables it for your team. Until then, an add creates a connection only when the owner has none for the provider, and otherwise returns 404 multiple_connections_unavailable. Requests without connection_intent, and reauthorization, are unaffected.

Labels and account identifiers

Two fields describe a connection but never identify it:

  • account_display_name is a label for people, such as “Work GitHub”. It is mutable and up to 255 characters, and several connections can share one. When a write sets a label that another connection for the same owner and provider already uses, the response includes a warnings entry with the code duplicate_account_display_name; the write still succeeds.
  • account_identifier describes which provider account a connection points at. It never identifies the connection; that is always connected_account_id. WorkOS does not infer it from a label or credential: it holds only what your application supplies.

WorkOS does not expose a developer-declared uniqueness field, and it neither refuses nor detects duplicate provider accounts. Two deliberate adds can connect the same provider account twice.

Adding a connection

Send connection_intent: "add" on any creation request. The owner gains another connection instead of reusing or replacing an existing one.

OAuth providers

Request an authorization URL with connection_intent: "add" and send the user through it. You can set the label up front with account_display_name.

curl --request POST \
--url "https://api.workos.com/data-integrations/github/authorize" \
--header "Authorization: Bearer sk_example_123456789" \
--header "Content-Type: application/json" \
-d @- <<'BODY'
{
"user_id": "user_01EHZNVPK3SFK441A1RGBFSHRT",
"connection_intent": "add",
"account_display_name": "Work GitHub"
}
BODY

When the user completes authorization, WorkOS creates the new connection. If the add fails, the user returns to your return_to URL with an error query parameter (or sees an error page when you set none), and a pipes.account_connection.add_failed event reports it.

API key and client credentials providers

Use the creation endpoints for API keys and client credentials. They accept only connection_intent: "add" and never update an existing connection.

curl --request POST \
--url "https://api.workos.com/data-integrations/github/api-key" \
--header "Authorization: Bearer sk_example_123456789" \
--header "Content-Type: application/json" \
--header "Idempotency-Key: 0b6c3f0e-7a51-4c3a-9d8e-2f1b5a6c7d80" \
-d @- <<'BODY'
{
"user_id": "user_01EHZNVPK3SFK441A1RGBFSHRT",
"secret": "sk-1234567890abcdef",
"connection_intent": "add",
"account_display_name": "Staging project"
}
BODY

To rotate a key or credentials later, use the PUT endpoints for API keys and client credentials with the connection’s connected_account_id. Sending add to a PUT endpoint returns 400.

Imported tokens

The token import endpoints for users and organizations accept connection_intent: "add" in the same way.

Limits

An add is refused once the owner has 25 connections for the provider, with 409 connected_account_limit_reached. Disconnect a connection before adding another.

Retrying an add safely

Retrying an add whose response was lost can create a second connection. To make a synchronous add retry-safe, send an Idempotency-Key header on the REST creation endpoints for API keys, client credentials, and imported tokens, or the idempotencyKey input on the equivalent Widgets API mutations. Reuse the same key for every retry of one request.

  • A matching retry within 24 hours returns the connection the first attempt created, unchanged, even if the retry carries different inputs.
  • Reusing the key for a different owner, provider, or authentication method, or after 24 hours while that connection exists, returns 409 idempotency_key_conflict.
  • Once that connection is deleted, the key creates a new connection.
  • A REST add and a Widgets API add of the same kind, for the same owner and provider, are one operation and share the key.

The key is optional; without it, retries can create duplicates. It is not lifetime duplicate prevention. OAuth adds take no key: each authorization completes once, and starting a second authorization creates a second connection.

Some WorkOS SDKs send an Idempotency-Key automatically when they retry a POST. If you write your own retries, generate one key per add and send it on every attempt.

Listing connections

Send supports_multiple_connections=true when listing providers to receive every connection for the owner in each provider’s connected_accounts array.

curl --request GET \
--url "https://api.workos.com/user_management/users/user_01EHZNVPK3SFK441A1RGBFSHRT/data_providers?supports_multiple_connections=true" \
--header "Authorization: Bearer sk_example_123456789"

A request that declares supports_multiple_connections: true but names no connection, while the owner has several, returns 409 rather than guessing. Ask the user which connection to use, then repeat the request with its connected_account_id:

{
"code": "account_selection_required",
"message": "Several connected accounts match this user for 'github'. Name one with a connected account id."
}

A malformed connected_account_id returns 400. An id that does not exist and an id that belongs to another owner or provider both return the same 404, so a failed selector never creates, changes, vends, or reveals another connection.

Using a specific connection

Pass connected_account_id when you fetch an access token or vend credentials:

curl --request POST \
--url "https://api.workos.com/data-integrations/github/token" \
--header "Authorization: Bearer sk_example_123456789" \
--header "Content-Type: application/json" \
-d @- <<'BODY'
{
"user_id": "user_01EHZNVPK3SFK441A1RGBFSHRT",
"connected_account_id": "data_installation_01HX1ZWK1Z5H5Q7Y7X5EJ09MQ1",
"supports_multiple_connections": true
}
BODY

With Relay, name the connection with the X-Relay-Connected-Account header. Relay removes the header before forwarding the request.

curl --request GET https://api.workos.com/relay \
--header "Authorization: Bearer sk_example_123456789" \
--header "X-Relay-URL: https://api.github.com/user/repos" \
--header "X-Relay-User: user_01EHZNVPK3SFK441A1RGBFSHRT" \
--header "X-Relay-Connected-Account: data_installation_01HX1ZWK1Z5H5Q7Y7X5EJ09MQ1"

Updating, reauthorizing, and deleting

  • Rename or update: pass connected_account_id to update a connected account, including its account_display_name.
  • Reauthorize: request an authorization URL with connection_intent: "reauthorize" and the connected_account_id to reauthorize. The new grant replaces that connection’s credentials.
  • Delete: pass connected_account_id to delete a connected account to remove exactly that connection.

Webhooks

Subscribe to the pipes.account_connection.* events, which cover every connection: connected, disconnected, reauthorization_needed, connection_failed, and add_failed. Each payload’s id is the connection’s connected_account_id, so match events to your records by it.

pipes.account_connection.add_failed reports a failed add. It can occur before any connection exists, so it carries no connected account id. See Pipes events for payloads.

In the Pipes widgets

The Pipes and Pipes Admin widgets support multiple connections in their sectioned view, which you turn on with the enableSharedConnections prop. This requires @workos-inc/widgets 1.20.0 or later. The flat list rendered without that prop shows one connection per provider.

  • One connection. The provider row looks as it did before. Its menu adds Rename and Connect another account.
  • Several connections. The provider row shows how many there are, and each connection gets its own row below it, oldest first, with its own status and actions. A row shows the connection’s name, falling back to its account identifier and then the provider name.
  • Adding. Add connection and Connect another account appear only when the owner can add one, so they stay hidden while multiple connections are unavailable and at the connection limit. In Pipes, adding asks for an optional name; Pipes Admin doesn’t, so admins name a connection with Rename afterwards. API key and client credentials adds send an idempotencyKey, so retrying after an ambiguous failure returns the same connection.
  • Acting on a connection. Reauthorizing, updating a key or credentials, and disconnecting act on exactly that row’s connection.
  • Renaming. A name another connection for the same provider already uses still saves, with a notice suggesting a distinct one.

The Shared connections section that members see still shows one row per provider, even when an organization shares several connections.

Errors

Status Code Meaning
400 An invalid combination: add with connected_account_id, reauthorize without it, add on a PUT endpoint, or a malformed selector.
404 multiple_connections_unavailable The add would create an additional connection before WorkOS has enabled them for your team.
404 The connected_account_id does not exist or belongs to another owner or provider.
404 connected_account_not_found The connection named for reauthorization does not exist for this owner and provider.
409 account_selection_required The request declared supports_multiple_connections, named no connection, and several match.
409 connected_account_limit_reached The owner already has 25 connections for this provider.
409 idempotency_key_conflict The Idempotency-Key belongs to a different add, or its 24-hour replay window has passed.

An OAuth reauthorization whose connection is deleted before the user returns sends the user to your return_to URL with error=target_deleted. No webhook event is emitted, since the connection no longer exists.

Existing integrations

Pipes integrations built before multiple connections send no connection_intent, no supports_multiple_connections, and often no connected_account_id. Those requests keep working permanently. To keep them predictable once an owner has several connections, every connection carries a connection_role:

  • compatibility: the connection that requests without these fields use. Each owner has at most one per provider.
  • standard: any further connection for the same owner and provider. Only requests that name it with connected_account_id, or that declare supports_multiple_connections: true, can see it.

The first connection for an owner becomes the compatibility connection, even when it is created with connection_intent: "add"; later adds create standard connections. An integration that follows the three rules above never needs to check the role. It does not mean preferred, primary, or oldest, and it never changes.

What requests without the new fields do

  • Reads without supports_multiple_connections return only the compatibility connection, and are never refused with account_selection_required. In a plural provider list, the singular connected_account field is the compatibility connection.
  • Token fetches, credential vending, and Relay without a selector use the compatibility connection.
  • PUT writes without connected_account_id update the compatibility connection.
  • Deletes without connected_account_id delete the compatibility connection.
  • Creation without connection_intent only ever addresses the compatibility connection: it fills an empty slot, and otherwise OAuth and PUT update the existing compatibility connection while token imports return 409. It never creates a standard connection.

An empty compatibility slot

Deleting the compatibility connection leaves the slot empty and never promotes a standard connection into it. The owner’s standard connections then stay invisible to requests without the new fields. While any standard connection remains, an add creates another standard connection; only a request without connection_intent refills the slot. The 25-connection limit never blocks that refill.

Webhooks

Pipes emits two families of connection events:

Family Covers Events
Compatibility pipes.connected_account.* Compatibility connections only connected, disconnected, reauthorization_needed, connection_failed
All-connections pipes.account_connection.* Compatibility and standard connections connected, disconnected, reauthorization_needed, connection_failed, add_failed

An endpoint subscribed to both receives two events for each compatibility connection change: two views of the same change, not two connections.

Migrating

  1. Store connection ids. Save connected_account_id against your own records, and stop assuming one connection per owner and provider.
  2. Read in plural. Send supports_multiple_connections: true and handle 409 account_selection_required.
  3. Name the connection you use. Pass connected_account_id when fetching tokens, vending credentials, calling Relay, updating, and deleting.
  4. Add explicitly. Send connection_intent: "add" when the user connects another account, with an Idempotency-Key on synchronous adds.
  5. Move webhook handlers to the pipes.account_connection.* family, and unsubscribe from pipes.connected_account.*.

Move every client that reads connections at the same time. A backend job or older widget that still reads without supports_multiple_connections keeps working on the compatibility connection, which may not be the one a user meant once they have several.

Custom providers Define your own provider in Pipes when the one you need isn't in the WorkOS catalog
Up next
© WorkOS, Inc.
FeaturesAuthKitSingle Sign-OnDirectory SyncAdmin PortalFine-Grained Authorization
DevelopersDocumentationChangelogAPI Status
ResourcesBlogPodcastPricingSecuritySupport
CompanyAboutCustomersCareersLegalPrivacy
© WorkOS, Inc.