By Raúl Pérez · Last updated: October 2026 · Reading time: 12 min
Salesforce and Jira are two of the most expensive line items in a typical software budget, and two of the easiest places to overpay. Both charge per user. Both accumulate abandoned accounts as people change roles, leave the company, or get a license “just in case” and never log in.
A manual review once a year is better than nothing, but it is slow and the savings leak back in. This guide shows you how to find inactive licenses in Salesforce and Jira with queries and scripts, how to automate the audit so it runs on a schedule, and how to reclaim licenses safely without locking out someone who needed access.
The examples are practical and adaptable, but both vendors change their APIs, licensing models and admin tools over time. Test everything in a sandbox or with read-only access first, and confirm current details in the official documentation.
Why Inactive Licenses Add Up Fast
Per-seat pricing means every unused account has a precise monthly cost. A few reasons waste accumulates:
- Departures not cleaned up. Offboarding checklists often forget SaaS tools that are not connected to single sign-on.
- Role changes. A salesperson moves to customer success and keeps a full Sales Cloud license.
- Over-licensing. Premium licenses are assigned to people who only need to view reports or collaborate.
- Pilot and project users. Temporary users added for a project are never removed.
- Contractors and consultants. Accounts outlive the engagement.
- No visibility. Admin consoles show who has a license, but few teams regularly check who actually uses it.
The savings math
Annual waste = inactive licenses x price per license per month x 12
Illustrative example: 40 inactive Salesforce licenses at $150 per month and 60 inactive Jira licenses at $10 per month would cost $72,000 and $7,200 a year respectively, nearly $80,000 in total. Your own numbers will differ, but even modest inactivity rates are expensive at enterprise list prices.
Step 0: Define What “Inactive” Means
Before writing any query, agree on rules with the business. A defensible policy:
| Rule | Typical choice |
|---|---|
| Inactivity window | 60 or 90 days without login |
| Never logged in | Account older than 30 days with no login at all |
| Exceptions | Executives, auditors, seasonal users (such as quarter-end finance), integration and service accounts, admins, break-glass accounts |
| Action | Notify, wait a grace period, then freeze or deactivate |
| Reactivation | Guaranteed within one business day, so nobody fears removal |
Write it down and get sign-off from IT, security and the business owner of each system. A clear policy prevents most disputes.
Part 1: Auditing Salesforce
How Salesforce licensing works (the short version)
Each user is assigned a user license through their profile (for example Salesforce, Salesforce Platform or Customer Community), and may also hold permission set licenses and feature licenses for add-ons. Deactivating a user releases the user license so it can be assigned to someone else, but Salesforce users cannot be deleted. Reclaiming licenses means deactivating accounts, not removing them.
Find license utilization
In the Salesforce UI, go to Setup → Company Information to see licenses available and used. You can query the same data with SOQL:
SELECT Name, Status, TotalLicenses, UsedLicenses
FROM UserLicense
ORDER BY Name
SELECT MasterLabel, TotalLicenses, UsedLicenses
FROM PermissionSetLicense
ORDER BY MasterLabel
Compare used to the number of active, recently logged-in users. The gap, plus the unused licenses you pay for but never assigned, is your optimization potential.
Find inactive users with SOQL
SELECT Id, Name, Username, Email, Profile.Name, Profile.UserLicense.Name,
LastLoginDate, CreatedDate
FROM User
WHERE IsActive = true
AND UserType = 'Standard'
AND (LastLoginDate < LAST_N_DAYS:90 OR LastLoginDate = null)
ORDER BY LastLoginDate ASC NULLS FIRST
Notes:
LastLoginDate = nullmeans the user has never logged in. AddAND CreatedDate < LAST_N_DAYS:30so you do not catch brand-new accounts.UserType = 'Standard'focuses on full users. Integration users and others are managed differently.LastLoginDaterecords interactive and API logins, which is useful: an integration user may look “inactive” from a human perspective but is actively used by a system. Check the username and profile before acting.
For a deeper look at who has actually been working in the org, LoginHistory and Setup Audit Trail can help, but retention is limited, so do not rely on them for long windows.
Run it automatically with the Salesforce CLI
sf data query \
--query "SELECT Id, Name, Username, Email, Profile.Name, LastLoginDate FROM User WHERE IsActive = true AND UserType = 'Standard' AND (LastLoginDate < LAST_N_DAYS:90 OR LastLoginDate = null)" \
--target-org my-prod-org \
--result-format csv > inactive_salesforce_users.csv
Authenticate the CLI with a connected app and a JWT flow in a pipeline, and use a dedicated, least-privilege integration user with read access to User and license objects.
Python alternative with a cost estimate
import os
import pandas as pd
from simple_salesforce import Salesforce
sf = Salesforce(
username=os.environ["SF_USERNAME"],
password=os.environ["SF_PASSWORD"],
security_token=os.environ["SF_TOKEN"],
)
soql = """
SELECT Id, Name, Username, Email, Profile.Name, Profile.UserLicense.Name, LastLoginDate
FROM User
WHERE IsActive = true AND UserType = 'Standard'
AND (LastLoginDate < LAST_N_DAYS:90 OR LastLoginDate = null)
"""
records = sf.query_all(soql)["records"]
df = pd.json_normalize(records).drop(columns=["attributes"], errors="ignore")
# Price per license per month (edit to match your contract)
price = {"Salesforce": 150, "Salesforce Platform": 25}
df["license"] = df["Profile.UserLicense.Name"]
df["annual_cost"] = df["license"].map(price).fillna(0) * 12
print(df.groupby("license")["annual_cost"].agg(["count", "sum"]))
df.to_csv("inactive_salesforce_users.csv", index=False)
For production, prefer an OAuth or JWT-based connection over a username, password and token.
Deactivating users: freeze first
A safer path than immediate deactivation is to freeze the user. Freezing blocks login but keeps the license assigned, giving you a reversible grace period. This is done by setting IsFrozen to true on the user’s UserLogin record. After the grace period, deactivate (IsActive = false) to release the license.
Before deactivating, check:
- Records owned by the user (reassign opportunities, accounts, cases);
- Scheduled reports, dashboards and flows running as that user;
- Approval processes or queues naming the user;
- Integrations authenticating as that user.
Reduce the license tier, not just the count
Beyond removing inactive users, look for people holding an expensive license who use only simple features:
- Users who only view dashboards may fit a cheaper license or a reports-only approach.
- Users who only work with custom objects may fit a Salesforce Platform license rather than a full one.
- Experience Cloud and community users have separate license models.
Review how your contract defines each license before downgrading, since features and entitlements vary.
Part 2: Auditing Jira (Atlassian Cloud)
How Jira billing works
In Atlassian Cloud, you pay based on the number of users with product access, not the number of accounts that exist. Removing someone’s access to Jira Software or Jira Service Management stops the charge, while merely leaving them in a group may not. Deactivating or removing a user’s product access is therefore the key action. Plans, tiers and billing rules change, so check your current Atlassian plan.
Also note the difference between licensed agents or users and customers in Jira Service Management, which are billed differently.
Find inactive users in the admin console
In Atlassian Administration, open Directory → Users. The user list shows a last active value per product, and you can filter or export it. Sort by oldest activity, and flag anyone with no activity within your window. Check the current admin documentation for export options, which have varied over time.
Atlassian also provides admin tools and settings for identifying and managing inactive users in some plans. Check what your subscription includes before building anything custom.
Automate with the Atlassian Admin API
The Atlassian organization API can list users and, depending on your plan and API version, their product access and last active dates. This sample illustrates the approach. Verify endpoint paths and field names against the current Admin API documentation, because they change.
import os, requests
from datetime import datetime, timedelta, timezone
ORG_ID = os.environ["ATLASSIAN_ORG_ID"]
API_KEY = os.environ["ATLASSIAN_ADMIN_API_KEY"] # org admin API key, store as a secret
CUTOFF = datetime.now(timezone.utc) - timedelta(days=90)
url = f"https://api.atlassian.com/admin/v1/orgs/{ORG_ID}/users"
headers = {"Authorization": f"Bearer {API_KEY}", "Accept": "application/json"}
inactive = []
while url:
r = requests.get(url, headers=headers, timeout=30)
r.raise_for_status()
payload = r.json()
for user in payload.get("data", []):
for access in user.get("product_access", []): # field names may differ
last = access.get("last_active")
if access.get("name", "").lower().startswith("jira"):
if last is None or datetime.fromisoformat(last.replace("Z", "+00:00")) < CUTOFF:
inactive.append((user.get("email"), access["name"], last))
url = payload.get("links", {}).get("next")
for row in inactive:
print(row)
print(f"{len(inactive)} inactive Jira seats")
Store the API key as a secret, give it the narrowest scope your plan allows, and run it read-only until you trust the output.
Remove product access safely
- Export the candidate list and send it to managers for confirmation.
- Wait the grace period (for example 14 days).
- Remove the user’s product access, or suspend or deactivate the account if they have left the company.
- Keep issue history. Jira preserves the user’s past activity, so removing access does not delete tickets.
- Check for automation rules, filters, dashboards and workflows owned by or running as the user, and reassign them.
Other Jira savings
- Duplicate accounts. The same person may have a personal and a company email in the instance.
- Over-provisioned products. Users with access to Jira Software, Confluence and Service Management who only use one.
- Tier fit. Compare your plan’s features to what teams really use before renewing at a higher tier.
- Agents vs customers. In Service Management, ensure only the people who actually resolve requests are licensed as agents.
Part 3: Automate the Whole Process
An audit run by hand will not survive busy quarters. Schedule it.
A simple automation pipeline
# .github/workflows/license-audit.yml
name: Weekly license audit
on:
schedule:
- cron: "0 7 * * 1" # every Monday at 07:00 UTC
workflow_dispatch:
jobs:
audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- run: pip install simple-salesforce pandas requests
- run: python audit_salesforce.py
env:
SF_USERNAME: ${{ secrets.SF_USERNAME }}
SF_PASSWORD: ${{ secrets.SF_PASSWORD }}
SF_TOKEN: ${{ secrets.SF_TOKEN }}
- run: python audit_jira.py
env:
ATLASSIAN_ORG_ID: ${{ secrets.ATLASSIAN_ORG_ID }}
ATLASSIAN_ADMIN_API_KEY: ${{ secrets.ATLASSIAN_ADMIN_API_KEY }}
- uses: actions/upload-artifact@v4
with:
name: inactive-license-report
path: "*.csv"
Then add steps to post a summary to Slack or email, create tickets for the managers to approve removals, and log every change.
Recommended workflow
- Weekly: generate the inactive-user report for each tool.
- Notify: message the user and their manager, explaining that the license will be removed on a given date unless they confirm they need it.
- Grace period: 7 to 14 days. Freeze rather than delete.
- Act: reclaim the license, record the change and the savings.
- Monthly: report licenses reclaimed, money saved and reactivation requests.
- Quarterly: review license tiers, contract terms and upcoming renewals.
Ownership and controls
- Assign a named owner for each system (Salesforce admin, Jira admin).
- Keep an audit trail: who approved, when, and which licenses changed.
- Apply the principle of least privilege to the audit credentials.
- Make reactivation easy and fast, so the business supports the process.
Common Mistakes
- Treating a long window as proof of inactivity. Seasonal and quarter-end users may log in rarely but be essential.
- Deactivating integration or service accounts. Check usernames, profiles and API usage before touching them.
- Forgetting dependent assets. Reports, flows, automations and approvals tied to a removed user can break.
- Counting only logins. A login is a weak signal of value. Combine it with activity (records created, issues updated) where you can.
- Reclaiming licenses without negotiating. Reclaimed seats help only if you can reduce what you pay at renewal. See [Enterprise Software Negotiation Tactics to Lower Annual Renewal Costs].
- Skipping communication. Surprise removals create resentment. A clear notice and quick reactivation keep trust high.
Frequently Asked Questions
How often should I audit inactive licenses? Weekly or monthly automation is ideal, with a deeper quarterly review that aligns to your renewal calendar.
Does deactivating a Salesforce user free the license? Yes. A deactivated user no longer consumes a user license, and the license can be reassigned. Users cannot be deleted in Salesforce.
Will removing a Jira user delete their tickets? No. Issue history and comments remain. Make sure automations and shared filters owned by the user are reassigned.
What inactivity window should I use? Sixty to ninety days is common. Use a longer window for roles with seasonal usage.
Can I do this without a SaaS management platform? Yes, with the queries and scripts above. Platforms add cross-tool visibility, workflow automation and renewal management as you scale. See [Best SaaS Management Platforms (SMP) to Audit License Waste].
Conclusion
Inactive Salesforce and Jira licenses are one of the quickest wins in any SaaS cost program. Define what inactive means, query for it with SOQL and the Atlassian Admin API, automate the report on a schedule, give users a short grace period, and make reactivation painless. Then carry the savings into your next renewal conversation. Done weekly, the process pays for itself many times over.
