--- title: "Security & Authentication" slug: "security-authentication" updated: 2026-07-13T15:41:55Z published: 2026-07-13T15:41:55Z canonical: "help.yahooinc.com/security-authentication" --- > ## Documentation Index > Fetch the complete documentation index at: https://help.yahooinc.com/llms.txt > Use this file to discover all available pages before exploring further. # Security & Authentication All DataX API calls must take place over HTTPS. The Yahoo DataX API utilizes the **OAuth 2.0 protocol** as a **standard and secure protocol** for handling client authentication and access control. The following are the prerequisite steps for obtaining an Access Token and successfully invoking the Yahoo DataX API. > [!WARNING] > #### Important > > - Token caching and reuse is required. New tokens should only be requested following token expiration. Clients are required to cache and reuse the token, only requesting a new token after the existing one has expired. > - The OAuth 2.0 Access Token Time-to-Live (TTL) has been increased from 3,600 seconds (1 hour) to 21,600 seconds (6 hours). > - The `expires_in` field within the JSON response determines the token’s exact validity and lifetime, ensuring future-proofing against potential changes to the default TTL. ## Authenticate the DataX API [Step 1 Get Your Client id and Client Secret](/datax/docs/security-authentication#step-1-get-your-client-id-and-client-secret) [Step 2 Generate the Access Token](/datax/docs/security-authentication#step-2-generate-the-access-token) - [Python](/datax/docs/security-authentication#python) - [Java](/datax/docs/security-authentication#java) - [C#](/datax/docs/security-authentication#c) [Step 3 Access the API](/datax/docs/security-authentication#step-3-access-the-api) --- ### Step 1 Get Your Client ID and Client Secret The Client ID and Client Secret are **mandatory credentials** required to access the Yahoo DataX API. These credentials must be obtained through a cryptographic key generation and secure exchange process. 1. Generate the **Private Key** using OpenSSL. ```plaintext openssl genpkey -aes256 -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out private_key.pem ``` 2. Using the generated Private Key, create the corresponding **Public Key**: ```plaintext openssl rsa -in private_key.pem -out public_key.pem -outform PEM -pubout ``` 3. Email the Public Key along with your onboarding request to [dataoperations@yahooinc.com](mailto:dataoperations@yahooinc.com). Yahoo will return an **encrypted file** containing the Client ID and Client Secret. 4. Decrypt this file using the **Private Key**. ```plaintext openssl rsautl -decrypt -inkey private_key.pem -in credential.enc -out my_credentials.txt ``` ### Step 2 Generate the Access Token DataX leverages the OAuth 2.0 `client_credentials` **grant type** and employs a **JSON Web Token (JWT)** mechanism for client assertion, which is then used to generate the final Access Token. The generated Access Token must be presented in the following `Authorization` header when making requests to the DataX Batch or DataX Online API. | Header | Value | Description | | --- | --- | --- | | `Authorization` | Bearer Use client ID and secret to generate the access token. | The value is the access token the JWT generates using the client ID and secret (refer to steps below). | #### JSON Web Token Construction The OAuth 2.0 client assertion uses a JWT to identify the client. A JWT comprises three distinct parts: - **Header** specifies the signing algorithm (`alg`) and token type (`typ`) - **Payload** (claims) contains a required set of claims for client identification. - **Signature** is computed using the **Client Secret** and the **HS256 algorithm** to ensure integrity. > [!WARNING] > Important > > It is strongly recommended that developers utilize a robust, third-party JWT library that supports the HS256 algorithm (for example, resources listed on [JWT Official Website](https://jwt.io/libraries)) to programmatically automate the generation of this JWT, eliminating the need for manual construction. **Header** ```json {   "alg" : "HS256"   "typ": "jwt" } ``` **Payload** ```json {   "aud": "https://id.b2b.yahooincapis.com/zts/v1",   "iss" : "",   "sub": "",   "iat" : ,   "exp" : } ``` **Signature** ```json {   "signature" : "" } ``` Generate the signature as follows. ```json jwt_signing_string = base64url_encode(header) + ‘.’ + base64url_encode(body) jwt_signature = base64url_encode(hmac_sha256(jwt_signing_string, client_secret)) final_jwt = jwt_signing_string + ‘.’ + jwt_signature ``` Sample of final JWT. ```plaintext eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJodHRwczovL2lkLmIyYi55YWhvb2luY2FwaXMuY29tL3p0cy92MSIsImlzcyI6ImlkYjJiLmRzcC5kYXRheC5iYXRjaC44ZDRkMjI2MC0yYTkyLTRjYjAtYmE1ZC0zYTMzODc0NjZiOGQiLCJzdWIiOiJpZGIyYi5kc3AuZGF0YXguYmF0Y2guOGQ0ZDIyNjAtMmE5Mi00Y2IwLWJhNWQtM2EzMzg3NDY2YjhkIiwiaWF0IjoxNzYyODUxODc0LCJleHAiOjE3NjI4NTI0NzR9.gVziT4YHIx-9MqEzvbn0fUbC4TBnurCBUR_3t8ugbjQ .. ``` > [!NOTE] > Note > > They are separated with a “.”. #### Generate a JWT Access Token The generated JWT is exchanged for the final Access Token by submitting a request to the OAuth 2.0 Token Endpoint. > [!NOTE] > Note > > - [Token caching and reuse is required](/datax/docs/security-authentication#important). **Request** ```plaintext POST https://id.b2b.yahooincapis.com/zts/v1/oauth2/token ``` **Headers** | Header | Value | | --- | --- | | `Content-Type` | application/x-www-form-urlencoded | | `Accept` | application/json | **Form Parameters** | Filed Name | Value | Description | | --- | --- | --- | | `grant_type` | `client_credentials` | The value is literal `client_credentials`. | | `client_assertion_type` | urn:ietf:params:oauth:client-assertion-type:jwt-bearer | The value is literal urn:ietf:params:oauth:client-assertion-type:jwt-bearer. | | `client_assertion` | JWT Generated | The JWT generated in the procedure above. | | `scope` | idb2b.dsp.datax:role.batch.writer OR idb2b.dsp.datax:role.online.writer | The scope is literal idb2b.dsp.datax:role.batch.writer for the DataX Batch API and idb2b.dsp.datax:role.online.writer for the DataX Online API. The scope value to use in the API calls is determined by the audience endpoint being called: - DataX Batch API uses the https://datax.yahooapis.com/v1/audience endpoint to upload audiences - DataX Online API uses the https://dataxonline.yahoo.com/online/audience endpoint. | **Sample ‘curl’ Command** Use the curl command to generate the access token. ```plaintext curl "https://id.b2b.yahooincapis.com/zts/v1/oauth2/token" \ -X POST \ -H "Content-Type: application/x-www-form-urlencoded" \ -H "Accept: application/json" \ -d "grant_type=client_credentials&client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer&scope=idb2b.dsp.datax:role.batch.writer&client_assertion==eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJodHRwczovL2lkLmIyYi55YWhvb2luY2FwaXMuY29tL3p0cy92MSIsImlzcyI6ImlkYjJiLmRzcC5kYXRheC5iYXRjaC44ZDRkMjI2MC0yYTkyLTRjYjAtYmE1ZC0zYTMzODc0NjZiOGQiLCJzdWIiOiJpZGIyYi5kc3AuZGF0YXguYmF0Y2guOGQ0ZDIyNjAtMmE5Mi00Y2IwLWJhNWQtM2EzMzg3NDY2YjhkIiwiaWF0IjoxNzYyODUxODc0LCJleHAiOjE3NjI4NTI0NzR9.gVziT4YHIx-9MqEzvbn0fUbC4TBnurCBUR_3t8ugbjQ ``` **Sample Response** ```plaintext {'access_token': 'eyJraWQiOiJpZGIyYi56dHMuZWMudXMtd2VzdC0yLjAiLCJ0eXAiOiJhdCtqd3QiLCJhbGciOiJFUzI1NiJ9.eyJzdWIiOiJpZGIyYi5kc3AuZGF0YXguYmF0Y2guOGQ0ZDIyNjAtMmE5Mi00Y2IwLWJhNWQtM2EzMzg3NDY2YjhkIiwic2NwIjpbIndyaXRlciJdLCJ2ZXIiOjEsImlzcyI6Imh0dHBzOi8vaWQuYjJiLnlhaG9vaW5jYXBpcy5jb20venRzL3YxIiwiY2xpZW50X2lkIjoiaWRiMmIuZHNwLmRhdGF4LmJhdGNoLjhkNGQyMjYwLTJhOTItNGNiMC1iYTVkLTNhMzM4NzQ2NmI4ZCIsImF1ZCI6ImlkYjJiLmRzcC5kYXRheC5iYXRjaCIsInVpZCI6ImlkYjJiLmRzcC5kYXRheC5iYXRjaC44ZDRkMjI2MC0yYTkyLTRjYjAtYmE1ZC0zYTMzODc0NjZiOGQiLCJhdXRoX3RpbWUiOjE3NjI4NTE4NzUsInNjb3BlIjoid3JpdGVyIiwiZXhwIjoxNzYyODczNDc1LCJpYXQiOjE3NjI4NTE4NzUsImp0aSI6IjU2ZWI4MThkLWY3ZDUtNDA1Ni05ZTZmLTUyODk1OGFkZmNhZSJ9.h0FeHjXxNo5u3kGLJ8Sk_Mvq09hu9t9cCtbPVk4PqtK5XU3m7DkiGg8pMpNGPbMva8_jvukmVbVCvXi88jiF_g', 'token_type': 'Bearer', 'expires_in': 21600} ``` ### Step 3 Access the API Upon successful Access Token generation, DataX API endpoints (Taxonomy, Audience and Partner Match APIs) can be accessed by including the token in the request's `Authorization` header. #### Authorization: Bearer **Sample ‘curl’ Command** For example, to GET the Taxonomy (download the taxonomy). ```plaintext curl "https://datax.yahooapis.com/v1/taxonomy" \ -H "Authorization: Bearer eyJraWQiOiJpZGIyYi56dHMuZWMudXMtd2VzdC0yLjAiLCJ0eXAiOiJhdCtqd3QiLCJhbGciOiJFUzI1NiJ9.eyJzdWIiOiJpZGIyYi5kc3AuZGF0YXguYmF0Y2guOGQ0ZDIyNjAtMmE5Mi00Y2IwLWJhNWQtM2EzMzg3NDY2YjhkIiwic2NwIjpbIndyaXRlciJdLCJ2ZXIiOjEsImlzcyI6Imh0dHBzOi8vaWQuYjJiLnlhaG9vaW5jYXBpcy5jb20venRzL3YxIiwiY2xpZW50X2lkIjoiaWRiMmIuZHNwLmRhdGF4LmJhdGNoLjhkNGQyMjYwLTJhOTItNGNiMC1iYTVkLTNhMzM4NzQ2NmI4ZCIsImF1ZCI6ImlkYjJiLmRzcC5kYXRheC5iYXRjaCIsInVpZCI6ImlkYjJiLmRzcC5kYXRheC5iYXRjaC44ZDRkMjI2MC0yYTkyLTRjYjAtYmE1ZC0zYTMzODc0NjZiOGQiLCJhdXRoX3RpbWUiOjE3NjI4NTE4NzUsInNjb3BlIjoid3JpdGVyIiwiZXhwIjoxNzYyODczNDc1LCJpYXQiOjE3NjI4NTE4NzUsImp0aSI6IjU2ZWI4MThkLWY3ZDUtNDA1Ni05ZTZmLTUyODk1OGFkZmNhZSJ9.h0FeHjXxNo5u3kGLJ8Sk_Mvq09hu9t9cCtbPVk4PqtK5XU3m7DkiGg8pMpNGPbMva8_jvukmVbVCvXi88jiF_g" ``` --- ## Code Examples to Generate the Access Token Please check code examples for obtaining an access token. ### Python ```python import jwt import time import requests import json # Import the json module for printing formatted output from datetime import datetime, timedelta, timezone # --- JWT Generation Function --- def generate_jwt_pyjwt(client_id: str, client_secret: str, expiry_minutes: int = 5) -> str:   """   Generates a JWT signed with HS256 using the PyJWT library,   matching the specified Header and Payload structure.   """   now = datetime.now(timezone.utc)   issued_at = int(now.timestamp())   expires_at = int((now + timedelta(minutes=expiry_minutes)).timestamp())   payload = {       "aud": "https://id.b2b.yahooincapis.com/zts/v1",       "iss": client_id,       "sub": client_id,       "iat": issued_at,       "exp": expires_at   }   custom_headers = {       "typ": "jwt"   }     encoded_jwt = jwt.encode(       payload=payload,       key=client_secret,       algorithm="HS256",       headers=custom_headers   )     return encoded_jwt # --- Token Retrieval Function (print full JSON response) --- def get_access_token(client_id: str, client_secret: str) -> str or None:   """   Performs the POST request to the token endpoint and prints the full JSON response.     Returns:       str or None: The access token string on success, or None on failure.   """     TOKEN_URL = "https://id.b2b.yahooincapis.com/zts/v1/oauth2/token"     client_assertion_jwt = generate_jwt_pyjwt(client_id, client_secret, expiry_minutes=5)     data = {       "grant_type": "client_credentials",       "client_assertion_type": "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",       "client_assertion": client_assertion_jwt,       "scope": "idb2b.dsp.datax:role.batch.writer"   }     try:       response = requests.post(TOKEN_URL, data=data)       response.raise_for_status()         except requests.exceptions.RequestException as e:       print(f"❌ An error occurred during the request: {e}")       if response is not None:            print(f"Server Error Response: {response.text}")       return None   try:       token_data = response.json()             # --- NEW: Print the full JSON response, nicely formatted ---       print("\n" + "--- Full JSON Response ---")       print(json.dumps(token_data, indent=4))       print("--------------------------\n")             access_token = token_data.get("access_token")             if access_token:           return access_token       else:           print("❌ Access token not found in the response body.")           return None             except json.JSONDecodeError:       print("❌ Failed to decode JSON response.")       print(f"Raw response text: {response.text}")       return None # --- Example Usage --- # NOTE: Replace these with your actual, private credentials! CLIENT_ID = "YOUR_CLIENT_ID_HERE" CLIENT_SECRET = "YOUR_CLIENT_SECRET_HERE" print(f"Attempting to obtain token for Client ID: {CLIENT_ID}...") # Call the function to execute the full flow final_access_token = get_access_token(CLIENT_ID, CLIENT_SECRET) print("\n" + "="*50) if final_access_token:   print("✅ Successfully obtained the access token.")   print(f"🔑 **ACCESS TOKEN:** {final_access_token}") else:   print("❌ Failed to complete the OAuth flow and obtain the access token.") print("="*50) ``` ### Java ```plaintext import com.auth0.jwt.JWT; import com.auth0.jwt.algorithms.Algorithm; import com.google.gson.Gson; import com.google.gson.JsonObject; import java.io.IOException; import java.net.URI; import java.net.URLEncoder; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.nio.charset.StandardCharsets; import java.time.Instant; import java.time.temporal.ChronoUnit; import java.util.Map; public class JwtTokenFetcher {   private static final String TOKEN_URL = "https://id.b2b.yahooincapis.com/zts/v1/oauth2/token";   private static final String AUDIENCE = "https://id.b2b.yahooincapis.com/zts/v1";   private static final String ASSERTION_TYPE = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer";   private static final String SCOPE = "idb2b.dsp.datax:role.batch.writer";   /**    * Generates a JWT signed with HS256, matching the specified Header and Payload.    */   private static String generateJwt(String clientId, String clientSecret) {       // Use a 5-minute expiry, as is standard for JWT client assertions       Instant issuedAt = Instant.now();       Instant expiresAt = issuedAt.plus(5, ChronoUnit.MINUTES);       try {           // The secret must be treated as bytes for HMAC           Algorithm algorithm = Algorithm.HMAC256(clientSecret.getBytes(StandardCharsets.UTF_8));           return JWT.create()                   .withHeader(Map.of("typ", "jwt")) // Custom Header field                   .withAudience(AUDIENCE)           // The 'aud' claim                   .withIssuer(clientId)             // The 'iss' claim                   .withSubject(clientId)            // The 'sub' claim                   .withIssuedAt(issuedAt)           // The 'iat' claim                   .withExpiresAt(expiresAt)         // The 'exp' claim                   .sign(algorithm);                 // Sign with HS256 algorithm       } catch (Exception e) {           System.err.println("❌ Error generating JWT: " + e.getMessage());           return null;       }   }   /**    * Constructs the URL-encoded form data for the POST request.    */   private static String buildFormData(String jwtAssertion) {       // Helper to encode values for URL-encoded form data       String encodedAssertion = URLEncoder.encode(jwtAssertion, StandardCharsets.UTF_8);       String encodedScope = URLEncoder.encode(SCOPE, StandardCharsets.UTF_8);       return String.format(               "grant_type=client_credentials&" +               "client_assertion_type=%s&" +               "client_assertion=%s&" +               "scope=%s",               URLEncoder.encode(ASSERTION_TYPE, StandardCharsets.UTF_8),               encodedAssertion,               encodedScope       );   }   /**    * Performs the OAuth 2.0 token exchange and prints the full JSON response.    */   public static String getAccessToken(String clientId, String clientSecret) {       String jwtAssertion = generateJwt(clientId, clientSecret);       if (jwtAssertion == null) {           return null;       }       String formData = buildFormData(jwtAssertion);       // 1. Build the HTTP Request       HttpRequest request = HttpRequest.newBuilder()               .uri(URI.create(TOKEN_URL))               .header("Content-Type", "application/x-www-form-urlencoded")               .POST(HttpRequest.BodyPublishers.ofString(formData))               .build();       // 2. Send the Request       try {           HttpClient client = HttpClient.newHttpClient();           HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());           // 3. Print the full JSON response           System.out.println("\n--- Full JSON Response ---");           // Use Gson to parse and pretty-print the JSON response body           Gson gson = new Gson();           JsonObject jsonResponse = gson.fromJson(response.body(), JsonObject.class);           System.out.println(gson.toJson(jsonResponse));           System.out.println("--------------------------\n");           // 4. Check for success (HTTP 200 OK)           if (response.statusCode() == 200) {               String accessToken = jsonResponse.get("access_token").getAsString();               return accessToken;           } else {               System.err.println("❌ Token exchange failed with HTTP status code: " + response.statusCode());               return null;           }       } catch (IOException | InterruptedException e) {           System.err.println("❌ Error during HTTP request: " + e.getMessage());           return null;       } catch (Exception e) {            System.err.println("❌ Error parsing response or extracting token: " + e.getMessage());            return null;       }   }   // --- Main Execution ---   public static void main(String[] args) {       // NOTE: Replace these with your actual, private credentials!       final String CLIENT_ID = "YOUR_CLIENT_ID_HERE";       final String CLIENT_SECRET = "YOUR_CLIENT_SECRET_HERE";       System.out.println("Attempting to obtain token for Client ID: " + CLIENT_ID + "...");       String finalAccessToken = getAccessToken(CLIENT_ID, CLIENT_SECRET);       System.out.println("\n" + "=".repeat(50));       if (finalAccessToken != null) {           System.out.println("✅ Successfully obtained the access token.");    System.out.println("🔑 **ACCESS TOKEN:** " + finalAccessToken);       } else {           System.out.println("❌ Failed to complete the OAuth flow and obtain the access token.");       }       System.out.println("=".repeat(50));   } } ``` ### C# ```plaintext using System; using System.Collections.Generic; using System.Net.Http; using System.Net.Http.Headers; using System.Security.Claims; using System.Text; using System.Text.Json; using System.Threading.Tasks; using Microsoft.IdentityModel.Tokens; using System.IdentityModel.Tokens.Jwt; public class JwtTokenFetcher {   private const string TOKEN_URL = "https://id.b2b.yahooincapis.com/zts/v1/oauth2/token";   private const string AUDIENCE = "https://id.b2b.yahooincapis.com/zts/v1";   private const string ASSERTION_TYPE = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer";   private const string SCOPE = "idb2b.dsp.datax:role.batch.writer";   /**    * Generates a JWT signed with HS256, matching the specified Header and Payload.    */   private static string GenerateJwt(string clientId, string clientSecret)   {       // 5 minutes from now for expiry       var now = DateTime.UtcNow;       var expires = now.AddMinutes(5);       // 1. Define the Signing Key (must be treated as bytes for HMAC)       var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(clientSecret));             // 2. Define the Credentials and Algorithm (HS256)       var signingCredentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);       // 3. Define the Claims (Payload)       var claims = new[]       {           // Registered Claims matching your specification           new Claim(JwtRegisteredClaimNames.Aud, AUDIENCE),           new Claim(JwtRegisteredClaimNames.Iss, clientId),           new Claim(JwtRegisteredClaimNames.Sub, clientId),           // iat and exp are handled by the SecurityTokenDescriptor       };       // 4. Create the Security Token Descriptor       var tokenDescriptor = new SecurityTokenDescriptor       {           Subject = new ClaimsIdentity(claims),           IssuedAt = now,           NotBefore = now, // Often set to IssuedAt           Expires = expires,           SigningCredentials = signingCredentials       };       // 5. Generate the Token Handler and Token       var tokenHandler = new JwtSecurityTokenHandler();       var token = tokenHandler.CreateToken(tokenDescriptor);             // 6. Set the custom "typ": "jwt" header property       // By default, it generates "typ": "JWT". We ensure it is lowercased if necessary,       // and add it explicitly via the token object's header property.       // The JwtSecurityTokenHandler generates the token with the correct "alg"       token.Header["typ"] = "jwt";             // Serialize the token to the standard Header.Payload.Signature string format       return tokenHandler.WriteToken(token);   }   /**    * Performs the OAuth 2.0 token exchange and prints the full JSON response.    */   public static async Task GetAccessToken(string clientId, string clientSecret)   {       string jwtAssertion = GenerateJwt(clientId, clientSecret);       if (jwtAssertion == null)       {           return null;       }       // 1. Define the POST request body (application/x-www-form-urlencoded)       var formContent = new FormUrlEncodedContent(new[]       {           new KeyValuePair("grant_type", "client_credentials"),           new KeyValuePair("client_assertion_type", ASSERTION_TYPE),           new KeyValuePair("client_assertion", jwtAssertion),           new KeyValuePair("scope", SCOPE)       });       // 2. Send the Request       using var httpClient = new HttpClient();       try       {           HttpResponseMessage response = await httpClient.PostAsync(TOKEN_URL, formContent);           string responseBody = await response.Content.ReadAsStringAsync();           // 3. Print the full JSON response           Console.WriteLine("\n--- Full JSON Response ---");           try           {               // Parse and pretty-print the JSON response body               using var doc = JsonDocument.Parse(responseBody);               Console.WriteLine(JsonSerializer.Serialize(doc, new JsonSerializerOptions { WriteIndented = true }));           }           catch (JsonException)           {               Console.WriteLine("Could not parse response as JSON. Raw body:");               Console.WriteLine(responseBody);           }           Console.WriteLine("--------------------------\n");                     // 4. Check for success (HTTP 200 OK)           response.EnsureSuccessStatusCode();           // 5. Deserialize response to get the access token           var tokenResponse = JsonSerializer.Deserialize>(responseBody);           if (tokenResponse != null && tokenResponse.TryGetValue("access_token", out var accessTokenElement))           {               return accessTokenElement.GetString();           }                     return null;       }       catch (HttpRequestException e)       {           Console.WriteLine($"❌ Error during HTTP request: {e.Message}");           return null;       }   }   // --- Main Execution ---   public static async Task Main(string[] args)   {       // NOTE: Replace these with your actual, private credentials!       const string CLIENT_ID = "YOUR_CLIENT_ID_HERE";       const string CLIENT_SECRET = "YOUR_CLIENT_SECRET_HERE";       Console.WriteLine($"Attempting to obtain token for Client ID: {CLIENT_ID}...");       string finalAccessToken = await GetAccessToken(CLIENT_ID, CLIENT_SECRET);       Console.WriteLine("\n" + new string('=', 50));       if (finalAccessToken != null)       {           Console.WriteLine("✅ Successfully obtained the access token.");    Console.WriteLine($"🔑 **ACCESS TOKEN:** {finalAccessToken}");       }       else       {           Console.WriteLine("❌ Failed to complete the OAuth flow and obtain the access token.");       }       Console.WriteLine(new string('=', 50));   } } ```