How to Migrate from Merge.dev Without Re-Authenticating Customers
Migrating from Merge.dev without re-authenticating users is possible if you own your OAuth apps. This guide covers token export, staged re-auth for non-portable accounts, and a phased migration timeline.
If you are evaluating a migration away from Merge.dev to another unified API, the single biggest fear is obvious: the migration cliff. Forcing hundreds of enterprise users to click "Reconnect" on their Salesforce, Workday, or HubSpot integrations is a massive friction point. It means generating support tickets, increasing churn risk, and spending weeks coordinating with enterprise clients who will not appreciate re-linking their core systems of record simply because you decided to switch infrastructure vendors.
The math on your current integration vendor might no longer make sense. The per-linked-account pricing might be dragging down your unit economics, or your enterprise customers are demanding custom field support that the rigid unified schema simply cannot handle. But you are paralyzed by the thought of asking every customer to re-authenticate.
The good news is that if you architected your initial implementation correctly—or even if you didn't, but are willing to put in a little operational work—there are concrete technical strategies to migrate OAuth tokens to a new unified API platform without touching your end users. You do not have to start over from scratch, and you do not have to email your customers.
This guide breaks down the exact technical strategy to export OAuth tokens, import them into Truto's generic credential context, handle rate limits post-migration, and use declarative mappings to mimic your old API responses so your frontend code does not have to change.
Can You Migrate Without Re-Authenticating Customers?
The short answer: it depends on whether you own your OAuth applications.
If you registered your own OAuth apps (your own Client ID and Client Secret) in each provider's developer portal and configured them as "partner credentials" in Merge.dev, then yes - you can export those tokens and import them into a new unified API platform like Truto without any customer-facing disruption. The tokens were issued under your application, and you can move them wherever you want.
If you used Merge's default OAuth applications - the ones that work out of the box with zero configuration - those tokens are bound to Merge's Client ID. There is no technical workaround. Those customers will need to re-authenticate. This is not a limitation specific to Merge; it is how OAuth works. A refresh token can only be used by the OAuth application that originally issued it.
For API key-based integrations (like BambooHR or 15Five), the credentials are static strings. They are always portable regardless of which unified API platform holds them.
Here is the realistic breakdown for most teams migrating from Merge.dev to another unified API:
| Credential Type | Can You Migrate Without Re-Auth? | Action Required |
|---|---|---|
| OAuth with your own app (BYO/partner credentials) | Yes | Export tokens, import into new platform, update redirect URIs |
| OAuth with Merge's default app | No | Staged re-authentication required |
| API key integrations | Yes | Extract key, import into new platform |
| Service account / basic auth | Yes | Extract credentials, import into new platform |
Most teams will have a mix. The rest of this guide covers both paths: the token transfer for accounts you can migrate, and a staged re-authentication strategy for the ones you cannot.
The Vendor Lock-In Trap of Unified APIs
Unified API platforms solve a real problem: they abstract away the pain of dealing with terrible vendor API docs, inconsistent pagination, and undocumented edge cases. The initial pitch is compelling: write to one schema, connect to hundreds of platforms. But the architecture of most platforms creates a dependency that is easy to miss during evaluation and incredibly painful to discover later.
When customers authenticate through Merge, Merge holds the OAuth tokens. Switching vendors typically means asking every customer to re-authenticate—a migration tax that creates real, structural lock-in. This isn't a bug in Merge's design. It is a natural consequence of how most unified APIs work: they operate the OAuth flow, they store the tokens, and they refresh them on your behalf.
The economic and technical pressure to migrate tends to build over time for three specific reasons:
First, the pricing models punish growth. According to SaaStr's 2025 SaaS Management Report, the average company now spends $49M annually on SaaS, with portfolio growth increasing to an average of 275 applications across all buyers (with enterprises averaging 660). When your customers expect you to integrate with a dozen of their internal tools, paying a flat monthly fee per linked account quickly destroys your profit margins. We covered this economic reality in detail in our direct comparison of Truto and Merge.dev.
Second, schema rigidity becomes a deal blocker. Enterprise customers heavily customize their systems of record. When a Fortune 500 prospect demands that you read and write to a custom object in their Salesforce instance, a rigid unified schema will stall the deal. You are forced to either upgrade to an expensive enterprise tier to unlock custom field mapping or bypass the unified model entirely to make raw passthrough requests.
Third, data privacy liabilities. Store-and-sync architectures copy your customers' third-party data onto the unified API vendor's servers. SaaS security is now a high priority for 86% of organizations, with 76% actively increasing their security budgets. Storing a replica of a customer's HRIS or accounting data on a third-party server creates significant compliance friction during enterprise InfoSec reviews. A pass-through architecture creates far less friction, which you can read more about in our guide on what zero data retention means for SaaS integrations.
So the business case for migration is clear. The technical barrier is the re-authentication cliff. Let's remove it.
The Prerequisite: Do You Own Your OAuth Apps?
Before we look at the extraction code, we have to address a hard technical truth. This is the single most important question in the entire migration: If you do not own the OAuth Client ID and Client Secret for each integration, you cannot migrate tokens. Full stop.
Here is why: An OAuth refresh token is cryptographically bound to the OAuth application that issued it. When you call a provider's /token endpoint with a refresh token, the provider checks that the client_id and client_secret in the request match the application that originally granted the token. If Merge's default OAuth application issued the token—using Merge's Client ID—then only Merge's credentials can refresh it.
Modern security standards make this ownership even more critical. OAuth 2.1 eliminates implicit flows and mandates Proof Key for Code Exchange (PKCE) for all clients. It also requires strict, exact-match redirect URIs. This tightening of the spec means there is zero room for creative workarounds. You cannot swap redirect URIs after the fact or use partial string matches.
When you set up an integration through a unified API platform, you typically have two choices for handling the OAuth flow:
- Use the platform's default OAuth apps: The provider uses their own Client ID and Client Secret to authenticate your users.
- Bring Your Own (BYO) OAuth app: You register your own application in the third-party developer portal, and you provide your own credentials to the unified API platform.
Merge.dev supports this second option, which they call "partner credentials." Their documentation for providers like Salesforce, HubSpot, and Dropbox walks you through creating your own OAuth application and entering the credentials into Merge's dashboard. For example, for Salesforce, you set the Callback URL to https://app.merge.dev/oauth/callback. For HubSpot, you add the same redirect URL.
If you set up your own OAuth apps through this process, you hold the keys to your own infrastructure. The tokens were issued under your application. You can extract the access_token and refresh_token pairs and move them to any infrastructure you want.
If you used Merge's default OAuth apps—the ones that work out of the box with zero configuration—those tokens belong to Merge's OAuth application. You will need to re-authenticate those customers. There is no technical path around this.
Audit your integrations now. Log into your Merge dashboard, go to Integrations, and check each provider. If it says "Merge credentials" or you never configured partner credentials, those linked accounts cannot be token-migrated. If it shows your own OAuth app credentials, you are ready to migrate.
Token Transfer Risks to Plan For
Even when you own the OAuth app, transferring tokens is not risk-free. Plan for these specific failure modes before you start the migration:
Scope mismatches. If your new platform requests different OAuth scopes than the original authorization, the imported tokens may lack permissions for certain API calls. For example, if Merge originally requested read scope on a provider but your new integration config also needs write, the imported token will fail on write operations. Audit the scopes granted on each token and compare them against what your new platform requires. Any mismatch means that specific account will need re-authentication to grant the additional scopes.
Token expiry during migration. Access tokens for most providers expire in 60 minutes or less. If you extract tokens and do not import them quickly, the access token will expire. This is usually fine - your new platform will use the refresh token to obtain a new access token. But some providers (notably Google) limit refresh token lifetimes or revoke them after extended inactivity. Verify that your refresh tokens are still valid before you begin, and prioritize speed in your import pipeline.
Provider-specific revocation policies. Some providers revoke existing refresh tokens when a new one is issued (refresh token rotation). If both Merge and your new platform attempt to refresh the same token concurrently during the transition window, one of them will invalidate the other's token. Coordinate the cutover so that only one platform is actively refreshing tokens for a given account at any point in time.
Redirect URI validation on re-consent. While existing refresh tokens do not require the redirect URI to match for token refresh calls, some providers (like Google) may force a re-consent screen if they detect the redirect URI has changed since the original authorization. This is rare for background refresh operations but can surface during edge cases.
How to Extract Your OAuth Tokens from Merge.dev
The first technical step is extracting your active connection data. You need to pull the raw credentials for every linked account in your system.
Merge.dev does not natively expose raw OAuth tokens through their standard unified API endpoints. Their authentication model uses an account_token per linked account—a Merge-specific identifier that authenticates your requests to their API, not the underlying provider's token.
However, you have three viable options for extracting your connection data:
Option 1: Contact Merge Support Directly
This is the most straightforward path. Request a complete credential export for your linked accounts. You need the access_token, refresh_token, expires_at timestamp, and any provider-specific context (like Salesforce's instance_url or a provider's subdomain). Depending on your plan and contract terms, Merge may accommodate this request, providing a clean export file.
Option 2: Intercept During Active Sessions
If you own the OAuth app, you can set up a parallel token exchange. Because you control the Client ID and Client Secret, you can call the provider's token endpoint directly with your credentials.
Most unified API platforms provide some form of endpoint to retrieve raw integration credentials or metadata. You will need to write a script that iterates through your active accounts. Here is a conceptual Node.js script demonstrating how you would batch extract this data if the platform exposes it. You will need to handle pagination and rate limits on the extraction API to ensure you do not drop any records.
async function extractTokens(linkedAccountIds) {
const extractedData = [];
for (const accountId of linkedAccountIds) {
try {
// Conceptual endpoint - check current provider capabilities
const response = await fetch(`https://api.current-provider.com/api/v1/account-tokens/${accountId}`, {
headers: {
'Authorization': `Bearer ${process.env.PROVIDER_API_KEY}`
}
});
if (!response.ok) {
console.error(`Failed to fetch tokens for ${accountId}`);
continue;
}
const data = await response.json();
extractedData.push({
originalAccountId: accountId,
integrationName: data.integration_name,
accessToken: data.credentials.access_token,
refreshToken: data.credentials.refresh_token,
expiresAt: data.credentials.expires_at,
tenantId: data.tenant_id // Your internal user/org ID
});
} catch (error) {
console.error(`Error extracting ${accountId}:`, error);
}
}
return extractedData;
}Option 3: Gradual Migration with Dual-Write via Passthrough
For non-OAuth integrations (API key-based connections like BambooHR, 15Five, or legacy systems), the credentials are static. You can extract the API key through Merge's passthrough variable system. You can securely access a linked account's stored credentials programmatically by including variables in double brackets (e.g., {{API_KEY}}).
You can build a script that retrieves these credentials for each linked account and stores them for import into your new platform.
# Pseudocode: Extract API key integrations via Merge passthrough
import requests
def extract_api_key_credential(merge_api_key, account_token, provider_path):
response = requests.post(
"https://api.merge.dev/api/hris/v1/passthrough",
headers={
"Authorization": f"Bearer {merge_api_key}",
"X-Account-Token": account_token,
"Content-Type": "application/json"
},
json={
"method": "GET",
"path": provider_path,
"request_format": "JSON"
}
)
return response.json()For each extraction method, build a mapping file that captures the provider name, the end-user's unique identifier, the credential type (OAuth or API key), and every field needed to reconstruct the connection. Treat this export file like production database credentials, because that is exactly what it is.
Importing Tokens into Truto's Integrated Account Context
Once you have your tokens, you need to import them into your new infrastructure.
Truto's credential architecture is built around a single concept: the integrated account. Instead of relying on hardcoded, provider-specific database columns (like a salesforce_refresh_token column), Truto stores all connection data in a highly flexible, generic JSON context object.
This means you can programmatically create an Integrated Account and inject your exported OAuth tokens directly into the context payload. An OAuth token for Salesforce and an API key for BambooHR live in the exact same structure, differentiated only by the JSON contents. You do not need to match a rigid internal schema; you simply write the token data into the context field, and the platform's generic engine takes over.
Here is how you structure the import payload for Truto using Node.js:
async function importToTruto(extractedAccount) {
const payload = {
environment_id: process.env.TRUTO_ENVIRONMENT_ID,
integration_name: extractedAccount.integrationName,
tenant_id: extractedAccount.tenantId,
authentication_method: "oauth2",
context: {
oauth: {
token: {
access_token: extractedAccount.accessToken,
refresh_token: extractedAccount.refreshToken,
expires_at: extractedAccount.expiresAt,
token_type: "Bearer"
}
}
}
};
const response = await fetch('https://api.truto.one/integrated-accounts', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.TRUTO_API_KEY}`
},
body: JSON.stringify(payload)
});
return response.json();
}If you prefer to run this as a batch script via CLI, here is what the import looks like for an OAuth2 integration using curl:
curl -X POST https://api.truto.one/integrated-account \
-H "Authorization: Bearer YOUR_TRUTO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"integration_name": "salesforce",
"environment_id": "env_abc123",
"tenant_id": "customer_xyz",
"context": {
"oauth": {
"token": {
"access_token": "IMPORTED_ACCESS_TOKEN",
"refresh_token": "IMPORTED_REFRESH_TOKEN",
"expires_at": "2026-04-07T12:00:00Z",
"token_type": "Bearer",
"scope": "full refresh_token"
}
},
"subdomain": "customer-org",
"instance_url": "https://customer-org.my.salesforce.com"
}
}'For API key integrations, the structure is even simpler:
curl -X POST https://api.truto.one/integrated-account \
-H "Authorization: Bearer YOUR_TRUTO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"integration_name": "bamboohr",
"environment_id": "env_abc123",
"tenant_id": "customer_xyz",
"context": {
"api_key": "IMPORTED_API_KEY",
"subdomain": "customer-company"
}
}'The Token Refresh Lifecycle
When you import these tokens, you are likely importing access_token values that are already expired or will expire within the hour. This is where the underlying architecture of your new platform matters immensely. We have all stared at a dashboard of 401 Unauthorized errors because a refresh token silently expired or a race condition caused two concurrent requests to attempt a refresh simultaneously, invalidating the token chain.
Once the integrated account is created with valid credentials in Truto, three things happen automatically:
- The token refresh lifecycle activates: Truto immediately evaluates the
expires_attimestamp. Truto automatically schedules a background task to proactively refresh the token 60 to 180 seconds before it expires. If an API request comes in and the token is within 30 seconds of expiration, Truto executes an on-demand refresh before proxying the request to the upstream provider. To prevent race conditions, Truto uses distributed locks to ensure that only one refresh operation runs per integrated account at a time. Concurrent API calls simply await the in-progress refresh promise, ensuring your newly imported tokens are never accidentally revoked. - Credential encryption kicks in: Sensitive fields like
access_token,refresh_token, andapi_keyare encrypted at rest automatically. No extra configuration is needed. - The redirect URI needs updating: Since you own the OAuth app, you must update the redirect URI in the provider's developer portal from
https://app.merge.dev/oauth/callbackto Truto's callback URL. Existing tokens don't need the redirect URI for refresh—it is only used during the initial authorization code exchange. But any new connections going forward will need the updated URI.
You can dive deeper into this specific mechanism in our guide on architecting reliable token refreshes.
sequenceDiagram
participant Client as Your App
participant Truto as Truto API
participant Lock as Distributed Lock
participant Upstream as Third-Party API
Client->>Truto: GET /unified/crm/contacts
Truto->>Truto: Check token expiry
alt Token expired or expiring in < 30s
Truto->>Lock: Acquire lock for account
Lock-->>Truto: Lock acquired
Truto->>Upstream: POST /oauth/token (Refresh)
Upstream-->>Truto: New Access Token
Truto->>Lock: Release lock
end
Truto->>Upstream: GET /contacts (with valid token)
Upstream-->>Truto: 200 OK
Truto-->>Client: Mapped ResponseTest before you cut over. Create integrated accounts for a handful of test customers first. Make a simple API call through Truto's proxy API (GET /proxy/contacts) to verify the imported tokens work. If the token has expired, Truto will attempt a refresh using the OAuth credentials configured on the integration. Watch for needs_reauth status—that signals the refresh failed and the token is irrecoverable.
If Token Transfer Is Not Possible - Staged Re-Auth Strategy
Let's be honest about the common case: many teams that evaluate a migration from Merge.dev will discover that some or all of their linked accounts used Merge's default OAuth apps. Those tokens cannot be transferred. You need a plan that does not involve emailing all your customers on the same day with a "please reconnect" link.
A staged re-authentication strategy minimizes churn risk by spreading the reconnection work across weeks, prioritizing high-value accounts, and making the experience as frictionless as possible for end users.
Step 1: Segment Your Linked Accounts
Pull a full inventory of your linked accounts and classify each one:
- Tier 1 - Portable (no re-auth needed): Accounts where you own the OAuth app, or API key-based integrations. These migrate silently via token import.
- Tier 2 - High-value re-auth: Enterprise accounts with high ARR or strategic importance. These get white-glove treatment - a dedicated CSM reaches out, schedules a 5-minute call, and walks them through the reconnect.
- Tier 3 - Standard re-auth: Mid-market accounts that can be handled via in-app prompts and self-service flows.
- Tier 4 - Low-activity re-auth: Accounts with minimal recent API activity. These can be batched into a final email campaign. Some may have already churned or gone dormant.
Step 2: Run Parallel Connectors During the Transition
Do not decommission Merge until every account is migrated or re-authenticated. Keep both platforms active simultaneously:
- Route Tier 1 accounts through your new platform immediately after token import.
- Continue routing Tier 2-4 accounts through Merge until each one has re-authenticated on the new platform.
- Use your internal
tenant_idmapping to decide which platform handles each request.
This dual-routing period is the most operationally complex part of the migration, but it guarantees zero disruption for accounts that have not yet reconnected.
Step 3: Trigger Re-Auth Through In-App Prompts
The highest-converting re-auth mechanism is an in-app banner or modal that appears when a user logs into your product. Something like: "We've upgraded our integration infrastructure. Click here to reconnect your [Provider Name] account - it takes 30 seconds." This converts far better than email because the user is already in your product and has immediate context.
Design the reconnect flow to use your new platform's OAuth link generation. The user clicks, completes the standard OAuth consent screen (which they have seen before), and the new integrated account is created automatically.
Step 4: Escalate with Email for Stragglers
After 2-3 weeks of in-app prompts, send targeted emails to accounts that have not yet reconnected. Be specific about which integration needs attention, and include a direct deep link to the reconnect flow in your app. Avoid generic "action required" language - tell them exactly what will stop working and when.
Set a hard deadline (typically 4-6 weeks from the start of re-auth) after which you will decommission the old platform. Communicate this deadline clearly and repeat it.
Handling Rate Limits and Retries Post-Migration
One of the most dangerous assumptions engineering teams make during a migration is assuming the new platform handles rate limits exactly like the old one. Many unified APIs attempt to be overly helpful by silently absorbing 429 Too Many Requests errors. They implement internal exponential backoff and retry the request on your behalf.
While this sounds convenient, it is an architectural anti-pattern for enterprise systems. When a unified API silently retries a request for 45 seconds, your frontend connection times out. Your background workers hang. Your engineers have zero visibility into the actual upstream rate limit consumption until the system completely falls over. Transparency beats magic.
Truto takes a radically honest approach to rate limits: Truto does not retry, throttle, or apply backoff on rate limit errors. When an upstream provider returns an HTTP 429, Truto passes that status code directly back to your application. There is no hidden retry loop and no absorption of errors behind a queue.
What Truto does do is solve the actual hard problem: normalizing the chaotic mess of upstream rate limit metadata. Every SaaS platform communicates rate limits differently. HubSpot uses X-HubSpot-RateLimit-Daily and X-HubSpot-RateLimit-Daily-Remaining. Salesforce returns Sforce-Limit-Info. Zendesk uses X-Rate-Limit.
Truto intercepts these disparate formats and normalizes them into standard response headers based on the IETF RateLimit header specification:
| Header | Meaning |
|---|---|
ratelimit-limit |
The maximum number of requests permitted in the current window. |
ratelimit-remaining |
The number of requests remaining before hitting the limit. |
ratelimit-reset |
The number of seconds until the rate limit window resets. |
This standardization is significant. Your engineering team gets consistent, predictable rate limit data regardless of whether you are calling HubSpot, Salesforce, or Zendesk through Truto. Your application is responsible for reading these standardized headers and implementing its own retry or backoff logic using tools like BullMQ or standard queueing systems.
Here is what a well-behaved client-side handler looks like in TypeScript:
async function callTrutoWithBackoff(url: string, options: RequestInit, maxRetries = 3) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
const response = await fetch(url, options);
if (response.status === 429) {
const resetSeconds = parseInt(
response.headers.get('ratelimit-reset') || '60', 10
);
const jitter = Math.random() * 2;
await sleep((resetSeconds + jitter) * 1000);
continue;
}
return response;
}
throw new Error('Rate limit retries exhausted');
}For a deeper look at designing resilient architectures around this pattern, review our best practices for handling API rate limits.
Matching the Schema: Zero-Code Endpoint Configuration
The final hurdle in your migration is the data schema. Your frontend components and backend database models are currently tightly coupled to the exact JSON structure that Merge.dev returns. If migrating to a new unified API requires rewriting thousands of lines of parsing logic across your codebase, the migration will stall into a multi-sprint project.
Truto solves this through its declarative mapping configuration layer. Instead of forcing you to adopt Truto's default schema, Truto allows you to define integration-specific behavior entirely as data using JSONata expressions.
JSONata is a functional query and transformation language purpose-built for reshaping JSON objects. In Truto, every field mapping between a provider's native response and the unified output—as well as query translation and conditional logic rules—is defined as data, not hardcoded into the runtime engine.
This means you can configure Truto's unified response to perfectly mimic Merge's exact response shape without changing a single line of your application code. For example, if your application expects a remote_id field and a specific nested account_details object, you can write a JSONata response mapping like this:
(
{
"id": response.id,
"remote_id": response.original_id,
"first_name": response.properties.firstname,
"last_name": response.properties.lastname,
"account_details": {
"company_name": response.company.name,
"domain": response.company.domain
},
"remote_data": response
}
)This flexibility extends to query parameters, request bodies, and even which provider endpoint gets called for a given operation. If Merge routed GET /contacts?email=jane@example.com to HubSpot's search endpoint with filterGroups, Truto's integration config defines the same routing through declarative expressions. The runtime engine evaluates the expression against each response item, producing whatever output shape you need.
The override system adds another layer of flexibility. You can customize mappings at three levels: platform-wide, per-environment, and per-integrated-account. If one enterprise customer's Salesforce instance has custom fields that need special handling, you apply a custom override to your Truto environment for just that account.
Zero code changes are required in your application repository. You simply point your base URL to Truto, ensure your authentication headers are updated, and your application continues functioning as if nothing changed.
Pre-Migration Checklist: Audit, Mapping, Consent, and Communication
Before you touch a single token, work through this checklist. Skipping any of these items is how migrations stall mid-flight.
Credential Audit
- For every integration in Merge, document whether it uses your own OAuth app or Merge's defaults.
- For each OAuth integration, record the Client ID, the scopes originally requested, and the redirect URI currently configured.
- For API key integrations, confirm you have a method to extract the key (passthrough variables, support export, or direct provider access).
- Identify any integrations with provider-specific constraints: refresh token rotation policies, token lifetime limits, or IP allowlisting.
Linked Account ID Mapping
- Export a complete list of Merge linked accounts with their
account_token,integration_name, and your internaltenant_id. - Create a mapping table between Merge's linked account identifiers and whatever identifier your new platform will use. This mapping is the source of truth for the entire migration.
- Verify that every linked account maps to an active customer in your system. Remove orphaned or churned accounts - there is no reason to migrate dead connections.
Scope and Permission Verification
- Compare the OAuth scopes that were granted under Merge's configuration with the scopes your new platform's integration config will request.
- Flag any accounts where the new platform needs broader scopes. These accounts will require re-authentication regardless of credential ownership.
- For providers that support incremental authorization (like Google), plan to request only the additional scopes during re-auth rather than forcing a full re-consent.
Legal and Consent Considerations
- Review your Terms of Service and DPA to confirm you are permitted to transfer customer credentials between infrastructure providers.
- If your customers signed agreements that reference a specific data processor (Merge), check whether moving tokens to a new processor requires notification or updated consent.
- For regulated industries (healthcare, finance), verify that your migration plan satisfies audit and data handling requirements.
Communication Plan
- Draft customer-facing messaging for accounts that will need re-authentication. Be transparent about why ("we are upgrading our integration infrastructure") without creating alarm.
- Prepare in-app UI components for the reconnect flow before you start the migration - not after.
- Brief your support team on the migration timeline, expected questions, and escalation paths.
- Set a clear internal deadline for the parallel-running period to avoid indefinite dual-platform costs.
The Migration Checklist
Here is the condensed, actionable sequence for a zero-downtime migration:
- Audit credential ownership: For every integration in Merge, confirm whether you configured your own OAuth app (partner credentials) or used Merge's defaults. This determines which accounts can be token-migrated.
- Export credentials: Use the appropriate extraction method (support request, passthrough API, or direct provider access) to collect access tokens, refresh tokens, and API keys for each linked account.
- Configure integrations in Truto: Set up each integration with your OAuth Client ID and Client Secret. Truto's "Bring Your Own OAuth App" support means your credentials go into the integration config, and every connected account under that integration uses them.
- Import integrated accounts: Create integrated accounts via API, injecting the exported credentials into the generic
contextfield. Verify token validity by making a test API call for each account. - Configure JSONata Schema Mappings: Set up environment-level overrides in Truto using JSONata expressions to format the unified API response so it perfectly matches the legacy schema your application expects.
- Update redirect URIs: Change the redirect URI on your OAuth apps from Merge's callback to Truto's. Existing tokens are unaffected; this only impacts new connections.
- Run parallel for 48-72 hours: Keep both platforms active. Route a percentage of traffic through Truto while monitoring for
needs_reauthevents and data consistency issues. - Cut over and Decommission: Once you have confidence in the imported connections, switch your application to point entirely at Truto's API endpoints. Monitor your webhooks for authentication errors, and delete the linked accounts in Merge to stop their sync jobs and avoid unnecessary billing.
Example Timeline: 3-Phase Migration
Here is a realistic timeline for migrating from Merge.dev (or any unified API) to Truto, assuming a mix of portable and non-portable accounts across 200-500 linked accounts.
Phase 1: Audit and Preparation (Week 1-2)
| Task | Owner | Duration |
|---|---|---|
| Audit all integrations for credential ownership | Engineering | 2-3 days |
| Export linked account inventory and build ID mapping table | Engineering | 1-2 days |
| Compare OAuth scopes between old and new platform configs | Engineering | 1 day |
| Review legal/DPA obligations for credential transfer | Legal + Engineering | 2-3 days |
| Configure integrations and OAuth apps in Truto | Engineering | 2-3 days |
| Build and test in-app reconnect UI for re-auth accounts | Frontend + Engineering | 3-5 days |
| Brief support team and draft customer communications | Support + Marketing | 1-2 days |
Phase 2: Token Migration for Portable Accounts (Week 3-4)
| Task | Owner | Duration |
|---|---|---|
| Extract tokens/API keys from Merge (support export or API) | Engineering | 1-3 days |
| Import credentials into Truto via batch script | Engineering | 1 day |
| Validate imported accounts with test API calls | Engineering + QA | 2-3 days |
| Configure JSONata schema mappings to match Merge's response format | Engineering | 2-3 days |
| Update redirect URIs on OAuth apps in provider developer portals | Engineering | 1 day |
| Route Tier 1 traffic through Truto, keep Merge active for Tier 2-4 | Engineering | 1 day |
Monitor for 48-72 hours: watch for needs_reauth, 401s, data drift |
Engineering | 3 days |
Phase 3: Staged Re-Auth for Non-Portable Accounts (Week 5-8)
| Task | Owner | Duration |
|---|---|---|
| Enable in-app reconnect prompts for Tier 2-4 accounts | Frontend | 1 day |
| White-glove outreach to Tier 2 (high-value) accounts | CSM + Support | 1-2 weeks |
| Monitor in-app re-auth conversion rates, iterate on UX | Product + Engineering | Ongoing |
| Send targeted email campaign to accounts that have not reconnected after 2 weeks | Marketing + Support | 1 day |
| Send final deadline reminder email at week 7 | Marketing | 1 day |
| Decommission Merge: delete linked accounts, cancel subscription | Engineering + Finance | 1 day |
Most teams complete the full migration in 6-8 weeks. If all your accounts are portable (you own every OAuth app), you can collapse this to 2-3 weeks by skipping Phase 3 entirely.
Strategic Next Steps and Vendor Evaluation
Escaping vendor lock-in requires a deliberate architectural strategy. You cannot afford to treat integration infrastructure as a black box. The lesson from migration pain is simple: always own your OAuth credentials from day one.
Any unified API vendor should support bringing your own OAuth apps. If they do not, or if they make it difficult, you are accepting vendor lock-in as a hidden cost of adoption. By ensuring you own your OAuth applications, carefully extracting your token pairs, and importing them into a pass-through architecture like Truto, you can eliminate per-connection pricing penalties and reclaim control over your data model—all without forcing a single customer to re-authenticate.
During your next vendor evaluation, ask these critical questions:
- Can I use my own OAuth Client ID and Secret for every provider?
- Are the raw OAuth tokens accessible through an API, or are they abstracted behind a proprietary identifier?
- If I leave, can I export my customers' credentials without forcing re-authentication?
- Does the platform store a copy of my customers' data, or does it operate as a pass-through?
The answers to these questions will save you (or cost you) months of engineering work down the road. Credential portability is not a nice-to-have. It is the difference between a vendor relationship and vendor captivity.
FAQ
- Can I migrate from Merge.dev to another unified API without re-authenticating customers?
- Yes, but only for accounts where you configured your own OAuth app (partner credentials) in Merge. Tokens issued under Merge's default OAuth apps are cryptographically bound to Merge's Client ID and cannot be transferred. API key-based integrations are always portable.
- What happens if I used Merge's default OAuth apps?
- Those customers will need to re-authenticate. There is no technical workaround because an OAuth refresh token can only be used by the application that originally issued it. Use a staged re-auth strategy with in-app prompts and tiered outreach to minimize disruption.
- How long does a full migration from Merge.dev to Truto take?
- Most teams complete the migration in 6-8 weeks across three phases: audit and preparation (weeks 1-2), token migration for portable accounts (weeks 3-4), and staged re-auth for non-portable accounts (weeks 5-8). If all accounts are portable, it can be done in 2-3 weeks.
- What are the risks of transferring OAuth tokens between unified API platforms?
- Key risks include OAuth scope mismatches (the new platform may need broader permissions), token expiry during migration, provider-specific refresh token rotation that can invalidate tokens if both platforms refresh concurrently, and redirect URI validation issues on some providers.
- Do I need to update redirect URIs when migrating OAuth tokens?
- Yes, but the timing is flexible. Existing refresh tokens do not require the redirect URI to match for token refresh calls. You must update the redirect URI before any new customers authenticate, but imported tokens will continue to refresh using the old URI configuration.