Claude-code-plugins-plus-skills salesforce-rate-limits
install
source · Clone the upstream repo
git clone https://github.com/jeremylongshore/claude-code-plugins-plus-skills
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/jeremylongshore/claude-code-plugins-plus-skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/plugins/saas-packs/salesforce-pack/skills/salesforce-rate-limits" ~/.claude/skills/jeremylongshore-claude-code-plugins-plus-skills-salesforce-rate-limits && rm -rf "$T"
manifest:
plugins/saas-packs/salesforce-pack/skills/salesforce-rate-limits/SKILL.mdsource content
Salesforce Rate Limits
Overview
Handle Salesforce API limits gracefully. Salesforce uses a 24-hour rolling limit (not per-minute), plus concurrent request limits and Bulk API quotas.
Prerequisites
- jsforce connection configured
- Understanding of your org's edition and license count
- Access to Setup > Company Information
Instructions
Step 1: Understand Salesforce API Limits
| Limit Type | Calculation | Example (Enterprise, 50 users) |
|---|---|---|
| Daily API Requests | Base + (per-user * licenses) | 100,000 + (1,000 * 50) = 150,000 |
| Concurrent API (long-running) | 25 per org | 25 |
| Bulk API 2.0 Ingest Jobs | 15,000/day | 15,000 |
| Bulk API 2.0 Query Jobs | 15,000/day | 15,000 |
| Composite Subrequests | 25 per call | 25 |
| SOQL Query Row Limit | 50,000 per query | 50,000 |
| sObject Collections | 200 records per call | 200 |
Key difference from most SaaS APIs: Salesforce limits are per-org, not per-user or per-key. All integrations sharing the same org share the same pool.
Step 2: Monitor Remaining Quota
import { getConnection } from './salesforce/connection'; async function checkApiLimits(): Promise<{ used: number; remaining: number; max: number; percentUsed: number; }> { const conn = await getConnection(); const limits = await conn.request('/services/data/v59.0/limits/'); const daily = limits.DailyApiRequests; const used = daily.Max - daily.Remaining; const percentUsed = (used / daily.Max) * 100; return { used, remaining: daily.Remaining, max: daily.Max, percentUsed: Math.round(percentUsed * 10) / 10, }; } // Also available in every REST API response header: // Sforce-Limit-Info: api-usage=135/150000
Step 3: Implement Backoff for REQUEST_LIMIT_EXCEEDED
async function withSalesforceRetry<T>( operation: () => Promise<T>, config = { maxRetries: 5, baseDelayMs: 2000, maxDelayMs: 60000 } ): Promise<T> { for (let attempt = 0; attempt <= config.maxRetries; attempt++) { try { return await operation(); } catch (error: any) { const errorCode = error.errorCode || error.name; // Only retry on transient/limit errors const retryable = [ 'REQUEST_LIMIT_EXCEEDED', 'SERVER_UNAVAILABLE', 'UNABLE_TO_LOCK_ROW', ]; if (attempt === config.maxRetries || !retryable.includes(errorCode)) { throw error; } // Exponential backoff with jitter const exponentialDelay = config.baseDelayMs * Math.pow(2, attempt); const jitter = Math.random() * 1000; const delay = Math.min(exponentialDelay + jitter, config.maxDelayMs); console.warn(`${errorCode}: retry ${attempt + 1}/${config.maxRetries} in ${Math.round(delay)}ms`); await new Promise(r => setTimeout(r, delay)); } } throw new Error('Unreachable'); }
Step 4: Pre-Flight Limit Check
class SalesforceQuotaGuard { private warningThreshold = 0.8; // Warn at 80% private blockThreshold = 0.95; // Block at 95% async canMakeRequest(estimatedCalls: number = 1): Promise<{ allowed: boolean; remaining: number; reason?: string; }> { const { remaining, max, percentUsed } = await checkApiLimits(); if (remaining < estimatedCalls) { return { allowed: false, remaining, reason: `Only ${remaining} API calls remain (need ${estimatedCalls})`, }; } if (percentUsed / 100 >= this.blockThreshold) { return { allowed: false, remaining, reason: `API usage at ${percentUsed}% — blocking to preserve quota`, }; } if (percentUsed / 100 >= this.warningThreshold) { console.warn(`API usage at ${percentUsed}% (${remaining} remaining)`); } return { allowed: true, remaining }; } }
Step 5: Reduce API Call Count
// STRATEGY 1: Use sObject Collections (1 call = 200 records) // Instead of 200 individual creates... const records = contacts.map(c => ({ FirstName: c.first, LastName: c.last, Email: c.email })); await conn.sobject('Contact').create(records); // 1 API call, not 200 // STRATEGY 2: Use Composite API (1 call = 25 operations) // See salesforce-core-workflow-b // STRATEGY 3: Use Bulk API for 10K+ records (1 job = unlimited records) // Bulk API has its own separate limit pool // STRATEGY 4: Cache describe calls — metadata rarely changes const describeCache = new Map<string, any>(); async function cachedDescribe(sObjectType: string) { if (!describeCache.has(sObjectType)) { describeCache.set(sObjectType, await conn.sobject(sObjectType).describe()); } return describeCache.get(sObjectType); } // STRATEGY 5: Use queryMore for pagination (doesn't count as extra API call) let result = await conn.query('SELECT Id, Name FROM Contact'); while (!result.done) { result = await conn.queryMore(result.nextRecordsUrl!); // Process result.records }
Output
- API limit monitoring with threshold alerts
- Automatic retry with exponential backoff for limit errors
- Pre-flight quota checks before batch operations
- Strategies to reduce API call consumption
Error Handling
| Error Code | HTTP Status | Meaning | Action |
|---|---|---|---|
| 403 | Daily API limit exceeded | Wait for 24hr window to reset |
| 403 | Too many concurrent requests | Queue and throttle |
| 503 | Salesforce temporarily down | Retry with backoff |
| 409-equivalent | Record contention | Retry with backoff |
Resources
Next Steps
For security configuration, see
salesforce-security-basics.