Skip to main content

Requirements

  • Python 3.9 or later
  • Dependencies: cryptography and typing_extensions (installed automatically with the PyPI package).

Installation

Install from PyPI as authforge-sdk. In code, import the authforge module:
Need a vendored single file? Copy authforge.py from the GitHub repository and ensure cryptography is declared in your environment. The published wheel is recommended for most projects.

Quick start

Constructor parameters

ttl_seconds (grace period)

Requested session token lifetime in seconds for /auth/validate. This is the grace period: how long the app keeps running on the signed session without contacting AuthForge. Pass None (or omit) to accept the server default of 24 hours. The server clamps to [3600, 604800] (1 hour to 7 days). The requested TTL is preserved across heartbeat refreshes, and apps relying on the grace period alone can extend their window up to 7 days.

Billing

  • Each successful login() or validate_license() costs 1 credit (one /auth/validate debit).
  • The grace period is free: local session re-verification makes no network calls and consumes no credits.
  • Online check-ins (if enabled) cost 1 credit per 10 successful heartbeats (billed on every 10th call).
  • With online check-ins, revocations take effect on the next check-in regardless of interval; without them, at session expiry.

Login

Returns True if authentication succeeded, False otherwise. On success, the SDK starts a background thread that re-verifies the signed session locally during the grace period (and sends online check-ins if online_heartbeat=True).

Validate license (no session)

Same /auth/validate flow and signatures as login, without storing session state on the client or starting the background thread. On success, the result includes optional entitlement convenience fields when the server sends them: session_expires_at, license_expires_at (None for lifetime keys after a JSON null), max_hwid_slots, hwid_count, and license_label. The full signed payload remains in session_data.

Failure callback

If authentication fails, an online check-in fails, or the session expires, the SDK calls your on_failure callback. Returning normally from the callback keeps the process running; if the callback raises an exception, the SDK calls os._exit(1). Without a callback (1.4.1+), a transient background check failure writes AuthForge: background check failed (<code>); retrying next interval to stderr and check-ins continue; every other failure (a rejected login(), a definitive check-in answer, the grace period running out) calls os._exit(1).

Check-in failures (1.4.0+)

Background check failures (online check-ins and the end of the grace period) reach on_failure("heartbeat_failed", exc), where exc is an AuthForgeError (a ValueError subclass):
  • exc.code: the server’s error code from the response body, whatever the HTTP status (revoked, expired, hwid_mismatch, blocked, session_expired, rate_limited, no_credits, system_error, malformed_request, …; for server codes str(exc) equals the code), or an SDK code: network_error, timeout, http_error_<status> (non-JSON error body), invalid_json_response, unexpected_response, signature_mismatch, nonce_mismatch. Codes this SDK version doesn’t know are passed through unchanged.
  • exc.transient / exc.fatal: the classification. is_transient_error(code_or_exc) is the same check as a function, and DEFINITIVE_ERROR_CODES lists the fatal codes.
A failed check-in only counts as an AuthForge verdict when the body is a JSON object with "status": "failed" and a non-empty error string. Any other failure body (for example a proxy’s JSON) is reported as unexpected_response, whose message includes the raw status and error. Request retries are automatic: rate_limited (or HTTP 429 with no error code) is retried after 2s, then 5s; no_credits, demo_quota_exceeded, and app_burn_cap_reached are never retried immediately and wait for the next interval; a network failure is retried once after 2s. Heartbeat network failures are reported once, as heartbeat_failed with code network_error or timeout. on_failure runs on the background heartbeat thread with no SDK lock held, so calling logout(), is_authenticated(), or login() from it is safe. A check-in still in flight when you call logout() or login() is discarded. Because the callback runs on a background thread, sys.exit() there only ends that thread. To tolerate short outages but shut down on a definitive answer, have the callback signal your main thread and let the main thread save and exit:
If your main thread is blocked (for example in input() or a long time.sleep()) rather than looping, call _thread.interrupt_main() from the callback instead: it raises KeyboardInterrupt on the main thread, so your existing try / finally cleanup runs. Calling os._exit(1) from the callback is a last resort for when the main thread cannot react at all: it skips finally blocks, atexit handlers and unflushed file writes, so save the user’s work first.
If you don’t set on_failure, a rejected login or a definitive background failure (including session_expired once the grace period runs out) terminates the process immediately with os._exit(1), skipping all cleanup. Transient check-in failures only print a warning to stderr. Always set a callback in production to handle graceful shutdown.

Reading variables

After a successful login, app variables and license variables are available on the client:

Grace period and online check-ins

By default, the app runs through the grace period after activation: the SDK re-verifies the signed session locally, makes no network calls, and stops when the session expires. Opt in to online check-ins for fast revocation and concurrent-use detection:
See Online Check-ins for a detailed comparison.

Offline license files (.authforge)

For machines that never connect to the internet, the operator mints a signed offline license file in the dashboard or Developer API (1 credit). The SDK verifies it locally with your public key and the machine HWID; no network, no check-ins, and online login() is untouched. Construct the client with app_secret=None (or "") so the air-gapped binary does not contain the App Secret.
Failures call on_failure("offline_login_failed", ValueError(code)) and return False (never os._exit). Codes, in check order: bad_armor, bad_signature, unsupported_version, malformed_payload, wrong_app, expired, hwid_mismatch. client.verify_license_file(path_or_text) and the module-level authforge.verify_license_file(text, app_id, public_key, hwid) run the same checks without touching state. Issued files cannot be revoked remotely; they stay valid until their own expiry. create_activation_request() / write_activation_request(path) produce the .authforge-request the operator uploads; hostname is omitted unless include_machine_name=True. See Activation requests.

Migrating from heartbeat_mode

heartbeat_mode="LOCAL" / "SERVER" is deprecated (it emits a DeprecationWarning) but still accepted:
  • heartbeat_mode="LOCAL": remove the option; the grace period is now the default.
  • heartbeat_mode="SERVER": use online_heartbeat=True instead.

Full example

GitHub

Full source, changelog, and issues: AuthForgeCC/authforge-python