session is not valid for use with the REST API

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.


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