How to Migrate from Apideck Without Re-Authenticating End Users
A step-by-step technical guide to migrating from Apideck without re-authenticating users - covering OAuth token export, API key extraction, credential import, testing, rollback strategy, and security.
If you are evaluating an Apideck alternative, you are likely staring down the barrel of a migration cliff. You want to switch infrastructure, but the thought of asking hundreds of enterprise customers to re-authenticate their Salesforce, HubSpot, BambooHR, or QuickBooks accounts is terrifying. Forcing users to click "Reconnect" generates support tickets, introduces immediate churn risk, and burns social capital with your best accounts.
The good news is you do not have to do this. As we've demonstrated in our migration guides for Merge.dev and Finch, you can migrate away from Apideck without asking a single end user to reconnect. The process involves exporting OAuth tokens from Apideck Vault, importing them into your new platform's credential context, mapping the old unified schema to the new one, and flipping the DNS.
This guide breaks down the exact technical strategy to extract your credentials, 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 Vendor Lock-In Trap: Why Teams Migrate from Apideck
Apideck is a well-built product. The docs are clean, the Vault connection UI is polished, and the real-time pass-through architecture that avoids caching customer data is a sound design decision. Integrations are a core revenue lever, and studies show that organizations use anywhere from 100 to more than 300 SaaS applications.
While Apideck helps teams get off the ground quickly, engineering leaders typically hit three specific scaling limits that force a migration conversation:
1. Virtual webhooks default to 24-hour polling. Apideck monitors enabled resources at regular intervals, typically every 24 hours, for providers without native webhook support. For providers like BambooHR and many HRIS platforms that don't support native webhooks, Apideck monitors integrations at an interval of every 24 hours by default. If you are building an applicant tracking system (ATS) integration and need immediate status updates, or a CRM sync syncing employee terminations or deal stage changes, a 24-hour delay is a compliance incident or a stale-pipeline problem.
2. Custom field mapping is hidden behind enterprise paywalls. As your customers scale, they heavily customize their CRMs and HRIS platforms. Apideck restricts Custom Field Mapping to its Scale plan and above. Their Launch plan at $599/month does not include it. You need the Scale tier at $1,299/month to access custom field mapping. The moment your first enterprise customer asks you to map their custom BambooHR employment type field or a custom Salesforce object, you are looking at more than doubling your monthly spend.
3. No auto-generated MCP server support. If your product team is building AI agents, those agents need secure, scoped access to third-party data. Agentic AI is becoming a standard pattern in B2B SaaS—nearly 33% of organizations with at least 1000 full-time employees have already deployed agentic AI. Apideck currently lacks native Model Context Protocol (MCP) server generation, forcing your engineers to manually build and maintain tool definitions for LLMs.
For a deeper dive into these architectural limits, see our technical breakdown in Truto vs Apideck: The Best Alternative for Enterprise SaaS Integrations.
The Migration Cliff: Why Re-Authenticating End Users is Not an Option
Unified API platforms abstract away the pain of dealing with terrible vendor API docs, inconsistent pagination, and undocumented edge cases. But the architecture of most platforms creates a dependency that is easy to miss during evaluation and incredibly painful to untangle later.
Apideck Vault acts as a centralized credential store. When your customer authenticates via the Apideck UI, the resulting OAuth access_token and refresh_token are held by Apideck. Your application only holds an Apideck consumer ID. If you simply switch integration vendors, those consumer IDs become useless. Forcing enterprise users to click "Reconnect" on their integrations is a nuclear option. The math is brutal:
- Support ticket volume: Every reconnection generates at least one support ticket. At 200 linked accounts, that is 200+ tickets in a single week.
- Churn risk: Enterprise buyers who have to re-link core systems of record will question whether your product is stable. Some will simply drop off.
- Coordination overhead: Enterprise clients often require their IT team to approve OAuth grants. Scheduling that across dozens of accounts takes weeks.
The good news: OAuth tokens are just strings with an expiry date. If you can extract them from your current vendor's credential store and insert them into a new one, the end user never knows you switched infrastructure. The token keeps working until it expires, at which point the new platform refreshes it automatically.
When Zero-Reauth Migration Is Possible
Before you commit to a migration timeline, you need to answer one question: who owns the OAuth application that issued the tokens?
This single factor determines whether you can migrate without any re-authentication, or whether some subset of your connections will require users to reconnect.
Decision Matrix: Can You Migrate Without Re-Auth?
| Scenario | OAuth App Owner | Tokens Portable? | Re-Auth Required? |
|---|---|---|---|
You brought your own client_id / client_secret to Apideck |
You | Yes | No |
You use Apideck's managed OAuth app (their client_id) |
Apideck | No | Yes |
| API key / Basic auth connections (non-OAuth) | N/A | Yes, if you can extract values | No |
| OAuth 2.0 Client Credentials (service-to-service) | You (typically) | Yes, re-acquire with your credentials | No |
Apideck supports bringing your own OAuth clients. In their dashboard, when configuring a connector, you can select "Use your client credentials" and enter your own client_id and client_secret. By default, Apideck uses sandbox OAuth apps for quick setup, but their own docs recommend switching to your own OAuth apps before going to production.
How to check which OAuth app you are using:
- Log into the Apideck dashboard at
https://platform.apideck.com/. - Navigate to Configuration and select the Unified API category (e.g., CRM, HRIS).
- Click on the specific connector (e.g., Salesforce, HubSpot).
- Look for the OAuth credentials section. If it shows "Use your client credentials" with your own values filled in, you own the OAuth app. If it shows Apideck's defaults or no custom credentials, Apideck's shared OAuth app was used.
- Cross-reference with your provider's developer console. For example, check your Salesforce Connected App or HubSpot Developer App to see if the redirect URI is set to
https://unify.apideck.com/vault/callback- and whether that app belongs to your organization.
Pre-Migration Action Item
Build a spreadsheet of every active connection. For each one, record the service_id, auth_type, consumer_id, and whether the OAuth app is yours or Apideck's. This inventory determines your migration path for each connection and gives you an accurate estimate of how many users (if any) need to re-authenticate.
If you own the OAuth app for a given provider, a zero-reauth migration is straightforward: the tokens were issued to your application, so any platform that holds your client_id and client_secret can refresh them. If Apideck owns the OAuth app, you have two choices: either negotiate a token export (more on this below) or plan a staggered re-auth for those specific connectors while migrating the rest silently.
Step 1: Exporting Credentials from Apideck Vault
The first technical hurdle is getting your data out. You need the raw OAuth tokens, the expiration timestamps, the scopes, and any provider-specific metadata (like subdomains or tenant IDs) stored in Apideck Vault.
Does Apideck Allow Token Export?
This is the question that trips up most teams. Here is the reality:
Apideck's Vault API has an import endpoint, but no corresponding export endpoint for raw tokens. Their migration guide documents POST /vault/connections/:unified_api/:service_id/import for importing tokens into Apideck, but there is no GET or export endpoint that returns raw access_token or refresh_token values. The standard GET /vault/connections endpoint returns connection metadata - service_id, state, auth_type, enabled status, and settings - but the actual OAuth tokens are stored server-side and injected into requests at runtime. They are never exposed in API responses.
This means you cannot programmatically self-serve a token export. Your extraction path depends on your setup:
Path A: You Own the OAuth App (Best Case)
If you brought your own OAuth app credentials when setting up integrations in Apideck, the tokens were issued to your OAuth application. This is the best-case scenario. You can:
- Contact Apideck support and request a secure, encrypted export of your Vault data. Be specific: you need the raw
access_token,refresh_token,expires_at, and any per-connection metadata for eachconsumer_id. - Negotiate the export as part of your offboarding. Most vendors will cooperate, especially if the OAuth app belongs to you. Frame it clearly: "These tokens were issued to our OAuth application. We need the raw credentials to continue serving our customers."
- If Apideck provides a data export mechanism (e.g., a support-assisted database export or a temporary API endpoint), request the data in a structured format like JSON or CSV with fields for
consumer_id,service_id,unified_api,access_token,refresh_token,expires_at, and any connectionsettings.
Path B: Apideck Managed OAuth Credentials
If you used Apideck's shared OAuth app credentials (Apideck's client_id), the tokens belong to Apideck's OAuth application. Migrating these tokens to a new platform that uses a different OAuth application will not work - the provider (Salesforce, HubSpot, etc.) will reject refresh attempts from mismatched client credentials.
In this case, your options are:
- Re-register a new OAuth app with each provider and use the new platform's connection flow, but stagger this over time so it is not a sudden cliff.
- Use the migration to switch to your own OAuth app, which gives you token portability going forward.
- Prioritize by impact. Sort your connections by activity volume and business criticality. High-value enterprise accounts get white-glove migration support (a scheduled 5-minute reconnection call). Low-activity accounts get an in-app prompt that lets them reconnect at their convenience.
OAuth App Ownership Matters If you do not own the OAuth app that issued the tokens, token migration is not possible without re-authentication. This is the single most important architectural decision for integration vendor portability. We wrote a deep dive on this in OAuth App Ownership: How to Avoid Vendor Lock-In.
Path C: Extracting API Keys and Non-OAuth Credentials
Not all Apideck connections use OAuth. Many integrations - particularly HRIS platforms like BambooHR, ticketing systems, and smaller SaaS tools - use API key or Basic authentication. These are simpler to migrate because there is no token refresh cycle or OAuth app dependency.
Apideck's Vault API does expose connection settings for non-OAuth integrations. You can retrieve these via the GET /vault/connections/:unified_api/:service_id endpoint, which returns the connection's settings object. For API key integrations, the settings typically contain the fields your user entered (API key, subdomain, etc.).
# List all connections for a specific consumer
curl -X GET 'https://unify.apideck.com/vault/connections' \
-H 'Authorization: Bearer {APIDECK_API_KEY}' \
-H 'x-apideck-app-id: {APP_ID}' \
-H 'x-apideck-consumer-id: {CONSUMER_ID}'The response includes connection metadata and settings. For API key connections in callable state, the settings object contains the user-provided fields:
{
"id": "hris+bamboohr",
"service_id": "bamboohr",
"unified_api": "hris",
"auth_type": "apiKey",
"state": "callable",
"settings": {
"subdomain": "yourcompany",
"api_key": "..."
}
}Verify API Key Visibility Apideck may mask sensitive fields in API responses depending on your plan and configuration. Before building your export script, make a test request for a known API key connection and confirm the raw values are returned, not masked with asterisks. If they are masked, you will need to request a support-assisted export for these connections as well.
To bulk-export API key connections, iterate over all your consumers and pull their connection settings:
// Bulk export API key connections from Apideck
async function exportApiKeyConnections(
apiKey: string,
appId: string,
consumerIds: string[]
) {
const connections = [];
for (const consumerId of consumerIds) {
const response = await fetch(
'https://unify.apideck.com/vault/connections',
{
headers: {
'Authorization': `Bearer ${apiKey}`,
'x-apideck-app-id': appId,
'x-apideck-consumer-id': consumerId,
},
}
);
const { data } = await response.json();
for (const conn of data) {
if (conn.state === 'callable' && conn.auth_type === 'apiKey') {
connections.push({
consumer_id: consumerId,
service_id: conn.service_id,
unified_api: conn.unified_api,
auth_type: conn.auth_type,
settings: conn.settings,
});
}
}
}
return connections;
}The Target Export Payload
Whichever method you use, you are looking to build a dataset that maps your internal identifiers to the raw provider credentials. The structure you need to extract for each connection looks like this:
// Structure each exported connection for import
interface ExportedConnection {
consumer_id: string; // Your internal user/account ID
service_id: string; // e.g., 'salesforce', 'bamboohr'
unified_api: string; // e.g., 'crm', 'hris'
auth_type: 'oauth2' | 'apiKey' | 'basic'; // Determines import path
credentials?: {
access_token: string;
refresh_token: string;
expires_at: string; // ISO 8601 timestamp
token_type: string; // Usually 'Bearer'
scope?: string; // Original scopes granted
};
settings?: Record<string, any>; // API key, subdomain, etc.
metadata: Record<string, any>; // Provider-specific: instance_url, realm_id, etc.
}Beware of Token Rotation Race Conditions OAuth 2.0 refresh tokens are often single-use. If Apideck's background workers refresh a token after you have exported the data but before you have switched your routing, the exported refresh token becomes invalid. You must coordinate a hard cutover or pause Apideck syncs during the migration window.
Step 2: Importing Credentials into Truto's Generic Context
Most unified APIs maintain separate code paths for each integration. Adding custom credentials requires mapping them to rigid, integration-specific database columns.
Truto takes a radically different approach. The entire platform contains zero integration-specific code. Integration behavior is defined entirely as declarative JSON configurations. This makes importing historical credentials incredibly straightforward.
In Truto, every connected account is an IntegratedAccount, and its credentials live in a flexible, provider-agnostic JSON context object:
{
"context": {
"oauth": {
"token": {
"access_token": "eyJhbGciOiJSUzI1NiIs...",
"refresh_token": "dGhpcyBpcyBhIHJlZnJlc2g...",
"expires_at": "2026-04-10T14:30:00.000Z",
"token_type": "Bearer",
"scope": "read write"
},
"scope": "read write"
},
"instance_url": "https://yourcompany.my.salesforce.com"
}
}Provider-specific metadata like Salesforce's instance_url or QuickBooks' realm_id goes into the context root. The platform resolves these using JSONata expressions in the integration configuration - no custom code needed.
Import via API: OAuth Connections
For each OAuth connection you exported from Apideck, create an integrated account in Truto by passing the raw tokens directly into the context:
const response = await fetch('https://api.truto.one/integrated-accounts', {
method: 'POST',
headers: {
'Authorization': `Bearer ${TRUTO_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
integration_id: 'salesforce',
environment_id: 'env_prod_123',
external_id: exportedConnection.consumer_id,
authentication_method: 'oauth2',
context: {
oauth: {
token: {
access_token: exportedConnection.credentials.access_token,
refresh_token: exportedConnection.credentials.refresh_token,
expires_at: exportedConnection.credentials.expires_at,
token_type: exportedConnection.credentials.token_type,
scope: exportedConnection.credentials.scope
}
},
// Provider-specific metadata
instance_url: exportedConnection.metadata.instance_url
}
})
});Import via API: API Key Connections
For non-OAuth connections (API key, Basic auth), the import is even simpler. The context stores the credentials exactly as the integration's configuration expects them:
// BambooHR (API Key + subdomain)
const response = await fetch('https://api.truto.one/integrated-accounts', {
method: 'POST',
headers: {
'Authorization': `Bearer ${TRUTO_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
integration_id: 'bamboohr',
environment_id: 'env_prod_123',
external_id: exportedConnection.consumer_id,
authentication_method: 'api_key',
context: {
api_key: exportedConnection.settings.api_key,
subdomain: exportedConnection.settings.subdomain
}
})
});// QuickBooks (OAuth + realm_id metadata)
const response = await fetch('https://api.truto.one/integrated-accounts', {
method: 'POST',
headers: {
'Authorization': `Bearer ${TRUTO_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
integration_id: 'quickbooks',
environment_id: 'env_prod_123',
external_id: exportedConnection.consumer_id,
authentication_method: 'oauth2',
context: {
oauth: {
token: {
access_token: exportedConnection.credentials.access_token,
refresh_token: exportedConnection.credentials.refresh_token,
expires_at: exportedConnection.credentials.expires_at,
token_type: 'Bearer',
scope: exportedConnection.credentials.scope
}
},
realm_id: exportedConnection.settings.realm_id
}
})
});When Truto receives this payload, it automatically encrypts sensitive fields (like access_token and refresh_token) at rest using AES-GCM encryption.
Proactive Token Refresh Architecture
One of the primary reasons integrations fail in production is reactive token refreshing - waiting until an API call returns a 401 Unauthorized before attempting to refresh the token. This creates latency spikes and often results in dropped webhooks if the refresh fails.
Truto eliminates this via proactive scheduling. The moment you import that OAuth token payload, Truto reads the expires_at timestamp. The platform schedules a durable background task to fire exactly 60 to 180 seconds before the token expires.
When the scheduled task fires, Truto uses your OAuth app's client_id and client_secret to execute the standard OAuth 2.0 refresh flow, updates the encrypted context with the new tokens, and schedules the next refresh.
If the refresh succeeds, the account stays active and your application never experiences a 401 Unauthorized. If it fails (e.g., invalid_grant because the refresh token was revoked by the user in Salesforce), the account is marked as needs_reauth and a webhook event is fired so you can prompt the specific user to reconnect.
sequenceDiagram
participant App as Your Application
participant Truto as Truto Platform
participant Provider as SaaS Provider (e.g., Salesforce)
App->>Truto: Import Apideck OAuth Tokens
Truto->>Truto: Encrypt tokens at rest
Truto->>Truto: Schedule proactive refresh (T-minus 180s)
Note over Truto: ... time passes ...
Truto->>Provider: Execute Refresh Grant (Background)
Provider-->>Truto: New Access & Refresh Tokens
Truto->>Truto: Update encrypted context
App->>Truto: API Request (e.g., GET Contacts)
Truto->>Provider: Authenticated Request (Always Valid)
Provider-->>Truto: 200 OK
Truto-->>App: Normalized ResponseStep 3: Handling Rate Limits Post-Migration (The Standardized Approach)
Here is where you need to be honest about what changes post-migration. When you were on Apideck, rate limit handling was opaque - Apideck managed the upstream API calls and handled (or didn't handle) rate limits internally. Many unified APIs attempt to "absorb" rate limits by holding requests in memory and applying exponential backoff.
This is a massive anti-pattern for enterprise engineering. Absorbing 429s hides backpressure from your application. Your workers stay open, waiting for HTTP responses that take 45 seconds to resolve, eventually causing cascading timeouts across your own infrastructure.
Truto does NOT retry, throttle, or apply backoff on rate limit errors.
When an upstream API returns a rate-limit error, Truto passes that 429 directly back to your caller. This keeps the platform transparent and gives you full control over your retry strategy.
However, dealing with 50 different rate limit headers across 50 different APIs is a nightmare. Some use X-HubSpot-RateLimit-Daily, others use Sforce-Limit-Info, and some just put it in the response body.
What Truto does do is normalize the rate limit information from every upstream provider into standardized response headers based on the IETF RateLimit specification:
| Header | Meaning |
|---|---|
ratelimit-limit |
Maximum requests allowed in the current window |
ratelimit-remaining |
Requests remaining in the current window |
ratelimit-reset |
Seconds until the rate limit window resets |
Building Your Backoff Logic
Because Truto normalizes the headers, your engineering team can build a single, unified retry queue on your side of the architecture. When your worker receives a 429, it simply reads the ratelimit-reset header, pauses the job, and safely retries.
async function callTrutoWithBackoff(
url: string,
options: RequestInit,
maxRetries = 3
): Promise<Response> {
for (let attempt = 0; attempt <= maxRetries; attempt++) {
const response = await fetch(url, options);
if (response.status === 429) {
// Read the standardized header provided by Truto
const resetSeconds = parseInt(
response.headers.get('ratelimit-reset') || '60',
10
);
// Add jitter to prevent thundering herd
const waitMs = resetSeconds * 1000 + Math.random() * 1000;
console.warn(
`Rate limited. Waiting ${resetSeconds}s before retry ${attempt + 1}`
);
await new Promise((resolve) => setTimeout(resolve, waitMs));
continue;
}
// Proactively slow down when remaining quota is low
const remaining = parseInt(
response.headers.get('ratelimit-remaining') || '100',
10
);
if (remaining < 10) {
console.warn(`Low rate limit quota: ${remaining} remaining`);
}
return response;
}
throw new Error('Max retries exceeded for rate-limited request');
}This is more work than having the platform absorb 429s for you. But it is also more honest. You know exactly when you are being rate limited, which provider is throttling you, and how long to wait. No black-box retry logic hiding upstream failures. For a comprehensive guide on architecting this, read our Best Practices for Handling API Rate Limits.
Step 4: Declarative Mappings to Mimic Apideck's Unified Schema
Migrating credentials is only half the battle. If you switch to a new unified API, the JSON response shapes will change. A Contact in Apideck looks different than a Contact in Truto.
Normally, this means rewriting your entire frontend UI and backend business logic to handle the new schema. With Truto, your frontend code currently expects Apideck's unified response format, and you do not have to touch it.
Truto relies on declarative JSONata expressions to map data between the provider's native format and the unified format. Because these mappings are exposed and editable, you can write a custom JSONata expression in Truto that outputs the exact JSON shape your application currently expects from Apideck.
Example: Mimicking Apideck's Contact Schema
Let's say your frontend expects Apideck's specific nested structure for contacts. Your JSONata mapping in Truto can reproduce that exact shape field-for-field:
{
"id": $.id,
"first_name": $.properties.firstname,
"last_name": $.properties.lastname,
"company_name": $.properties.company,
"emails": $.properties.emails.{
"email": value,
"type": type
},
"phone_numbers": [
{
"number": $.properties.phone,
"type": "primary"
}
],
"custom_mappings": {
"apideck_legacy_id": $.properties.hs_object_id,
"employee_band": $.properties.Employee_Band__c
},
"updated_at": $.properties.lastmodifieddate
}This mapping is defined as configuration data, not code. You can adjust it per-provider, per-customer, or per-environment without deploying anything. If your biggest Salesforce customer has a custom Employee_Band__c field that needs to appear as custom_mappings.employee_band in the response, you add a single line to the JSONata expression for that customer's account.
By deploying this mapping at the Truto layer, your backend receives the exact payload it is used to. You achieve a complete infrastructure migration without a single breaking change to your application logic.
Step 5: Staged Migration Strategies - Bulk Import vs. Phased by Tenant
Once your credential export is ready, you have two broad execution paths. Because Apideck's Vault acts as a proxy holding your users' auth tokens, moving to another unified API without re-authenticating means every one of those Vault proxy auth tokens needs a controlled hand-off to Truto. The right execution path depends on your account count, provider mix, and how much risk you are willing to absorb in a single window.
Option A: Bulk import, single cutover. Suitable when you have fewer than 100 connected accounts, all on providers where you own the OAuth app, and a maintenance window you can announce. You import every credential in one batch, run smoke tests against a canary subset, then flip the routing flag globally. Fast, but if something breaks, it breaks for everyone at once.
Option B: Phased by tenant. The safer default for anything above 100 accounts or with mixed OAuth ownership. You segment your customers into cohorts (by size, provider, risk tolerance, or activity level) and migrate one cohort at a time. Each cohort gets its own import batch, its own canary window, and its own rollback fence.
A common cohort structure:
| Cohort | Selection Criteria | Cutover Timing |
|---|---|---|
| Cohort 0 (internal) | Your own test tenants | Day 0 |
| Cohort 1 (low-risk) | Free-tier or trial accounts, single-provider | Day 1-3 |
| Cohort 2 (mid-market) | Paid customers, 1-3 integrations, moderate activity | Week 2 |
| Cohort 3 (enterprise) | Named accounts, high call volume, custom mappings | Week 3-4, scheduled |
Route traffic per tenant using a feature flag keyed on customer_id. This lets you promote or roll back a single cohort without touching others. If Cohort 2 exposes a mapping bug for HubSpot custom properties, you fix it, revalidate against Cohort 2, and only then start Cohort 3.
For enterprise accounts (Cohort 3), skip the automated path entirely. Schedule a 15-minute call with each customer's IT contact, migrate their connections during a low-traffic window, and stay on the line while you run smoke tests.
Bulk Import Batching
Even within a single cohort, do not fire thousands of POST /integrated-accounts calls at once. Chunk imports at 50 to 100 accounts per batch, wait for each batch's proactive refresh to fire and confirm success, then move to the next. This gives you a natural rollback boundary and prevents a single misconfigured field from cascading into a full-tenant outage.
async function importCohort(
connections: ExportedConnection[],
batchSize = 50
): Promise<ImportResult[]> {
const results: ImportResult[] = [];
for (let i = 0; i < connections.length; i += batchSize) {
const batch = connections.slice(i, i + batchSize);
const batchResults = await Promise.all(
batch.map((conn) => importToTruto(conn).catch((err) => ({
consumer_id: conn.consumer_id,
status: 'failed' as const,
error: err.message,
})))
);
results.push(...batchResults);
// Halt the cohort if failure rate exceeds threshold
const failed = batchResults.filter((r) => r.status === 'failed').length;
if (failed / batch.length > 0.05) {
throw new Error(`Batch failure rate ${failed}/${batch.length} exceeds 5%`);
}
}
return results;
}Step 6: Handling Expired or Non-Exportable Refresh Tokens
Not every token will make it across cleanly. Some will have already expired, some will be rotated in the gap between export and import, and some you simply cannot get out of Apideck. Plan for each case explicitly.
Case 1: Refresh Token Rotated Between Export and Import
Providers like HubSpot rotate the refresh token on every refresh. If Apideck's background workers refresh a token after your export snapshot, the token in your export file is dead on arrival.
Mitigation:
- Freeze the export window. Pause Apideck's background sync (via their dashboard or by disabling polling schedules) for the accounts you are about to migrate. Export immediately. Import within the same window.
- Prefer accounts that were recently active. A token refreshed 20 minutes ago is more likely to still be valid than one refreshed 3 days ago that could rotate at any moment.
- Detect stale tokens on import. After importing, force a synchronous refresh against each account. Any account that returns
invalid_grantgets flagged for targeted re-auth immediately, before you cut over traffic.
Case 2: Apideck Refuses or Delays the Export
Vendor cooperation is not guaranteed. If your Apideck support request stalls, or the export scope is narrower than you need, you have three levers:
- Escalate through commercial channels. Reference your contract's data portability clause. Most B2B SaaS contracts include one, and GDPR Article 20 provides an additional legal basis for EU customer data.
- Extract what you can, re-auth the rest. API key connections you can pull yourself via the
GET /vault/connectionsendpoint. For OAuth connections you cannot export, treat them as a re-auth cohort - see the communication template later in this guide. - Run in parallel and let tokens migrate organically. For providers where you cannot get a bulk export, add Truto as a second connection alongside Apideck. When a user next takes an action that requires a fresh authorization (e.g., adding a new scope or connecting a new sub-account), route them through Truto's connect flow. Over 30 to 90 days, most active accounts migrate themselves without a coordinated re-auth event.
Case 3: The Token Is Fine, But the OAuth App Is Not Yours
If Apideck's shared OAuth app issued the token, no amount of export will help. The provider (Salesforce, HubSpot) will reject any refresh attempt from your client_id. Your only clean path is to register your own OAuth app and re-auth those users through Truto. Prioritize by revenue and communicate individually.
Case 4: You Have the Token But No Provider Metadata
Some providers require metadata beyond the token itself. QuickBooks needs realm_id. Salesforce needs instance_url. Xero needs the tenant_id. If Apideck's export omits these fields, the token is useless.
Before cutover, do a completeness check:
const requiredMetadata: Record<string, string[]> = {
quickbooks: ['realm_id'],
salesforce: ['instance_url'],
xero: ['tenant_id'],
netsuite: ['account_id'],
shopify: ['shop_domain'],
};
function isImportReady(conn: ExportedConnection): boolean {
const required = requiredMetadata[conn.service_id] ?? [];
return required.every(
(field) => conn.metadata?.[field] || conn.settings?.[field]
);
}Any connection that fails this check goes into a "manual review" bucket. Either recover the metadata from provider-side records (e.g., Salesforce's /services/oauth2/userinfo) or plan a targeted re-auth.
Case 5: Token Has Already Expired
If the expires_at on an exported token is in the past, do not import as active. Import with a needs_reauth marker and skip the smoke-test bucket. For long-lived refresh tokens (Google, most enterprise SaaS), an expired access token is fine as long as the refresh token is still valid. Truto will attempt a refresh on first use and either succeed or transition the account to needs_reauth with a webhook event.
Step 7: Parallel-Run Configuration and Validation Checklist
The safest cutover strategy runs Apideck and Truto side-by-side for a window before you decommission. This is where you catch mapping bugs, missing metadata, and edge-case rate limit behavior before they affect real users.
Adding Redirect URIs Ahead of Cutover
Before any parallel-run configuration, register Truto's callback URL as an additional redirect URI on every OAuth app in your provider developer consoles. Most providers allow multiple redirect URIs per app, which means you can keep the Apideck callback active while adding Truto's alongside it.
| Provider | Where to add the redirect URI |
|---|---|
| Salesforce | Setup > App Manager > Connected App > Edit > Callback URL (add one per line) |
| HubSpot | Developer Portal > Apps > Auth > Redirect URLs |
| Cloud Console > APIs & Services > Credentials > OAuth 2.0 Client > Authorized redirect URIs | |
| Microsoft (Azure AD) | App registrations > Authentication > Redirect URIs |
| Xero | Developer Portal > My Apps > OAuth 2.0 credentials > Redirect URIs |
Do this at least 48 hours before your first cutover. Some providers (Salesforce, Google) cache OAuth app configuration for up to an hour on their edge, and you do not want to discover the propagation lag during a live cutover.
The Parallel-Run Playbook (Timing)
- T-14 days: Add Truto redirect URIs to every provider OAuth app. Do not remove Apideck's yet.
- T-10 days: Import Cohort 0 (internal test tenants). Run for the full period to observe at least one proactive refresh cycle per account.
- T-7 days: Send the customer heads-up email to any tenant that will need re-auth.
- T-3 days: Import Cohort 1. Enable shadow reads at 10% sampling.
- T-1 day: Freeze Apideck configuration changes. No new connectors, no scope changes.
- T-0: Flip routing flag for Cohort 1. Watch metrics for 4 hours before promoting Cohort 2.
- T+14 days: Every cohort has been on Truto for at least 7 days. Begin Apideck deprovisioning.
- T+30 days: Remove Apideck redirect URIs from provider OAuth apps. Delete exported credential files from cold storage per your retention policy.
Routing Shim
Introduce a routing shim in your application that reads a per-tenant feature flag. All integration calls go through this shim, which decides at request time whether to hit Apideck or Truto. Do not embed the vendor URL directly in your business logic.
async function shadowRead(tenantId: string, endpoint: string, params: any) {
const primary = getPrimary(tenantId); // 'apideck' or 'truto'
const secondary = primary === 'apideck' ? 'truto' : 'apideck';
const [primaryResp, secondaryResp] = await Promise.allSettled([
callVendor(primary, endpoint, params),
callVendor(secondary, endpoint, params),
]);
if (
primaryResp.status === 'fulfilled' &&
secondaryResp.status === 'fulfilled'
) {
logDiff(tenantId, endpoint, primaryResp.value, secondaryResp.value);
}
return primaryResp.status === 'fulfilled'
? primaryResp.value
: Promise.reject(primaryResp.reason);
}Validation Checklist
Before promoting a tenant from parallel-run to Truto-only, tick every box:
- Every integrated account for this tenant returns 200 on a lightweight read
- Manual refresh succeeds for every OAuth account
- Shadow-read diffs show zero unexpected field differences over 48 hours
- Rate limit headers (
ratelimit-limit,ratelimit-remaining,ratelimit-reset) are populated on 90%+ of responses - Webhook subscriptions are registered in Truto and delivering to your endpoint
- Write operations (create/update) succeed against a sandbox record
-
needs_reauthaccounts have been contacted and either resolved or explicitly deferred - The tenant's custom field mappings have been ported and validated against real records
- Your alerting rules fire correctly when you simulate a token failure
- Rollback via feature flag has been tested end-to-end (not just theorized)
Minimizing User Impact During Parallel Run
- Time cutovers to low-activity windows. For B2B products, that usually means outside 9-to-5 in your customers' primary timezones.
- Cap shadow-read traffic. Run shadow reads at 10% sampling for high-volume tenants to avoid doubling your rate limit consumption against upstream providers.
- Silence duplicate webhooks. During parallel run, both platforms may deliver the same webhook event. Dedupe on your side using a
(provider, external_id, event_type, occurred_at)composite key. - Do not migrate mid-session. If a user has an active sync in flight, wait for it to complete before flipping their routing flag. Track active operations in a lightweight state table.
- Prefer read-only cutovers first. Route reads to Truto for a few days before routing writes. Reads are idempotent and easier to reconcile if something breaks.
Testing, Smoke Tests, and Rollback Strategy
A migration of this scale requires strict operational discipline. Do not attempt a "big bang" cutover. Use a phased approach.
Pre-Migration Checklist
- Verify OAuth app ownership: Confirm you own the
client_id/client_secretfor every provider. If not, plan your re-auth strategy for those providers. - Import tokens into a staging environment: Truto supports environment-level credential overrides, so you can test with production tokens in a sandboxed context.
- Validate token refresh: For each provider, trigger a manual token refresh and confirm the new
access_tokenworks against the provider's API. - Shadow Reads: Configure your application to perform "shadow reads." Fetch data from Apideck (to serve the user) and simultaneously fetch it from Truto (in the background). Diff the JSON payloads to ensure your JSONata mappings are perfectly mimicking the Apideck schema.
- Test rate limit header normalization: Make enough requests to see rate limit headers appear. Verify your backoff logic reads them correctly.
- Verify webhook delivery: If you are using Apideck's virtual webhooks, set up equivalent webhook subscriptions in Truto and confirm events arrive.
Smoke Tests for Each Provider
After importing credentials, run these checks for every provider before routing any production traffic:
- Token validity check. Make a lightweight read request (e.g.,
GET /unified/crm/contacts?limit=1) for each imported account. A 200 response confirms the token is live. A 401 means the token was already rotated or revoked - flag that account for re-auth. - Token refresh check. Use Truto's manual refresh endpoint (
POST /integrated-account/refresh-credentialswith the integrated account ID) to force a proactive refresh. Confirm the newaccess_tokenis different from the imported one and that subsequent API calls succeed. - Write operation check (if applicable). If your application creates or updates records, test a write against a sandbox record. Some providers issue tokens with different write scopes than Apideck requested - verify that the scopes you need are present.
- Schema mapping check. Compare a Truto response against the equivalent Apideck response for the same record. Use a JSON diff tool. Every field your frontend reads must match exactly.
- Webhook delivery check. If you rely on webhooks, trigger a test event (e.g., update a contact in the provider) and confirm the webhook arrives at your endpoint with the expected payload shape.
Cutover Sequence
sequenceDiagram
participant App as Your Application
participant GW as API Gateway / Feature Flag
participant Old as Apideck
participant New as Truto
App->>GW: API Request (integration call)
alt Feature flag: canary group
GW->>New: Route to Truto
New-->>GW: Unified response
else Feature flag: default
GW->>Old: Route to Apideck
Old-->>GW: Unified response
end
GW-->>App: Response (same shape)- Canary a single low-risk integration (e.g., a file storage connector with low traffic). Route 5% of traffic through Truto.
- Monitor for 48 hours. Watch for token refresh failures, response shape mismatches, and rate limit behavior.
- Expand to 100% for that integration. Then repeat for the next provider.
- Deprovision Apideck connections only after the new platform has successfully refreshed each token at least once.
Rollback Plan and Support Playbook
Assume something will break. The question is how quickly you can recover.
Rollback Triggers
Define these before cutover and get sign-off from engineering leadership:
| Metric | Threshold | Action |
|---|---|---|
| Token refresh failure rate | > 5% over 30 minutes | Halt cutover, investigate |
| API error rate (5xx or unexpected 4xx) | > 2% over 15 minutes | Halt cutover, investigate |
| Shadow-read diff rate | > 1% of reads | Fix mappings, do not promote |
| Customer-reported broken integrations | 3+ from distinct tenants in 1 hour | Roll back affected cohort |
| Webhook delivery failure rate | > 10% over 1 hour | Investigate, consider partial rollback |
| p95 latency increase | > 50% above pre-migration baseline | Investigate, consider partial rollback |
Rollback Procedure by Phase
Phase 1 - Canary (0-10% of traffic): Flip the feature flag back to Apideck. Instant, no code deploy required. Keep the Truto integrated accounts in place for post-mortem.
Phase 2 - Ramp (10-90% of traffic): Same as Phase 1, but also disable shadow reads on Truto to avoid amplifying the incident. File a support ticket with Truto with the offending request IDs so their team can help diagnose in parallel.
Phase 3 - Full cutover, Apideck still provisioned: Route affected tenants back to Apideck by flag. Because Apideck's tokens may have gone stale during the Truto-primary window (HubSpot, Xero, and other single-use-refresh providers), expect some accounts to require re-auth on rollback. Have the customer communication template ready.
Phase 4 - Apideck deprovisioned: No clean rollback. This is why you keep Apideck active for at least 14 days after full cutover. If you find yourself here, the recovery is a forward-fix, not a rollback. Focus on isolating the affected tenants and issuing targeted re-auth flows.
Per-Provider and Per-Account Granularity
Your feature flag must support three levels of granularity:
- Global kill switch. Route all traffic back to Apideck instantly.
- Per-integration flag. If only Salesforce is broken, route Salesforce back to Apideck while keeping HubSpot on Truto.
- Per-tenant flag. If a specific enterprise account is affected, roll back just their traffic without impacting other tenants.
If your current flag system only supports global on/off, add granularity before you start Cohort 1. Retrofitting during an incident is not a plan.
Support Playbook
Your support team needs a decision tree, not a wiki page. Pin this in your incident channel:
- Customer reports "integration disconnected":
- Check the integrated account status in Truto. If
needs_reauth, send the personalized reconnect link. - If
activebut calls are failing, checklast_errorandratelimit-remainingon recent responses.
- Check the integrated account status in Truto. If
- Customer reports "wrong data" or "missing fields":
- Pull the raw provider response and the Truto normalized response for the same record.
- If a field is missing, it is almost always a JSONata mapping issue. File a ticket tagged
mapping.
- Customer reports "sync is slow":
- Confirm they are not hitting
ratelimit-remaining: 0. If so, this is upstream throttling, not a Truto issue. - Check whether the tenant is on a provider with polling (no native webhooks). Polling intervals are configurable.
- Confirm they are not hitting
- Customer reports "I got logged out of my integration":
- This should not happen with a clean migration. If it does, treat as a Sev-2. Check whether the OAuth app configuration changed during cutover or whether Apideck's background workers rotated the refresh token post-import.
- Customer reports "webhooks stopped firing":
- Confirm the Truto webhook subscription is active for their tenant. Check delivery logs for 4xx/5xx from your endpoint.
- If the provider is a polling-only integration, confirm the polling schedule is active.
Escalation Path
- Sev-3: Single account affected, workaround available. Support handles, engineer on-call notified async.
- Sev-2: Multiple accounts on a single provider, or one enterprise account fully broken. Page the migration lead.
- Sev-1: Widespread outage across providers or tenants. Page engineering leadership and initiate global rollback per Phase 1/2 procedures.
Monitoring, Alerts, and Post-Cutover Verification
You cannot fly blind through a migration. Wire up these signals before the first cutover.
Metrics to Track
Per integrated account:
- Status (
active,needs_reauth,disabled) - Last successful refresh timestamp
- Consecutive refresh failures
- Time since last successful upstream call
Per integration (provider):
- Refresh success rate (rolling 1h and 24h)
- API call error rate broken down by status code
- p50/p95/p99 latency for read and write operations
- Rate limit hit rate (percentage of requests receiving 429)
Platform-wide:
- Total accounts by status
- Webhook delivery success rate
- Diff rate from shadow reads (during parallel run)
- Feature flag rollout percentage per cohort
Alerts to Configure
- Refresh failure spike: Any single integration's refresh failure rate crosses 10% in a 30-minute window. Page on-call.
- Individual account stuck: A single account has 3+ consecutive refresh failures. Ticket, not page.
needs_reauthcliff: More than 5 accounts enterneeds_reauthin a 15-minute window. Page on-call - this usually indicates an OAuth app misconfiguration or a scope mismatch.- Webhook backlog: Webhook delivery lag exceeds 5 minutes. Ticket.
- Rate limit saturation: Any tenant hits 90% of their upstream rate limit for 3 consecutive intervals. Notify the account owner.
- Shadow-read diff rate spike: Diff rate crosses 1% for any tenant. Ticket tagged
mapping.
Subscribe to Truto's webhook events for account-level signals so you can react without polling:
integrated_account:authentication_error- the account needs re-authintegrated_account:refresh_success- useful for detecting the first successful refresh post-importintegrated_account:refresh_failure- transient or persistent, checklast_error
Post-Cutover Verification
24 hours after cutover:
- Every imported OAuth account has completed at least one successful proactive refresh.
- Zero unexplained
needs_reauthtransitions. - API error rate is within 20% of the pre-migration baseline.
- No customer-reported incidents attributable to the migration.
7 days after cutover:
- Every account has refreshed at least twice (confirms the refresh cycle is stable).
- Shadow-read diff rate is below 0.1% (or shadow reads are turned off if diffs are clean).
- Webhook delivery rate matches pre-migration baseline.
- Support ticket volume attributable to integrations has returned to baseline.
30 days after cutover:
- Full month of proactive refresh data confirms no drift.
- You are ready to deprovision Apideck. Revoke API keys, cancel subscriptions, remove Apideck redirect URIs from provider OAuth apps, and archive exported credentials in cold storage per your retention policy.
Customer Communication Template
For accounts that require re-authentication (Path B connections, expired refresh tokens, non-exportable credentials), a clear communication plan reduces support load and preserves trust. Send these on a schedule, not all at once.
T-7 days: heads-up email
Subject: Upcoming maintenance to your [Provider] integration
Hi [Name],
We are upgrading the infrastructure that powers your [Provider] integration with [Product] over the next two weeks. For most customers this change is invisible. For your account, we need a one-time reconnection because of how your original authorization was set up.
What you need to do: On [Date], click the "Reconnect" button in [Product] > Settings > Integrations. The flow takes under a minute and does not require IT approval if you originally granted access yourself.
What happens if you do not reconnect: Data sync for [Provider] will pause until you reconnect. No data is lost, and historical records remain available.
Reply to this email if you would like a 15-minute call with our team to walk through it.
T-24 hours: reminder with direct link
Subject: Your [Provider] integration needs a quick reconnect tomorrow
Direct reconnect link: [personalized URL] Estimated time: under 60 seconds.
T+0: cutover notice (only if you need to pause syncs)
Subject: [Provider] sync paused - reconnect to resume
Your [Provider] sync is paused pending reconnection. Click here to reconnect: [personalized URL]. Sync will resume within 5 minutes of reconnection.
T+72 hours: escalation for non-responders
Subject: Action needed: [Provider] integration still disconnected
Your [Provider] integration has been disconnected for 3 days. This means [specific business impact, e.g., new leads are not syncing to your CRM]. Reconnect here: [personalized URL], or reply to schedule a call.
For enterprise accounts (>$50k ACV or named accounts), skip the email cadence and assign a CSM to walk them through it live. The 30 minutes of white-glove work is cheaper than a churn conversation.
Security and Compliance Checklist for Token Transfer
Moving OAuth tokens between platforms means handling your customers' credentials in transit. Treat this with the same rigor as a database migration involving PII.
In Transit:
- All API calls to Apideck's Vault API and Truto's API are over HTTPS/TLS 1.2+. Do not log raw token values to stdout, application logs, or error tracking services (Sentry, Datadog, etc.) during the migration.
- If you write exported tokens to a file for batch processing, encrypt the file at rest. Use GPG or an equivalent tool. Delete the file immediately after import.
- Never commit tokens to version control, even temporarily. Use environment variables or a secrets manager (e.g., HashiCorp Vault, AWS Secrets Manager) as the intermediary.
At Rest in Truto:
- Truto encrypts sensitive fields - including
access_token,refresh_token,api_key, andclient_secret- at rest using AES-GCM encryption. These fields are stored in a separate encrypted column and are masked when listing integrated accounts via the API. - Full token values are only decrypted in-memory when making outbound API calls to the third-party provider. They are never returned in API responses to your application.
Audit trail:
- Record which tokens were exported, when, and by whom. Record when each token was imported into Truto and when the first successful refresh occurred.
- After migration, confirm that the old platform (Apideck) no longer holds active refresh tokens for your connections. Revoking tokens from the provider side is the most thorough approach, but only do this after the new platform has its own valid tokens.
Provider-specific considerations:
- Salesforce rotates refresh tokens by default. If both Apideck and Truto attempt to refresh the same token, the second call will fail with
invalid_grant. Coordinate your cutover to avoid this. - Google refresh tokens do not expire unless the user revokes access, but they are subject to a limit of 50 outstanding tokens per user per OAuth app. Migrating to a new OAuth app consumes one of these slots.
- HubSpot refresh tokens are single-use. The moment either platform refreshes the token, the old refresh token is dead. This makes the cutover timing especially important.
- QuickBooks requires the
realm_id(company ID) as part of every API request. Make sure this metadata is included in your export - without it, the tokens are useless.
Troubleshooting Common Failure Modes
Even well-planned migrations hit edge cases. Here are the most common failure modes and how to resolve them.
invalid_grant on First Refresh
Cause: The most common failure. Either the refresh token was already rotated by Apideck between export and import, or you are using a different client_id / client_secret than the one that issued the token.
Fix: Verify that the OAuth app credentials configured in Truto exactly match the ones used in Apideck. If the client_id doesn't match, the provider will reject the refresh. If the token was rotated, you need to either re-export from Apideck (if you haven't deprovisioned yet) or re-authenticate that specific user.
needs_reauth Status After Import
Truto marks an integrated account as needs_reauth when a token refresh fails. After bulk import, you may see some accounts immediately enter this state.
Fix: Check Truto's last_error field on the integrated account. Common causes:
- Missing provider-specific metadata (e.g.,
realm_idfor QuickBooks,instance_urlfor Salesforce) - Scopes mismatch - the token was issued with scopes that the new OAuth app doesn't request
- The user revoked access on the provider side
For each needs_reauth account, the resolution is almost always a targeted re-authentication for that specific user. Truto fires an integrated_account:authentication_error webhook event, so you can automate sending the user a reconnection prompt.
API Key Connections Return 401
Cause: API keys extracted from Apideck may have been masked or truncated in the export.
Fix: Verify the full API key value is present in the Truto integrated account context. Make a test request via Truto's proxy endpoint. If the key is invalid, the user will need to regenerate it from the provider's settings and update the connection.
Response Shape Mismatches
Cause: Your JSONata mappings don't perfectly match Apideck's response format for a specific provider.
Fix: Run shadow reads during the canary phase. For every Truto response, compare it field-by-field against the equivalent Apideck response. Common differences:
- Date format variations (
ISO 8601vs. Unix timestamps) - Null handling (Apideck may return empty strings where Truto returns
null, or vice versa) - Nested array structures for multi-value fields like emails and phone numbers
- Pagination cursor format differences
Fix these in the JSONata mapping configuration - no code deployment needed.
Rate Limit Spikes During Migration
Cause: During the shadow-read phase, you are making double the API calls (one to Apideck, one to Truto), which doubles your rate limit consumption against the upstream provider.
Fix: Run shadow reads at reduced traffic (e.g., 10% of requests). Use the ratelimit-remaining header from Truto to throttle your shadow-read traffic dynamically.
The Trade-Offs You Should Know About
Let's be direct about what this migration costs you:
- Engineering time: Plan for 2 to 4 weeks of dedicated engineering effort for a team with 50+ linked accounts across 5+ providers. The token import is fast; writing and testing JSONata mappings to match Apideck's exact response shape is the slow part.
- Rate limit responsibility shifts to you. On Apideck, rate limit handling was opaque. On Truto, you get standardized headers and must implement your own backoff. This is more control, but also more code.
- Provider-specific quirks don't disappear. Salesforce's SOQL-based filtering, HubSpot's association API, QuickBooks'
realm_idrequirement - these all still exist regardless of which unified API sits in front of them. A new platform doesn't make bad vendor APIs better.
The upside: you get custom field mappings on every plan, proactive token refresh that catches failures before users do, and a declarative architecture where adding a new integration is a data operation, not a code deployment.
What Comes Next
If you are at the point where Apideck's constraints are blocking enterprise deals or causing compliance gaps, the migration is worth doing. The average company uses over 100 SaaS apps, and your customers expect every one of them to integrate with your product.
Check your OAuth app ownership first, export your tokens before deprovisioning anything, and use declarative schema mappings to preserve your frontend contract. By understanding the mechanics of OAuth token portability and taking control of your own rate limit queues, you can upgrade your integration infrastructure without ever asking a customer to hit "Reconnect."
FAQ
- Can I migrate from Apideck to another unified API without re-authenticating my users?
- Yes, if you brought your own OAuth app credentials (your own client_id and client_secret) when setting up integrations in Apideck. The tokens were issued to your OAuth application, so any platform holding those same credentials can refresh them. If you used Apideck's managed OAuth app, those specific connections will require re-authentication.
- Does Apideck expose raw OAuth tokens through its Vault API?
- No. Apideck's Vault API has an import endpoint for bringing tokens into Apideck, but no corresponding export endpoint that returns raw access_token or refresh_token values. The standard Get Connections endpoint returns connection metadata but not the actual tokens. You will need to contact Apideck support for a credential export.
- How do I check if I own the OAuth app used in Apideck?
- Log into the Apideck dashboard, navigate to Configuration, select your connector, and look for the OAuth credentials section. If it shows 'Use your client credentials' with your own values, you own the app. You can also check your provider's developer console to see if the OAuth app with redirect URI https://unify.apideck.com/vault/callback belongs to your organization.
- What happens to API key connections during migration from Apideck?
- API key connections are simpler to migrate than OAuth. You can retrieve connection settings through Apideck's Vault API GET endpoint, extract the API key and configuration values, and import them into Truto's generic context. There is no token refresh cycle or OAuth app dependency to worry about.
- How does Truto handle token refresh for migrated OAuth connections?
- Truto uses proactive token refresh. When you import an OAuth token, the platform reads the expires_at timestamp and schedules a background task to refresh the token 60-180 seconds before it expires. If the refresh fails, the account is marked as needs_reauth and a webhook event is fired so you can prompt only the affected user to reconnect.
- What is the rollback strategy if the Apideck migration fails?
- During canary testing, keep Apideck connections active and use a feature flag to route traffic. If issues arise, flip the flag to route back to Apideck instantly. Only deprovision Apideck connections after Truto has successfully refreshed every token at least once. Keep your Apideck subscription active for at least 2 weeks after full cutover.