You pay per user. You suspect a chunk of those seats are dormant. And no screen in Jira answers the plain question: who has not touched anything in ninety days? Jira shows you who exists, not who is present. Bulk handling of inactive accounts is one of the higher-voted requests on the identity tracker (ID-8172).
Start with the org directory, not Jira
The first sweep is at admin.atlassian.com. Under Directory, export managed accounts to CSV; it includes a "last seen" column, the most recent authentication to any Atlassian product. Sort it ascending and the dormant tail is obvious. Two limits: "last seen" is org-wide, so a user who lives in Confluence and never opens Jira still reads as active, and the export needs org admin rights with a verified domain. One fact to hold on to: deactivating an account on that Directory screen is the only action that actually frees a license seat.
The DIY approach: derive last Jira activity per user
- Enumerate the roster with
GET /rest/api/3/users/search, keeping human accounts only. - Find each user's newest activity with one enhanced JQL query per user:
assignee = "<accountId>" OR reporter = "<accountId>" ORDER BY updated DESC,maxResults=1,fields=updated. The single result's updated timestamp is a defensible last-activity date. - Respect the limits. Search is rate limited: paginate with
nextPageTokenrather than offsets, and honor 429 with Retry-After. - Decide what a failed lookup means before you run. Unknown is never the same as inactive. A user whose activity you could not resolve stays off the reclaim list.
Once you have days-idle per user, sort into three buckets: idle, never active (resolved, no activity ever), and unresolved (excluded). Review the first two with managers before touching anything. And keep the seat mechanics straight: removing a user from a Jira group does not free a seat; deactivation in the org directory does.
The catch is that you now own a roster crawler with rate-limit handling, failure tracking, and a re-run schedule. Miss the re-runs and "newly idle since last month" stops being answerable.
A ready-made option: Inactive-User Hygiene
Inactive-User Hygiene runs the method above on a daily schedule: it enumerates human accounts, derives each user's last activity from issue history, classifies everyone against a threshold you set, and never flags a user it could not resolve. The result is a worklist rather than a data dump: sort by days idle, filter by classification, spot who became idle since the last scan, and export as CSV, one row per user. It is honest on the report itself that it measures Jira activity, not org-wide login, because the org admin API is not reachable by a tenant app.
Key facts
- Where seats are freed: only by deactivation in the org directory, never by group changes in Jira.
- Best Jira-specific signal: the newest issue a user is assignee or reporter on, which is what the JQL query returns.
- The rule that keeps you safe: unresolved is not inactive.
- Least privilege: user read and work read, plus app storage. The report path writes nothing.
Related reading: What deactivated users leave behind in Jira and the Entra ID version of the same problem.
A version of this piece also appears on Atlassian Community: read it on App Central.
Written by Abaho Katabarwa at Katabarwa Labs. We build small, single-purpose Atlassian and Azure tools that run entirely inside your own tenant. Questions: support@llmgraph.ai.