Debugging 401 Unauthorized after token refresh — clock skew between servers

Stuck on a persistent 401 error that only happens after my access token expires and I attempt to refresh it. I am building a simple Python script to pull interaction data from CXone, and while the initial OAuth token request works fine, the refresh flow fails immediately after the token’s TTL hits.

Context:
I am using the requests library in Python. My setup involves storing the access_token, refresh_token, and expires_in timestamp. When a new API call is made, I check if the current time is past the expiration time. If so, I call the /oauth/token endpoint with grant_type=refresh_token.

Here is the relevant snippet for the refresh logic:

import requests
import time

def get_new_token(client_id, client_secret, refresh_token, org_id):
 url = f"https://api.mynice.com/oauth/token"
 headers = {"Content-Type": "application/x-www-form-urlencoded"}
 data = {
 "grant_type": "refresh_token",
 "refresh_token": refresh_token,
 "client_id": client_id,
 "client_secret": client_secret,
 "org_id": org_id
 }
 response = requests.post(url, headers=headers, data=data)
 return response.json()

When this executes, I get a 401 Unauthorized. The response body is minimal:

{
 "error": "invalid_grant",
 "error_description": "Token has expired or is invalid"
}

I have verified that the client_id and client_secret are correct. I also checked the refresh token itself in the CXone admin UI, and it is still active and not revoked. However, I noticed that my local server time is exactly 3 minutes ahead of the CXone server time (I checked this by comparing the date header in the initial token response).

I suspect this clock skew is causing the CXone server to reject my refresh request because it thinks the token is already expired or the request timestamp is invalid.

Question:
How should I handle this clock skew in my Python code? Should I be sending an aud or iat claim manually? Or is there a specific way to adjust the expiration logic to account for server time differences? I want to avoid hardcoding a time offset if possible. Any examples of robust token refresh logic that handles this would be appreciated.