NinjaOne End-User Access Report
It reads the NinjaOne Public API v2 with a read-only credential and assembles every end-user account across every organization into one interactive HTML report: who exists, which devices each account can reach (classified against the organization’s own inventory), what roles it holds and where they came from, whether its invitation was ever accepted, and its full portal sign-in history from the retained activity log. Each run diffs against your previous run, and a history page compares any two runs. Run it on demand or on a schedule.
The gap
NinjaOne shows end-user accounts inside each organization, but there is no single cross-organization view of who exists, what devices they can reach, or whether they still sign in - and the user objects carry no last-login field at all. For a provider managing dozens of client organizations, an access review means clicking through every org and still not knowing which accounts are dormant. The tool assembles that entire picture in one read-only pass: the account facts from the user endpoints, and the sign-in story recovered from NinjaOne’s own activity log.
What it does
Every capability below is visible in the screenshots - there is no dashboard to host, no database, and no agent on client devices. Each run writes one HTML file you can open, archive, or email, plus CSV and JSON exports of the same data.

What it does: lists every end user with organization, roles (and whether each came from direct assignment or SSO/SCIM), portal level, auth type, invitation status, and devices - classified against the org’s own inventory as Subset (N of M), All org devices, Cross-org, Other org, Unresolved, or None.
What it gives you: the exceptional states stand out - a user whose access spans more than one organization is flagged, and the healthy least-privilege norm reads as exactly that. Click any sign-in count to expand that account’s full history.

What it does: the headline counts - disabled, no device access, cross-org access, no sign-in on record, expired invites - are clickable filters, and an organization dropdown plus a free-text filter stack on top.
What it gives you: one click turns “53 users with no sign-in on record” from a number into the actual list, and Export CSV downloads exactly the filtered view - the deprovisioning list, ready to hand off.

What it does: every run auto-compares against your previous run and reports users added or removed, and role and device access expanded or removed, per account.
What it gives you: headline chips with the detail collapsed until you ask for it, plus a diff.csv of the same changes. The first run simply records a baseline.

What it does: keeps every run in its own dated folder and regenerates a history page listing them all, newest first.
What it gives you: pick any two runs and the full access diff renders inline - entirely client-side, working straight off the files on disk - so a quarterly review is not limited to comparing against just the previous run.
Inside a run
One run writes a timestamped folder containing the interactive report and its machine-readable exports, then refreshes the shared history page.
One self-contained HTML file - the summary cards, the changes-since-last-run section, and the full end-user table with sorting, filtering, expandable sign-in histories, filtered CSV export, and print-to-PDF.
| File | Contents |
end-user-access.html | The interactive report - the one file to open. |
end-users.csv | One row per end user: identity, org, roles with sources, access scope, devices, sign-in history. |
by-device.csv | The inverse view: each device and the end users who can reach it. |
roles.csv | End-user role reference with member counts. |
end-users.json | Full records for downstream automation. |
diff.csv | Only what changed since the previous run. |
index.html | Regenerated every run in the parent folder: the run history with the compare-any-two panel. |
See it work
A screen recording of the actual report being driven against a fully fictional tenant - clicking a summary card to filter the table, focusing one organization, expanding an account’s sign-in history, opening the changes-since-last-run detail, and comparing two saved runs on the history page. Every click is a real control.
The tool writes two kinds of file, both real and interactive on fictional data here: a per-run report and a single history page that compares any two runs. Both open in a new tab.
How it works
Authenticate with OAuth2 client credentials, collect organizations, devices, end users, roles, and sign-in activity over GET requests, analyze access scope and the diff against your previous run, then write one report and its exports. Each run becomes the baseline for the next.
Every API call is a GET against the public API v2; the OAuth credential carries the read-only monitoring scope, which cannot modify the tenant. The client survives real-world API behavior: it pages until an empty batch, retries transient failure responses instead of silently truncating, and de-duplicates overlapping activity pages.
Trust & safety
The credential, the transport, and the report copy are all designed so the tool cannot overstate what it knows or touch what it audits.
Authenticates with OAuth2 client credentials carrying only the monitoring scope and issues only GET requests - the credential itself cannot modify the tenant.
Credentials resolve from environment variables, NinjaOne Secure custom fields (readable only during agent automation), or a masked interactive prompt - never from a visible script variable, and never hard-coded.
Sign-in claims are bounded by NinjaOne’s activity-log retention, so the report prints how far back the log actually reached rather than implying a complete history.
Honest about scope. The report covers end-user accounts only - technician access is a different problem with different API coverage, handled by the companion Technician Permissions Reporter. “No sign-in on record” means none within the retained activity log, not proof the account never signed in. And a device id that no longer resolves is labeled Unresolved rather than being silently counted as cross-org access.
In practice
The tool is one PowerShell script that runs on a Windows workstation, or unattended as a NinjaOne scheduled automation on a management endpoint. It needs a NinjaOne API client application (machine-to-machine, client credentials, monitoring scope only) - created once under Administration → Apps → API.
Inside a single run
The script finds its credential - environment variables if set, NinjaOne Secure custom fields when running as a scheduled automation, or a masked prompt when run by hand - and exchanges it for a read-only bearer token held in memory for the run.
It reads organizations, devices, end users, and end-user roles, then walks the retained activity log for portal sign-in events. Device access is classified against each organization’s inventory, and the result is diffed against your most recent prior run.
It writes the interactive report, the CSV and JSON exports, and the change diff into a timestamped folder, then regenerates the run-history page. Exit code zero on success; a failed run fails loudly rather than shipping a silently incomplete report.
Every run after the first
Each new run auto-selects the most recent prior run as its baseline and reports exactly which users and which access changed since then. The very first run simply records the baseline.
Cadence is yours: run it ad hoc from a workstation, or let a NinjaOne scheduled task on a management endpoint run it unattended with the credential in Secure custom fields. The history page accumulates either way.
This walkthrough documents how the tool operates; it is not currently published for self-service use. The live sample report and run history above are the best way to see what a run produces.