Hi everyone,
I am trying to generate a comprehensive audit report of all the groups within our Atlassian organization. Specifically, I need a list that shows:
Every group (including legacy or empty groups that currently have 0 users).
The users inside those groups.
The Apps/Product Access assigned to each group (e.g., Jira Software, Confluence, Jira Service Management).
What I've tried so far:
UI User Export: Running a user export from admin.atlassian.com gives me the apps and groups, but it only shows groups that currently have active users assigned to them. Empty groups are completely left out of this report.
Organization API v2: I tried writing a script using the (https://api.atlassian.com/admin/v2/) endpoints (specifically /groups/search with expansions). I had generated an personal API endpoint. I have site admin role so i can access admin.atlassian.com
but when i query it via the script i get error saying "401 Unauthorized error". I was doing bearer authorization but ofcrouse my token is personal.
I can query this endpoint though using basic authentication, my email ID, site name and the api token.
{JIRA_URL}/rest/api/3/group/bulk
Why is this failing?
As a site admin can I not consume the the api.atlassian.com endpoint via basic authentication??
Do i need to get a bearer API token?
Two different auth planes, and that's the whole of it. Calls to your site host take basic auth with your email plus a personal API token, which is why group/bulk works for you. Point that same token at api.atlassian.com/admin and it dies both ways: basic gives you 401, bearer gives you 401. That host wants an Admin API key, a separate credential you create at admin.atlassian.com under Settings, API keys, and then send as a Bearer token. Worth knowing org admin and site admin are distinct roles up there too, so if that page isn't showing you anything, that's your next thread to pull.
Better news. Your three requirements don't need that API at all.
Every group including the empty ones is GET /rest/api/3/group/bulk on basic auth, the credential you already have working. It lists groups whether or not anyone is in them, which is the exact thing the UI export drops on the floor. Members per group is GET /rest/api/3/group/member?groupId={groupId}.
Product access by group is GET /rest/api/3/applicationrole. It arrives inverted, product by product with the groups listed underneath, so you flip it in the script. Cheap to do. That endpoint is Jira's own though, which means Software and Service Management land in it while Confluence access doesn't. For the Confluence half you're back at the admin API with a real key.
The empty-groups gap is real: the admin export only lists groups with active users, and /rest/api/3/group/bulk is the one call that returns every group including empty ones (paginate with startAt). Product access per group comes from /rest/api/3/applicationrole (groups and defaultGroups per role), members from /rest/api/3/group/member?groupId=…&includeInactiveUsers=true. Stitching those three gives you the roster you describe.
If you then also need to know where each group is used (schemes, roles, filters, dashboards, product access) before you clean up: that is a per-group question, and it is what I built a read-only Marketplace app for — disclosure: I'm the developer. It will not give you the org-wide roster in one go, so for your first step the three calls above are the better tool. https://marketplace.atlassian.com/apps/1133367000
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
@Dejan V _DVLS Labs_ has the right call for a one-time audit, and he's right that empty groups are the hole the CSV leaves.
One flag on the parameter though, because it points the error the wrong way. /rest/api/3/group/member excludes deactivated users by default. includeInactiveUsers is documented as default false, and it's false in the v3 spec I pulled this morning, so passing false changes nothing and what you're exposed to isn't inflated counts, it's the opposite. Group membership comes back smaller than admin.atlassian.com shows, and the difference is every deactivated account still sitting in a group. Pass includeInactiveUsers=true if the numbers have to reconcile against the directory. If they don't, put a line in the methodology saying the counts are active users only, because that gap is the first thing an auditor asks about.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
You are right, @Gabriela - LeanZero, and thank you for catching it - I had that parameter
backwards. The default is false, so deactivated accounts are already excluded and
passing false changes nothing. The exposure is the opposite of what I described:
the API returns fewer members than admin.atlassian.com does, and the difference is
deactivated accounts still sitting in groups.
@sana_faiyaz, take her version, not mine.
One thing worth adding on top of it, because it turns that gap from a nuisance into
something useful. Deactivating an account does not remove it from any group - the
membership stays exactly where it was. So the difference between the two calls is
not only a reconciliation artefact, it is a list: every dormant grant that would
come back the moment someone reactivates that account.
That is worth looking at deliberately, especially for the privileged groups. A
deactivated account still sitting in jira-administrators is a finding on its own,
and it is invisible in an active-users-only report.
So in practice I would run it both ways: the default call for "who can log in
today", and includeInactiveUsers=true for "what grants still exist". The delta is
your cleanup list - and writing it down answers the reconciliation question before
an auditor asks it.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
That dormant-grant framing is the better use of it, and you can get there in one call rather than two. Every member object /rest/api/3/group/member returns carries an active flag, and the v3 spec's own example for that endpoint shows active true and active false sitting side by side in the same response. So run it once with includeInactiveUsers=true and filter on active false. No delta arithmetic. On the paging you flagged, maxResults caps at 50 on this one, so a script that reads a single page reads fifty members and looks finished.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
That is better, and simpler - one call and a filter beats two calls and
arithmetic. The active flag is right there in each member object, so there is no
reason to do it the way I described.
On the 50 cap, one thing worth adding for anyone scripting this. The endpoint
returns isLast, so that is the condition to loop on - comparing page size against
maxResults is fragile. But the failure mode that bites hardest is a loop that
never advances startAt at all. It keeps re-reading page one, and it looks exactly
like a correct single-page result, just with duplicates. Easy to write, hard to
notice, and it only surfaces on an instance large enough to have a second page.
Thanks for both corrections - this thread ended up more useful than my original
answer was.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
Hi Sana,
Gabriela already put the finger on the real problem - the two auth systems. Worth
restating in one line because it catches almost everyone: <site>.atlassian.net/rest/api/3/...
takes your email plus a personal API token as basic auth, while api.atlassian.com/admin/...
takes an organisation API key as a Bearer token. They are not interchangeable, and a
401 is exactly what it looks like when you mix them.
One thing that has not been mentioned, and it may save you the script entirely. If
what you need is an audit deliverable rather than an automated feed, go to
admin.atlassian.com > Directory > Users and use Export users. The CSV carries product
access and group membership as columns, in one file, for the whole org. That covers
two of your three requirements immediately.
What it does not give you is empty groups - a group with no members produces no rows -
so you would still need /rest/api/3/group/bulk for those. But that is a much smaller
job than building the whole report from scratch.
If you do script it, three things that will otherwise make the report quietly wrong:
- /rest/api/3/group/member includes deactivated users by default. Pass
includeInactiveUsers=false unless you want them, because deactivated accounts inflate
every group count and an auditor will ask why the numbers do not match.
- Both /group/bulk and /group/member are paginated. It is easy to end up with a
clean-looking result that is really just the first page.
- Group membership in Jira Cloud is flat - there is no group nesting, so a member is a
member and you do not need to resolve any hierarchy. That is good news for an audit
report, and worth stating explicitly if you have to document your methodology.
One caveat on the product access part: /rest/api/3/applicationrole gives you which
groups grant access to which product, which is the mapping you want. But a person can
hold a seat through any one of several groups, so "does this user have Jira access" is
a union across their groups rather than a single lookup. If you go the CSV route, that
is already resolved for you.
Is this a one-time audit, or something you will need to re-run regularly? That changes
which of the two routes is worth the effort.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
You need to use a bearer token to access the Admin API; a personal API token or basic authentication won't work. That's why you're getting the 401 Unauthorized error.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.