Skip to main content

Users

The Users tab is the per-app roster of accounts that have registered through the client SDK: one row per end_users document, created on register and refreshed on every login. It lives inside an application (Applications, open one, Users), and two apps never share a user list even when the same person registers with the same username on both.

When the tab is empty

Three situations produce a stubbornly empty table.

  • The app runs in license-only mode. Clients call /api/client/license, which validates the key and sets its used_by to "license-only" without ever touching end_users. Manage those activations from the Keys tab instead.
  • The app uses username and password auth, but no client has called register yet.
  • You are signed in as a reseller without view_keys. In that case the tab is hidden from the sidebar entirely; if you can see it, you can list it.

Columns

The list endpoint is GET /api/apps/{app_id}/users, defined in backend/routers/licenses.py. Every column below is populated from a single response.

ColumnSource fieldNotes
UsernameusernameClick to copy.
Createdcreated_atServer timestamp when register was accepted. Hover for the raw ISO value.
Last Loginlast_loginSet on every successful /api/client/login. Shows never for a user that has registered but not yet logged in.
IPipLast IP observed on login. Click to copy. Hidden from resellers that lack view_ip.
HWIDhwidCurrent HWID lock when the app has HWID lock enabled. Click to copy. Hidden from resellers that lack view_hwid.
LevellevelSubscription level assigned by whichever license activated the user.
Locationlast_geoCountry and city derived from the last login IP.
Screenlast_screenshot_pathThumbnail of the screenshot captured on the last activation, when screenshot capture is enabled on the app.
Expiresexpires_atDate-only slice of the ISO timestamp, or when the account has no expiry.
Statusbannedactive (green) or banned (red).

The column count adapts to your role. IP and HWID drop out entirely for resellers without those permissions, and the empty-state row's colSpan shifts to match.

info

Screenshot thumbnails require both view_ip and view_hwid. A reseller missing either permission sees an empty Screen cell regardless of whether capture is enabled on the app.

Click to copy

Username, IP, and HWID render through the internal CopyText component. Click a value and it goes to your clipboard via navigator.clipboard.writeText, with a green check that flashes for about a second. On older browsers or insecure origins the component falls back to a hidden <textarea> and document.execCommand("copy").

There is no keyboard shortcut. Point, click, done.

Actions

Every row shows up to three icon buttons on the right, each gated by a reseller permission. Owners, admins, and developers see all three no matter what.

IconActionEndpointRequired permission
Counter-clockwise arrowReset HWIDPOST /api/users/{user_id}/reset-hwidreset_hwid
ProhibitBan / UnbanPATCH /api/users/{user_id}/ban with {"banned": true | false}freeze_unfreeze
TrashDelete userDELETE /api/users/{user_id}delete_keys

Reset HWID wipes both hwid and known_hwids on the user document. The next successful login re-locks the account to whatever device authenticated. Use it for legitimate hardware swaps.

Ban flips banned to true. The row stays, but every future /api/client/login returns 401 Invalid username or password, deliberately indistinguishable from a wrong password. Unban to reverse.

warning

Delete is permanent. The end_users document is removed and any live heartbeat session referencing it becomes orphaned. The underlying license key stays intact and reusable unless you also revoke it from the Keys tab.

All three actions write an audit event that shows up in the Logs tab.

Reseller visibility

Reseller scoping is enforced server-side in backend/access.py, not just hidden in the UI. Two mechanisms combine.

scoped_user_filter narrows the query to reseller_id ∈ {reseller_id, ...admin descendants}. A reseller only ever sees users registered under keys they, or resellers they manage, generated. Owners and admins with no parent see everything for the app.

strip_reseller_hidden then runs on every returned document and pops fields the reseller is not allowed to see:

  • No view_hwid: hwid and known_hwids are removed.
  • No view_ip: ip and known_countries are removed, and geo objects (geo, last_geo, activation_geo) have ip, city, region, country, lat, lon scrubbed from them.
  • Missing either view_hwid or view_ip: screenshot paths are removed, leaving the Screen column empty.

The dashboard mirrors both rules with CanReseller("view_ip") and CanReseller("view_hwid") so the affected columns never render as blank placeholders. Because the filter runs inside the database query itself, a hand-crafted curl from a scoped reseller cannot see a peer's users.

Bulk behavior and limits

The tab has no bulk-select UI. Actions run one row at a time from here; for scripted cleanup, drive the endpoints directly.

The list endpoint returns at most 2000 users per app, sorted by created_at descending. Apps above that cap need paginated calls straight to the API, since the dashboard truncates.

Refresh

The tab does not poll. It fetches once when you open the app and re-fetches after every mutating action (reset HWID, ban, delete). Switch tabs and back, or hit the browser refresh, to force a manual reload.