10

I have http request batch class with the endpoint url is set by using Named Credential in apex code

Named Credential:

  1. Given as My Domain URL
  2. Identity type as Named Principal
  3. Authentication Protocol as OAuth
  4. Authentication Provider as Salesforce
  5. I have checked Generate Authorization Header checkbox

    req.setEndpoint('callout:Createddateconversionrate/services/data/v43.0/sobjects/DatedConversionRate');

Batch class

global class CreateDatedConversionRate implements Database.Batchable<sObject>,Database.Stateful,Database.AllowsCallouts {

    private String sessionId;

     public  string  querystring{get;set;}
    global CreateDatedConversionRate(){

        this.sessionId = querystring;
    }

    global Database.QueryLocator start(Database.BatchableContext BC) {    
        String query = '';
        List<AggregateResult> startDateList = [SELECT StartDate FROM DatedConversionRate GROUP BY STARTDATE ORDER BY STARTDATE DESC LIMIT 1];
        System.debug(LoggingLevel.INFO,'Last exchangeDate:'+startDateList.get(0));        
        String startDate = DateTime.newInstance(((Date)startDateList.get(0).get('StartDate')), Time.newInstance(0,0,0,0)).format('yyyy-MM-dd');        
        System.debug(LoggingLevel.INFO,'query string:'+startDate);

        query = 'SELECT IsoCode, ConversionRate FROM DatedConversionRate WHERE STARTDATE = '+ startDate +' ORDER BY IsoCode';
        System.debug(LoggingLevel.INFO,'Executed query:'+query);
        return Database.getQueryLocator(query);
    }

    global void execute(Database.BatchableContext BC, List<DatedConversionRate> conversionList) {
        System.debug(LoggingLevel.INFO,'conversionList size:'+conversionList.size()); 
        String todayDate = DateTime.newInstance(System.Date.today(), Time.newInstance(0, 0, 0, 0)).format('yyyy-MM-dd'); 
        if(conversionList != null && conversionList.size()>0){
            for(DatedConversionRate conversionRate : conversionList){
                String str = '' ;
                str +='{ "IsoCode" : "';
                str += conversionRate.IsoCode +'", "ConversionRate" : ';
                str += conversionRate.ConversionRate + ', "StartDate" : "';
                str += todayDate  + '"';
                str += '}';
                /*REST API CALL TO INSERT RECORDS.*/

                Http h = new Http();
                HttpRequest req = new HttpRequest();

                req.setBody(str);
                req.setEndpoint('callout:Createddateconversionrate/services/data/v43.0/sobjects/DatedConversionRate');


                req.setMethod('POST');
                System.debug(LoggingLevel.INFO,'request body:'+req.getBody());
                if(!Test.isRunningTest()){
                    HttpResponse res = h.send(req);
                   System.debug(LoggingLevel.INFO,'res'+res.getBody());
                    System.debug(LoggingLevel.INFO,'res'+res.getStatus());
                }
            }
        }  
    }
    global void finish(Database.BatchableContext BC) {

    }
}

When i am trying to execute the Batch class.I am getting following error

{"message":"This session is not valid for use with the REST API","errorCode":"INVALID_SESSION_ID"}]

How to rectify this error?

For GET Method is working,only for POST Method is not working? please guide me for answer

enter image description here

  • Does the authenticated Named Principal user have API access permission? – David Reed Jul 10 '18 at 13:22
  • yes the user has api access permission @DavidReed – Vinoth Balasubramaniyan Jul 10 '18 at 14:47
  • 3
    Can you post your apex code? Are you setting up the Authorization header as metntioned here? salesforce.stackexchange.com/questions/183578/… Secondly the user who is running the batch class, and the endpoint you are trying to reach are in same org ? – Pranay Jaiswal Jul 13 '18 at 5:50
  • 3
    @sfdcfox was nice enough to put a bounty on this for you, but you need to do your part by providing the relevant parts of your code if you expect anyone here to be able to help you. Seeing the essence of the http callout being made from your batch class is going to be essential to helping you with this. – crmprogdev Jul 13 '18 at 22:31
  • Along with what @PranayJaiswal has suggested, please make sure that your setup has appropriate callbacks configured: eltoroit.herokuapp.com/Blog.app?page=ETLC_APIsFromApex – Rohit Kunal Jul 15 '18 at 18:49
7
+500

Issue 1:

When i am trying to execute the Batch class.I am getting following error

{"message":"This session is not valid for use with the REST API","errorCode":"INVALID_SESSION_ID"}]

I suspect what you've run into is covered by the idea Batch Jobs - Multi User - Named Credential Usage. There is also the corresponding SFSE question Using Named Credentials in Batch Job (multi user). Basically, the Named Credential isn't working in a batch context if they are per user.

It appears that a Named Credential doing oAuth with a refresh token may be more successful. See Using Named Credentials to get Salesforce sessionId.

Issue 2:

For GET Method is working,only for POST Method is not working?

When I first checked this I didn't have "Advanced Currency Management" enabled in the org. As such, the metadata I could see for DatedConversionRate via /services/data/v43.0/sobjects/DatedConversionRate indicated is neither createable (the typical reason to do a POST call) nor updateable (which would be the PATCH verb).

DatedConversionRate is not creatable

After enabling "Advanced Currency Management" the DatedConversionRate records became createable and updateable.


Test POST from Workbench to create a new DatedConversionRate for a known currency ISOCode on a specific StartDate:

POST /services/data/v43.0/sobjects/DatedConversionRate

{
  "IsoCode" : "GBP",
  "ConversionRate" :  0.667500,
  "StartDate" : "2018-07-16"
}

201 Response

{
  "id" : "04w1W000000k9dOQAQ",
  "success" : true,
  "errors" : [ ]
}

Next steps in debugging...:

  1. Do the same POST from Anonymous Apex with the local Session ID
  2. Do the same POST from within a batch with an established Session ID (login API)
  3. Do the same POST from Anonymous Apex with a Named Credential
  4. Do the same POST from within a batch with a Named Credential

Do the same POST from Anonymous Apex with the local Session ID

public class DateConversionRateCreate {
    public void create(string isoCode, decimal conversionRate, DateTime startDate) {

        map<string, object> mapToSerialize = new map<string, object>();
        mapToSerialize.put('IsoCode', isoCode);
        mapToSerialize.put('ConversionRate', conversionRate);
        mapToSerialize.put('StartDate', startDate.format('yyyy-MM-dd'));
        string jsonstring = JSON.serialize(mapToSerialize);

        Http h = new Http();
        HttpRequest req = new HttpRequest();
        // Use the "local" session ID from the active transaction
        req.setHeader('Authorization', 'OAuth ' + UserInfo.getSessionId());
        req.setHeader('Content-Type', 'application/json');
        req.setBody(jsonstring);
        // Hardcoded endpoint for expediency. Has been added as a remote site.
        req.setEndpoint('https://na5.salesforce.com/services/data/v43.0/sobjects/DatedConversionRate');


        req.setMethod('POST');
        System.debug(LoggingLevel.INFO,'request body:'+req.getBody());

        HttpResponse res = h.send(req);
        System.debug(LoggingLevel.INFO,'res'+res.getBody());
        System.debug(LoggingLevel.INFO,'res'+res.getStatus());
    }
}

Run with anonymous Apex:

DateConversionRateCreate dcrc = new DateConversionRateCreate();
dcrc.create('GBP', 0.667400, DateTime.now());

Response

02:06:26.1 (19329463)|USER_DEBUG|[19]|INFO|request body:{"StartDate":"2018-07-18","ConversionRate":0.667400,"IsoCode":"GBP"}
02:06:26.1 (637348591)|USER_DEBUG|[22]|INFO|res{"id":"04w1W000000k9dTQAQ","success":true,"errors":[]}
02:06:26.1 (637464103)|USER_DEBUG|[23]|INFO|resCreated

Do the same POST from within a batch with an established Session ID (login API)

I used a variation of the above code, but passed the Session ID into the constructor. This works for a quick test, but isn't recommended for actual batches.

public class CreateDatedConversionRateBatch implements Database.Batchable<sObject>,Database.Stateful,Database.AllowsCallouts {
    private string sessionId = null;
    public CreateDatedConversionRateBatch(string sessionIdParam) {
        // Need to use this quickly as it is likely to expire before the batch runs to completion, which is less than ideall
        sessionId = sessionIdParam;
    }

    public Database.QueryLocator start(Database.BatchableContext BC) {
        return Database.getQueryLocator('SELECT Id, IsoCode, ConversionRate FROM DatedConversionRate');
    }

    public void execute(Database.BatchableContext BC, List<DatedConversionRate> conversionList) {
        System.debug(LoggingLevel.INFO, 'executing batch');
        DateConversionRateCreate dcrc = new DateConversionRateCreate(sessionId);
        dcrc.create('GBP', 0.667400, DateTime.now());
    }

    public void finish(Database.BatchableContext BC) {
        System.debug(LoggingLevel.INFO, 'finish batch');
    }
}

Then start it with:

CreateDatedConversionRateBatch batchJob = new CreateDatedConversionRateBatch(UserInfo.getSessionId());
Id batchJobId = Database.executeBatch(batchJob, 1);
System.debug(batchJobId);

From the SerialBatchApexRangeChunkHandler debug log:

enter image description here

Do the same POST from Anonymous Apex with a Named Credential

I created a Named Credential, Connected App, and Auth Provider using the instructions in Using Named Credentials with the Apex Wrapper Salesforce Metadata API (apex-mdapi), they are pretty comprehensive on what is required.

Then I modified DateConversionRateCreate to handle the Session ID being null and to switch to the named credential in that scenario.

    Http h = new Http();
    HttpRequest req = new HttpRequest();
    req.setHeader('Content-Type', 'application/json');
    req.setBody(jsonstring);
    if(sessionId == null) {
        System.debug(LoggingLevel.INFO, 'Using Named Cred');
        req.setHeader('Authorization', 'OAuth {!$Credential.OAuthToken}');
        req.setEndpoint('callout:LocalRestAPI/services/data/v43.0/sobjects/DatedConversionRate');
    }
    else {
        req.setHeader('Authorization', 'OAuth ' + sessionId);
        req.setEndpoint('https://na87.salesforce.com/services/data/v43.0/sobjects/DatedConversionRate');
    }

    req.setMethod('POST');

The logs showed this worked and used the Named Credential for authentication.

Do the same POST from within a batch with a Named Credential

Test with:

CreateDatedConversionRateBatch batchJob = new CreateDatedConversionRateBatch(null);
Id batchJobId = Database.executeBatch(batchJob, 1);
System.debug(batchJobId);

Result:

enter image description here

So that is all working. The Named Credential (using OAuth and a refresh token) is providing a valid Session ID to use in a batch context. The callout is hitting the REST API and creating the new DateConversionRate.

  • @VinothBalasubramaniyan So you can make a GET request from the batch job using the named credential, but not a POST request to create new records? If so, that would make sense, as DatedConversionRate records can't be created via the API. – Daniel Ballinger Jul 16 '18 at 8:44
  • @VinothBalasubramaniyan Try doing manual updates with Workbench or Data Loader. If you can get those to work then you can emulate the same API calls as required. – Daniel Ballinger Jul 16 '18 at 9:21
  • @VinothBalasubramaniyan Can you provide the samples of the POST method being used in workbench. A screenshot would be ideal. It might be that workbench is internally switching to a PATCH request using the methods I indicated. – Daniel Ballinger Jul 16 '18 at 9:55
  • @Vinoth Balasubramaniyan, please do not request help, it can be considered rude, people here are already providing assistance on their free time, Thanks – glls Jul 17 '18 at 1:34
  • 1
    Fantastic Explanation,awesome it worked – Vinoth Balasubramaniyan Jul 20 '18 at 14:02
2

I was also running into the same problem of Invalid Session Id. The Error is because batch class runs in future when it gets the free salesforce Resource and in future, Salesforce is not able to identify the Session-Id even if the same user is logged in. So, as an alternative, You need to generate the SessionId in the execute method of the batch class.

What You Needed

  1. Create a Connected App which because you need the Client Id and Secret in this implementation. Refer image for the same enter image description here
  2. Create a Custom Setting with the same field as given in the image below enter image description here

  3. Create a record for custom setting with UserName, Password, Security Toke, Client Id, Client Secret.

  4. Use the below code to generate the SessionId from the class
  5. Copy the Code From Link and you can use the same to generate the sessionId. I tried to paste the code Here using {} but the formatting is not as expected so I did put the code there in gist. So do not mind :)
  6. To make the callout You need to add Remote Site Setting.

Go to Setup -> Security Control -> Remote Site Settings -> New ->

For Sandbox Add - https://test.salesforce.com/ and For Production add https://login.salesforce.com/

Note: - In the above logic we are using password authentication for getting the SessionId. Please refer to the below document for the same.

I am using the connected app because to get the SessionId it requires the Client Id and Client Secret.

Understanding the Username-Password OAuth Authentication Flow

  • Yes. it will work for the scheduled classes. I Have updated the answer with the reference image for the connected application. You need to generate the Session from the execute method and be sure that every time a batch runs you are generating the token only one not for all execute chunks – Amit Singh Jul 17 '18 at 5:25
  • Using the connected to get the ClientId and Client Secret. Yes, UserName and Password will be different but in every org, you need to create an instance of Custom Setting where you can store the username and password. – Amit Singh Jul 17 '18 at 6:00
  • Yes. That's correct – Amit Singh Jul 17 '18 at 6:03
  • Access Token is what we are generating with Code. Because UserInfo.getSessionId and Named Credentials will not work in case of Batch Class and future method while calling the Salesforce Own API. For Token Value, you need to put the Security token there, See the link for resetting the toke: - Reset Your Security Token – Amit Singh Jul 17 '18 at 7:14
  • Yes. You need to change OR you can create a User Specific for this functionality with Password Never Expires Policy. – Amit Singh Jul 17 '18 at 7:20

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.