Rest API via C#

Forum for users and developers of Bullhorn's API service.

Moderators: StaffingSupport, s.emmons, BullhornSupport

Post Reply
sbrown
User
Posts: 1
Joined: Wed Nov 14, 2018 4:26 pm

Rest API via C#

Post by sbrown » Thu Nov 15, 2018 9:59 am

Does anyone have a sample using c# to get an authorization token and login to Rest API?

riyasathe
User
Posts: 2
Joined: Sat Mar 16, 2019 3:18 am

Re: Rest API via C#

Post by riyasathe » Sat Mar 16, 2019 3:27 am

Not yet! I have a same question.

newclique
User
Posts: 4
Joined: Fri Mar 22, 2019 6:21 pm

Re: Rest API via C#

Post by newclique » Fri Mar 22, 2019 6:26 pm

Here's how I do it in a console app. Be sure to add the necessary references and Newtonsoft nuget pkg:

Code: Select all

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net;
using System.Web;

using System.IO;
using Newtonsoft.Json;

namespace GrabBullhorn
{
    class Program
    {
        static string _ClientID = "your-id-here";
        static string _Secret = "your-secret-here";
        static string _Username = "whatever.restapi";
        static string _Password = "your web login pwd";
        static string _authCode = "";
        static string _stateCode = Guid.NewGuid().ToString(); //prob not necessary
        static BullhornTokens _tokens = null;

        static void Main(string[] args)
        {
            _authCode = getBHAuthCode();
            if (!string.IsNullOrEmpty(_authCode))
            {
                _tokens = getAccessToken();
                if (_tokens != null)
                {
                    //can make api calls for 10 minutes...

                    //...or make a refresh token call, get a refresh token (if your account is allowed one), and then make calls for hours
                    Console.WriteLine("Authentated successfully! You can now implement your API calls for entities, etc.");
                    Console.WriteLine("Press {Enter} to exit...");
                    Console.ReadLine();
                    Environment.Exit(0);
                }
            }
            Console.WriteLine("Unable to authenticate and generate an access token.\nPress {Enter} to exit...");
            Console.ReadLine();
            Environment.Exit(-1); //booo!
        }

        static string getBHAuthCode()
        {
            string url = $@"https://auth.bullhornstaffing.com/oauth/authorize?client_id={_ClientID}&response_type=code&action=Login&username={_Username}&password={_Password}&state={_stateCode}";
            
            var request = HttpWebRequest.CreateHttp(url);
            request.Method = "GET";
            var responseObj = (HttpWebResponse)request.GetResponse();
            string response = null;

            using (var streamReader = new StreamReader(responseObj.GetResponseStream()))
            {
                if (responseObj.StatusCode == HttpStatusCode.OK)
                {
                    var vals = System.Web.HttpUtility.ParseQueryString(responseObj.ResponseUri.Query);
                    if (vals.HasKeys() && vals["code"] != null)
                    {
                        response = vals["code"];
                    }
                }
            }
            
            return response;
        }

        static BullhornTokens getAccessToken()
        {
            string url = $@"https://auth.bullhornstaffing.com/oauth/token?grant_type=authorization_code&code={_authCode}&client_id={_ClientID}&client_secret={_Secret}";
            var request = HttpWebRequest.CreateHttp(url);
            request.Method = "POST";
            request.Accept = "application/json";
            var responseObj = (HttpWebResponse)request.GetResponse();

            using (var streamReader = new StreamReader(responseObj.GetResponseStream()))
            {
                if (responseObj.StatusCode == HttpStatusCode.OK)
                {
                    var content = streamReader.ReadToEnd();
                    var tokens = JsonConvert.DeserializeObject<BullhornTokens>(content);
                    return tokens;
                }
            }
            return null;
        }

    }

    public class BullhornTokens
    {
        public BullhornTokens()
        {
            this.TokensCreated = DateTime.Now;
        }
        public DateTime? TokensCreated { get; private set; }

        [JsonProperty(PropertyName="access_token")]
        public string AccessToken { get; set; }
        [JsonProperty(PropertyName ="expires_in")]
        public int ExpiresInSeconds { get; set; }
        [JsonProperty(PropertyName ="token_type")]
        public string TokenType { get; set; }
        [JsonProperty(PropertyName="refresh_token")]
        public string RefreshToken { get; set; }

        DateTime? _expiration;
        public DateTime? GetExipration()
        {
            if (_expiration.HasValue || !TokensCreated.HasValue)
                return _expiration;

            _expiration = TokensCreated.Value.AddSeconds(ExpiresInSeconds);
            return _expiration;
        }
    }
}

newclique
User
Posts: 4
Joined: Fri Mar 22, 2019 6:21 pm

Re: Rest API via C#

Post by newclique » Mon Apr 08, 2019 3:59 pm

Oops, I left out two final steps. First, you have to get the REST Token (could this API be any more complex?) and the CorpID with yet another login call. Second, you have to use these new pieces of information to try to call the REST services themselves.

Code: Select all

_tokens = getAccessToken();
                if (_tokens != null)
                {
                    _RESTInfo = getBHRESTLoginInfo();
                    if (_RESTInfo != null)
                    {
                        //can make api calls for 10 minutes...
                        var url = new Uri(_RESTInfo.REST_URL);
                        string corptoken = url.Segments[url.Segments.Count()-1];
                        corptoken = corptoken.Remove(corptoken.Length - 1, 1);
                        string fieldlist = "id,title";
                        //can make api calls for 10 minutes...
                        var request = HttpWebRequest.CreateHttp($@"https://rest.bullhornstaffing.com/rest-services/{corptoken}/search/JobOrder?BhRestToken={_RESTInfo.REST_Token}&fields={fieldlist}");
                        
                        request.Method = "GET";
                        var responseObj = (HttpWebResponse)request.GetResponse();
                        string response = null;

                        using (var streamReader = new StreamReader(responseObj.GetResponseStream()))
                        {
                            if (responseObj.StatusCode == HttpStatusCode.OK)
                            {
                                var vals = System.Web.HttpUtility.ParseQueryString(responseObj.ResponseUri.Query);
                                if (vals.HasKeys() && vals["code"] != null)
                                {
                                    response = vals["code"];
                                }
                            }
                        }
                        //...or make a refresh token call, get a refresh token (if your account is allowed one), and then make calls for hours
                        Console.WriteLine("Authentated successfully! You can now implement your API calls for entities, etc.");
                        
I had to implement one more class to hold the de-serialized JSON of this rest login call:

Code: Select all

    public class BullhornRESTLoginInfo
    {
        [JsonProperty(PropertyName = "BhRestToken")]
        public string REST_Token { get; set; }
        [JsonProperty(PropertyName ="restUrl")]
        public string REST_URL { get; set; }
    }
Be warned: The examples in the API guide do not seem to be accurate. Currently, following those guides, I am getting this back from a call asking for all of my current JobOrder entities (using the exact format and path listed in their API guide):

Code: Select all

{"error":"This URL is reserved for login purposes."}
So, I dunno if it is me or it is their documentation. I'll post back when I actually get some data. Here's the URL format I am using (with my tokens, obviously):

Code: Select all

https://rest.bullhornstaffing.com/rest-services/{corptoken}/search/JobOrder?BhRestToken={_RESTInfo.REST_Token}&fields={fieldlist}

newclique
User
Posts: 4
Joined: Fri Mar 22, 2019 6:21 pm

Re: Rest API via C#

Post by newclique » Mon Apr 08, 2019 4:18 pm

Ugh. My last post had errors due to the API guide not being very clear about needing to use the returned REST_URL from the rest-login call to make all future calls. I'm sure this was obvious to some but the examples that show {corpToken} really threw me because I thought that meant I needed to use their exact example URLs and just tack on my token. IMHO, the API guide should {parameterize} everything in the URL that you need to sub out for yourself, e.g. {Returned REST_URL}/{your query here}

If this does't give someone enough information to finish the C# code, let me know and I will gladly post the finished product.

redaw77
User
Posts: 1
Joined: Thu May 16, 2019 4:18 am

Re: Rest API via C#

Post by redaw77 » Thu May 16, 2019 4:23 am

Hi, Thank you for posting this, its very helpful. I agree the BH documentation is a bit confusing, especially as it seem to constantly change.

I was just trying to use the code, but there is no method 'getBHRESTLoginInfo'. do you have this to share? Any help would be greatly appreciated.

Thanks

newclique
User
Posts: 4
Joined: Fri Mar 22, 2019 6:21 pm

Re: Rest API via C#.net

Post by newclique » Fri Sep 27, 2019 10:59 am

Alrighty, scratch all that code above. This is how I am now doing it. There's one class with all the "meat" and then a driver (program.cs) to test it out as a console app. Just replace the class variables with your own keys and credentials.

------------------------

Code: Select all

namespace GrabBullhorn
{
    public class MPAuth
    {
        static string _ClientID = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx";
        static string _Secret = "????????????????????????"; //24 characters - can't remember where this came from...maybe the REST signup?
        static string _Username = "username.restapi";  //log into the UI and register for REST to get this
        static string _Password = "************";  //REST password, I think
        static string _stateCode = Guid.NewGuid().ToString();
        static BullhornRESTLoginInfo _RESTInfo = null;

        /// <summary>
        /// Call this third. 
        /// </summary>
        /// <returns></returns>
        public static BullhornRESTLoginInfo getBHRESTLoginInfo(BullhornTokens tokens)
        {
            string url = $@"https://rest.bullhornstaffing.com/rest-services/login?version=2.0&access_token={tokens.AccessToken}";
            var request = HttpWebRequest.CreateHttp(url);
            request.Method = "POST";
            request.Accept = "application/json";
            var responseObj = (HttpWebResponse)request.GetResponse();

            using (var streamReader = new StreamReader(responseObj.GetResponseStream()))
            {
                if (responseObj.StatusCode == HttpStatusCode.OK)
                {
                    var content = streamReader.ReadToEnd();
                    var restTokens = JsonConvert.DeserializeObject<BullhornRESTLoginInfo>(content);
                    return restTokens;
                }
            }
            return null;
        }

        /// <summary>
        /// Call this first. Send the results to getAccessToken().
        /// </summary>
        /// <returns></returns>
        public static string getBHAuthCode()
        {
            string url = $@"https://auth.bullhornstaffing.com/oauth/authorize?client_id={_ClientID}&response_type=code&action=Login&username={_Username}&password={_Password}&state={_stateCode}";

            var request = HttpWebRequest.CreateHttp(url);
            request.Method = "GET";
            var responseObj = (HttpWebResponse)request.GetResponse();
            string response = null;

            using (var streamReader = new StreamReader(responseObj.GetResponseStream()))
            {
                if (responseObj.StatusCode == HttpStatusCode.OK)
                {
                    var vals = System.Web.HttpUtility.ParseQueryString(responseObj.ResponseUri.Query);
                    if (vals.HasKeys() && vals["code"] != null)
                    {
                        response = vals["code"];
                    }
                }
            }

            return response;
        }

        /// <summary>
        /// Call this second.
        /// </summary>
        /// <param name="authCode">Obtain this from a successful call to getBHAuthCode().</param>
        /// <returns></returns>
        public static BullhornTokens getAccessToken(string authCode)
        {
            string url = $@"https://auth.bullhornstaffing.com/oauth/token?grant_type=authorization_code&code={authCode}&client_id={_ClientID}&client_secret={_Secret}";
            var request = HttpWebRequest.CreateHttp(url);
            request.Method = "POST";
            request.Accept = "application/json";
            var responseObj = (HttpWebResponse)request.GetResponse();

            using (var streamReader = new StreamReader(responseObj.GetResponseStream()))
            {
                if (responseObj.StatusCode == HttpStatusCode.OK)
                {
                    var content = streamReader.ReadToEnd();
                    var tokens = JsonConvert.DeserializeObject<BullhornTokens>(content);
                    return tokens;
                }
            }
            return null;
        }
    }

    public class BHServiceData
    {
        public delegate void MessageDelegate(string message);

        public static event MessageDelegate OnServiceMessage;

        public static List<BHJobOrder> GetOpenJobOrders(BullhornRESTLoginInfo RESTInfo)
        {
            //can make api calls for 10 minutes...
            string query = "isOpen:1+AND+isPublic:1+AND+isDeleted:0";
            string fieldlist = "address,costCenter,dateAdded,dateClosed,dateEnd,employmentType,branchCode,id,isInterviewRequired,isOpen,isPublic,payRate,publicDescription,salary,status,title,type";
            var request = HttpWebRequest.CreateHttp($@"{RESTInfo.REST_URL}search/JobOrder?fields={fieldlist}&query={query}&BhRestToken={RESTInfo.REST_Token}");

            //broadcast what we're calling
            if (OnServiceMessage != null)
                OnServiceMessage("CALLING: " + request.RequestUri.ToString());

            request.Method = "GET";
            var responseObj = (HttpWebResponse)request.GetResponse();
            List<BHJobOrder> response = null;

            using (var streamReader = new StreamReader(responseObj.GetResponseStream()))
            {
                if (responseObj.StatusCode == HttpStatusCode.OK)
                {
                    var content = streamReader.ReadToEnd();
                    var data = JsonConvert.DeserializeObject<BHResults>(content);
                    response = JsonConvert.DeserializeObject<List<BHJobOrder>>(data.data.ToString());
                }
            }
            return response;
        }
    }

    /// <summary>
    /// This class' values allow you to make REST calls to the REST_URL.
    /// </summary>
    public class BullhornRESTLoginInfo
    {
        /// <summary>
        /// Include this in the query path of all REST calls as .../path?BhRestToken=xxxxx
        /// </summary>
        [JsonProperty(PropertyName = "BhRestToken")]
        public string REST_Token { get; set; }
        /// <summary>
        /// Make all REST calls to the URL contained in this property.
        /// </summary>
        [JsonProperty(PropertyName = "restUrl")]
        public string REST_URL { get; set; }
    }

    public class BullhornTokens
    {
        public BullhornTokens()
        {
            this.TokensCreated = DateTime.Now;
        }
        public DateTime? TokensCreated { get; private set; }

        [JsonProperty(PropertyName = "access_token")]
        public string AccessToken { get; set; }
        [JsonProperty(PropertyName = "expires_in")]
        public int ExpiresInSeconds { get; set; }
        [JsonProperty(PropertyName = "token_type")]
        public string TokenType { get; set; }
        [JsonProperty(PropertyName = "refresh_token")]
        public string RefreshToken { get; set; }

        DateTime? _expiration;
        public DateTime? GetExipration()
        {
            if (_expiration.HasValue || !TokensCreated.HasValue)
                return _expiration;

            _expiration = TokensCreated.Value.AddSeconds(ExpiresInSeconds);
            return _expiration;
        }
    }
    public class BHResults
    {
        //{"total":1,"start":0,"count":1,"data":[{...}]
        public int count { get; set; }
        public dynamic data { get; set; }
        public int total { get; set; }
        public int start { get; set; }
    }
    public class BHAddress
    {
        public string address1 { get; set; }
        public string city { get; set; }
        public string state { get; set; }
        public string zip { get; set; }
        public int countryID { get; set; }
    }
    public class BHJobOrder
    {
        public BHAddress address { get; set; }
        public string costCenter { get; set; }
        [JsonConverter(typeof(JSTimeConverter))]
        public DateTime? dateAdded { get; set; }
        [JsonConverter(typeof(JSTimeConverter))]
        public DateTime? dateClosed { get; set; }
        [JsonConverter(typeof(JSTimeConverter))]
        public DateTime? dateEnd { get; set; }
        public string employmentType { get; set; }
        public string branchCode { get; set; }
        public int id { get; set; }
        public bool isInterviewRequired { get; set; }
        public bool isOpen { get; set; }
        public bool isPublic { get; set; }
        public float payRate { get; set; }
        public string publicDescription { get; set; }
        public float salary { get; set; }
        public string status { get; set; }
        public string title { get; set; }
        public int type { get; set; }
    }

    public class JSTimeConverter : DateTimeConverterBase
    {
        private static readonly DateTime _epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);

        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            writer.WriteRawValue(((DateTime)value - _epoch).TotalMilliseconds.ToString()); // + "000"); not sure about the necessity of the zeroes
        }

        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            if (reader.Value == null) { return null; }
            var ticks = (Convert.ToInt64(reader.Value.ToString()) * 10000) + 621355968000000000;
            return new DateTime(ticks);
        }
    }
}
And here's the driver:
----------------------------------

Code: Select all

using System;
using System.Collections.Generic;

namespace GrabBullhorn
{
    class Program
    {
        static void Main(string[] args)
        {
            string _authCode = MPAuth.getBHAuthCode();
            if (!string.IsNullOrEmpty(_authCode))
            {
                BullhornTokens _tokens = MPAuth.getAccessToken(_authCode);
                if (_tokens != null)
                {
                    BullhornRESTLoginInfo _RESTInfo = MPAuth.getBHRESTLoginInfo(_tokens);
                    if (_RESTInfo != null)
                    {
                        BHServiceData.OnServiceMessage += new BHServiceData.MessageDelegate(msg => Console.WriteLine(msg));
                        try
                        {
                            List<BHJobOrder> jobs = BHServiceData.GetOpenJobOrders(_RESTInfo);

                            //...or make a refresh token call, get a refresh token (if your account is allowed one), and then make calls for hours
                            Console.WriteLine("Authentated successfully! You can now implement your API calls for entities, etc.");
                        }catch (Exception ex)
                        {
                            Console.WriteLine("ERROR: " + ex.Message);
                        }
                        Console.WriteLine("Press {Enter} to exit...");
                        Console.ReadLine();
                        Environment.Exit(0);
                    }
                }
            }
            Console.WriteLine("Unable to authenticate and generate an access token.\nPress {Enter} to exit...");
            Console.ReadLine();
            Environment.Exit(-1); //booo!
        }
    }
}

Post Reply