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
Metadata
Release files for sdkey 0.4.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| sdkey-0.4.0.tar.gz | 20.2 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| sdkey-0.4.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 35.4 kB
Release files / sdkey-0.4.0.tar.gz
| Download URL | sdkey-0.4.0.tar.gz |
|---|---|
| Size | 20.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
f350890580c82650af55e4d047fc22707f8caa53ad605fa87f1c62af230f9e12
|
|
BLAKE2b-256 checksum How to use checksums |
de65022649aa37528d68f5016d316b7d780aa18be0055efbf1ebf969dc711c09
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 31, 2026.
Transparency logRelease files / sdkey-0.4.0-py3-none-any.whl
| Download URL | sdkey-0.4.0-py3-none-any.whl |
|---|---|
| Size | 15.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
b3fb960f5718bf4741f9103986643f2414d589dcb3ae19d7a054bf5b7d0d0a99
|
|
BLAKE2b-256 checksum How to use checksums |
59cb26e9f2c03be8bc3f0bf8839f1f0e4c82f17a03c4febb8636cfac0ef22be4
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 31, 2026.
Transparency logSecurity for sdkey 0.4.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for sdkey-0.4.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 31, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.