← Documentation overview

Management API Reference

Register users and manage systems, keys, variables, resellers, and security reads from your own server-side tools.

Copy this reference as markdown for an AI: perfect for implementation assistance or clarifying questions.

Management API

The Management API lets a server-side tool manage one System Locker system over REST and JSON. Keep management credentials on your server. Never embed them in desktop software or publish them in a repository.

Version 2 is available at https://systemlocker.net/api/v2.

Management API v1 has been retired. Use v2 for management integrations.

Reference implementation

The System Locker Discord Bot is a public example of a Discord bot built with Management API v2.

Create a credential

Creating and using credentials requires an active subscription with more than 50 users. If the plan expires, is placed on hold, or no longer qualifies, requests return HTTP 423 with PLAN_INVALID; existing credentials can still be listed or revoked in the developer portal. Access resumes when the plan qualifies again.

Open the Systems page in the developer portal, select a system, and create a Management API v2 credential. Each credential belongs to that system and has only the scopes you select. Its complete value is shown once:

slm_<token_id>_<secret>

Send it as a bearer credential:

Authorization: Bearer slm_<token_id>_<secret>
Accept: application/json

Requests with a body must also send Content-Type: application/json. A credential cannot access another system, even when both systems have the same developer. Revoke a credential immediately if it may have been exposed.

Each credential can make 10 requests per 5 seconds. A rate-limited request returns HTTP 429 and a Retry-After header.

Scopes

  • systems.read, systems.update, systems.delete
  • users.manage
  • keys.create, keys.read, keys.update, keys.delete, individual_free_trial
  • variables.create, variables.read, variables.update, variables.delete
  • security.read
  • resellers.create, resellers.read, resellers.update, resellers.delete

A missing scope returns HTTP 403. A resource outside the credential's system returns HTTP 404.

Read the current credential's permissions

Send GET /api/v2/permissions with your bearer credential and Accept: application/json. No scope is required. It returns only that credential's granted scopes:

{
    "data": {
        "scopes": ["keys.create", "keys.read"]
    }
}

An empty scope list returns {"data":{"scopes":[]}}. This endpoint cannot inspect another credential. Invalid, revoked, or expired credentials return HTTP 401, and the normal per-credential rate limit applies. Responses include Cache-Control: no-store.

Use this endpoint to check an integration's permissions during setup or at startup:

  • An assistant helping a developer configure an integration can verify that the credential has the scopes needed for the intended workflow. If a required scope is missing, it can explain which permission to select when creating a replacement credential.
  • A Discord bot can enable commands according to the scopes returned. For example, it can disable key creation when keys.create is missing while keeping key lookup available when keys.read is present.

These are the credential's grants; operations still enforce plan requirements and resource access checks.

Systems

Method Path Purpose
GET /api/v2/systems List the credential's bound system.
GET /api/v2/systems/{system} Read its name, ID, version, program hash, and pause state.
GET /api/v2/systems/{system}/statistics Read online and total user counts for the system.
PATCH /api/v2/systems/{system} Update version and/or program_hash. Send null or an empty string to clear the program hash.
PUT /api/v2/systems/{system}/pause Pause authentication and end active sessions.
DELETE /api/v2/systems/{system}/pause Resume authentication. An optional JSON body of {"compensate": true} extends active key expiries by the paused duration.
DELETE /api/v2/systems/{system} Permanently delete the system and its associated data.

Statistics use the same user definition as v1: an account is counted once, while a redeemed key without an account is counted once by key. online_users is refreshed about every two minutes; total_users is refreshed hourly. Each value includes its own *_computed_at timestamp in RFC 3339 UTC.

User registration

Send POST /api/v2/systems/{system}/users to create a System Locker user account and claim license keys for it. The credential must have the users.manage scope and belong to {system}. users.manage replaces the previous users.create scope; existing credentials are updated when this change is deployed. Use the bearer credential and JSON headers described above.

{
    "username": "new-user",
    "email": "[email protected]",
    "password": "a-long-example-password",
    "license_keys": ["KEY-ONE", "KEY-TWO", "KEY-UNAVAILABLE"]
}
Field Requirements
username Required string, 1–31 characters after trimming. Must be unique across System Locker user accounts.
email Required valid email address, up to 140 characters after trimming. Stored in lowercase and must be unique across user accounts, ignoring case.
password Required string, 8–255 bytes. For ASCII passwords, this is 8–255 characters.
license_key A single key string or an array of key strings. Supply this field, license_keys, or both.
license_keys An array of key strings; a single string is also accepted. At least one key is required across the two fields.

Usernames and emails cannot contain spaces, line breaks, semicolons, pipes, angle brackets, ampersands, or single or double quotes. Usernames also cannot contain colons.

Each key must be a non-empty string of at most 128 characters. Both key fields are combined, with a maximum of 100 entries per request before duplicates are removed. Keys are trimmed and duplicate keys are claimed only once. For one key, you can replace license_keys in the example with "license_key": "KEY-ONE".

License-key results

Keys must belong to this system, be unclaimed and unredeemed, and be regular licenses. Free-trial keys cannot be claimed. The developer's plan must not be frozen, and each claim must fit within the plan's user limit.

If at least one key can be claimed, the endpoint returns HTTP 201:

{
    "data": {
        "id": 123,
        "username": "new-user",
        "email": "[email protected]",
        "license_keys": {
            "KEY-ONE": true,
            "KEY-TWO": true,
            "KEY-UNAVAILABLE": false
        },
        "verification_number": 457
    }
}

data.license_keys is an object mapping each distinct submitted key to a boolean: true means it was claimed for the new account; false means it was skipped. Check every result, even after HTTP 201. Claims are evaluated in order, so a batch can claim some keys and reach the plan's limit before the remaining keys are evaluated.

Claiming a key redeems it immediately. Any expiry measured from redemption starts at registration, before email verification.

If no keys can be claimed, the endpoint returns HTTP 422 with error.code set to VALIDATION_ERROR, and no account is created. Invalid fields, disallowed characters, and a username or email already used by another account also return HTTP 422 with VALIDATION_ERROR.

Email verification

The new account starts unverified, and System Locker sends a verification link to its email address. Show the returned verification_number to the user in your signup flow. It is a three-digit integer from 100 to 999.

The user must open the emailed link, enter that number on the System Locker website, and complete the CAPTCHA shown there. They can open the verification form without signing in first. Opening the link alone does not verify the account, and the API response does not sign the user in.

Add keys to an existing user

Send POST /api/v2/systems/{system}/users/keys to add keys to an existing user account. The credential must have the users.manage scope and belong to {system}. This action also requires the account's username and password, so keep it on a trusted server and never send those credentials from customer-controlled software.

{
    "username": "existing-user",
    "password": "the-account-password",
    "license_keys": ["KEY-ONE", "KEY-TWO"]
}
Field Requirements
username Required existing username, 1 to 120 characters after trimming.
password Required password for that account, 1 to 255 bytes. It is used only to authorize the request.
license_key A single key string or an array of key strings. Supply this field, license_keys, or both.
license_keys An array of key strings; a single string is also accepted. At least one key is required across the two fields.

Each key must be a non-empty string of at most 128 characters. Both key fields are combined, with a maximum of 100 entries per request before duplicates are removed. The account must already have a verified email. As on the user portal, keys must be regular, unclaimed licenses from {system}, and each claim must fit within the developer's user limit.

If at least one key can be claimed, the endpoint returns HTTP 200:

{
    "data": {
        "license_keys": {
            "KEY-ONE": true,
            "KEY-TWO": false
        }
    }
}

data.license_keys maps each distinct submitted key to its claim result. Check every value: a false result means that key was skipped. If no submitted keys can be claimed, the endpoint returns HTTP 422 with error.code set to VALIDATION_ERROR and does not add any keys. Duration-based keys begin their expiry when they are claimed.

License keys

Create 1–100 keys with POST /api/v2/systems/{system}/keys:

{
    "count": 2,
    "notes": "August order",
    "format": "@-%%%%-%%%%-%%%%",
    "free_trial": false,
    "expiry": {
        "type": "after_redemption",
        "seconds": 2592000
    }
}

format defaults to @-%%%%-%%%%-%%%%. Other custom formats require a plan with custom-key formatting and must contain exactly one @ or ! system-name placeholder plus 9–42 % random-character placeholders. reseller can contain a token associated with the selected system when the account has an active subscription. An unavailable reseller assignment returns HTTP 422 with VALIDATION_ERROR.

Expiry is one of:

  • {"type": "perpetual"}
  • {"type": "after_redemption", "seconds": 2592000}
  • {"type": "at", "at": "2026-09-01T00:00:00Z"}

Explicit timestamps can also be Unix seconds. RFC 3339 input must be UTC and end in Z. Timestamp output always uses RFC 3339 UTC.

Method Path Purpose
GET /api/v2/systems/{system}/keys/{licenseKey} Returns redemption status, HWID present, freeze state, and all timestamps
PATCH /api/v2/systems/{system}/keys/{licenseKey} Pause with compensation, freeze, or unfreeze.
POST /api/v2/systems/{system}/keys/{licenseKey}/hwid-reset Reset one key's HWID.
POST /api/v2/systems/{system}/keys/hwid-reset Reset every HWID in the system.
POST /api/v2/systems/{system}/keys/{licenseKey}/time Add time with a positive seconds integer. Perpetual keys cannot be extended.
DELETE /api/v2/systems/{system}/keys/{licenseKey} Permanently delete one key.

Adding time to an unredeemed duration-based key extends its future redemption duration without starting its clock.

Send {"paused":true} to freeze a key while its expiry continues. Add "compensate":true for Pause with compensation: access pauses and the elapsed time is restored to a redeemed key with a fixed expiry when it resumes. An unredeemed key has no running expiry, so compensation is not applied. The response includes the paused boolean and a pause_type value of standard, compensated, or null. compensate must be a boolean and is valid only while paused is true.

Individual free trials

The individual_free_trial scope creates a single trial key that can be used only once per HWID within the system. It is separate from the ordinary free_trial option. Trial keys require an active Lifetime plan or subscription and do not require custom-key formatting.

Create one with POST /api/v2/systems/{system}/individual-free-trials. It accepts the same notes, format, reseller, and expiry fields as key creation, plus an optional identifier string of up to 256 characters:

{
    "identifier": "discord-user-123",
    "expiry": { "type": "after_redemption", "seconds": 2592000 }
}

An identifier is unique within its system. Check it first with GET /api/v2/systems/{system}/individual-free-trials/identifier-availability?identifier=discord-user-123, which returns {"data":{"available":true}}. Set, replace, or clear it later with PATCH /api/v2/systems/{system}/individual-free-trials/{licenseKey} and {"identifier":"..."} or {"identifier":null}. We recommend using identifiers for a value like the user's Discord User ID.

Individual-trial HWIDs cannot be reset by the user or API. Deleting the key also removes its individual-trial registry entry.

Server-side variables

Method Path Purpose
POST /api/v2/systems/{system}/variables Create a variable with name, value, and optional protected.
GET /api/v2/systems/{system}/variables/{name} Read one variable.
PATCH /api/v2/systems/{system}/variables/{name} Update its value; optionally send a new name or protected value.
DELETE /api/v2/systems/{system}/variables/{name} Delete one variable.

Variable names can contain letters, numbers, and underscores, up to 40 characters. Values can contain up to 500 characters. Your plan's per-system variable limit still applies.

Resellers

Reseller management requires an active plan with more than 50 users and custom-key formatting. A plan on hold or without these features returns HTTP 423 with PLAN_LOCKED for reseller management requests. Reseller tokens and newly generated passwords are sensitive: a password is returned only when creating a reseller or resetting its password.

Method Path Purpose
POST /api/v2/systems/{system}/resellers Create one reseller.
GET /api/v2/systems/{system}/resellers List reseller names and tokens.
GET /api/v2/systems/{system}/resellers/{token} Read one reseller.
GET /api/v2/systems/{system}/resellers/{token}/permissions Read its permissions.
PATCH /api/v2/systems/{system}/resellers/{token}/permissions Replace its permissions.
GET /api/v2/systems/{system}/resellers/{token}/allowance Read its allowance, if configured.
PUT /api/v2/systems/{system}/resellers/{token}/allowance Create or replace its allowance.
DELETE /api/v2/systems/{system}/resellers/{token}/allowance Remove its allowance. Uses resellers.delete.
POST /api/v2/systems/{system}/resellers/{token}/password-reset Generate and return a new password.
DELETE /api/v2/systems/{system}/resellers/{token} Permanently delete the reseller and allowance.

Reseller names are required and limited to 80 characters after trimming. Create with name, a complete permissions object, and an enabled boolean. When enabled is true, set type to overall with overall_key_limit, or duration with all of day_key_limit, week_key_limit, month_key_limit, month_three_key_limit, year_key_limit, and lifetime_key_limit. Every limit is an integer from 0 to 4,294,967,295. Set enabled to false to create a reseller without an allowance.

POST /api/v2/systems/{system}/resellers accepts:

{
    "name": "Wholesale",
    "permissions": {
        "can_create_keys": true,
        "can_ban_keys": false,
        "can_freeze_keys": true,
        "can_reset_hwid": true,
        "can_access_all_keys": false
    },
    "enabled": true,
    "type": "duration",
    "day_key_limit": 1,
    "week_key_limit": 2,
    "month_key_limit": 3,
    "month_three_key_limit": 4,
    "year_key_limit": 5,
    "lifetime_key_limit": 6
}

It returns HTTP 201 and:

{
    "data": {
        "token": "RESELLER-system-abc123",
        "password": "a1b2c3d4e5",
        "name": "Wholesale",
        "permissions": {
            "can_create_keys": true,
            "can_ban_keys": false,
            "can_freeze_keys": true,
            "can_reset_hwid": true,
            "can_access_all_keys": false
        },
        "allowances": {
            "type": "duration",
            "overall_key_limit": 0,
            "day_key_limit": 1,
            "week_key_limit": 2,
            "month_key_limit": 3,
            "month_three_key_limit": 4,
            "year_key_limit": 5,
            "lifetime_key_limit": 6
        }
    }
}

GET /api/v2/systems/{system}/resellers lists each reseller's token and name only. GET /api/v2/systems/{system}/resellers/{token} returns the same shape as the creation response, without password; allowances is null when the reseller has none.

Reseller responses include a permissions object with the boolean keys can_create_keys, can_ban_keys, can_freeze_keys, can_reset_hwid, and can_access_all_keys. The permissions endpoint returns that same object under data; replace it with PATCH using {"permissions": {"can_create_keys": true, "can_ban_keys": false, "can_freeze_keys": true, "can_reset_hwid": true, "can_access_all_keys": false}}.

Allowance endpoints return the allowances object from the creation response, or data: null when no allowance is configured. PUT accepts the same enabled, type, and limit fields as creation; send {"enabled": false} to disable an allowance. password-reset returns the new password under data.

Security reads

GET /api/v2/systems/{system}/security/ip-lookup?ip=8.8.8.8 performs an Aegis manual IP lookup when the developer's plan includes Aegis.

GET /api/v2/systems/{system}/keys/{licenseKey}/logs returns the latest five authentication logs for that key. Logging access is required. IP addresses are included only when the developer's logging level permits IP visibility.

Errors

Errors use their HTTP status and one JSON shape:

{
    "error": {
        "code": "INSUFFICIENT_SCOPE",
        "message": "This API key cannot create keys."
    }
}

Common statuses are 401 for an invalid credential, 403 for a missing scope or plan feature, 404 for an inaccessible resource, 422 for invalid input or a plan limit, 423 when the plan does not permit API access or an operation, and 429 for rate limiting.