Skip to main content
instantly-upgrade-migration Analyze, plan, and execute Instantly SDK upgrades with breaking change detection.
Use when upgrading Instantly SDK versions, detecting deprecations,
or migrating to new API versions.
Trigger with phrases like "upgrade instantly", "instantly migration",
"instantly breaking changes", "update instantly SDK", "analyze instantly version".
npx skills add jeremylongshore/claude-code-plugins-plus-skills --skill instantly-upgrade-migration ai automation claude-code devops mcp ai-agents
Instantly Upgrade Migration: API v1 to v2
Overview
Migrate from Instantly API v1 (deprecated January 2026) to API v2. Key changes: Bearer token auth replaces query-string API keys, REST-standard endpoints replace legacy paths, scoped API keys replace single global key, and cursor-based pagination replaces offset pagination. Existing v1 integrations via Zapier/Make continue working, but new integrations must use v2.
Prerequisites
Existing Instantly API v1 integration
Access to Instantly dashboard to generate v2 API keys
Understanding of Bearer token authentication
Migration Map
Authentication Change
// v1: API key as query parameter
// DEPRECATED — do not use
const v1Url = `https://api.instantly.ai/api/v1/campaign/list?api_key=${API_KEY}`;
// v2: Bearer token in Authorization header
const v2Response = await fetch("https://api.instantly.ai/api/v2/campaigns", {
headers: { Authorization: `Bearer ${API_KEY}` },
});
Endpoint Migration Table
Operation v1 Endpoint v2 Endpoint Method Change List campaigns GET /api/v1/campaign/list
Get campaign GET /api/v1/campaign/getGET /api/v2/campaigns/{id}Query -> Path param
Create campaign POST /api/v1/campaign/createPOST /api/v2/campaignsREST standard
Launch campaign POST /api/v1/campaign/launchPOST /api/v2/campaigns/{id}/activateNew path
Pause campaign POST /api/v1/campaign/pausePOST /api/v2/campaigns/{id}/pauseNew path
Add leads POST /api/v1/lead/addPOST /api/v2/leadsSimplified
List leads GET /api/v1/lead/listPOST /api/v2/leads/listGET -> POST
Delete leads POST /api/v1/lead/deleteDELETE /api/v2/leads/{id}REST standard
Get analytics GET /api/v1/analytics/campaignGET /api/v2/campaigns/analyticsNew path
List accounts GET /api/v1/account/listGET /api/v2/accountsSimplified
Request Body Changes // v1: Campaign creation
const v1Body = {
api_key: "your-key",
name: "Campaign Name",
// Flat structure
};
// v2: Campaign creation — structured schedule and sequences
const v2Body = {
name: "Campaign Name",
campaign_schedule: {
start_date: "2026-04-01",
schedules: [{
name: "Business Hours",
timing: { from: "09:00", to: "17:00" },
days: { "1": true, "2": true, "3": true, "4": true, "5": true, "0": false, "6": false },
timezone: "America/New_York",
}],
},
sequences: [{
steps: [{
type: "email",
delay: 0,
variants: [{ subject: "Hello {{firstName}}", body: "Hi {{firstName}}..." }],
}],
}],
};
Lead Operation Changes // v1: Add leads to campaign
const v1AddLeads = {
api_key: "your-key",
campaign_id: "campaign-uuid",
leads: [
{ email: "[email protected] ", first_name: "Jane" },
],
};
// v2: Add leads individually (POST /api/v2/leads)
const v2AddLead = {
campaign: "campaign-uuid", // "campaign_id" -> "campaign"
email: "[email protected] ",
first_name: "Jane",
skip_if_in_workspace: true, // New: deduplication control
verify_leads_on_import: true, // New: auto-verification
custom_variables: { role: "CTO" }, // New: custom fields
};
// v2: Bulk operations use POST /api/v2/leads/move for batch moves
Instructions
Step 1: Audit Existing v1 Calls set -euo pipefail
# Find all v1 API calls in your codebase
grep -rn "api/v1/" src/ --include="*.ts" --include="*.js" --include="*.py" || echo "No v1 calls found"
grep -rn "api_key=" src/ --include="*.ts" --include="*.js" --include="*.py" || echo "No query-string keys found"
Step 2: Create Migration Adapter // src/instantly-migration.ts
// Drop-in adapter that maps v1 calls to v2 endpoints
export class InstantlyV1ToV2Adapter {
private apiKey: string;
private baseUrl = "https://api.instantly.ai/api/v2";
constructor(apiKey: string) {
this.apiKey = apiKey;
}
private async request<T>(path: string, options: RequestInit = {}): Promise<T> {
const res = await fetch(`${this.baseUrl}${path}`, {
...options,
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${this.apiKey}`,
...options.headers,
},
});
if (!res.ok) throw new Error(`Instantly ${res.status}: ${await res.text()}`);
return res.json() as Promise<T>;
}
// v1: campaign/list -> v2: GET /campaigns
async listCampaigns() {
return this.request("/campaigns?limit=100");
}
// v1: campaign/get?campaign_id=X -> v2: GET /campaigns/{id}
async getCampaign(campaignId: string) {
return this.request(`/campaigns/${campaignId}`);
}
// v1: campaign/launch -> v2: POST /campaigns/{id}/activate
async launchCampaign(campaignId: string) {
return this.request(`/campaigns/${campaignId}/activate`, { method: "POST" });
}
// v1: campaign/pause -> v2: POST /campaigns/{id}/pause
async pauseCampaign(campaignId: string) {
return this.request(`/campaigns/${campaignId}/pause`, { method: "POST" });
}
// v1: lead/add (bulk) -> v2: POST /leads (one at a time)
async addLeads(campaignId: string, leads: Array<{ email: string; first_name?: string }>) {
const results = [];
for (const lead of leads) {
const result = await this.request("/leads", {
method: "POST",
body: JSON.stringify({
campaign: campaignId,
email: lead.email,
first_name: lead.first_name,
skip_if_in_workspace: true,
}),
});
results.push(result);
}
return results;
}
// v1: analytics/campaign -> v2: GET /campaigns/analytics
async getCampaignAnalytics(campaignId: string) {
return this.request(`/campaigns/analytics?id=${campaignId}`);
}
}
Step 3: Pagination Migration // v1: Offset-based (skip/limit)
// const v1 = await fetch(`/api/v1/lead/list?api_key=${key}&campaign_id=${id}&skip=100&limit=50`);
// v2: Cursor-based (starting_after)
async function* paginateV2<T extends { id: string }>(
path: string,
pageSize = 100
): AsyncGenerator<T[]> {
let startingAfter: string | undefined;
while (true) {
const qs = new URLSearchParams({ limit: String(pageSize) });
if (startingAfter) qs.set("starting_after", startingAfter);
const page = await instantly<T[]>(`${path}?${qs}`);
if (page.length === 0) break;
yield page;
startingAfter = page[page.length - 1].id;
if (page.length < pageSize) break;
}
}
Step 4: New v2 Features to Adopt // These features are v2-only — no v1 equivalent
// Scoped API keys
// POST /api/v2/api-keys — create keys with specific scopes
await instantly("/api-keys", {
method: "POST",
body: JSON.stringify({ name: "analytics-only", scopes: ["campaigns:read"] }),
});
// Subsequences (conditional follow-ups)
// POST /api/v2/subsequences
await instantly("/subsequences", {
method: "POST",
body: JSON.stringify({
parent_campaign: campaignId,
name: "Re-engage interested leads",
conditions: { crm_status: [1] }, // triggered when lead is "Interested"
}),
});
// Inbox placement testing
// POST /api/v2/inbox-placement-tests
await instantly("/inbox-placement-tests", {
method: "POST",
body: JSON.stringify({
name: "Pre-launch deliverability test",
email_subject: "Test Subject",
email_body: "Test body content",
type: 1,
}),
});
// Block list management (bulk)
// POST /api/v2/block-lists-entries/bulk-create
await instantly("/block-lists-entries/bulk-create", {
method: "POST",
body: JSON.stringify({
entries: ["competitor.com", "internal.com"],
}),
});
Migration Checklist
Error Handling Error Cause Solution 401 on v2Using v1 key format Generate new v2 Bearer token 404 on v2 pathUsing v1 endpoint path Check migration table above 422 on lead addNew validation rules in v2 Add required fields per v2 schema Missing pagination data Using skip instead of starting_after Convert to cursor pagination
Resources
Next Steps For CI/CD integration, see instantly-ci-integration.
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Set up and use 1Password CLI (op). Use when installing the CLI, enabling desktop app integration, signing in (single or multi-account), or reading/injecting/running secrets via op.
CLI to manage emails via IMAP/SMTP. Use `himalaya` to list, read, write, reply, forward, search, and organize emails from the terminal. Supports multiple accounts and message composition with MML (MIME Meta Language).
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Set up and use 1Password CLI (op). Use when installing the CLI, enabling desktop app integration, signing in (single or multi-account), or reading/injecting/running secrets via op.
CLI to manage emails via IMAP/SMTP. Use `himalaya` to list, read, write, reply, forward, search, and organize emails from the terminal. Supports multiple accounts and message composition with MML (MIME Meta Language).