Terraform plan fails on PR with 403 when using OIDC token from GitHub Actions

We are trying to set up a CI/CD pipeline for our CXone infrastructure using the Nice CXone Terraform provider. The goal is simple: run terraform plan on every pull request to validate changes, and run terraform apply only when the PR is merged into the main branch. We are using GitHub Actions and authenticating via OIDC to avoid storing long-lived credentials.

The issue is that the plan step fails with a 403 Forbidden error when the workflow runs on a pull request event. It works fine on push events to main. Here is the relevant part of our workflow file:

name: Terraform Plan
on:
 pull_request:
 branches: [main]

jobs:
 plan:
 runs-on: ubuntu-latest
 permissions:
 id-token: write
 contents: read
 steps:
 - uses: actions/checkout@v4
 - uses: hashicorp/setup-terraform@v3
 - name: Authenticate to CXone
 run: |
 echo "Fetching OIDC token..."
 curl -s -X POST https://{{ORG_ID}}.my.cxone.com/oauth/token \
 -H "Content-Type: application/x-www-form-urlencoded" \
 -d "grant_type=urn:ietf:params:oauth:grant-type:token-exchange&subject_token_type=urn:ietf:params:oauth:token-type:id-token&subject_token=$(oidc-token)&requested_token_type=urn:ietf:params:oauth:token-type:access_token" \
 | jq -r '.access_token' > /tmp/access_token
 - name: Terraform Init
 run: terraform init
 - name: Terraform Plan
 run: terraform plan -var-file="env.tfvars" -out=tfplan

The OIDC token exchange seems to work, as we get a token back. But when Terraform tries to read the current state or plan, the API rejects it.

What I have tried:

  • Verified the OIDC provider configuration in CXone Admin. The audience claim matches.
  • Checked the token in jwt.io. The claims look correct.
  • Tried passing the token directly as an environment variable CXONE_TOKEN instead of exchanging it. Same 403.
  • Confirmed the client ID has the right scope for admin:organization.

Is there a specific permission or claim required for PR events that differs from push events? Or is the token exchange flow different for temporary workflows? The error message from the API is just Forbidden. No extra details in the response body.