diff --git a/ShippingRates/Models/FedEx/FedExErrorResponse.cs b/ShippingRates/Models/FedEx/FedExErrorResponse.cs
new file mode 100644
index 0000000..2643b16
--- /dev/null
+++ b/ShippingRates/Models/FedEx/FedExErrorResponse.cs
@@ -0,0 +1,18 @@
+using System.Text.Json.Serialization;
+
+namespace ShippingRates.Models.FedEx
+{
+    class FedExErrorResponse
+    {
+        [JsonPropertyName("errors")]
+        public FedExErrorItem[] Errors { get; set; }
+    }
+
+    class FedExErrorItem
+    {
+        [JsonPropertyName("code")]
+        public string Code { get; set; }
+        [JsonPropertyName("message")]
+        public string Message { get; set; }
+    }
+}
diff --git a/ShippingRates/Services/FedEx/FedExOAuthService.cs b/ShippingRates/Services/FedEx/FedExOAuthService.cs
new file mode 100644
index 0000000..53d496b
--- /dev/null
+++ b/ShippingRates/Services/FedEx/FedExOAuthService.cs
@@ -0,0 +1,77 @@
+using ShippingRates.ShippingProviders;
+using System;
+using System.Collections.Generic;
+using System.Net.Http;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using System.Threading.Tasks;
+
+namespace ShippingRates.Services.FedEx
+{
+    internal class FedExOAuthService
+    {
+        static string GetOAuthRequestUri(bool isProduction)
+            => $"https://{(isProduction ? "apis" : "apis-sandbox")}.fedex.com/oauth/token";
+
+        public static async Task<string> GetTokenAsync(FedExProviderConfiguration configuration, HttpClient httpClient, Action<Error> reportError)
+        {
+            var token = TokenCacheService.GetToken(configuration.ClientId);
+            if (!string.IsNullOrEmpty(token))
+                return token;
+
+            var requestMessage = new HttpRequestMessage(HttpMethod.Post, GetOAuthRequestUri(configuration.UseProduction));
+            var postData = new List<KeyValuePair<string, string>>
+            {
+                new KeyValuePair<string, string>("grant_type", "client_credentials"),
+                new KeyValuePair<string, string>("client_id", configuration.ClientId),
+                new KeyValuePair<string, string>("client_secret", configuration.ClientSecret)
+            };
+            requestMessage.Content = new FormUrlEncodedContent(postData);
+
+            var responseMessage = await httpClient.SendAsync(requestMessage);
+            var response = await responseMessage.Content.ReadAsStringAsync();
+
+            if (responseMessage.IsSuccessStatusCode)
+            {
+                var result = JsonSerializer.Deserialize<FedExOAuthResponse>(response);
+
+                TokenCacheService.AddToken(configuration.ClientId, result.AccessToken, result.ExpiresIn);
+
+                return result.AccessToken;
+            }
+            else
+            {
+                var errorResponse = JsonSerializer.Deserialize<Models.FedEx.FedExErrorResponse>(response);
+                if ((errorResponse?.Errors?.Length ?? 0) > 0)
+                {
+                    foreach (var error in errorResponse.Errors)
+                    {
+                        reportError(new Error()
+                        {
+                            Number = error.Code,
+                            Description = error.Message
+                        });
+                    }
+                }
+                else
+                {
+                    reportError(new Error() { Description = $"Unknown error while fetching FedEx OAuth token: {responseMessage.StatusCode} {response}" });
+                }
+
+                return null;
+            }
+        }
+
+        class FedExOAuthResponse
+        {
+            [JsonPropertyName("token_type")]
+            public string TokenType { get; set; }
+            [JsonPropertyName("access_token")]
+            public string AccessToken { get; set; }
+            [JsonPropertyName("expires_in")]
+            public int ExpiresIn { get; set; }
+            [JsonPropertyName("scope")]
+            public string Scope { get; set; }
+        }
+    }
+}
diff --git a/ShippingRates/Services/TokenCacheService.cs b/ShippingRates/Services/TokenCacheService.cs
index 882b506..d9501c4 100644
--- a/ShippingRates/Services/TokenCacheService.cs
+++ b/ShippingRates/Services/TokenCacheService.cs
@@ -4,7 +4,7 @@
 namespace ShippingRates.Services
 {
     /// <summary>
-    /// Token caching for UPS
+    /// Token caching for UPS, FedEx
     /// </summary>
     internal class TokenCacheService
     {
@@ -12,7 +12,7 @@ internal class TokenCacheService
         /// <summary>
         /// Get token for a given client ID
         /// </summary>
-        /// <param name="clientId">UPS Client ID</param>
+        /// <param name="clientId">Client ID</param>
         /// <returns>Token string or null</returns>
         public static string GetToken(string clientId)
         {
@@ -29,7 +29,7 @@ public static string GetToken(string clientId)
         /// <summary>
         /// Add token
         /// </summary>
-        /// <param name="clientId">UPS Client ID</param>
+        /// <param name="clientId">Client ID</param>
         /// <param name="token">Token</param>
         /// <param name="expiresIn">Expiration interval in seconds</param>
         public static void AddToken(string clientId, string token, int expiresIn)
diff --git a/ShippingRates/ShippingProviders/FedExProviderConfiguration.cs b/ShippingRates/ShippingProviders/FedExProviderConfiguration.cs
index a502086..db400f7 100644
--- a/ShippingRates/ShippingProviders/FedExProviderConfiguration.cs
+++ b/ShippingRates/ShippingProviders/FedExProviderConfiguration.cs
@@ -8,6 +8,9 @@ namespace ShippingRates.ShippingProviders
 {
     public class FedExProviderConfiguration
     {
+        /// <summary>
+        /// FedEx Account Number
+        /// </summary>
         public string AccountNumber { get; set; }
         public string Key { get; set; }
         public string MeterNumber { get; set; }
@@ -19,5 +22,13 @@ public class FedExProviderConfiguration
         /// If not using the production Rate API, you can use 5531 as the HubID per FedEx documentation.
         /// </summary>
         public string HubId { get; set; }
+        /// <summary>
+        /// FedEx Client Id (required for REST API)
+        /// </summary>
+        public string ClientId { get; set; }
+        /// <summary>
+        /// FedEx Client Secret (required for REST API)
+        /// </summary>
+        public string ClientSecret { get; set; }
     }
 }