---
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 <token> 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" : "<client_id>",
   "sub": "<client_id>",
   "iat" : <issued at time as Unix Epoch in seconds>,
   "exp" : <Expiry time as Unix Epoch in seconds>
}
```

**Signature**

```json
{
   "signature" : "<signature generated using HS256 algorithm, see below>"
}
```

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

<base64url-encoded header>.<base64url-encoded claims>.<base64url-encoded signature>
```

> [!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 <token>

**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<String> 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<string> 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<string, string>("grant_type", "client_credentials"),
           new KeyValuePair<string, string>("client_assertion_type", ASSERTION_TYPE),
           new KeyValuePair<string, string>("client_assertion", jwtAssertion),
           new KeyValuePair<string, string>("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<Dictionary<string, JsonElement>>(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));
   }
}
```
