sdkey
Official Python client for SDKey license authentication.
Implements the sealed session protocol: Ed25519-verified handshake, HKDF session keys, and AES-256-GCM envelopes for validate plus client auth (register / login / upgrade). See PROTOCOL.md.
Install
pip install sdkey
Requires Python 3.10+.
Quick start
Embed these values from the SDKey dashboard when you ship your app. app_version must exactly match the application version configured on the server (clientVersion on session init); mismatch returns APP_OUTDATED.
from sdkey import SdkeyClient, SdkeyError
client = SdkeyClient(
api_base_url="https://api.sdkey.dev",
app_id="YOUR_APP_ID",
app_version="1.0.0",
app_public_key_b64="YOUR_APP_PUBLIC_KEY_BASE64",
)
try:
# hwid is optional (omit for web clients — server skips HWID checks)
result = client.validate("SDKY-XXXX-XXXX-XXXX-XXXX", "machine-hwid")
if result.success:
print("licensed", result.status, result.expires_at, result.subscription_tier)
print("message", result.message)
else:
print("denied", result.code, result.message)
except SdkeyError as err:
# Init / transport failures use `error` text from the server when present
print(err.code, err.message)
raise
validate, register, login, and upgrade call init() automatically when no session exists and reuse an active session when present. Sessions last ~15 minutes server-side; on SESSION_EXPIRED the client clears local state so the next call re-handshakes.
Hardware ID (desktop)
Use get_hardware_id() on desktop apps to bind a license to the machine. It is opt-in — pass the result explicitly; the client never auto-injects HWID.
from sdkey import SdkeyClient, get_hardware_id
client = SdkeyClient(...)
result = client.validate("SDKY-XXXX-XXXX-XXXX-XXXX", get_hardware_id())
get_hardware_id() reads a stable OS machine identifier (Windows MachineGuid, Linux /etc/machine-id, macOS IOPlatformUUID), then returns the lowercase SHA-256 hex digest. On unsupported platforms or missing IDs it raises SdkeyError with code HWID_UNAVAILABLE — it does not invent a random ID. Omit hwid for web clients.
Client auth (sealed)
Register, login, and upgrade use the same sealed-session wire model as validate. Application binding and version gating come from the crypto session — do not send appId / clientVersion in the auth body.
reg = client.register(
username="player1",
password="••••••••",
license_key="SDKY-XXXX-XXXX-XXXX-XXXX",
hwid="machine-hwid", # optional
)
if not reg.success:
# Sealed failures expose plaintext `message` on ClientAuthResult.error
print(reg.code, reg.error)
else:
print(reg.session_token, reg.user, reg.license)
login = client.login(username="player1", password="••••••••")
upgrade = client.upgrade(username="player1", license_key="SDKY-HIGHER-TIER-KEY")
upgrade takes username + license key only (no password). The new key’s subscriptionTier must be strictly greater than the user’s current tier.
Breaking change (0.4.0): plaintext JSON client-auth bodies are rejected when the API has CRYPTO_ENFORCE=true (400 CRYPTO_REQUIRED). Use this SDK (or another sealed client) against production.
Where message vs error appears
Per-app responseMessages may customize many strings. The SDK surfaces whatever the server returns.
| Surface | Success text field | Failure text field |
|---|---|---|
| Session init | (none) | error (raised as SdkeyError.message) |
| Sealed validate | message |
message |
| Sealed register / login / upgrade | message |
message (exposed as ClientAuthResult.error) |
Example JSON shapes
Init failure (plaintext):
{ "success": false, "error": "Client version outdated", "code": "APP_OUTDATED" }
Sealed validate success (message):
{
"success": true,
"code": "OK",
"message": "validated",
"status": "active",
"expiresAt": "2026-01-01T00:00:00.000Z",
"subscriptionTier": 0,
"sessionId": "...",
"timestamp": 1720000001,
"v": 1
}
Sealed validate failure (still message, not error):
{
"success": false,
"code": "HWID_MISMATCH",
"message": "Hardware ID mismatch",
"status": null,
"expiresAt": null,
"sessionId": "...",
"timestamp": 1720000001,
"v": 1
}
Sealed client auth failure (message inside the opened plaintext):
{
"success": false,
"code": "TIER_NOT_HIGHER",
"message": "License tier must be higher than the current tier",
"sessionId": "...",
"timestamp": 1720000001,
"v": 1
}
API
SdkeyClient(options)
| Option | Type | Description |
|---|---|---|
api_base_url |
str |
API origin (no trailing slash) |
app_id |
str |
Application UUID |
app_version |
str |
Exact app version → sent as clientVersion on session init |
app_public_key_b64 |
str |
Raw Ed25519 public key (32 bytes), base64 |
http_post |
callable | Optional HTTP POST override (tests / custom transport) |
Methods
init()— challenge handshake; verifies the signed hello; derives the AES session key; sendsclientVersionvalidate(license_key, hwid=None)— sealed validate; omitshwidJSON key when not provided; always decrypts then verifies the Ed25519 signature before trustingsuccessregister(...)/login(...)/upgrade(...)— sealedPOST /api/v1/client/*(same outer envelope + verify order as validate)get_session()/clear_session()— inspect or drop the local sessionget_hardware_id()— package-level helper; SHA-256 hex of a stable OS machine ID (desktop opt-in)
Errors
Protocol / transport failures raise SdkeyError with a code and message (server error text when the API provides one):
INIT_FAILED · APP_OUTDATED · HELLO_SIGNATURE_INVALID · VALIDATE_RESPONSE_INVALID · RESPONSE_INVALID · RESPONSE_SIGNATURE_INVALID · SESSION_MISMATCH · CLOCK_SKEW · NETWORK · HWID_UNAVAILABLE
License denials (banned, HWID mismatch, etc.) return a normal ValidateResult with success=False — they are not raised. Auth denials return ClientAuthResult(success=False, code=..., error=...) where error is the sealed plaintext message.
This package does not include developer tooling / Bearer (sdk_live_…) management APIs.
Security notes
- Never ship app private keys in a client.
- Do not skip signature verification — that is the anti-spoof binding.
- This package is open source; the SDKey server remains a separate product.
Development
python -m pip install -e ".[dev]"
pytest
License
MIT
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file sdkey-0.4.0.tar.gz.
File metadata
- Download URL: sdkey-0.4.0.tar.gz
- Upload date:
- Size: 20.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f350890580c82650af55e4d047fc22707f8caa53ad605fa87f1c62af230f9e12
|
|
| MD5 |
ef06f927137cf70a97054796f660a8ff
|
|
| BLAKE2b-256 |
de65022649aa37528d68f5016d316b7d780aa18be0055efbf1ebf969dc711c09
|
Provenance
The following attestation bundles were made for sdkey-0.4.0.tar.gz:
Publisher:
publish.yml on SDKeyDev/sdkey-python
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
sdkey-0.4.0.tar.gz -
Subject digest:
f350890580c82650af55e4d047fc22707f8caa53ad605fa87f1c62af230f9e12 - Sigstore transparency entry: 2299617054
- Sigstore integration time:
-
Permalink:
SDKeyDev/sdkey-python@3d4c4e9fdc4de2b87f5decb1605d8e3052261fd8 -
Branch / Tag:
refs/tags/v0.4.0 - Owner: https://github.com/SDKeyDev
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@3d4c4e9fdc4de2b87f5decb1605d8e3052261fd8 -
Trigger Event:
release
-
Statement type:
File details
Details for the file sdkey-0.4.0-py3-none-any.whl.
File metadata
- Download URL: sdkey-0.4.0-py3-none-any.whl
- Upload date:
- Size: 15.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b3fb960f5718bf4741f9103986643f2414d589dcb3ae19d7a054bf5b7d0d0a99
|
|
| MD5 |
dfa2f295f602f0d27a2d34b05e0b9a1f
|
|
| BLAKE2b-256 |
59cb26e9f2c03be8bc3f0bf8839f1f0e4c82f17a03c4febb8636cfac0ef22be4
|
Provenance
The following attestation bundles were made for sdkey-0.4.0-py3-none-any.whl:
Publisher:
publish.yml on SDKeyDev/sdkey-python
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
sdkey-0.4.0-py3-none-any.whl -
Subject digest:
b3fb960f5718bf4741f9103986643f2414d589dcb3ae19d7a054bf5b7d0d0a99 - Sigstore transparency entry: 2299617086
- Sigstore integration time:
-
Permalink:
SDKeyDev/sdkey-python@3d4c4e9fdc4de2b87f5decb1605d8e3052261fd8 -
Branch / Tag:
refs/tags/v0.4.0 - Owner: https://github.com/SDKeyDev
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@3d4c4e9fdc4de2b87f5decb1605d8e3052261fd8 -
Trigger Event:
release
-
Statement type: