For years, the “Standard Operating Procedure” for Atlassian integrations was simple: create a fake user called jira_bot, buy it a license, and generate an API token that lasts indefinitely. It worked, it was convenient, and plenty of teams used it.
The shift from human “bot” accounts to purpose-built Service Accounts is not just a technical update; it is a move toward cleaner, more professional identity management. The catch is that it is often harder than it looks, because it forces you to rethink how your integrations are built and governed.
From "Bot Users" to Clean Governance
The old way of working often relied on “ghost identities”: human accounts used by machines or integrations. Today, this approach is generally considered outdated and no longer recommended, not only within Atlassian, but across many platforms and tools.
In practice, three problems kept coming back:
- Companies were paying for seats that no human ever sat in.
- Because these accounts were human, they could often bypass SSO or MFA, becoming permanent back doors.
- When an integration broke, finding the person who created the token five years ago was often impossible.
Why the Switch is Harder Than You Think
If a colleague asked me about the migration, this is what I would bring up first. Moving to Service Accounts changes how your integrations communicate, and unlike human API tokens, service account credentials follow a stricter routing and security model.
The URL transformation
Many development teams spend days debugging 404 Not Found errors because they try to use their old site URLs. Service Accounts cannot talk to <your-site>.atlassian.net. You must use the Global API Gateway: api.atlassian.com.
Legacy Bot User
-
Base URL:
<your-site>.atlassian.net/rest/api/3/... -
Routing: Direct to site
Service Account
-
Base URL:
api.atlassian.com/ex/jira/{cloudId}/rest/api/3/... -
Routing: Requires a Cloud ID lookup
Cloud ID = a unique GUID for your site.
Found by calling: GET https://<your-site>.atlassian.net/_edge/tenant_info
Immutable scopes
With legacy tokens, “admin” was “admin.” With Service Accounts, you must select specific API Scopes (like read:jira-work) during creation.
Scopes are immutable. Once a credential is created, its scopes cannot be modified. If an integration evolves and requires additional access, the existing credential must be revoked, a new one created, and the integration updated accordingly.
There are two common approaches to handle this:
-
Revoke the existing credential and issue a new one with the required scopes.
-
Maintain multiple credentials, each with a clearly defined and limited scope, aligned to specific use cases.
The dual-gate authorization model
A request must pass two independent gates:
-
The Scope Gate (The Token): The created token itself has permission to perform the action (like
read:jira-work) - The Permission Gate (The Product): The Service Account must be explicitly added to Jira Project Roles or Confluence Space Permissions.
Implementation: API tokens vs. OAuth 2.0
Atlassian offers two ways to authenticate your Service Account.
Option A: API Tokens (Bearer Auth)
Static, scoped credentials linked to a service account, used for simple and direct API access.
Best suited for legacy tools or low-complexity integrations. Tokens expire between 1 and 365 days.
# Bearer Auth using the generated API Token
## URLs for API tokens with scopes for Jira
curl -v \
https://api.atlassian.com/ex/jira/{cloudId}/rest/api/3/project \
-H "Authorization: Bearer my-api-token" \
-H "Accept: application/json"
## URLs for API tokens with scopes for Confluence
curl -v \
https://api.atlassian.com/ex/confluence/{cloudId}/wiki/rest/api/space \
-H "Authorization: Bearer my-api-token" \
-H "Accept: application/json"
Option B: OAuth 2.0 Credentials (Bearer Auth)
Dynamic, short-lived access tokens with enforced scopes, designed for secure and governed integrations.
This is the recommended standard for secure machine-to-machine communication.
# Step 1: Exchange credentials for a 60-minute token
curl --request POST \
--url "https://api.atlassian.com/oauth/token" \
--header "Content-Type: application/json" \
--data '{
"grant_type": "client_credentials",
"client_id": "YOUR_CLIENT_ID",
"client_secret": "YOUR_CLIENT_SECRET"
}'
# Step 2: Use the Access Token in the header
curl --request GET \
--url "https://api.atlassian.com/ex/jira/YOUR_CLOUD_ID/rest/api/3/issue/PROJ-123" \
--header "Authorization: Bearer YOUR_ACCESS_TOKEN"
Migration checklist
Refactoring your scripts requires managing new operational realities:
- Tokens expire after a maximum of 365 days (API tokens) or 60 minutes (OAuth), so your code must handle rotation.
- Document required scopes (e.g.,
read:issue:jira,write:comment:jira) before creation, because scopes lock once saved. - If your token is valid but you get a 403, it's almost always a permission issue. Open the space settings and make sure the Service Account has been granted the right role.
- Organizations needing more than five free service accounts should explore Atlassian Guard for advanced lifecycle management.
Key takeaway
The transition to Service Accounts moves from implicit trust to explicit control. While it requires more discipline upfront (mapping scopes, discovering Cloud IDs, and refactoring base URLs), it eliminates "zombie" licenses and strengthens your security posture. Treat your integrations as first-class services, and the security debt of the "bot user" era will become history.