Skip to main content

Manage Keys

Every license on your account, in one flat table. Use this panel to search, filter, act on individual keys, and run bulk operations across applications. Key generation gets its own route further down.

Live updates

The Live chip in the top right turns green when the SSE stream is connected. If it drops, the table falls back to polling.

Any of these events refresh the table in place without a full reload: license.generate, license.ban, license.delete, license.extend, license.bulk_extend, license.reset_hwid, client.license, client.register.

Counts strip

Chips at the top summarize the whole key set before any filter is applied. total counts every key, active covers used keys still inside their duration window, banned covers blocked ones, unactivated covers keys that were never redeemed, and expired covers redeemed keys whose window has passed.

Status is computed in the browser. A key counts as expired only when it has an expires_at in the past and is not lifetime. Lifetime keys and keys with duration_days === 0 never expire regardless of anything else.

Filters

Three filters stack above the table and compose before sorting is applied.

The search box does a case-insensitive substring match against key, used_by, and hwid. The app select is either All apps or one specific application by name. The status select is All statuses, Active, Banned, Unactivated, or Expired.

Columns

Every column is sortable. Click a header once for ascending, click again for descending. The default sort is created_at descending.

ColumnSort keyNotes
KeykeyFull license string. Click to copy.
Appapp_nameApp name, falls back to a truncated app_id if the app is gone.
Userused_byUsername bound to the key, or unactivated.
HWIDhwidHardware ID captured at activation. Click to copy.
IPipLast known IP. Click to copy.
Durationduration_sortHuman label: 30 days, 1 week, lifetime.
Activatedactivated_atRelative time. Derived from expires_at minus duration_value * unit.
Createdcreated_atRelative time the key was minted.
Last Loginlast_loginRelative time of the most recent successful client login.
Statusstatus_calcColored badge: active, unactivated, banned, expired.

Hovering a truncated timestamp cell reveals the full local date and time as a tooltip.

Row actions

Each row exposes up to four icon buttons on the right, gated by role.

IconActionEndpointGate
ClockExtend / reducePOST /licenses/{id}/extendOwner and Admin only. Disabled for lifetime and for keys that have not been activated.
RefreshReset HWIDPOST /licenses/{id}/reset-hwidreset_hwid permission. Disabled when no HWID is bound.
Prohibit / CheckBan / UnbanPATCH /licenses/{id}/banban_unban_keys. Toggles based on current status.
TrashDeleteDELETE /licenses/{id}delete_keys. Permanent.

Reseller accounts see only the actions their permission set allows. See Team and Resellers for the full permission matrix.

Extend

The extend dialog shows the key text, its current expiry, and a Days field. Positive values add time, negative values subtract time, and zero is rejected client-side.

The request body is:

{
"value": 7,
"unit": "days",
"subtract": false
}

Negative input is sent as value: abs(days) with subtract: true.

Reset HWID

A confirm-only modal. On confirm the HWID binding is cleared, and the next successful client login binds a fresh one.

tip

Use this for legitimate hardware swaps and support tickets. It is not a workaround for HWID lock issues that have a real root cause; see HWID Lock if you keep hitting mismatches.

Delete

Permanent removal. If the key is currently bound to a user, the dialog surfaces that fact so you know whose access you are about to revoke. Deletion takes effect immediately for the next client request from that user.

Bulk actions

Selecting rows via the leftmost checkbox column opens an action bar above the table. The header checkbox toggles every row currently visible under the active filters.

The bar shows the selection count plus buttons for extend, unban, ban, delete, and clear. Availability follows the same permission gates as the row actions.

Extend uses the number input next to the button as the day delta and accepts negatives to subtract. The frontend sends one request per selected row, so lifetime keys or unactivated keys in a mixed selection are simply counted in the failed tally that the closing toast reports as X succeeded, Y failed.

warning

Bulk delete is not reversible. There is no soft-delete or trash bin, and active users on any deleted key lose access on their next request.

Generate Keys

Generate lives on its own route in the sidebar. It requires the generate_keys permission, and resellers without it get an access-denied card instead of the form. If you have no applications yet, the panel points you to Applications to create one first.

Form

The generator collects these fields before minting.

FieldRangeNotes
ApplicationrequiredYour chosen app_id is echoed under the field for reference.
Quantity1 to 500Keys minted in this batch.
Levelinteger, min 1Access-tier tag written onto every key. Your app decides what it means.
Duration chips30d / 90d / 180d / 365d / lifetimePresets. lifetime disables the day input.
Duration input1 to 3650Explicit day count when the chips do not fit.
Key formatmask stringSee below.
Character classesUppercase / Lowercase / NumbersAt least one must be checked.
NotesoptionalFree text stored on every key in the batch.

Masks

The mask is the literal template the generator emits. Any *, #, or X character becomes a random slot filled from the enabled character set. Everything else passes through unchanged, including letters, digits, dashes, underscores, and spaces.

MaskSample output (uppercase + numbers)
****-****-****-****A3F9-Z1QK-7BME-XV20
OBS-**********OBS-K7Q2M9AZ0P
promo-####-####promo-8XA2-Q19Z

A live preview below the mask input rerenders as you change the mask or the character classes.

Credits and pricing

Each application defines a key_costs map. The generator looks up the price per key from the closest matching tier and multiplies by the quantity.

per_key = key_costs["days:{N}"] when the exact tier exists
= key_costs["lifetime"] for lifetime keys
= max(1, ceil((days / 30) * key_costs["days:30"])) fallback when days:30 is set and greater than zero
= 1 when no key_costs is configured, or the fallback tier is missing or zero

Hours are rounded up to at least one day, weeks are multiplied by seven, and months are multiplied by thirty before the tier lookup.

The footer of the form shows total (per-key x quantity). For reseller accounts it also shows your current balance and blocks generation when the total exceeds it.

Request

The submit button posts:

POST /apps/{app_id}/licenses
Content-Type: application/json

{
"amount": 25,
"duration": 30,
"unit": "days",
"level": 1,
"mask": "****-****-****-****",
"alphabet": "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789",
"note": "launch batch"
}

The response is an array of created license objects. The panel extracts every key field and renders them in a scrollable result panel.

Result panel

The result panel appears once, right after generation. It gives you:

  • A Copy all button that puts every key on the clipboard, newline-separated.
  • A Download .txt button that emits a header block followed by the keys as a plain text file. The filename combines the app name, the key count, and a timestamp.
  • A per-row copy button for individual keys.
  • An Open app link back to the application detail page.

A yellow banner sits above the list: these keys are shown once. save them now.

danger

The plaintext key list is not retrievable from the API after the response has closed. Only the metadata rows in the Manage Keys table persist. If you close the panel without copying or downloading, those keys are gone from your side.

Recent batches

The last five batches from this browser session are cached in sessionStorage under generateKeys.recentBatches. Click a chip to re-open the same key list in the result panel without hitting the API. A clear button next to the chips wipes the cache.

Recent batches do not survive a browser restart, and they are not shared across devices or tabs.