OAuth2 refresh token not working for SBOM batch uploads in TeamCity 12.3

Running TC 12.3 with OAuth2 authentication for our automated SBOM batch upload process. The initial authentication works fine, but when the access token expires during long-running batch jobs, the refresh token mechanism fails completely.

Our Java batch client authenticates successfully and starts uploading SBOM components, but after about 45-50 minutes (when the access token expires), subsequent API calls return 401 Unauthorized. The refresh token request itself returns 400 Bad Request.

TokenResponse refresh = oauth.refreshToken(refreshToken);
// Returns: {"error":"invalid_grant","error_description":"Refresh token expired"}

I’ve verified the OAuth2 refresh token config in site.xconf shows refresh_token lifetime as 7200 seconds (2 hours), but tokens seem to expire much earlier. The grant type is set to “authorization_code” with scope “Teamcenter.API.Full”. Batch uploads typically run 2-3 hours processing thousands of SBOM records.

Is there a specific configuration for refresh tokens in batch server connectivity scenarios? The OAuth2 documentation doesn’t cover long-running automated processes clearly.

Here’s the complete solution covering all three critical areas for OAuth2 refresh tokens in batch SBOM uploads:

1. OAuth2 Refresh Token Configuration First, fix your token configuration in site.xconf and OAuth client settings:

<Property name="wt.auth.oauth.refreshTokenLifetime" value="86400"/>
<Property name="wt.auth.oauth.refreshTokenReuseWindow" value="300"/>
<Property name="wt.auth.oauth.allowRefreshTokenRotation" value="true"/>

The refresh token lifetime should be 86400 seconds (24 hours) for batch processes, not 7200. The reuse window of 300 seconds allows for network latency and retry logic. Enable rotation to get new refresh tokens with each refresh.

2. Grant Type and Scope - Critical Change Your authorization_code grant type is incorrect for batch automation. Update your OAuth client configuration:

// Change from authorization_code to client_credentials
OAuthClient client = new OAuthClient("batch_sbom_client");
client.setGrantType("client_credentials");
client.setScope("Teamcenter.API.Full offline_access");

Client credentials flow doesn’t use refresh tokens the same way - it gets long-lived access tokens directly. However, if you must use authorization_code (for user context), you MUST include “offline_access” scope to get refresh tokens that work beyond session lifetime.

In OAuth Provider admin console: Client Settings > batch_sbom_client > Allowed Scopes > Add “offline_access” and “Teamcenter.SBOM.Write”.

3. Batch Server Connectivity - Token Management Implement proper token rotation handling in your batch client:

// Pseudocode - Token refresh with rotation:
1. Store both access_token AND refresh_token from initial auth
2. Before each API call, check if access_token expires in <5 minutes
3. If expiring soon, call refresh endpoint with current refresh_token
4. CRITICAL: Store the NEW refresh_token from refresh response
5. Update access_token and continue batch processing
6. Handle 401 errors by immediately refreshing and retrying request
// See OAuth2 spec RFC 6749 Section 6 for refresh flow details

Key points:

  • Always use the most recently issued refresh token
  • Implement token expiry prediction to refresh proactively
  • Add retry logic for 401 errors during the refresh window
  • For batch processes over 2 hours, client_credentials is strongly recommended

Additional Configuration: Verify batch server NTP sync: ntpq -p should show time offset under 1 second. Update your OAuth client in TC:

Go to Organization > Security > OAuth Clients > batch_sbom_client

  • Set Token Endpoint Auth Method to “client_secret_post”
  • Enable “Allow Refresh Token Rotation”
  • Set Access Token Lifetime to 3600 (1 hour)
  • Set Refresh Token Lifetime to 86400 (24 hours)

Test with a single SBOM batch job monitoring token refresh in logs before deploying to production batch processing.


This draft is based on general Teamcenter knowledge. It has not been verified against your specific version and environment. Practitioners: verify the steps and share your experience below.

Check if your batch process is actually storing and reusing the NEW refresh token from each refresh response. OAuth2 in TC 12.3 issues a new refresh token with every refresh request and invalidates the old one. If your code keeps using the original refresh token after the first refresh, subsequent attempts will fail with invalid_grant. This is rotating refresh token behavior.

Tested this on Windchill 12.3 with OAuth2 batch uploads, and setting wt.auth.oauth.refreshTokenLifetime to 86400 in site.xconf eliminated our token expiration failures mid-upload.

The 7200 second setting is correct but there’s a separate configuration for refresh token reuse window. In TC 12.3, check wt.auth.oauth.refreshTokenReuseWindow in your security configuration. If it’s set too low or missing, the refresh token becomes single-use only. Also verify that your client_id has the offline_access scope explicitly granted - batch processes need this for extended refresh token validity beyond user session lifetimes.

I suspect the grant type might be the issue. For batch automation, you should be using “client_credentials” grant type, not “authorization_code”. The authorization_code flow is designed for interactive user sessions and ties token lifetime to user session state. Client credentials flow is meant for service-to-service authentication and has different token lifecycle management. That would explain why your refresh tokens aren’t behaving as expected in a batch context.

Also check the batch server connectivity configuration itself. If your batch client is running on a different server than your OAuth provider, there might be clock skew issues causing premature token expiration. We had a similar problem where the batch server clock was 3 minutes ahead, and tokens were being rejected as expired before their actual expiry time. NTP synchronization fixed it for us.

Good point on the rotating refresh tokens - I wasn’t updating the stored refresh token after each refresh. That’s definitely part of the problem. Also confirmed we’re missing the offline_access scope. Will test with client_credentials grant type as that makes more sense for our batch scenario.