Jenkins API tokens: create, scope, rotate, and revoke
How Jenkins API tokens differ from passwords, how to create one, what they can and cannot be scoped to, and how to store and rotate them without leaking your CI server.
Written by Loris Siegenthaler · Maker of BuildCaptainAn API token is the credential you hand to anything that is not a human sitting at a browser: a script, a deployment pipeline, a monitoring tool, a desktop client. Jenkins makes them easy to create, which is why so many of them end up pasted into a shell script in a repository, still valid three years later.
This is the whole lifecycle — what a token actually is, how to create one, the scoping limitation nobody warns you about, and how to rotate and revoke without breaking things.
Tokens versus passwords
Both work for HTTP basic authentication against Jenkins, so on the wire they look identical. What differs is everything around them.
- Tokens are revocable individually. One token per consumer means you can kill the leaked one without changing the password every other consumer is using.
- Tokens survive SSO. If your Jenkins authenticates against SAML, OIDC, LDAP, or GitHub OAuth, there is often no password to send at all — the token is the only credential that works for non-interactive access.
- Tokens skip the CSRF crumb. Jenkins protects state-changing requests with a crumb (its CSRF token), and the documentation is explicit that requests authenticating with an API token are exempt from it. That is why
curl -X POSTwith a token just works, while the same request carrying a session cookie gets a 403 "No valid crumb was included" unless you fetch one from/crumbIssuer/api/jsonfirst. Jenkins' own remote-access guidance prefers tokens over crumb-juggling for exactly this reason. - Tokens are traceable. Each one carries the name you gave it, a creation date, and a usage counter, so you can see which of them are actually being used and which were minted for a script that no longer exists.
What tokens are not is a lower-privilege credential. See the scoping section below — that is the part that catches people.
Creating a token
Click your name in the top-right corner, then Security in the sidebar (older versions call the page Configure). /me/security is the shortcut on any Jenkins. Under API Token, choose Add new token, give it a name that says who is using it — deploy-script, grafana, buildcaptain-laptop, not token1 — and click Generate.
Copy the value now. Jenkins says so itself: "Copy this token now, because it cannot be recovered in the future." It stores only a hash.
Administrators can do the same for a service account they do not have the password to, at /user/<username>/security (via Manage Jenkins → Security → Users). That is how you provision a bot account without ever logging in as it.
Tokens can also be created without the UI, which is what provisioning scripts want:
curl -X POST -u "alice:$JENKINS_ADMIN_TOKEN" \
"https://jenkins.example.com/user/jenkins-bot/descriptorByName/jenkins.security.ApiTokenProperty/generateNewToken" \
--data "newTokenName=deploy-script"
The endpoint is POST-only. The response is JSON with a data object containing tokenName, tokenUuid, and tokenValue. Keep the UUID — it is what the matching revoke call takes:
curl -X POST -u "alice:$JENKINS_ADMIN_TOKEN" \
"https://jenkins.example.com/user/jenkins-bot/descriptorByName/jenkins.security.ApiTokenProperty/revoke" \
--data "tokenUuid=$TOKEN_UUID"
Use /me/… in place of /user/<id>/… for your own account. Either way you need to be that user or a Jenkins administrator.
Using a token
Everything in Jenkins has a JSON representation — append /api/json to almost any URL you can open in a browser.
# Who am I, and what can I see?
curl -u "jenkins-bot:$JENKINS_TOKEN" \
"https://jenkins.example.com/api/json?pretty=true"
# The last five builds of one job, only the fields you need
curl -u "jenkins-bot:$JENKINS_TOKEN" \
"https://jenkins.example.com/job/my-app/api/json?tree=builds[number,result,timestamp]{0,5}"
# The console log of a specific build
curl -u "jenkins-bot:$JENKINS_TOKEN" \
"https://jenkins.example.com/job/my-app/42/consoleText"
The tree parameter is worth learning early. Without it, /api/json on a busy job returns a payload measured in megabytes; with it you get the four fields you asked for.
Triggering builds should be a POST. Jenkins core will in fact accept GET on both build endpoints, but POST is what the documentation uses and what every proxy, log, and reviewer expects of a state-changing call — and a plain GET to /build on a parameterised job renders the parameter form instead of starting anything, which is a confusing five minutes.
# A job with no parameters
curl -X POST -u "jenkins-bot:$JENKINS_TOKEN" \
"https://jenkins.example.com/job/my-app/build"
# A parameterised job
curl -X POST -u "jenkins-bot:$JENKINS_TOKEN" \
"https://jenkins.example.com/job/my-app/buildWithParameters" \
--data-urlencode "BRANCH=release/2.4" \
--data-urlencode "DEPLOY=false"
A successful trigger returns 201 Created with a Location header pointing at the queue item, not at a build. The build number does not exist yet — Jenkins has to leave the queue first. Poll the queue item's /api/json until an executable object appears, and read the number from there.
-u user:token puts the token in the process list, where any other user on the machine can read it. On a shared host, use curl --netrc-file with a 0600 file, or pipe the credential in, instead of interpolating it into the command line.Scoping: the part that surprises people
A Jenkins API token is not scoped. It is not a scoped credential with a permission list attached, the way a GitHub fine-grained token is. It authenticates as you, and it therefore carries every permission your account has.
If you are a Jenkins administrator and you paste a token into a shell script, that script can now reconfigure jobs, install plugins, and run arbitrary Groovy on the controller. It does not matter that all you wanted was to read a build status.
Since there is no per-token scoping, scope the user instead. Create a dedicated service account per consumer and give it the minimum:
- Read-only monitoring —
Overall/Read,Job/Read, andView/Read. Enough to list jobs, read build results, and fetch console logs. - Triggering builds — add
Job/Build, andJob/Cancelif the tool needs to stop runs. - Never —
Overall/Administer,Job/Configure, orRun/Replayon a service account.Run/Replayin particular means arbitrary pipeline code execution.
The two plugins that make this practical are Matrix Authorization Strategy (matrix-auth — a grid of users against permissions, optionally per folder or per job) and Role-based Authorization Strategy (role-strategy — named roles applied to users and to job-name patterns). Matrix is simpler and fine up to a few dozen accounts; role strategy scales better once you have real teams. Either way the goal is the same: the blast radius of a leaked token is whatever that one service account could do.
Folder-scoped permissions are the underrated trick here. Grant the service account Job/Read on the platform/ folder only, and a token that leaks cannot even enumerate the rest of your jobs.
Rotation and revocation
Jenkins tokens do not expire. Nothing will remind you, so put it on a schedule — quarterly is a reasonable default, immediately on any suspicion, and always when someone leaves the team.
Named tokens make rotation a non-event, provided you do it in this order:
- Create a new token on the same account, named for the same consumer plus a date:
deploy-script-2026-08. - Roll it out to the consumer and confirm it works.
- Revoke the old token from Security → API Token.
Never revoke first. The overlap window is the entire reason multiple named tokens exist.
The usage counter next to each token tells you whether a rotation actually landed: after the new token has been in place for a day, the old one's counter should have stopped moving. It is also the fastest way to find dead tokens — anything with no recent use is either a script nobody runs any more or a credential someone copied and forgot, and both should be revoked.
If a token has leaked, revoking it is the first action, not the second. Then check Manage Jenkins → System Log and your reverse proxy's access log for what that account did, and rotate the rest of that account's tokens as well — a leak from a shared laptop or a public repository rarely involves exactly one secret.
An administrator can revoke another user's tokens from that user's configuration page, which is what you want on the day somebody leaves. Deleting or disabling the account itself invalidates all of its tokens at once.
Where to keep the token
The rules, shortest version:
- Not in a repository. Not in a script, not in a
.env, not in a YAML file, not in a comment. Git remembers; rewriting history after the fact is a bad day and an incomplete fix. - Not in CI logs. Never
echoa token, and be careful withset -xin a shell step — it will happily print the whole curl command. - Not in a shared password note. One token per consumer is the entire mechanism; sharing one across four tools throws it away.
On macOS the right store is the Keychain, and it is scriptable:
# Store it once (the -w flag prompts, so it never lands in your shell history)
security add-generic-password -a jenkins-bot -s jenkins-api-token -w
# Read it back when you need it
JENKINS_TOKEN=$(security find-generic-password -a jenkins-bot -s jenkins-api-token -w)
Inside Jenkins itself, a token belongs in the credentials store as a "Username with password" credential and comes out with withCredentials, which masks it in the build log:
withCredentials([usernamePassword(
credentialsId: 'jenkins-bot-api',
usernameVariable: 'JENKINS_USER',
passwordVariable: 'JENKINS_TOKEN'
)]) {
sh 'curl -sS -u "$JENKINS_USER:$JENKINS_TOKEN" "$JENKINS_URL/api/json"'
}
Note the single quotes around the sh script. With double quotes Groovy interpolates the secret into the command string before Jenkins ever sees it, the masking does not apply, and your token appears in the console log in plain text.
A short checklist
- One named token per consumer, named after the consumer.
- Service accounts, not your own account, for anything automated.
- Minimum permissions via Matrix or Role Strategy — folder-scoped where you can.
- Stored in a real secret store: Keychain, the Jenkins credentials store, or your secrets manager.
- Rotated on a calendar, with new-before-old.
- Revoked the moment a consumer is retired or a laptop goes missing.
If you are connecting our own app, the short version of all this lives in the Help Center: Connect your Jenkins server walks through creating the token and entering it. To disclose the obvious: BuildCaptain is our app, a native macOS menu bar client for Jenkins. It follows its own advice — the token goes into the macOS Keychain, never into a config file, and a read-only service account with Job/Read is enough to watch your pipelines.
BuildCaptain