--- name: instantly-common-errors description: '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". ' allowed-tools: Read, Write, Edit, Bash(curl:*), Grep version: 1.12.0 license: MIT author: Jeremy Longshore tags: - saas - instantly - debugging - errors compatibility: Designed for Claude Code, also compatible with Codex and OpenClaw --- # 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) ```typescript // Diagnosis: check campaign requirements async function diagnoseCampaign(campaignId: string) { const campaign = await instantly(`/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("/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) ```typescript async function fixUnhealthyAccounts(campaignId: string) { // 1. Get accounts assigned to campaign const accounts = await instantly("/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) ```typescript // 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 ```typescript async function withBackoff( operation: () => Promise, maxRetries = 5 ): Promise { 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 ```bash 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 | ## Resources - [Instantly API v2 Docs](https://developer.instantly.ai/) - [Instantly Help Center](https://help.instantly.ai) - [API Schemas](https://developer.instantly.ai/api/v2/schemas) ## Next Steps For structured debugging, see `instantly-debug-bundle`.