Instantly Common Errors
Overview
Diagnostic reference for Instantly API v2 errors. Covers HTTP status codes, campaign state errors, account health issues, lead operation failures, and webhook delivery problems.
Prerequisites
- Completed
instantly-install-auth setup
- Access to Instantly dashboard for verification
- API key with appropriate scopes
HTTP Status Codes
| Status |
Meaning |
Common Cause |
Fix |
400 |
Bad Request |
Malformed JSON, invalid field values |
Validate request body against schema |
401 |
Unauthorized |
Invalid, expired, or revoked API key |
Regenerate key in Settings > Integrations |
403 |
Forbidden |
API key missing required scope |
Create key with correct scope (e.g., campaigns:all) |
404 |
Not Found |
Invalid campaign/lead/account ID |
Verify resource exists with a GET call first |
422 |
Unprocessable Entity |
Business logic violation (duplicate lead, invalid state) |
Check error body for details |
429 |
Too Many Requests |
Rate limit exceeded |
Implement exponential backoff (see below) |
500 |
Internal Server Error |
Instantly server issue |
Retry with backoff; check status.instantly.ai |
Campaign Errors
Campaign Won't Activate (Stuck in Draft)
// Diagnosis: check campaign requirements
async function diagnoseCampaign(campaignId: string) {
const campaign = await instantly<Campaign>(`/campaigns/${campaignId}`);
const issues: string[] = [];
// Check sequences
if (!campaign.sequences?.length || !campaign.sequences[0]?.steps?.length) {
issues.push("No email sequences — add at least one step with subject + body");
}
// Check schedule
if (!campaign.campaign_schedule?.schedules?.length) {
issues.push("No sending schedule — add schedule with timing and days");
}
// Check sending accounts
const mappings = await instantly(`/account-campaign-mappings/${campaignId}`);
if (!Array.isArray(mappings) || mappings.length === 0) {
issues.push("No sending accounts assigned — add via PATCH /campaigns/{id} with email_list");
}
// Check for leads
const leads = await instantly<Lead[]>("/leads/list", {
method: "POST",
body: JSON.stringify({ campaign: campaignId, limit: 1 }),
});
if (leads.length === 0) {
issues.push("No leads — add leads via POST /leads");
}
if (issues.length === 0) {
console.log("Campaign looks ready to activate");
} else {
console.log("Issues preventing activation:");
issues.forEach((i) => console.log(` - ${i}`));
}
}
Campaign Status Codes
| Status |
Label |
Meaning |
0 |
Draft |
Not yet activated |
1 |
Active |
Currently sending |
2 |
Paused |
Manually paused |
3 |
Completed |
All leads processed |
4 |
Running Subsequences |
Main sequence done, subsequences active |
-1 |
Accounts Unhealthy |
Sending accounts have SMTP/IMAP errors |
-2 |
Bounce Protect |
Auto-paused due to high bounce rate |
-99 |
Suspended |
Account-level suspension |
Fix: Accounts Unhealthy (-1)
async function fixUnhealthyAccounts(campaignId: string) {
// 1. Get accounts assigned to campaign
const accounts = await instantly<Account[]>("/accounts?limit=100");
// 2. Test vitals for each
const vitals = await instantly("/accounts/test/vitals", {
method: "POST",
body: JSON.stringify({ accounts: accounts.map((a) => a.email) }),
});
// 3. Identify and fix broken accounts
for (const v of vitals as any[]) {
if (v.smtp_status !== "ok" || v.imap_status !== "ok") {
console.log(`BROKEN: ${v.email} — SMTP=${v.smtp_status}, IMAP=${v.imap_status}`);
// Pause the broken account
await instantly(`/accounts/${encodeURIComponent(v.email)}/pause`, { method: "POST" });
console.log(` Paused ${v.email}. Fix credentials, then resume.`);
}
}
}
Lead Errors
Duplicate Lead (422)
// Prevent duplicates by setting skip flags
await instantly("/leads", {
method: "POST",
body: JSON.stringify({
campaign: campaignId,
email: "user@example.com",
first_name: "Jane",
skip_if_in_workspace: true, // skip if email exists anywhere in workspace
skip_if_in_campaign: true, // skip if already in this campaign
}),
});
Lead Status Reference
| Status |
Label |
Description |
1 |
Active |
Eligible to receive emails |
2 |
Paused |
Manually paused |
3 |
Completed |
All sequence steps sent |
-1 |
Bounced |
Email bounced |
-2 |
Unsubscribed |
Lead unsubscribed |
-3 |
Skipped |
Skipped (blocklist, duplicate, etc.) |
Rate Limit Handling
async function withBackoff<T>(
operation: () => Promise<T>,
maxRetries = 5
): Promise<T> {
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
return await operation();
} catch (err: any) {
if (err.status === 429 && attempt < maxRetries) {
const wait = Math.pow(2, attempt) * 1000;
console.warn(`429 Rate Limited. Waiting ${wait}ms (attempt ${attempt + 1}/${maxRetries})`);
await new Promise((r) => setTimeout(r, wait));
continue;
}
throw err;
}
}
throw new Error("Unreachable");
}
Webhook Errors
| Issue |
Diagnostic |
Fix |
| Events not delivered |
Check webhook status: GET /webhooks |
Webhook may be paused — resume with POST /webhooks/{id}/resume |
| Wrong event format |
Compare to expected schema |
Ensure event_type matches: email_sent, reply_received, etc. |
| Delivery failures |
Check GET /webhook-events/summary |
Fix target URL, ensure 2xx response within 30s |
| Retries exhausting |
Instantly retries 3x in 30s |
Return 200 immediately, process async |
Quick Diagnostic Script
set -euo pipefail
echo "=== Instantly Health Check ==="
# Test auth
curl -s -o /dev/null -w "Auth: HTTP %{http_code}\n" \
https://api.instantly.ai/api/v2/campaigns?limit=1 \
-H "Authorization: Bearer $INSTANTLY_API_KEY"
# Count campaigns by status
curl -s https://api.instantly.ai/api/v2/campaigns?limit=100 \
-H "Authorization: Bearer $INSTANTLY_API_KEY" | \
jq 'group_by(.status) | map({status: .[0].status, count: length})'
# Check account health
curl -s https://api.instantly.ai/api/v2/accounts?limit=100 \
-H "Authorization: Bearer $INSTANTLY_API_KEY" | \
jq '[.[] | {email, status, warmup_status}] | .[:5]'
Error Handling
| Error |
Cause |
Solution |
401 after key rotation |
Old key cached |
Restart app / clear env cache |
403 on campaign activate |
Missing campaigns:update scope |
Regenerate API key with correct scopes |
422 duplicate lead |
Lead already in workspace |
Use skip_if_in_workspace: true |
Campaign -2 bounce protect |
Bounce rate >5% |
Clean lead list, verify emails before import |
| Warmup health dropping |
Too many campaign emails too soon |
Reduce daily_limit, extend warmup period |
Instructions
- Classify identity, sender, consent, suppression, recipient validation, quota, schedule, delivery, and webhook errors before changing configuration.
- Reproduce once with synthetic recipients and a draft-only campaign, capturing only status, latency, quota, and opaque IDs.
- Check sender scope, consent, suppression, campaign state, and quota in that order.
- Apply one reversible change at a time and escalate a redacted bundle when the error persists.
Output
Return error class, correlation ID, campaign scope, consent/suppression state, probe outcome, remediation attempted, and next owner. Do not include addresses, copy, sender details, or credentials.
Examples
status=429; campaign=sandbox-only; correlation=send-opaque-11; action=bounded-backoff; consent=pass; suppression=pass; sends=0 supports a safe handoff.
Resources
Next Steps
For structured debugging, see instantly-debug-bundle.
1---2name: instantly-common-errors3description: Diagnose and fix Instantly.ai API v2 common errors and exceptions. Use when encountering Instantly errors, debugging failed requests, or troubleshooting campaign/account/lead issues. Trigger with phrases like "instantly error", "instantly 401", "instantly 429", "instantly api failed", "instantly debug", "instantly troubleshoot".4license: MIT5---6# Instantly Common Errors
7
8## Overview
9
10Diagnostic reference for Instantly API v2 errors. Covers HTTP status codes, campaign state errors, account health issues, lead operation failures, and webhook delivery problems.
11
12## Prerequisites
13
14- Completed `instantly-install-auth` setup
15- Access to Instantly dashboard for verification
16- API key with appropriate scopes
17
18## HTTP Status Codes
19
20| Status | Meaning | Common Cause | Fix |
21|--------|---------|-------------|-----|
22| `400` | Bad Request | Malformed JSON, invalid field values | Validate request body against schema |
23| `401` | Unauthorized | Invalid, expired, or revoked API key | Regenerate key in Settings > Integrations |
24| `403` | Forbidden | API key missing required scope | Create key with correct scope (e.g., `campaigns:all`) |
25| `404` | Not Found | Invalid campaign/lead/account ID | Verify resource exists with a GET call first |
26| `422` | Unprocessable Entity | Business logic violation (duplicate lead, invalid state) | Check error body for details |
27| `429` | Too Many Requests | Rate limit exceeded | Implement exponential backoff (see below) |
28| `500` | Internal Server Error | Instantly server issue | Retry with backoff; check status.instantly.ai |
29
30## Campaign Errors
31
32### Campaign Won't Activate (Stuck in Draft)
33
34```typescript
35// Diagnosis: check campaign requirements
36async function diagnoseCampaign(campaignId: string) {
37 const campaign = await instantly<Campaign>(`/campaigns/${campaignId}`);
38
39 const issues: string[] = [];
40
41 // Check sequences
42 if (!campaign.sequences?.length || !campaign.sequences[0]?.steps?.length) {
43 issues.push("No email sequences — add at least one step with subject + body");
44 }
45
46 // Check schedule
47 if (!campaign.campaign_schedule?.schedules?.length) {
48 issues.push("No sending schedule — add schedule with timing and days");
49 }
50
51 // Check sending accounts
52 const mappings = await instantly(`/account-campaign-mappings/${campaignId}`);
53 if (!Array.isArray(mappings) || mappings.length === 0) {
54 issues.push("No sending accounts assigned — add via PATCH /campaigns/{id} with email_list");
55 }
56
57 // Check for leads
58 const leads = await instantly<Lead[]>("/leads/list", {
59 method: "POST",
60 body: JSON.stringify({ campaign: campaignId, limit: 1 }),
61 });
62 if (leads.length === 0) {
63 issues.push("No leads — add leads via POST /leads");
64 }
65
66 if (issues.length === 0) {
67 console.log("Campaign looks ready to activate");
68 } else {
69 console.log("Issues preventing activation:");
70 issues.forEach((i) => console.log(` - ${i}`));
71 }
72}
73```
74
75### Campaign Status Codes
76
77| Status | Label | Meaning |
78|--------|-------|---------|
79| `0` | Draft | Not yet activated |
80| `1` | Active | Currently sending |
81| `2` | Paused | Manually paused |
82| `3` | Completed | All leads processed |
83| `4` | Running Subsequences | Main sequence done, subsequences active |
84| `-1` | Accounts Unhealthy | Sending accounts have SMTP/IMAP errors |
85| `-2` | Bounce Protect | Auto-paused due to high bounce rate |
86| `-99` | Suspended | Account-level suspension |
87
88### Fix: Accounts Unhealthy (-1)
89
90```typescript
91async function fixUnhealthyAccounts(campaignId: string) {
92 // 1. Get accounts assigned to campaign
93 const accounts = await instantly<Account[]>("/accounts?limit=100");
94
95 // 2. Test vitals for each
96 const vitals = await instantly("/accounts/test/vitals", {
97 method: "POST",
98 body: JSON.stringify({ accounts: accounts.map((a) => a.email) }),
99 });
100
101 // 3. Identify and fix broken accounts
102 for (const v of vitals as any[]) {
103 if (v.smtp_status !== "ok" || v.imap_status !== "ok") {
104 console.log(`BROKEN: ${v.email} — SMTP=${v.smtp_status}, IMAP=${v.imap_status}`);
105 // Pause the broken account
106 await instantly(`/accounts/${encodeURIComponent(v.email)}/pause`, { method: "POST" });
107 console.log(` Paused ${v.email}. Fix credentials, then resume.`);
108 }
109 }
110}
111```
112
113## Lead Errors
114
115### Duplicate Lead (422)
116
117```typescript
118// Prevent duplicates by setting skip flags
119await instantly("/leads", {
120 method: "POST",
121 body: JSON.stringify({
122 campaign: campaignId,
123 email: "user@example.com",
124 first_name: "Jane",
125 skip_if_in_workspace: true, // skip if email exists anywhere in workspace
126 skip_if_in_campaign: true, // skip if already in this campaign
127 }),
128});
129```
130
131### Lead Status Reference
132
133| Status | Label | Description |
134|--------|-------|-------------|
135| `1` | Active | Eligible to receive emails |
136| `2` | Paused | Manually paused |
137| `3` | Completed | All sequence steps sent |
138| `-1` | Bounced | Email bounced |
139| `-2` | Unsubscribed | Lead unsubscribed |
140| `-3` | Skipped | Skipped (blocklist, duplicate, etc.) |
141
142## Rate Limit Handling
143
144```typescript
145async function withBackoff<T>(
146 operation: () => Promise<T>,
147 maxRetries = 5
148): Promise<T> {
149 for (let attempt = 0; attempt <= maxRetries; attempt++) {
150 try {
151 return await operation();
152 } catch (err: any) {
153 if (err.status === 429 && attempt < maxRetries) {
154 const wait = Math.pow(2, attempt) * 1000;
155 console.warn(`429 Rate Limited. Waiting ${wait}ms (attempt ${attempt + 1}/${maxRetries})`);
156 await new Promise((r) => setTimeout(r, wait));
157 continue;
158 }
159 throw err;
160 }
161 }
162 throw new Error("Unreachable");
163}
164```
165
166## Webhook Errors
167
168| Issue | Diagnostic | Fix |
169|-------|-----------|-----|
170| Events not delivered | Check webhook status: `GET /webhooks` | Webhook may be paused — resume with `POST /webhooks/{id}/resume` |
171| Wrong event format | Compare to expected schema | Ensure `event_type` matches: `email_sent`, `reply_received`, etc. |
172| Delivery failures | Check `GET /webhook-events/summary` | Fix target URL, ensure 2xx response within 30s |
173| Retries exhausting | Instantly retries 3x in 30s | Return 200 immediately, process async |
174
175## Quick Diagnostic Script
176
177```bash
178set -euo pipefail
179echo "=== Instantly Health Check ==="
180
181# Test auth
182curl -s -o /dev/null -w "Auth: HTTP %{http_code}\n" \
183 https://api.instantly.ai/api/v2/campaigns?limit=1 \
184 -H "Authorization: Bearer $INSTANTLY_API_KEY"
185
186# Count campaigns by status
187curl -s https://api.instantly.ai/api/v2/campaigns?limit=100 \
188 -H "Authorization: Bearer $INSTANTLY_API_KEY" | \
189 jq 'group_by(.status) | map({status: .[0].status, count: length})'
190
191# Check account health
192curl -s https://api.instantly.ai/api/v2/accounts?limit=100 \
193 -H "Authorization: Bearer $INSTANTLY_API_KEY" | \
194 jq '[.[] | {email, status, warmup_status}] | .[:5]'
195```
196
197## Error Handling
198
199| Error | Cause | Solution |
200|-------|-------|----------|
201| `401` after key rotation | Old key cached | Restart app / clear env cache |
202| `403` on campaign activate | Missing `campaigns:update` scope | Regenerate API key with correct scopes |
203| `422` duplicate lead | Lead already in workspace | Use `skip_if_in_workspace: true` |
204| Campaign `-2` bounce protect | Bounce rate >5% | Clean lead list, verify emails before import |
205| Warmup health dropping | Too many campaign emails too soon | Reduce daily_limit, extend warmup period |
206
207## Instructions
208
2091. Classify identity, sender, consent, suppression, recipient validation, quota, schedule, delivery, and webhook errors before changing configuration.
2102. Reproduce once with synthetic recipients and a draft-only campaign, capturing only status, latency, quota, and opaque IDs.
2113. Check sender scope, consent, suppression, campaign state, and quota in that order.
2124. Apply one reversible change at a time and escalate a redacted bundle when the error persists.
213
214## Output
215
216Return error class, correlation ID, campaign scope, consent/suppression state, probe outcome, remediation attempted, and next owner. Do not include addresses, copy, sender details, or credentials.
217
218## Examples
219
220`status=429; campaign=sandbox-only; correlation=send-opaque-11; action=bounded-backoff; consent=pass; suppression=pass; sends=0` supports a safe handoff.
221
222## Resources
223
224- [Instantly API v2 Docs](https://developer.instantly.ai/)
225- [Instantly Help Center](https://help.instantly.ai)
226- [API Schemas](https://developer.instantly.ai/api/v2/schemas)
227
228## Next Steps
229
230For structured debugging, see `instantly-debug-bundle`.