I am making a callout to a webservice to retrieve records and upsert them into Salesforce. When I execute my batch class with a small set of records, they're inserted in Salesforce properly. However when I expand the scope of records I hit the 'too many callouts 101' error. In looking at the debug log, the line where I'm facing the issue is cod = getOutBoundRecords(str) I see that because it's inside a for loop it's triggering this error. Because the web service doesn't have a method to retrieve records in bulk, I have to make a callout for each individual record. Is there a way to make multiple callouts without hitting this error? Thank you.


 public class ConcordBuilder implements Database.AllowsCallouts {

//Build Fax records and get Get details (Callout)

public static void getListOutBoundRecords(DateTime endDate ){     

    map<string,Custom__c> COD2Update = new map <string,Custom__c>();   // index of records by job id
    Concord__c con = new Concord__c();
    List<Custom__c> stConcord = new  List<Custom__c>();

    accountmanagementreportingwebservice2.AccountManagementReportingWebserviceSoap s= new accountmanagementreportingwebservice2.AccountManagementReportingWebserviceSoap();
    accountmanagementreportingwebservice2.ListOutboundActivityRequest  req = new accountmanagementreportingwebservice2.ListOutboundActivityRequest();
    accountmanagementreportingwebservice2.ListOutboundActivityResponse res = new accountmanagementreportingwebservice2.ListOutboundActivityResponse();
    accountmanagementreportingwebservice2.Authentication a =new accountmanagementreportingwebservice2.Authentication();

    //Auth for ListOutbound Class      
    a.Username = '-------';
    a.Password = '-------!'; 
    req.Authentication = a;
    req.CustomerId = 1224; 
    req.ExcludeSubLevelEntries = Boolean.valueOf('true');
    req.PageIndex = 0;
    req.PageSize = 100;
    req.SortOption = 0;
    s.timeout_x = 120000;

    req.StartDate = datetime.valueOf('2019-11-1' + '' + ' 06:05:05');
    req.EndDate = datetime.now();

    //  Make the callout to get Job Ids        
    res = s.ListOutboundActivity(req);
    String jsonResponse = (JSON.serializePretty(res));

    JSON2APex result = JSON2APex.parse(jsonResponse);

    set<string> jobIds = new set<string>();

    //****************** Parse the first callout response and build records
    Custom__c co = new Custom__c();
    system.debug('Total Records = ' +result.TotalRecords);
     if(result.TotalRecords >0){
    for(JSON2APex.Om om: result.OutboundMessageList.om){


        co.Duration__c = om.dur;
        co.Delivery_Attempts__c = om.atmpts;
        co.Delivered_Pages__c = om.pgct;
        co.JOB_ID__c = om.id;
        co.Concord_Account_ID__c = String.valueOf(om.cid);
        co.Sender_Country__c = om.fcc;
        co.Recipient_Fax_Number__c = om.fax;
        co.Status__c = om.stat_x;

        //index of Concord Records
        COD2Update.put(co.JOB_ID__c, co);

   Custom__c cod = new Custom__c(); //stores fax details
   Custom__c cod2 = new Custom__c(); // to be updated

    //loop over Job IDs and 
    for(string str :COD2Update.keyset()){         

        System.debug('First JobId is :' + str);
        //****************Line below is causing callout error

      cod = getOutBoundRecords(str);

        System.debug('Call Out response');
        cod2 = COD2Update.get(str);
        System.debug('Soon to be updated');

       // cod2 = COD2Update.get(str);
        if(cod2 != null){
        cod2.Start_Date__c = cod.Start_Date__c;
        cod2.Stop_Date__c = cod.Stop_Date__c;
        cod2.Submitted_Date__c = cod.Submitted_Date__c;
        cod2.Scheduled_Date__c = cod.Scheduled_Date__c;
        cod2.Status_Description__c = cod.Status_Description__c;
        cod2.Speed__c = cod.Speed__c;
        cod2.Name = cod.Name;
        cod2.Recipient_Country__c = cod.Recipient_Country__c;
        cod2.Recipient_Name__c = cod.Recipient_Name__c;
        cod2.Recipient_City__c = cod.Recipient_City__c;    
        cod2.JOB_ID__c = str;
        cod2.Duration__c = cod.Duration__c;
        cod2.Page_Count__c = cod.Page_Count__c;
        cod2.Sender_Fax_Number__c = cod.Sender_Fax_Number__c ;
        cod2.Sender_CSID__c = cod.Sender_CSID__c;
        cod2.Sender_Name__c = cod.Sender_Name__c ;
        cod2.Sender_City__c = cod.Sender_City__c;

        //overwrite existings with updated

       upsert COD2Update.values();   


}// end method

//Get Fax Details
public static Concord__c getOutBoundRecords(string Jobid){   
    // public static void getOutBoundRecords(string Jobid){    
    Custom__c conc = new Custom__c ();
    accountmanagementreportingwebservice2.AccountManagementReportingWebserviceSoap s= new accountmanagementreportingwebservice2.AccountManagementReportingWebserviceSoap();
    accountmanagementreportingwebservice2.GetOutboundDetailRequest req = new accountmanagementreportingwebservice2.GetOutboundDetailRequest();
    accountmanagementreportingwebservice2.GetOutboundDetailResponse res = new accountmanagementreportingwebservice2.GetOutboundDetailResponse();
    accountmanagementreportingwebservice2.Authentication a = new accountmanagementreportingwebservice2.Authentication();

    a.Username = '----------';
    a.Password = '--------'; 
    req.Authentication = a;
    req.JobId = Jobid;          

    res = s.GetOutboundDetail(req);
    String jsonResponse = (JSON.serializePretty(res));

  //Retrieve the JSON response from the second callout and map the Custom__c fields with SF fields  

    String jsonResponse2 = (JSON.serializePretty(res));
    JSON2ApexOFD results = JSON2ApexOFD.parse(jsonResponse2);

    for(JSON2ApexOFD.OutboundFaxDetail om2: results.OutboundFaxMessage.OutboundFaxDetail){
        String convertTime = om2.StartDt;
        String Startruncated = convertTime.left(convertTime.length()-5);
        String truncated2 = Startruncated.replace('T',' ');
        conc.Start_Date__c = DateTime.valueOf(truncated2);

        String convertTimestop = om2.StopDt;
        String Stoptruncated = convertTimestop.left(convertTime.length()-5);
        String truncated3 = Stoptruncated.replace('T',' ');
        conc.Stop_Date__c = DateTime.valueOf(truncated3);

        conc.Duration__c = om2.Duration;
        String convertTimeSubmit = om2.SubmitDt;
        String Submittruncated = convertTimeSubmit.left(convertTime.length()-5);
        String truncated4 = Submittruncated.replace('T',' ');
        conc.Submitted_Date__c = DateTime.valueOf(truncated4);

        String convertSchedule = om2.ScheduledDt;
        String Scheduletruncated = convertTimeSubmit.left(convertTime.length()-5);
        String truncated5 = Scheduletruncated.replace('T',' ');
        conc.Scheduled_Date__c = DateTime.valueOf(truncated5);

        conc.Name = om2.SenderDeptName;
        conc.Recipient_Country__c = om2.RecipientCountry;
        conc.Recipient_City__c = om2.RecipientCity;
        conc.Recipient_Name__c = om2.RecipientName;
        conc.Speed__c = om2.Speed;
        if(om2.PageCount != null){ conc.Page_Count__c = om2.PageCount;}
        if(om2.SenderFax != null){conc.Sender_Fax_Number__c = om2.SenderFax;}
        conc.Sender_Name__c = om2.SenderName;     
        conc.Sender_CSID__c = om2.SenderCSID;
        conc.Sender_Country__c = om2.SenderCountry;
        conc.Sender_City__c = om2.SenderCity;

    if (conc != null){

       return conc;

    } else{

        system.debug('no record found for '+ Jobid );
        return null; 

}// end class

Batch Class

global class BatchableClass implements Database.Batchable<SObject>, Database.AllowsCallouts {

    global BatchableClass (){


    global Database.QueryLocator start(Database.BatchableContext BC){

        String soql = 'Select Id, JOB_ID__c from Custom__c LImit 1';

        return database.getQueryLocator(soql);


    global void execute(Database.BatchableContext BC, List<Custom__c> scope){

          Datetime endDate = System.today();    
          Builder.getListOutBoundRecords( endDate); 

    global void finish(Database.BatchableContext BC){


  • This code is hard to follow. How is COD2Update populated? Fundamentally you need to chunk this work into smaller pieces that can run in a single invocation of execute().
    – David Reed
    Nov 27 '19 at 21:34
  • Thank you for your assistance. I added the code around how the COD2Update map is populated. Essentially I'm going through the first for loop to populate the map. Then I iterate over the map to retrieve the key (JobId) that I'll use to make callout. By breaking down the code into smaller pieces do you mean using an increment counter to limit the number of records in each for loop iteration? Thank you for your assistance.
    – AL 600
    Nov 27 '19 at 21:49
  • When schedule the batch, what is the batch size? And could you share the Builder class with getListOutBoundRecords method? Based on your batch size, the batch split the list of records you queried as seperate transaction. For example, 400 records with batch size 200. This will be 2 transaction each with 200 records, but each transaction has limit of callout 100. To avoid that, the batch size should be 100 or less.
    – Wayne Ni
    Nov 27 '19 at 23:09
  • @WayneNi thank you for your help. I've added the code for the getListOutboundRecords method. When I was executing a batch, I've tried a size of 100.
    – AL 600
    Nov 28 '19 at 11:36

Each invocation of execute() in an Apex batch class is a single transaction. The callout limit for a transaction is 100. While it's not clear what exactly Builder.getListOutBoundRecords() is doing, it appears to be sourcing more than 100 records at a time.

Meanwhile, start() is identifying exactly one record:

    String soql = 'Select Id, JOB_ID__c from Custom__c LImit 1';

Meaning that your batch class will only ever run a single batch, which doesn't get you any benefit over, say, a Queuable.

This suggests to me that the batch class is iterating over the wrong object or element here. Instead of iterating over exactly one Custom__c, run a query for whatever the other related object is in start(), and iterate over that object. You can set your batch size to 100 so that each invocation of execute() gets exactly 100 records to make callouts for.

  • Confused by the start method as well. Looks like he is not using the scope records at all in the execute method.
    – Wayne Ni
    Nov 27 '19 at 23:15

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.