[SalesForce] session is not valid for use with the REST API

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

Best Answer

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.