Darren Hill Consulting Inc.

5 min read

Seven days is a setting

Replacing TBA before 2027.1: where NetSuite's OAuth 2.0 docs and a test account disagree

From NetSuite 2027.1, you can't create new integrations that use token-based authentication, and Oracle has "tentatively planned" the end of support for existing ones "for the 2028.2 release." Every TBA integration you have will move to OAuth 2.0.

I built three integration records in a NetSuite 2026.2 test account and checked Oracle's OAuth 2.0 documentation against what the account actually did. Most of it held. This post is about the parts that didn't, and the parts the docs leave out.

The refresh token

Oracle says "The refresh token is valid for seven days." On a confidential client it was, by default. But the integration record has a Refresh token validity (in hours) field, which the docs describe only for public clients, and it was editable on my confidential client too. Entering 8,760 failed with "oauth2refreshvalidity: Invalid number (must be between 1 and 720)." At 720, the refresh token NetSuite issued carried a 30-day expiry.

So seven days is a default, and 30 days is the ceiling. A person still has to return to the consent screen at least once a month, but they don't have to consent again: with the consent policy set to Always Ask, the second authorization just said when I'd authorized the application before and offered Continue.

Two more things the docs don't mention:

  • Re-authorizing doesn't revoke the earlier refresh token. Both kept working, so several can be live at once.
  • On a public client, one replayed refresh token kills the chain. Every refresh there returns a new refresh token, and reusing an old one was rejected, as documented. Then the newest one was rejected too. One retry bug that replays a refresh token cuts the integration off until someone authorizes it again.

Certificates

The client credentials grant, which NetSuite calls machine-to-machine, has no refresh token and no person: the integration signs a short JSON Web Token with a private key, and NetSuite checks it against a mapped certificate. Two things didn't match the docs:

  • The assertion lifetime limit is double what's documented. Oracle says the assertion's expiry "must be less than 60 minutes greater than" its issue time. NetSuite accepted 61, 90 and 120 minutes, and rejected 121.
  • A wrong algorithm looks like a wrong key. RS256 isn't on Oracle's list and was rejected, but as a bare invalid_grant, with nothing to say the algorithm was the problem.

Rotation, without the UI

Oracle documents a certificate rotation endpoint with three operations, list, add and revoke, but not the paths or the request body. These worked, with an access token issued for the REST web services scope:

GET  /services/rest/auth/oauth2/v1/clients/{clientId}/certificates
POST /services/rest/auth/oauth2/v1/clients/{clientId}/certificates
POST /services/rest/auth/oauth2/v1/clients/{clientId}/certificates/{certificateId}/revoke

The add request took four tries. An empty body failed with "Couldn't parse PEM-encoded X.509 certificate," a raw PEM failed as invalid JSON, and {"certificate": …} failed with an error that listed the fields it knew. This is the body that worked:

{
  "fileName": "next-cert.pem",
  "fileContent": "-----BEGIN CERTIFICATE-----\n…",
  "entity": 1234,
  "role": 3
}

entity and role are internal IDs; the integration record comes from the path. The response carried the new certificate ID, which goes in the kid header of every assertion signed with the new key, and a token from the new certificate worked straight away.

Two details shape a rotation script. The five-active-certificate limit counts RSA and EC certificates together, so keep a slot free or the add fails. And revoking a certificate didn't end the access token already issued from it: a token minted just before the revoke still answered a query afterwards. Revocation stops new tokens; the old ones run out their hour.

When it fails, look in your own logs

Oracle's preparation guide finds TBA integrations with a Login Audit Trail search on "Token-based Access Token Name" not empty. That works for TBA, but every OAuth 2.0 call the trail recorded had that column blank and the integration record's name in Token-based Application Name. To see how far a migration has got, filter on the application name instead.

The trail is better than the API at saying why something failed, when it records the failure at all. Where the token endpoint said only invalid_request or invalid_grant, the Detail column said PkceFailed, InvalidState, UnsupportedAlgo, SignatureValidation or TokenBlocked. But I matched the day's trail against every call I'd made, and some rejections never appeared: an assertion with too long an expiry, a wrong PKCE verifier, a public client without PKCE, a request signed with a revoked certificate. Nor does the trail say whether the API call worked: it records authentication, so a request that authenticated and then failed is a Success.

What I'd do

If nobody is there when an integration runs, put it on client credentials from the start: no refresh setting gets you past 30 days. Keep the assertion lifetime under the documented hour no matter what NetSuite accepts today, script the rotation against the endpoint above with a slot free, and never retry a refresh with a token you've already used. And log the full error body on every failed token request, because NetSuite may not log it for you.


Sources — Oracle NetSuite Help Center

Not sure which integrations still run on TBA?

© 2026 Darren Hill Consulting Inc.