System.LimitException: Too many callouts: 101
What does this error mean?
Salesforce limits any single Apex transaction to 100 HTTP callouts (REST, SOAP, or any external service invocation). The limit resets for each new transaction context. Asynchronous Apex (@future, Queueable, Batch, Scheduled) shares the same 100-callout limit per execution.
Note that Salesforce also enforces a combined limit: if your transaction mixes callouts and DML, the callout must happen before any DML — otherwise you get a different error (You have uncommitted work pending). The 100-callout limit is separate from this ordering constraint.
Common Causes
1. Callout inside a loop
Calling an external API once per record in a trigger or batch is the most common cause. With 200 records in a trigger, 200 callouts fire — double the limit.
2. Recursive or nested callout chains
A utility method that calls one endpoint, which triggers another method calling a second endpoint, compounding quickly in complex integrations.
3. No bulk API support on the external system
Some integrations call a single-record REST endpoint repeatedly because the external system doesn't offer a bulk endpoint — forcing developers to serialize callouts across many records.
How to Fix It
Solution 1: Move callouts outside loops — batch your payload
Collect all the data you need first, then make one callout with all records in the request body. Most modern REST APIs accept arrays.
// ❌ BAD — one callout per record
for (Account acc : accounts) {{
HttpRequest req = new HttpRequest();
req.setBody(JSON.serialize(acc));
new Http().send(req); // fires per record!
}}
// ✅ GOOD — single callout with all records
HttpRequest req = new HttpRequest();
req.setEndpoint('callout:My_API/bulk-accounts');
req.setMethod('POST');
req.setBody(JSON.serialize(accounts)); // send all at once
req.setHeader('Content-Type', 'application/json');
HttpResponse res = new Http().send(req);
Solution 2: Use @future or Queueable for per-record callouts
If the external system truly requires one call per record, dispatch each callout to its own @future or Queueable job. Each asynchronous execution gets a fresh 100-callout limit.
public class CalloutDispatcher {{
@future(callout=true)
public static void sendRecord(String recordJson) {{
HttpRequest req = new HttpRequest();
req.setEndpoint('callout:My_API/record');
req.setMethod('POST');
req.setBody(recordJson);
HttpResponse res = new Http().send(req);
// handle response...
}}
}}
// From trigger — each future gets its own 100-callout quota
for (Account acc : Trigger.new) {{
CalloutDispatcher.sendRecord(JSON.serialize(acc));
}}
Pro Tip: Use Limits.getCallouts() and Limits.getLimitCallouts() to monitor callout consumption in debug logs. Platform Events or Change Data Capture are also worth exploring as alternatives to direct callouts for outbound integrations.