Hitting API limits, running into timeouts, or watching batch jobs choke on large data volumes? These aren’t edge cases—they’re common bottlenecks in growing Salesforce environments. Whether you’re managing high-volume integrations, optimising Apex for scale, or troubleshooting slow SOQL queries, API performance is critical to system reliability. In a platform governed by strict limits, efficiency isn’t optional—it’s architectural. Let’s discuss 10 high-impact strategies to optimise your Salesforce API usage, complete with real Apex code and use cases. From leveraging Bulk API and Composite API to caching and asynchronous processing, you’ll walk away with practical techniques to improve throughput, reduce overhead, and keep your org running smoothly.
1. Use Bulk API for Large Data Operations
Why it matters:
Standard DML operations in Apex (like insert
, update
) work fine for small data volumes. But for large data jobs, you’ll want to use Bulk API to reduce API calls and improve performance.
Use case:
Nightly import of 100,000 new records from an external source.
Salesforce-native approach (Apex + Bulk API):
If you’re initiating this from Apex, use a Named Credential
and an HttpRequest
to hit the Bulk API endpoint. Always prefer Bulk API for jobs involving thousands of records. You’ll avoid hitting DML and API limits.
HttpRequest req = new HttpRequest();
req.setEndpoint('callout:My_Bulk_Named_Credential/services/data/v57.0/jobs/ingest');
req.setMethod('POST');
req.setHeader('Content-Type', 'application/json');
req.setBody(JSON.serialize(new Map<String, Object>{
'object' => 'Lead',
'operation' => 'insert',
'contentType' => 'CSV'
}));
HttpResponse res = new Http().send(req);
System.debug('Bulk Job Created: ' + res.getBody());
2. Write Efficient SOQL Queries
Why it matters:
Inefficient queries waste resources and can easily hit governor limits.
Use case:
You’re querying opportunities for a monthly report but running into limits due to fetching too many fields or records. Use indexed fields in WHERE clauses. Use the Query Plan Tool in Developer Console to analyse query cost.
Better Apex approach:
List<Opportunity> opps = [
SELECT Id, Name, Amount, StageName
FROM Opportunity
WHERE CloseDate = THIS_MONTH AND StageName = 'Closed Won'
];
3. Use Composite API to Bundle Requests
Why it matters:
Sending multiple API calls to Salesforce in quick succession is inefficient. Composite API lets you send up to 25 sub-requests in a single call.
Use case:
You’re creating a new Account and related Contacts from an external app.
Salesforce Composite API via Apex callout:
HttpRequest req = new HttpRequest();
req.setEndpoint('callout:My_Named_Credential/services/data/v57.0/composite');
req.setMethod('POST');
req.setHeader('Content-Type', 'application/json');
Map<String, Object> body = new Map<String, Object>{
'allOrNone' => true,
'compositeRequest' => new List<Object>{
new Map<String, Object>{
'method' => 'POST',
'url' => '/services/data/v57.0/sobjects/Account',
'referenceId' => 'refAccount',
'body' => new Map<String, Object>{'Name' => 'Acme Ltd'}
},
new Map<String, Object>{
'method' => 'POST',
'url' => '/services/data/v57.0/sobjects/Contact',
'referenceId' => 'refContact1',
'body' => new Map<String, Object>{
'FirstName' => 'Anna',
'LastName' => 'Smith',
'AccountId' => '@{refAccount.id}'
}
}
}
};
req.setBody(JSON.serialize(body));
HttpResponse res = new Http().send(req);
System.debug('Composite API Response: ' + res.getBody());
4. Add Error Handling and Retry Logic
Why it matters:
Salesforce APIs can temporarily fail due to timeouts, rate limits, or network issues.
Use case:
You’re performing an external callout from Apex and want to safely retry if it fails due to a transient error.
Salesforce Apex approach:
Integer maxRetries = 3;
Integer attempts = 0;
Boolean success = false;
while (!success && attempts < maxRetries) {
try {
HttpRequest req = new HttpRequest();
req.setEndpoint('callout:My_API');
req.setMethod('GET');
HttpResponse res = new Http().send(req);
if (res.getStatusCode() == 200) {
success = true;
} else {
attempts++;
System.debug('Retrying... Attempt ' + attempts);
}
} catch (Exception e) {
attempts++;
System.debug('Caught Exception: ' + e.getMessage());
}
}
5. Cache Frequently Accessed Data
Why it matters:
If your Apex repeatedly fetches the same data (like config settings or reference lists), that’s wasted SOQL and API overhead.
Use case:
A Lightning Web Component fetches Account types every time it loads.
Salesforce-native approach (Platform Cache):
String cachedTypes = (String)Cache.Org.get('accountTypes');
if (cachedTypes == null) {
List<String> types = new List<String>();
for (AccountType__c at : [SELECT Name FROM AccountType__c]) {
types.add(at.Name);
}
cachedTypes = JSON.serialize(types);
Cache.Org.put('accountTypes', cachedTypes);
}
6. Use Asynchronous Processing
Why it matters:
Long-running processes in Apex can cause timeouts or hit limits when done synchronously.
Use case:
A trigger tries to insert thousands of related records and fails.
Salesforce-native solution: Queueable Apex
public class MyAsyncProcessor implements Queueable {
public void execute(QueueableContext context) {
List<Custom__c> recordsToInsert = new List<Custom__c>();
// populate list...
insert recordsToInsert;
}
}
// Enqueue job from your controller or trigger
System.enqueueJob(new MyAsyncProcessor());
7. Optimise Apex Code
Why it matters:
Poorly written Apex slows things down and can silently consume API calls.
Use case:
Your Apex class is processing too much data in memory, hitting heap size limits.
Better Apex pattern:
List<Account> accounts = [
SELECT Id, Name FROM Account
WHERE CreatedDate = LAST_N_DAYS:30
];
for (Account acc : accounts) {
// process only necessary fields
}
Use Limits.getHeapSize()
and Limits.getCpuTime()
in debug logs to analyse bottlenecks.
8. Monitor API Usage and Limits
Why it matters:
If you don’t monitor usage, you’ll eventually hit limits—especially in high-traffic orgs or integrations.
Use case:
Your daily limit was exceeded without warning, halting key processes.
Salesforce-native solution: Apex + Limits API
HttpRequest req = new HttpRequest();
req.setEndpoint('callout:My_Named_Credential/services/data/v57.0/limits');
req.setMethod('GET');
HttpResponse res = new Http().send(req);
System.debug('Current Limits: ' + res.getBody());
You can also add a dashboard component or build a scheduled job to notify admins.
9. Use Indexing for High-Volume Queries
Why it matters:
SOQL performance drops if your WHERE clause doesn’t use indexed fields.
Use case:
Querying millions of records in a custom object for reporting.
Salesforce-native solution:
List<CustomObject__c> results = [
SELECT Id, Name
FROM CustomObject__c
WHERE CreatedDate = LAST_N_DAYS:7
AND Status__c = 'Active'
];
Contact Salesforce Support to index custom fields used often in filters.
10. Regularly Review and Optimise Integrations
Why it matters:
What worked a year ago may now be the biggest bottleneck in your system.
Use case:
An old integration is now calling multiple APIs instead of batching.
Salesforce-native steps:
- Use Setup → API Usage to identify top consumers.
- Review connected apps and Named Credentials.
- Optimise integrations to use Bulk API, Composite API, or cache layers.
- De-duplicate overlapping automations.
Example:
Replace 100 small REST calls from an ETL job with one Bulk API job per object.
SalesforceDev #SalesforceAPI #ApexCode #BulkAPI #CompositeAPI #SalesforceIntegration #APIOptimization #SalesforcePerformance