function readOnly(count){ }
Starting November 20, the site will be set to read-only. On December 4, 2023,
forum discussions will move to the Trailblazer Community.
+ Start a Discussion
Kris WebsterKris Webster 

system limitexception too many callouts: 101 - HOW TO FIX

I am attempting to fix my class that is making API calls to an external system. I am thinking that nextUrl is returning more than 100 nextUrl's which is making the code call out more than 100 times. I basically want to be able to call nextUrl until there is no longer a nextUrl returned from the call. What would be the best way to solve this issue ??

Here is the code for context. (I have removed the original URL and header for security reasons)
 
public class Zenefits_Time_Off implements Schedulable, Database.AllowsCallouts {
    public void execute(SchedulableContext SC) {
        makeCallout();
    }
    @future(callout = true)
    public static void makeCallout() {
        string url = 'xxxxxxxxxx'; 
        do {
            url = makeHttpCall(url);
        } while(url != null);
        processData();
    }

    static map<String,Id> ProjectList = new Map<string,Id>();
    static List<Object> vacationRequests = new List<Object>();
    static {
        for (pse__Proj__c proj : [SELECT Id, Name FROM pse__Proj__c WHERE Name Like 'PTO%'LIMIT 1]) {
            ProjectList.put('pto',proj.Id);
        }
    }

    public static String makeHttpCall(String url) {
        string nextUrl;
        Http http = new Http();
        HttpRequest request = new HttpRequest();
        request.setHeader('Authorization', 'xxxxxxxxxxxxx');
        request.setEndpoint(url);
        request.setMethod('GET');
        HttpResponse response = http.send(request);
        // If the request is successful, parse the JSON response.
        if (response.getStatusCode() == 200) { 
            // Deserializes the JSON string into collections of posts.
            Map<String, Object> wrapper = (Map<String, Object>) JSON.deserializeUntyped(response.getBody());
            if(wrapper != null) {
                Map<String, Object> wrapper2 = (Map<String, Object>) wrapper.get('data');
                if (wrapper2 != null) {
                    nextUrl = (String) wrapper2.get('next_url');
                    vacationRequests.addAll((List<Object>)wrapper2.get('data'));
                }
            }
        }
        return nextUrl;
    }

    public static void processData() {
        List<Time_Off_Request__c> torToUpdate = new List<Time_Off_Request__c> ();
        Map<String,map<String, Object>> ptoMap = new Map<String,map<String, Object>>();
        set<string> employeeIdSet = new set<string>();

        for (Object vacationRequestWrapper : vacationRequests) {
            Map<String, Object> vacationRequest = (Map<String, Object>) vacationRequestWrapper;
            if (vacationRequest.get('status') == 'approved') {
                Map<String, Object> wrapper3 = (Map<String, Object>) vacationRequest.get('creator');
                Map<String,Object> empValues = new Map<String,Object>(); 
                string ptoUrl = (String) wrapper3.get('url');
                string ptoId = (string)vacationRequest.get('id');
                string employeeId = ptoUrl.substring(ptoUrl.length() - 7, ptoUrl.length() - 0);
                string hours = (String)vacationRequest.get('hours');
                decimal hoursDec = decimal.valueOf(hours);
                string status = (String)vacationRequest.get('status');
                empValues.put('startDate',vacationRequest.get('start_date'));
                empValues.put('endDate',vacationRequest.get('end_date'));
                empValues.put('ptoId',vacationRequest.get('id'));
                empValues.put('hours', hoursDec);
                empValues.put('empId', employeeId);
                ptoMap.put(ptoId, empvalues);
                employeeIdSet.add(employeeId);
            }
        } 
        Map<String, Object> tempEmpValues = new Map<String, Object>();
        Map<String,Object> contactVal = new Map<String,Object>();
        map<String,Id> conZenIdMap = new map<String,Id>();

        for (Contact con : [SELECT FirstName, LastName, Zenefits_ID__c FROM Contact WHERE Zenefits_ID__c IN : employeeIdSet]) {
            conZenIdMap.put(con.Zenefits_ID__c,con.Id);
        }
        for(Time_Off_Request__c tor1 : [SELECT ID, Zenefits_ID__c FROM Time_Off_Request__c WHERE Zenefits_ID__c IN : ptoMap.keySet()]) {
            ptoMap.remove(tor1.Zenefits_ID__c); //remove existing Time Off Requests
        }
        for(String zId: ptoMap.keyset()) {
            map<String,Object> currMap = ptoMap.get(zId);
            map<String, Integer> hours = new Map<String, Integer>();
            Time_Off_Request__c tor = new Time_Off_Request__c();
            if(conZenIdMap.get((string)currMap.get('empId')) != null) {
                tor.Employee__c =conZenIdMap.get((string)currMap.get('empId')); 
                tor.Project__c = (string)ProjectList.get('pto');
                tor.First_Day_Off__c = date.valueof((string)currMap.get('startDate'));
                tor.Last_Day_Off__c = date.valueof((string)currMap.get('endDate'));
                tor.Status__c = 'Saved';
                tor.Zenefits_ID__c = zId;
                tor.hours_off__c = (Decimal)currMap.get('hours');
                torToUpdate.add(TOR);
            }   
        }
        insert torToUpdate;
    }
}

 
AnudeepAnudeep (Salesforce Developers) 
Batch classes will try to basically execute all batches in one go so since your test class is set to see all data  = true, The test class will try to run all the outbound calls in one execution thread. 

Best practice in test batch apex would be to create the test cases in the test class and set see all data to false. Con of that would mean you will create the custom setting record in the test class itself ass well

By annotating your class with @isTest(SeeAllData=true), you allow class methods access to all org records. The best practice is to create data in the test classes instead.