Jira OAuth "token is expired" / invalid access token in CI
Atlassian OAuth 2.0 (3LO) access tokens are short-lived (about one hour). When CI reuses a stale token, Jira answers 401 with an "invalid_token" or "token is expired" description; the fix is to refresh, not re-authorize.
What this error means
A Jira call fails with 401 and a WWW-Authenticate header saying error="invalid_token", error_description="The access token is expired", often only on longer pipelines.
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer error="invalid_token",
error_description="The access token is expired"Common causes
A cached access token outlived its one-hour TTL
The pipeline stored an access token and reused it after expiry instead of exchanging the refresh token for a fresh one.
No refresh step before the API call
The job assumes a long-lived token; Atlassian access tokens expire quickly, so any non-trivial delay invalidates them.
How to fix it
Exchange the refresh token before calling Jira
- Store the OAuth client id, secret, and refresh token as CI secrets.
- POST to the token endpoint to mint a fresh access token at job start.
- Use the returned access token as a Bearer token for the API calls.
curl -sf -X POST https://auth.atlassian.com/oauth/token \
-H "Content-Type: application/json" \
-d '{"grant_type":"refresh_token","client_id":"'"$JIRA_CLIENT_ID"'",
"client_secret":"'"$JIRA_CLIENT_SECRET"'","refresh_token":"'"$JIRA_REFRESH_TOKEN"'"}'Prefer an API token for simple server-to-server calls
If you do not need per-user OAuth scopes, basic auth with email plus API token avoids token-expiry handling entirely.
How to prevent it
- Refresh the access token at the start of the job, not once per pipeline.
- Store the refresh token in a secret and rotate the client secret on schedule.
- For unattended CI, prefer an API token unless OAuth scopes are required.