The Merge.dev Migration Playbook: Moving to a Flexible Unified API Without Downtime
A senior engineer's runbook for migrating off Merge.dev without forcing customers to re-authenticate. Discover OAuth token replay and staged cutover plans.
If you are a senior product manager or engineering lead at a B2B SaaS company staring at a Merge.dev renewal quote and quietly running the numbers, this guide is for you. If you are evaluating a migration away from Merge.dev to a more flexible unified API, your biggest fear is likely the migration cliff. Forcing hundreds of enterprise users to click "Reconnect" on their Salesforce, Workday, or HubSpot integrations is a massive friction point. It generates support tickets, increases churn risk, and burns social capital with your best accounts.
The math on your current integration vendor might no longer make sense. The pricing structure might be dragging down your unit economics, or your enterprise customers are demanding custom field support that a rigid common data model simply cannot handle. You know you need to switch infrastructure, but you are paralyzed by the operational risk 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 specific operational work—there are concrete technical strategies to migrate OAuth tokens to a new unified API platform without touching your end users.
This is not theoretical. Teams do this every quarter. The reason it feels terrifying is that nobody writes down the runbook. We've documented similar playbooks for migrating beyond Finch, and the core principles remain the same. This guide breaks down the exact technical playbook to export OAuth tokens, import them into a 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.
The Merge.dev Migration Cliff: Why Teams Hesitate to Move
Migrating infrastructure is inherently risky. When that infrastructure handles the authentication and data synchronization for your customers' core systems of record, the perceived risk skyrockets. Product managers and engineering leaders often delay migrating off a legacy unified API because they assume the process requires a hard cutover.
A hard cutover means deprecating the old OAuth applications, spinning up new ones, and emailing every customer a guide on how to re-authorize your application in their CRM or HRIS. In a B2B environment, getting a system administrator to approve and execute a new OAuth grant can take weeks. During that window, data stops syncing. Workflows break. Customers complain.
But staying is worse. Integration sprawl on the customer side keeps growing. The average company now manages 305 SaaS applications, and organizations add roughly 12 new SaaS applications every 30 days, which compounds to 34% portfolio growth annually. Every one of those apps is another integration request that lands in your backlog.
Because of this fear, engineering teams often choose to stay on platforms that no longer serve their needs. They write brittle middleware to handle custom fields that the unified API drops. They build separate microservices to handle real-time webhook events because their vendor defaults to 24-hour polling. They accept that adding a new integration requires waiting for the vendor's roadmap.
A unified API vendor that cannot keep up with custom fields, custom objects, or weird per-tenant schemas is actively slowing your roadmap. Staying on a platform that limits your product velocity is a slow death for B2B SaaS. To move upmarket, you need infrastructure that adapts to your enterprise customers, not the other way around. The goal of the migration is not to just "swap vendors." The goal is to remove the bottleneck so your team ships integrations as fast as customers ask for them, without breaking what already works. You can avoid the migration cliff entirely by executing a zero-downtime token migration.
Assessing the Vendor Lock-In: Pricing, Schemas, and Data Storage
Before you write a single line of migration code, do an honest audit of exactly which architectural constraints you are trying to escape. Vendor lock-in in the SaaS integration space typically manifests in three distinct ways: pricing penalties for growth, rigid data schemas, and compliance risks associated with data storage. Write these three down. They become the acceptance criteria for the new platform.
1. Punitive Pricing at Scale
Integration sprawl is a reality. As your customers connect more tools, your infrastructure costs should scale predictably. However, Merge.dev's pricing model can become prohibitively expensive at scale. Charging $650 per month for up to 10 linked accounts, and $65 for each additional account, creates a scenario where your integration infrastructure actively penalizes your growth. When your unit economics depend on adding 50 free-tier accounts for every paid account, a flat per-connection fee inverts your margin. The pricing model rewards your vendor for your customer's success, not yours.
2. The Trap of Rigid Common Models
Unified APIs promise a single data model for all integrations. This works perfectly for standard fields like first_name or email. But enterprise software runs on custom objects. Your largest customer might have 47 custom fields on the Salesforce Opportunity object. Your unified schema returns 12 of them. The remaining 35 require either a passthrough request, an unsupported workaround, or a contract upgrade. If your vendor forces all data through a rigid, pre-defined schema, those custom fields are either dropped or shoved into an unstructured metadata blob that requires custom code to parse. This is the single most common reason enterprise integrations stall in QA.
3. Sync-and-Cache Compliance Risks
Many legacy unified APIs rely on a sync-and-cache architecture. They pull data from third-party APIs, store it in their own databases, and serve it to you. This means your vendor is indefinitely storing your customers' PII (Personally Identifiable Information). If your vendor stores customer data to serve list endpoints quickly, you inherit their data retention posture during every security review. For compliance-strict industries like healthcare or finance, introducing a third-party database into the data flow complicates SOC 2, GDPR, and HIPAA audits. A real-time pass-through architecture, which does not store customer data at rest, is easier to defend than admitting "we cache 90 days of CRM data in a third party."
Quick Sanity Check: What is Vendor Lock-In in SaaS? Vendor lock-in occurs when systems rely on proprietary technologies or opaque authentication handling. In the API space, lock-in is often driven by who actually owns the OAuth Client ID and Secret. If you originally registered partner OAuth apps in Merge with your own Client ID and Secret, you can migrate tokens. If Merge owns the OAuth apps, you cannot. Most enterprise customers explicitly required "bring your own OAuth" during procurement, so this is usually fine—but verify in the dashboard before you commit to a date.
The Zero-Downtime Strategy: Migrating OAuth Tokens
As we detailed in our step-by-step guide on migrating from Finch to a multi-category unified API, the entire premise of a zero-downtime migration rests on one technical requirement: you must own your OAuth applications.
If you registered your own OAuth apps in each provider's developer portal—like Salesforce or HubSpot—and configured them as "partner credentials" or custom apps in your current vendor, you hold the keys. The vendor is simply storing the access and refresh tokens on your behalf. Because you own the Client ID and Secret, you can export the active tokens from your current vendor and import them into a new platform. The third-party API does not know or care that the HTTP requests are originating from a different IP address, as long as the Bearer token matches the Client ID.
This is the part everyone gets wrong on the first attempt. The mechanics are not hard, but the sequencing matters.
Step 1: Export Tokens from Merge
Use the Merge Account Token endpoint to enumerate every linked account in scope. For each account, store the following in a temporary, encrypted staging table:
- The end customer identifier (your internal tenant ID)
- The provider name (salesforce, hubspot, etc.)
- The access token, refresh token, token expiry, and refresh token expiry
- Any provider-specific metadata you currently use (instance URL for Salesforce, hub ID for HubSpot)
Step 2: Import into a Generic Credential Context
A platform built on the interpreter pattern treats credentials as data. You POST the token blob into the new platform's integrated account endpoint, attach the same OAuth app (since you own the Client ID and Secret), and the platform takes over refresh from there.
// Pseudo-code: replay a token into a new integrated account context
await fetch('https://api.truto.one/integrated-account', {
method: 'POST',
headers: { Authorization: `Bearer ${TRUTO_API_KEY}` },
body: JSON.stringify({
integration: 'salesforce',
tenant_id: customer.tenant_id,
oauth_app_id: SALESFORCE_OAUTH_APP_ID,
credentials: {
access_token: merge.access_token,
refresh_token: merge.refresh_token,
expires_at: merge.expires_at,
instance_url: merge.metadata.instance_url
}
})
});When imported into a platform like Truto, the runtime engine takes over the token lifecycle. It proactively schedules work ahead of token expiry, utilizing the refresh token to acquire a new access token without any user intervention. Because the OAuth app is the same, the provider does not see a new consent event. From the customer's perspective, nothing changed.
Step 3: Verify Silently Before Cutover
Do not flip traffic yet. For each migrated account, fire a read-only call against the new platform and diff the response against the live Merge response. Log mismatches. Fix mappings. Repeat until the diff is empty. For deeper context on bringing your own OAuth, see our guide on OAuth App Ownership: How to Avoid Vendor Lock-In When Choosing a Unified API Provider.
Normalizing the Data: Mapping Responses to Match Your Old API
Migrating tokens solves the authentication problem. But if the new API platform returns data in a different JSON shape than Merge.dev, you will have to rewrite your entire frontend and backend data ingestion logic. The value of a declarative mapping layer shows up here. If you have to change your frontend code, the migration's cost balloons.
To achieve true zero downtime, the new API responses must exactly mimic the legacy schema you are migrating away from, on a per-account basis if needed. Most platforms cannot do this because they hardcode their integration logic. Truto takes a radically different approach: zero integration-specific code. The entire platform handles hundreds of integrations without a single if (provider === 'hubspot') statement in its runtime logic.
Integration behavior is defined entirely as data using JSONata—a functional query and transformation language for JSON. A JSONata expression is just a string, stored in the database, that says "given this input JSON, produce this output JSON." Expressions are versioned, hot-swappable, and overridable per environment or per account without a code deploy.
The 3-Level Override Hierarchy
To mimic legacy schemas and handle per-customer custom fields, you can utilize a three-level customization stack that deep-merges mapping modifications:
- Platform Base: The default unified mapping.
- Environment Override: A mapping override applied to your specific staging or production environment. You can write a JSONata expression here that reshapes all responses to match your old vendor's schema.
- Account Override: Individual connected accounts can have their own mapping overrides. If one specific enterprise customer has a highly customized Salesforce instance, you can override the mapping just for them.
flowchart LR A[Provider Response] --> B[Platform-level<br>JSONata mapping] B --> C[Environment override<br>deep-merged] C --> D[Account override<br>deep-merged] D --> E[Unified Response<br>matching old Merge shape]
Here is a contrived example. Merge returns CRM contacts in one shape; you want to emit Merge's exact shape from the new platform so your frontend stays untouched:
/*
Example JSONata expression to mimic a legacy unified API response.
This transforms the raw provider data into the exact schema your
frontend already consumes, extracting custom fields dynamically.
*/
{
"id": id,
"remote_id": properties.hs_object_id,
"first_name": properties.firstname,
"last_name": properties.lastname,
"email_addresses": [{
"email_address": properties.email,
"email_address_type": "WORK"
}],
"phone_numbers": properties.phone ? [{
"phone_number": properties.phone,
"phone_number_type": "WORK"
}] : [],
/* Extracting custom fields without writing custom code */
"custom_fields": properties.custom_enterprise_tier ? {
"enterprise_tier": properties.custom_enterprise_tier
} : null,
"last_activity_at": properties.notes_last_updated,
"remote_created_at": createdAt,
"modified_at": updatedAt
}By applying this mapping at the environment level, your application continues to receive the exact JSON structure it expects. You do not have to update your database schemas, your frontend components, or your background workers. When your largest customer has 35 custom fields on their Salesforce Opportunity, you do not want to add them to the global schema. Instead, apply an account-level override.
Read more about this architecture in our guide to 3-Level API Mapping: Per-Customer Data Model Overrides Without Code.
Handling Rate Limits Post-Migration
One of the most misunderstood aspects of unified APIs is how they handle rate limits. This is the section migration plans usually skip and then regret. A common marketing claim is that unified APIs "absorb" or "handle" rate limit errors for you. This is architecturally dishonest.
If you are making real-time pass-through requests to a third-party API and that API returns an HTTP 429 (Too Many Requests), a unified API cannot magically force the upstream provider to accept the request. Truto's stance is explicit and radically transparent: the platform does not retry, throttle, or apply backoff on rate limit errors. When an upstream API returns a 429, Truto passes that error directly back to the caller.
This is the right design. A unified API that silently retries on 429 will eventually mask a much worse failure: the provider has revoked your token, or your customer has hit their daily SOQL limit, and your sync is quietly dying in a retry loop. Surfacing 429 means your code stays in control.
What Truto does do is normalize the wildly inconsistent rate limit headers from hundreds of different providers into the standardized IETF specification:
ratelimit-limit: The maximum number of requests allowed in the current window.ratelimit-remaining: The number of requests remaining in the current window.ratelimit-reset: The time at which the rate limit window resets.
The IETF draft defines these to advertise quota policies and current service limits so clients can avoid being throttled. RateLimit-Limit is the quota for the time window, RateLimit-Remaining is the quota left in the current window, and RateLimit-Reset is the time remaining in the current window in seconds.
As you build your migration playbook, you must ensure your application logic is prepared to handle these standardized headers and implement its own exponential backoff with jitter. A minimal client-side handler looks like this:
// Example: Client-side exponential backoff handling standardized IETF headers
async function callWithBackoff(url: string, attempt = 0): Promise<Response> {
const res = await fetch(url, { headers: { Authorization: `Bearer ${TOKEN}` } });
if (res.status !== 429 || attempt >= 5) {
return res;
}
const reset = Number(res.headers.get('ratelimit-reset') ?? 1);
const jitter = Math.random() * 500;
const waitMs = (reset * 1000) + jitter;
console.warn(`Rate limited. Waiting ${waitMs}ms before retry.`);
await new Promise(r => setTimeout(r, waitMs));
return callWithBackoff(url, attempt + 1);
}Add a circuit breaker per tenant so one customer's runaway sync does not poison the queue for everyone else. For patterns, see API Rate Limit Best Practices.
Executing the Switch: The DNS Flip and Rollout Plan
The technical work is done. Now you sequence the cutover so a bad mapping in production does not cause a P1 incident. Because you have migrated the tokens and mapped the responses to match your existing schemas, you can execute the switch at the routing layer without dropping requests or webhooks.
The Non-Negotiable Rule: Never cut over more than 10% of tenants in the first 24 hours. The mappings that survive your test suite will still fail on real customer data. Budget for it.
Here is the step-by-step runbook for a zero-downtime rollout:
Step 1: Sandbox Parity Testing Create a sandbox environment in your new platform. Export a small batch of test tokens from your legacy provider and import them. Point your local development environment to the new API endpoints and verify that the JSONata mappings output the exact data shapes your application expects. Resolve every non-trivial difference at the mapping layer, not in your frontend.
Step 2: Shadow Traffic For 1-2 weeks, dual-write or dual-read in production for internal tenants only. Log diffs to a dashboard. Watch for slow drift on edge cases (deleted records, archived contacts, paginated cursors).
Step 3: Batch Token Migration Export your production OAuth tokens from the legacy vault and import them into Truto's credential context. Both platforms now hold valid access and refresh tokens. Move 5 tenants, watch error rates for 24 hours, move 20 more, repeat. Have a rollback path: keep the Merge tokens in your staging table until the new platform has been stable for 30 days.
Step 4: The Routing Flip
Update your API gateway or internal routing logic (via a feature flag) to direct requests for a specific subset of customer accounts to the new platform. Your API client should read from integrations.provider per tenant. Default new tenants to Truto on day one; flip existing tenants in waves.
sequenceDiagram
participant Client as Your Application
participant Gateway as API Gateway
participant Legacy as Legacy Unified API
participant Truto as Truto API
participant Provider as 3rd Party (e.g. Salesforce)
Note over Client, Provider: Phase 1: Pre-Migration (Legacy)
Client->>Gateway: GET /crm/contacts
Gateway->>Legacy: Forward Request
Legacy->>Provider: API Call (Token A)
Provider-->>Legacy: Raw Response
Legacy-->>Gateway: Mapped Response
Gateway-->>Client: Data
Note over Client, Provider: Phase 2: The Routing Flip<br>(Tokens migrated to Truto)
Client->>Gateway: GET /crm/contacts
Gateway->>Truto: Route flipped to new infrastructure
Truto->>Provider: API Call (Token A)
Provider-->>Truto: Raw Response
Truto->>Truto: JSONata Mapping (Mimics Legacy)
Truto-->>Gateway: Mapped Response
Gateway-->>Client: Data (Identical Shape)Step 5: Re-Point Webhooks Last If you rely on third-party webhooks, configure your new platform to ingest webhooks alongside your legacy provider. Inbound webhooks from providers are the most fragile part. Truto processes incoming webhooks, verifies signatures, and normalizes the payloads. Route these new webhooks to a staging endpoint on your server. Do not turn off the Merge webhook endpoint until you have seen 24 hours of normalized events flowing through the new pipeline.
Step 6: Decommission Merge Connections Only after the new platform has run all production traffic for 30 days should you formally decommission the old connections. Keep the OAuth app credentials intact; you own them, and you might need them again.
What to Watch During Cutover
- 429 response rates per integration: Should be near-zero if your client backoff is correct.
- Token refresh failure rates: Expect a small spike as the new platform takes over refresh.
- p95 latency per endpoint: Pass-through is typically slower than a cached API; budget 200-500ms more.
- Webhook event delivery counts: Compare before and after.
If any of these regress more than 2x baseline, pause the rollout and triage. The whole point of staging the migration in batches is that you can.
Strategic Wrap-Up
Migrating core integration infrastructure is a serious engineering undertaking, but it does not have to be a customer-facing crisis. A Merge.dev migration done right is unglamorous. No customer notices. No engineer pages at 3am. The runbook reduces to: own your OAuth, export tokens, replay into a declarative engine, mirror the response schema with JSONata, normalize rate limit headers, and cut over in waves.
The real value of migrating to a platform like Truto is not just escaping punitive pricing. It is adopting an architecture where integration-specific behavior is treated as data, not code. The payoff is what comes after the migration. Custom fields stop being a contract upgrade. New integrations stop being a six-week project. Per-account schema quirks stop being a reason to say no to enterprise deals. You move from managing a vendor to actually shipping integrations.
If you want a second set of eyes on your specific migration plan—especially the OAuth app ownership audit and the JSONata mapping strategy for your existing schema—we are happy to walk through it.
FAQ
- Can I migrate off Merge.dev without asking users to reconnect?
- Yes, provided you registered your own OAuth applications (Client ID and Client Secret) and configured them as partner credentials. You can export the active tokens and securely import them into your new infrastructure.
- What happens to my frontend code during a Merge.dev migration?
- If the new platform supports declarative response mapping (such as JSONata expressions), you can mirror Merge's exact response shape on a per-integration and even per-account basis. Your frontend code stays untouched.
- How do I handle custom fields during a Merge.dev migration?
- You can use declarative JSONata mappings to extract custom fields from the provider's raw response and map them to your exact required schema, bypassing rigid unified models through per-account overrides.
- How are rate limits handled after migrating off Merge.dev?
- Truto normalizes upstream rate limit information into standardized headers (ratelimit-limit, ratelimit-remaining, ratelimit-reset) per the IETF spec. It passes HTTP 429 errors directly to the caller, meaning your service is responsible for implementing retry logic with exponential backoff.
- Will migrating API providers cause webhook downtime?
- No. By running both ingestion pipelines in parallel and migrating tokens in batches, you can flip the routing at the API gateway layer. Always run both for at least 24 hours before disabling the old endpoint to ensure zero dropped webhooks.