You need to sign in to do that
Don't have an account?

Calling Apex Rest custom web service from Apex code
Hi everyone,
I am a newbie to FDC (so please correct me if I am wrong anywhere) . I have gone thorugh Apex REST API and found that we can create our custom web services in Apex and expose them as REST services using Apex REST. But in most of the references it is given that we can use any programming language of our choice ( i.e. from outside FDC) to invoke this custom WS via REST API. But I want to invoke the same using Apex code (i.e., from inside FDC) via REST API.
Any help is highly appreciated
You beat me, Simon :-)
Vikash - try adding the following line after req.setEndpoint(url);
Simon - it's a bit odd that it would complain about API being disabled, rather than a bad or missing token :-/
All Answers
Hi Vikash,
I assume that you're trying to call an Apex REST service in one org (let's call it the 'target' org) from another (let's call it the 'source' org), since if the caller and service were the same org, you could just call the function directly :-)
Here's some sample code for the target org - it's very simple indeed:
It's a little bit more involved for the source org, since the source app needs to authenticate to the target org. All of the following instructions apply to the source org...
First, add https://login.salesforce.com/ and your target org instance URL (e.g. https://na1.salesforce.com or https://mycompany.my.salesforce.com) as Remote Sites (Setup | Administration Setup | Security Controls | Remote Site Settings).
Next, create a remote access application (Setup | App Setup | Develop | Remote Access) - you can just give it a dummy http://localhost as the callback URL.
Now you'll need to copy JSONObject from http://code.google.com/p/apex-library/source/browse/trunk/JSONObject/src/unpackaged/classes/JSONObject.cls?r=14 to a new Apex Class in your source org.
Finally, here's the sample code for the source org:
You will need to insert your remote access app consumer key/secret and target org username/password in the appropriate places in the restTest() method.
What that source app code does is to authenticate to the target org via OAuth username/password flow, then call the REST service and return the result. If you execute RestTest.restTest('Vikash'); in the system log, you should see Hello Vikash in the debug output.
Hi Pat,
I am very much thankful for your help, I am giving you my code so that you can get better Idea of wat I am doing
My target org code:
and my source org code is:
which on cliking submit button should give me 'Hi, You have invoked getservice1 created using Apex Rest and exposed using REST API'..........
Instead of which I am getting '[{"message":"API is disabled for this User","errorCode":"API_CURRENTLY_DISABLED"}]'
I have already added 'https://vikashtalanki-developer-edition.na12.force.com/services/apexrest/GetService' in Remote Sites (Setup | Administration Setup | Security Controls | Remote Site Settings).
Plz correct me if there is any flaw/mistake/wrong in the way I am doing this???
Does your user in the source org have API access? The error indicates that it does not.
Hi Vikash,
As Daniel says, the error message gives the clue - check that your user has API access.
Are you using one org here, or two?
Cheers,
Pat
Hi,
Yes I already have the API enabled and I am using same target and source org for this example. Since I am using a GET method , It should work if I open https://vikashtalanki-developer-edition.na12.force.com/services/apexrest/GetService is a browser but am getting a 403 error when I do this.
You are not sending an Authorization: OAuth {someToken} header to authenticate your request.
Yes I am not sending an Authorization: OAuth {someToken} header to authenticate my request. But is that neccessary???
You beat me, Simon :-)
Vikash - try adding the following line after req.setEndpoint(url);
Simon - it's a bit odd that it would complain about API being disabled, rather than a bad or missing token :-/
Yes - it's very necessary - all calls to the API [1] must carry a token.
[1] Well, nearly all - the 'root' /services/data call doesn't. You can call it without a token and it will tell you what API versions the REST service supports:
Take a bow Pat, Awesome , it worksssssssssssssssssssssssssssssssssssssss.................. Can you just explain me about this a little bit???
Sure - as I mentioned, every API call must have a token representing an authenticated user. The REST API accepts an OAuth token (pretty much a session ID) in the 'Authorization' HTTP header (see http://wiki.developerforce.com/index.php/Getting_Started_with_the_Force.com_REST_API for more detail). In Apex Code, UserInfo.getSessionId() returns you a session ID that you can then add as a header to your outbound call.
Now... Why are you using the REST API to call into your own org? Why not just call getservice1.postRestMethod()?
BTW - this mechanism WILL NOT WORK for calls from one org to another. For that you will need to obtain a token valid for the target org, which is much more tricky. I posted a username/password mechanism earlier in this thread. You can also do OAuth web server flow (see wiki.developerforce.com/index.php/Digging_Deeper_into_OAuth_2.0_on_Force.com) with code similar to https://github.com/metadaddy-sfdc/Force.com-Toolkit-for-Facebook/blob/master/src/classes/FacebookLoginController.cls - actually, that's code to do OAuth against Facebook, but the principle is the same :-)
Yeah Pat, when I try to call it from another org I am getting the following error
System.CalloutException: Unauthorized endpoint, please check Setup->Security->Remote site settings. endpoint = https://vikashtalanki-developer-edition.na12.force.com/services/apexrest/GetService?name=vikash
Class.getservice2.getNameMethod: line 19, column 29 Class.getservice2.submit: line 8, column 17 External entry point
Can you tell what to add in my code to get it authenticated
As the error message indicates, you need login to the web app, goto setup -> security -> remote sites and add new remote site for that url.
It has already been added in the remote site settings, I am worrying about authenticated code for my web app when I access it from another org
You have to add the target org host to the remote sites in the source org. You might have it the other way round...
I have added 'https://vikashtalanki-developer-edition.na12.force.com' as target host in remote site settings, but I am getting the error as " [{"message":"Could not find a match for URL /GetService","errorCode":"NOT_FOUND"}]".
What is to be added in remote site
'https://vikashtalanki-developer-edition.na12.force.com'
or
'https://vikashtalanki-developer-edition.na12.force.com/services/apexrest/GetService'
Just https://vikashtalanki-developer-edition.na12.force.com
Which end did you move to your new org - source or target?
source
Hmm - not sure. If you've made no other changes, you're passing a source org session ID to the target org in the Authentication header, you're confusing the hell out of things. I'm not even sure web server OAuth flow is going to work here, since all the cookies may end up in the same domain. Let me try it out on my DE org(s) and get back to you...
I apologise for messing up things.
This code is working fine if both source and target are in same org.
I need to know what the neccessary changes to be made in above code if the source is moved to another org
I just got this working. It's pretty tricky, because you have to get a token for a user in the target org. You can't do this using login.salesforce.com, since you'll likely just get a token for your source org, which won't work.
So... Step 1 - you need to set up 'My Domain' in the target org - Setup | Administration Setup | Company Profile | My Domain. Note that this takes a few hours to take effect. You'll end up with a domain name of the form yourdomain-developer-edition.my.salesforce.com. This is the key to getting org->org OAuth working - you need two distinct domain names.
Step 2. Here is the code for the source org. The target org is unchanged (apart from setting up My Domain):
Controller
Page - I called it 'RestCall'
Step 3. Create a remote access application (Setup | App Setup | Develop | Remote Access) in the source org with your Visualforce page URL (e.g. https://c.na12.visual.force.com/apex/RestCall) as the callback URL. Edit the controller to set the client ID and client secret values (consumer key and consumer secret in the remote access app page).
Now when you go to the page, you may be prompted to log in (depending on whether or not you have a session in the target org), then you should be prompted to allow the remote access app to access your data. After that, the REST call should take place and you'll see the result :-)
The code is a little horrible, since it puts the whole oauth response in a cookie, but it works :-) A better solution is to generate a random string for the cookie and save the oauth response in a custom object record, indexed by the cookie. You can see how that works in the latest Facebook toolkit code at https://github.com/metadaddy-sfdc/Force.com-Toolkit-for-Facebook - look at FacebookObject.cls.
BTW - it works equally well with a 'site' at the source org - then you only have to login to the target org.
Hi Pat,
I am trying the way you showed but unable to get the web page for logging in and it shows the error as http 400 bad request. I am totally stuck at this point
My code goes as below:
Hi Vikash,
You are still using UserInfo.getSessionId() - this won't work when you call between orgs - you have to use a token from the target org - the login() method gets this and puts it in a cookie.
Your getNameMethod should look like this:
Cheers,
Pat
Hi Pat,
I have made the neccessary changes in getNameMethod. But according to the code, as soon as the visual page loads, it should redirect me to https://vikashtalanki-developer-edition.my.salesforce.com where i can give my credentials. But I am geeting HTTP 400 Bad Request and the URL was https://vikashtalanki-developer-edition.my.salesforce.com/services/oauth2/authorize?client_id=3MVG9QDx8IX8nP5Qzkd59wBVVRJOGUaJhMDBir6pY11AnG1s4FHjSH0fMt1.9.yZPFGfCGnqPFrhQPfIK0mFB&redirec%E2%80%8Bt_uri=https%3A%2F%2Fc.na12.visual.force.com%2Fapex%2Frestservicepage&response_type=code
Hi Pat,
The Error it sowing is "redirect url mismatch", but as far as I know , we have followed your code correctly, Really stuck at this point
Hi Vikash,
Double check that the callback URL you set in the remote access app is identical to the one in your code - https://sandeepreddy-developer-edition--c.na12.visual.force.com/apex/restservicepage
Also - I notice some corruption in the URL you posted - 'redirec%E2%80%8Bt_uri' should be 'redirect_uri'. Also, in the source, you seem to have some corruption in the highlighted line:
That '&redirect_uri' seems to have some non-printable characters between '&redirec' and 't_uri'. If you retype that string, and make sure the URLs match, it should work.
Cheers,
Pat
Hi Pat,
The error is exaclty what you said, now its working very fine. We are done with it. Im very much thankful to you, you really helped us alot, Can you just tell me whether this OAuth authentication is one time authentication or not? Because when I open the page for the first time it is going to loginUrl as expected, & on subsequent access of that page , its directly giving the visual force page. But when I cloase the browser window and open again, it again taking me to loginUrl page, So I think this is not one time authentication, Can you guide about this plz
The authentication is session-based, just like logging in to login.salesforce.com. If you keep your browser open, you can keep going to pages within salesforce.com until your session expires (by default, I think this is 2 hours). Similarly for this integration here.
By the way - the code I posted is 'proof of concept' - it would need some tidying up for production. In particular, there is no error handling. To be robust, you will need to handle a 401 response (invalid or expired token) for the REST call. If you get a 401, you need to repeat the login process to get a new access token.
Hi Vikash,
Access tokens follow the same rules as regular Salesforce session IDs - they have an idle timeout that is set in Setup | Administration Setup | Session Settings | Session Timeout. The default session timeout is 2 hours, but an org admin can set it to any of a variety of values from 15 minutes to 8 hours. See https://login.salesforce.com/help/doc/user_ed.jsp?loc=help&target=admin_sessions.htm§ion=Security for more details.
Cheers,
Pat
Hi Pat,
I have a small idea, If I store the access token in a database at source org and use that for further communications btw source & target, do I still need to authorize when I access the target webservice from another machine? I mean, can the same accesstoken be used(until it doesnt get expired) from different machines to invoke target service??
Suppose user A logged into machine A with his own credentials and invoked the target web service ans as a result got accesstoken. Now can this accesstoken be used by another/same user on different machine, say machine B???
My intention is to authorize the target webservice only once(that is for the first time) and use that accesstoken from anywhere with out any need to authorize again.
Hi Vikash,
Yes, I think that would work - token expiration is your only problem.
You know, most of this stuff, you can just try it out quicker than writing a question here, and certainly quicker than waiting for a reply :smileywink:
Cheers,
Pat
Hi Vikash,
I've just been reminded that you receive a long-lived 'refresh token' as part of the response in the web server flow. You can store this persistently and use it to obtain a new access token when the old one expires. See my article for a deeper discussion of refresh tokens and their use.
Cheers,
Pat
Hi Pat,
As far as I know, storing the accesstoken in the database is not a better solution as far as security is concerned. Is there any way to implement this using OpenSSO??
Hi Vikash,
You would store the refresh token (not the access token) in the database for a given user. Why would this not be secure? You can lock security down so it is only accessible to the relevant user.
I don't think OpenSSO really helps, since you have one salesforce org calling another.
Cheers,
Pat
But is it not accessable even to administrators of salesforce??? I am looking for a very secured way of implementing OAuth(using this token) & also not making the user to give the credentials of target again & again(this is why I have stored it in db)
Hi Pat,
I am trying to explain my requirement in a better way here.................
As explained here , I have installed OpenAM in my machine which is acting as a identity provider. when I give the credentials to log into OpenAM , using SAML assertion I am able to login to salesforce without again giving Salesforce credentials. Now using the same SAML assertion(or token) I wanna invoke the service of target org from source org. The reason for this is , if I store the accesstoken in the cookie , it gets invalid once I close the browser. If I store accesstoken in db , It can be compromised(I am not so happy with this even if its safe).............. So I think this can be done in 2 ways(may be both are not possible)
1. Use this SAML assertion as accesstoken to invoke target org from source using Oauth (this may not possible as accesstoken is being given by authorizaton server)
2. Any other way to use this SAML assertion as token with out using OAuth concept.
I need your ideas on this &
Pat, your guidance is really putting me in the driving seat........... I am really thankful to you,
Hi Vikash,
If you have a SAML assertion issued by an IdP that your Salesforce org trusts, your app can exchange it for an OAuth token - see https://na1.salesforce.com/help/doc/en/remoteaccess_oauth_web_sso_flow.htm
Cheers,
Pat
"I assume that you're trying to call an Apex REST service in one org from another since if the caller and service were the same org, you could just call the function directly"
Not true. Salesforce is exposing functionality via its REST API (like chatter @mentions) that are not available from within Apex.
In my client's case, this is exactly what we'd like to do (that along with some reporting stuffs).
tggagne - you're right, and in that case (calling REST APIs in the same org) it's very straightforward - see http://blogs.developerforce.com/developer-relations/2011/03/calling-the-rest-api-from-javascript-in-visualforce-pages.html
I'd found that earlier, but I don't want to call it from Javascript. I want to talk to my own org from Apex.
tggagne - that is possible - vikash posted code to do exactly that earlier in this thread - http://boards.developerforce.com/t5/REST-API-Integration/Calling-Apex-Rest-custom-web-service-from-Apex-code/m-p/328359#M884
Hi Vikas,
how did you start writing sourchis code , can you write this one in apexclasses.
and how did you got source code url.
Please give me stepwise information
Thanks,
Sarvesh.
HttpRequest req = new HttpRequest();
HttpResponse res = new HttpResponse();
Http http = new Http();
//String endPoint1 = instanceURL + '/services/apexrest/Insurance';
req.setEndpoint('https://na15.salesforce.com/services/apexrest/Insurance');
req.setMethod('POST');
req.setHeader('Content-Type', 'application/json; charset=UTF-8');
req.setHeader('Accept', 'application/json');
req.setHeader('Authorization', 'OAuth'+sessionId);
req.setBody('{"Name" : "New1"}');
Please let me know if i m missing something or not.
this is my exception
Visualforce ErrorHelp for this Page
System.CalloutException: Invalid HTTP method: post
Error is in expression '{!getResult}' in component <apex:commandButton> in page wq: Class.getservice21.getResult: line 19, column 1
Class.getservice21.getResult: line 19, column 1
thanks in advance.
@Pat Patterson,
I used your code for connecting one salesforce Org to another using Connected App.
My code as Follows:
public class RestTest {
public String restTest { get; set; }
private static JSONObject oauthLogin(String loginUri, String clientId,
String clientSecret, String username, String password) {
HttpRequest req = new HttpRequest();
req.setMethod('GET');
req.setEndpoint(loginUri+'/services/oauth2/token');
req.setBody('grant_type=password' +
'&client_id=' + clientId +
'&client_secret=' + clientSecret +
'&username=' + EncodingUtil.urlEncode(username, 'UTF-8') +
'&password=' + EncodingUtil.urlEncode(password, 'UTF-8'));
Http http = new Http();
HTTPResponse res = http.send(req);
System.Debug('*****************************'+res);
System.debug('BODY: '+res.getBody());
System.debug('STATUS:'+res.getStatus());
System.debug('STATUS_CODE:'+res.getStatusCode());
return new JSONObject(res.getBody());
}
public static String restTest(String name) {
JSONObject oauth = oauthLogin('<a href= "https://login.salesforce.com" target="_blank">https://login.salesforce.com</a>',
'3MVG9Y6d_Btp4xp6GjWvM6Vqowzgn3PxnMwVPSeWRuVClTo..VJYYOIeHxVdyMpoeDeGeDC8MzTFwaPSLBoAU',
'4075257097069349419',
'abc@xyz.demo',
'*****');
String accessToken = oauth.getValue('access_token').str,
instanceUrl = oauth.getValue('instance_url').str;
HttpRequest req = new HttpRequest();
req.setMethod('GET');
req.setEndpoint(instanceUrl+'/services/apexrest/Account35?name='+name);
req.setHeader('Authorization', 'OAuth '+accessToken);
Http http = new Http();
HTTPResponse res = http.send(req);
System.debug('BODY: '+res.getBody());
System.debug('STATUS:'+res.getStatus());
System.debug('STATUS_CODE:'+res.getStatusCode());
return res.getBody();
}
}
I am getting error Like:
System.CalloutException: no protocol: <a href= "https://login.salesforce.com"; target="_blank">https://login.salesforce.com</a>/services/oauth2/token
Class.prabhaks.RestTest.oauthLogin: line 18, column 1
Class.prabhaks.RestTest.restTest: line 28, column 1
How to Resolve this..
Any one can help .
Thanks in advance...
I've also added my domain name "https://akashdalvi-dev-ed.my.salesforce.com" in Remote site setting.I'm getting "URL No Longer Exists" error whenever I'm executing the VF page.Please find the below codes.
Target code :
@RestResource(urlMapping='/GetService/*')
global with sharing class getservice1
{
@HttpGet
global static String getRestMethod()
{
RestRequest req = RestContext.request;
RestResponse res = RestContext.response;
String name = req.params.get('name');
return 'Hello '+name+', you have just invoked a custom Apex REST web service exposed using REST API' ;
}
}
Source Code :
public abstract class OAuthRestController {
static String clientId = '3MVG9Y6d_Btp4xp7FzYaVu5vF77qEk0y_iGvHzdmXyk71gTL75PCoi5rOmM7f_kMqP9THR3Y7iXsRl9i_DwZg'; // Set this in step 3
static String clientSecret = '4692916739247047542'; // Set this in step 3
static String redirectUri = '<a href="https://c.ap1.visual.force.com/apex/GetService" target="_blank" rel="nofollow">https://c.ap1.visual.force.com/apex/GetService</a>'; // YOUR PAGE URL IN THE SOURCE ORG
static String loginUrl = '<a href="https://akashdalvi-dev-ed.my.salesforce.com" target="_blank" rel="nofollow">https://akashdalvi-dev-ed.my.salesforce.com</a>'; // YOUR MY DOMAIN URL IN THE TARGET ORG
static String cookieName = 'oauth';
public PageReference login() {
// Get a URL for the page without any query params
String url = ApexPages.currentPage().getUrl().split('\\?')[0];
System.debug('url is '+url);
String oauth = (ApexPages.currentPage().getCookies().get(cookieName) != null ) ?
ApexPages.currentPage().getCookies().get(cookieName).getValue() : null;
if (oauth != null) {
// TODO - Check for expired token
}
System.debug('oauth='+oauth);
if (oauth != null) {
// All done
return null;
}
// If we get here we have no token
PageReference pageRef;
if (! ApexPages.currentPage().getParameters().containsKey('code')) {
// Initial step of OAuth - redirect to OAuth service
System.debug('OAuth Step 1');
String authuri = loginUrl+'/services/oauth2/authorize?'+
'response_type=code&client_id='+clientId+'&redirect_uri='+redirectUri;
pageRef = new PageReference(authuri);
} else {
// Second step of OAuth - get token from OAuth service
String code = ApexPages.currentPage().getParameters().get('code');
System.debug('OAuth Step 2 - code:'+code);
String body = 'grant_type=authorization_code&client_id='+clientId+
'&redirect_uri='+redirectUri+'&client_secret='+clientSecret+
'&code='+code;
System.debug('body is:'+body);
HttpRequest req = new HttpRequest();
req.setEndpoint(loginUrl+'/services/oauth2/token');
req.setMethod('POST');
req.setBody(body);
Http h = new Http();
HttpResponse res = h.send(req);
String resp = res.getBody();
System.debug('FINAL RESP IS:'+EncodingUtil.urlDecode(resp, 'UTF-8'));
ApexPages.currentPage().setCookies(new Cookie[]{new Cookie(cookieName,
res.getBody(), null,-1,false)});
// Come back to this page without the code param
// This makes things simpler later if you end up doing DML here to<br> // save the token somewhere<br> pageRef = new PageReference(url);
pageRef.setRedirect(true);
}
return pageRef;
}
public static String getRestTest() {
String oauth = (ApexPages.currentPage().getCookies().get(cookieName) != null ) ?
ApexPages.currentPage().getCookies().get(cookieName).getValue() : null;
JSONObject oauthObj = new JSONObject( new JSONObject.JSONTokener(oauth));
String accessToken = oauthObj.getValue('access_token').str,
instanceUrl = oauthObj.getValue('instance_url').str;
HttpRequest req = new HttpRequest();
req.setMethod('GET');
req.setEndpoint(instanceUrl+'/services/apexrest/superpat/TestRest?name=Pat');
req.setHeader('Authorization', 'OAuth '+accessToken);
Http http = new Http();
HTTPResponse res = http.send(req);
System.debug('BODY: '+res.getBody());
System.debug('STATUS:'+res.getStatus());
System.debug('STATUS_CODE:'+res.getStatusCode());
return res.getBody();
}
}
Page :
<apex:page controller="OAuthRestController" action="{!login}">
<h1>Rest Test</h1>
getRestTest says "{!restTest}"
</apex:page>
Please share your valuable thoughts and kindly reply me, what went wrong and how would I fix it?
I was able to set up a sandbox to call itself. Let's call this one SB1. I set up the Remote Site and the Connected App and was able to have it call out to itself which is great.
But then I next tried to move all of my code to a new sandbox (Let's call it SB2). I wanted that new sandbox to try and connect to itself. So, I set up a Remote Site & Connected App, but I always get an authentication error.
Then, I tried this next:
In SB2 I created a Remote Site with the endpoint of SB1 so that I could try calling SB1 from SB2. Also I used the client Id and client Secret of SB1. I used my username/password for Sb2 and it worked.
I am really confused because I didn't think I could use the url, clientId, clientSecret for SB1 with the credentials of SB2.
And further, I don't know why I can't use credentials of SB2 with the url, clientId, clientSecrent of SB2.
I'm wondering if I only need 1 Connected App for all my Sandboxes? Any help would be apprectiated.
Thanks,
Chris
Could you please help me in this problem ,
In fact , i need to get the authorization code automatically in the controller without redirecting to the authorization URL to hget the code (using pagereference). Is there a manner to do it?
In fact from an enduser side , when clicking on a button he is automatically connected without being interrupted with the link to get the code.
Thank you for your help,
this helped me to call a Webservice as callout in another Webservice in same org.
Thank you for posting solution. @ Pat Patterson
This will help you for sure.Here rest api integration is performed by taking example of two salesforce system.
https://www.sfdc-lightning.com/2019/01/salesforce-rest-api-integration.html
I Use this code and it is working Fine.
Source Url:- https://fintechhop.com/can-you-add-venmo-to-google-pay/
I get this code from https://filipinoguru.com/how-to-top-up-shopeepay-using-gcash/