OpenAuth binds license keys to devices, signs every reply so a fake server gets nowhere, and keeps your software running offline on device-bound leases. Start with one HTTPS request; drop in an SDK when you want the rest.
What your software sees on the signed API: a normal login, an offline activation, a reply from a fake server and a replayed reply.
$ client.login(key="OA-7Q2M-K9XD-4HPL")→POST /v2.php action=loginnoncehG1v0dQ2dW4mS8pPq3Lz5vX9epk P-256, made for this request←200 · 41 ms · eyJhbGciOiJFUzI1.eyJ2IjoyLCJhY3Qi.bXaz54VV…✓ alg ES256 · typ oa-response+jwt✓ signature verifies · kid f13358ad67d9✓ nonce, aud and act match · exp in 300 s✓ rcpt = thumbprint(epk)ok class 2 · Standard · [core, export]
$ client.ensure(key="OA-7Q2M-K9XD-4HPL")→POST /v2.php action=activatedevice_key P-256 · 3vQx…9Tkproof signed by the device key←200 · 58 ms · lease issued✓ lease signature verifies · ES256✓ bound to this device · 3vQx…9Tk✓ window 7 d · renews at 50% (3 d 12 h)✓ stored encrypted on this deviceok runs offline until Thu 23 Oct
$ client.login(key="OA-7Q2M-K9XD-4HPL")→POST /v2.php action=login! auth.openauth.gg → 127.0.0.1 (hosts file)←200 · 2 ms {"status": "ok", "class": "9"}✗ no jws: the reply isn't signed✗ handled like a network errorunsigned nothing unlocked
Generate up to 1,000 keys a batch, with an expiry date, your own text and a class for each tier you sell. Name your classes, give them features your software checks, and target them anywhere with a selector.
Bulk batches
Expiring
Custom text
Freeze
Notes
CSV import
Class selectortry 2+, 1-3, Pro or 1, 9
Example classes
Class
Name
Features
Matches
1
Trial
core
2
Standard
core, export
3
Pro
core, export, pro-tools
9
Staff
core, export, pro-tools, debug
Matches Standard, Pro and Staff
Keys or accounts
Customers type a license key, or sign in with a username and password. Accounts come with a customer portal where they redeem keys, download your files and move their own device.
License keyOA-7Q2M-…
User accountuser.openauth.gg
Device and IP locking
The first login binds a key to that device or network, and every other one is turned away. Customers move devices from the portal or your Discord bot, with a cooldown you set.
A key seen from more IP addresses or countries in 24 hours than one customer plausibly would is flagged. You get an alert, and OpenAuth can freeze it for you.
Block or relax logins by country, class, device or IP. Turn away VPNs, proxies and hosting providers, and keep block and allow lists.
when country = DE → restrict hwid
when class = 1 →blockwhen class = 4+ → bypass ip
vpn blockingon
Release checks
Enforce the version, build ID and file signature of your releases, so old and modified copies can't sign in.
version 1.5.0 ✓build 2026.10.12 ✓sig a41f…07c2 ✗ sigmodified files rejected
Variables
Send links, messages and settings inside the signed reply, to every class or only some. Keep values plain, Base64-encoded, or encrypted with a key only your software has.
motdplain Version 1.5 is out.
download.urlbase64 aHR0cHM6Ly9jZG4u…
license.saltaes-gcm ●●●●●●●●●●●●
only for 2+
Insights
Every login, activation and download, with its result, country and API version. Search by key, HWID or IP to answer "why can't this customer sign in?"
22:41:07ok OA-7Q2M… DE v2
22:40:52hwid OA-C3NF… US v1
22:40:31expired OA-1XQ2… BR v2
22:40:12ok OA-K8RW… GB v2
Team and resellers
Invite support staff, moderators, administrators and managers, each with exactly the access they need. Resellers generate keys within the allowance, classes and durations you set.
Owner
Manager
Administrator
Moderator
Support
Reseller
Automation
Create a key after every sale, look up a customer, or reset a device from your own server with the Management API. Each API key gets only the scopes it needs. Post logins to Discord, start from the Discord bot example, or connect your XenForo forum.
The first login binds the key to that device or network, and every other one is refused. Keys that still turn up from more IP addresses or countries in a day than you allow are flagged, and can be frozen automatically.
What their copy hears back
→ login key=OA-7Q2M-K9XD-4HPL hwid=9A04…B2C1
bound to 7C1E…44F0 since 12 Oct←hwid This license is registered to another device.
Enforce the version, build ID and file signature of your releases, so old and modified copies can't sign in. Keep the parts that matter out of the public download entirely: stream them only after a successful login.
What their copy hears back
→ login version=1.5.0 sig=a41f…07c2
expected sig 5be0…e19d←sig The application files have been modified.
Every reply is signed with your application's private key, and your software holds the public key. A cloned server or a hosts-file redirect can answer, but it can't produce a signature that verifies.
What their copy hears back
→ POST auth.openauth.gg → 127.0.0.1← 200 {"status": "ok", "class": "9"}
✗ unsigned: no jws, so nothing is unlocked
Each request carries a fresh random nonce. A signed reply has to echo it, name your application and the action, and be less than five minutes old, so yesterday's answer matches none of today's requests.
What their copy hears back
← 200 a reply signed 26 hours ago
✓ signature verifies
✗ tampered: nonce and exp don't match this request
Each reply is encrypted with ECDH-ES and AES-256-GCM to a one-time key your software made for that request alone. There's no key to dig out of your build, and a captured reply can't be read later.
What the proxy sees
← 200 {"enc": "ECDH-ES+A256GCM",
"iv": "3o9Yx2ZkQh7rLm0p", "data": "u2J0qV8c…"}
✓ rcpt = thumbprint(epk): addressed to this request only
A lease names the one device key it was issued to. That key is made on the device and never leaves it, and the lease is stored encrypted. Copied to another PC it can't be read, and activating there counts against your device limit.
What their copy hears back
copy lease → DESKTOP-2
✗ tampered: the local store can't be read here
←install_limit 1 of 1 devices already active
The SDKs remember the latest time they've seen, in two places, and refuse to run if the clock falls more than five minutes behind it. A runtime budget counts actual running time, so a frozen clock can't stretch a lease either.
What their copy hears back
clock 2026-10-02 09:00 (set back 14 days)
seen 2026-10-16 22:41
✗ clock The system clock was set backwards.
Turn away logins from known VPNs, proxies and hosting providers. Countries come from Cloudflare, not from anything the client says, so access rules by country can't be talked around.
What their copy hears back
→ login ip=185.220.101.4 (hosting provider)←proxy_blocked
Please disconnect from your VPN or proxy and try again.
The first check that fails is the reply, so the code always tells you exactly where a login stopped. Frozen and expired keys are refused on every plan.
01API key belongs to an appconnection
02OpenAuth is onlineglobal
03Base monthly allowancepings
04Open access is offreturns class
05Matches the sign-in methodinvalid
06App is accepting loginsclient
07Key or account is validlogin
08Key isn't frozenfrozen
09Key hasn't expiredexpired
10VPN and proxy blockingproxy_blocked
11Access rulesregion_blocked
12Allow listskips the rest
13Block listblacklisted
14HWID lock, then IP lockhwid · ip
15Version, build and signatureversion · build · sig
Every reply is a JSON Web Signature, signed with your application's key and tied to the request that asked for it. Your software runs five checks before it believes a word.
1Unwrap. Take the JWS from jws, or decrypt it from enc.
2Verify the signature. ES256 over header and payload, with the public key built into your software.
3Match the request.nonce, aud and act are yours, and exp hasn't passed.
4Check the recipient.rcpt is the thumbprint of your one-time key.
5Then read it.status, class, features and vars.
The Python and C# SDKs do all five on every reply.
Encrypts each reply to a key agreed for that request alone. No stored key to extract, and captured replies stay unreadable.
Your software needs
A fresh epk with each request. The SDKs send one.
04Offline licensing
Keeps working when the connection doesn't
Your software checks the license online once and receives a signed lease bound to that device. Customers keep working on a plane, behind a firewall or through an outage, and the lease renews itself when they're back online.
A device that's online renews after 3 d 12 h. One that stays offline keeps working for 7 days, then needs to connect once.
Signed, device-bound leases
Each lease is signed with ES256 and names the one device key it was issued to.
Devices per key
Allow 1 to 100 devices per key, and give each class its own limit.
Copying it does nothing
The lease is stored encrypted and bound to a key that never leaves the device.
Clock tricks don't help
Turning the clock back is detected, and a runtime budget ends the lease even if the clock is frozen.
Every renewal re-checks
A revoked, frozen or downgraded key changes at the next renewal, along with its class and features.
Revoke any installation
See every installation in the dashboard and revoke the ones you no longer trust.
05Releases and files
Ship updates through the same license check
Publish releases on channels, or let OpenAuth import them from GitHub. Your software asks for the newest version on its channel and gets the file only after its license checks out.
Link a public or private repository and import its releases, or each commit that changes a build file. Store a copy on OpenAuth or send customers to GitHub. On Lite and Premium, call releases.sync from your CI.
repo acme/launcher privatepattern MyApp-*-win64.zip
webhookrelease published → imported
Files behind the login
Keep program files and modules on OpenAuth and stream them only after a successful login, or offer them in the customer portal. Up to 6 files of 100 MB each on Premium.
Nothing reaches customers before its check. A file is held for review only when 10 or more engines call it malicious. Labels that only describe what a tool is, such as PUA, HackTool, GameHack or Packed, are shown but don't count.
MyApp-1.5.0-win64.ziplive
3 of 70 engines · 7 more only as PUA / Packedreview at 10
06Developers
One request to start. An SDK when you want the rest.
A login is one POST with a plain-text or JSON reply, from any language that speaks HTTPS. The single-file SDKs add signed replies, offline leases and updates.
1Create an application and copy its API key.
2Generate keys in bulk, or import the ones you already sold.
3Send the request, or add openauth.py or OpenAuth.cs to your project.
from openauth import OpenAuth
client = OpenAuth(
api_key="YOUR_API_KEY",
app_id="YOUR_APP_ID",
public_key={"kty": "EC", "crv": "P-256", "x": "...", "y": "..."},
product="my-product",
)
# Online the first time, offline after that.
lease = client.ensure(key=token_from_customer)
# Renew in the background; lock_the_app runs if the license is lost.
client.start(on_lost=lock_the_app)
if lease.has_feature("export"):
enable_export()
Lease2Verified on the device: class, features and vars, valid offline for the window you set.
using OpenAuth;
var auth = newOpenAuthClient(newOpenAuthOptions
{
ApiKey = "YOUR_API_KEY",
AppId = "YOUR_APP_ID",
PublicKeyJwk = """{"kty":"EC","crv":"P-256","x":"...","y":"..."}""",
Product = "my-product",
});
// Online the first time, offline after that.Lease lease = await auth.EnsureAsync(key: tokenFromCustomer);
// Renew in the background; LockTheApp runs if the license is lost.
auth.Start(onLost: error => LockTheApp(error.Message));
if (lease.HasFeature("export")) EnableExport();
Lease2.NET 8 or newer, no NuGet packages. Lease state is protected with DPAPI on Windows.
Reply2The key's class. Anything else, such as login or hwid, says why the login failed.
import requests
reply = requests.post(
"https://auth.OpenAuth.gg/index.php/",
data={
"apikey": "YOUR_API_KEY",
"hwid": get_hwid(),
"lkey": token,
},
timeout=10,
).text.strip()
if reply in {"1", "2"}: # the classes you hand outprint(f"Signed in with class {reply}")
else:
print(f"Login rejected: {reply}")
Reply2token is the key your customer entered; get_hwid() is your own function that returns a stable device ID.
const string ApiUrl = "https://auth.OpenAuth.gg/index.php/";
using var http = newHttpClient { Timeout = TimeSpan.FromSeconds(10) };
var form = newFormUrlEncodedContent(newDictionary<string, string>
{
["apikey"] = "YOUR_API_KEY",
["hwid"] = GetHwid(),
["lkey"] = token,
});
var response = await http.PostAsync(ApiUrl, form);
var reply = (await response.Content.ReadAsStringAsync()).Trim();
Console.WriteLine(reply is"1"or"2"
? $"Signed in with class {reply}"
: $"Login rejected: {reply}");
Reply2token is the key your customer entered; GetHwid() is your own function that returns a stable device ID.
// Node 18 or newer, saved as login.mjsconst API_URL = "https://auth.OpenAuth.gg/index.php/";
const response = awaitfetch(API_URL, {
method: "POST",
body: newURLSearchParams({
apikey: "YOUR_API_KEY",
hwid: getHwid(),
lkey: token,
}),
signal: AbortSignal.timeout(10_000),
});
const reply = (await response.text()).trim();
console.log(["1", "2"].includes(reply)
? `Signed in with class ${reply}`
: `Login rejected: ${reply}`);
Reply2token is the key your customer entered; getHwid() is your own function that returns a stable device ID.
07Pricing
Start free. Upgrade when you need more.
Plans apply per application, so a free test app can sit next to a Premium product.
No. A login on the original endpoint is one POST request with a plain-text or JSON reply, from any language that can make HTTPS requests. The Python and C# SDKs are single files you add to your project when you want signed replies, offline leases and updates without writing the verification yourself.
What happens when a customer goes offline?
With offline licensing turned on, a successful activation gives the device a signed lease. Your software keeps working until the window you set runs out, anywhere from 20 minutes to 90 days, and renews the lease in the background once it's back online. Without it, treat a network error as a failed login.
How does HWID locking work?
Your software sends a device ID with every login. The first successful login binds the key to that device, and logins from any other device are refused with hwid. When a customer changes computer, reset the binding from the dashboard, or let them do it themselves from the portal or your Discord bot, with a cooldown. IP locking works the same way with IP addresses.
Can I move my keys from KeyAuth or auth.gg?
Yes. Export your keys from KeyAuth or auth.gg (or any spreadsheet) and upload the file under Control Panel > Import tokens. Classes, expiry dates and notes come along, and customers keep their existing keys. Importing is part of Lite and Premium.
Will VirusTotal hold my launcher because it's packed?
Not for that alone. Detections that only say what kind of tool a file is (PUA, HackTool, GameHack, or Packed and protectors such as Themida or VMProtect) are shown next to the file but don't count. A file is held for review only when 10 or more engines flag it as malicious.
Is my API key a secret?
No. The API key and the public signing key ship inside your software and can only check licenses, so it's fine if customers find them. Your secret key and Management API keys are the ones to protect: keep them on your own server and never put them in software you distribute.
$ client.ensure(key=token) ✓ ok · class 2
Protect your next release
Create a free application in a minute. Upgrade to Premium when you want device locking, offline licensing and the rest.