Skip to content

Commit 9f8237b

Browse files
committed
Productize grace period; deprecate heartbeat mode
The grace period (running on the signed session until the TTL expires) is now the default and needs no configuration. Online check-ins are opt-in via online_heartbeat=True. Legacy heartbeat mode values still work behind a deprecation shim: LOCAL maps to the default, SERVER maps to online check-ins. README and AGENTS rewritten with the new vocabulary and a migration section. Version bumped to 1.1.0.
1 parent 11c50c0 commit 9f8237b

5 files changed

Lines changed: 236 additions & 66 deletions

File tree

‎AGENTS.md‎

Lines changed: 35 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,21 @@
1-
# AuthForge SDK — AI Agent Reference
1+
# AuthForge SDK: AI Agent Reference
22

33
> This file is optimized for AI coding agents (Cursor, Copilot, Claude Code, etc.).
44
> It contains everything needed to correctly integrate AuthForge licensing into a project.
55
66
## What AuthForge does
77

8-
AuthForge is a license key validation service. Your app sends a license key + hardware ID to the AuthForge API, gets back a cryptographically signed response, and runs background heartbeats to maintain the session. If the license is revoked or expired, the heartbeat fails and you handle it (typically exit the app).
8+
AuthForge is a license key validation service. Your app activates by sending a license key + hardware ID to `POST /auth/validate`; the server checks revocation, expiry, HWID binding, and credits, then returns an Ed25519-signed session with a TTL. By default the app then runs through the **grace period**: it keeps running on the signed session with no network calls, and a background check fails when the session TTL expires. Optionally, enable **online check-ins** (`online_heartbeat=True`): periodic `POST /auth/heartbeat` calls for fast revocation and concurrent-use detection. If the license is revoked or expired, the check-in fails and you handle it (typically exit the app).
99

1010
## Billing model (so you can pick sensible intervals)
1111

1212
- **1 `login()` or `validate_license()` = 1 credit** (one `/auth/validate` debit each).
13-
- **10 heartbeats = 1 credit** (billed on every 10th successful heartbeat per license).
14-
- Keep `heartbeat_interval` at `>= 10` seconds (`900` / 15 min is the typical desktop default). `/auth/heartbeat` is limited to 6 requests/minute per license key, and revocations still take effect on the **next** heartbeat.
13+
- **10 online check-ins = 1 credit** (billed on every 10th successful `/auth/heartbeat` per license). Grace period checks are local and free.
14+
- Keep `heartbeat_interval` at `>= 10` seconds (`900` / 15 min is the typical desktop default). `/auth/heartbeat` is limited to 6 requests/minute per license key, and revocations still take effect on the **next** check-in.
1515

1616
## Installation
1717

18-
Prefer **`pip install authforge-sdk`** from [PyPI](https://pypi.org/project/authforge-sdk/) (installs the `cryptography` dependency). Imports remain **`from authforge import …`**. For a vendored single-file layout, copy `authforge.py` and add `cryptography` to your environment. Requires Python 3.9+.
18+
Prefer **`pip install authforge-sdk`** from [PyPI](https://pypi.org/project/authforge-sdk/) (installs the `cryptography` dependency). Imports remain **`from authforge import ...`**. For a vendored single-file layout, copy `authforge.py` and add `cryptography` to your environment. Requires Python 3.9+.
1919

2020
## Minimal working integration
2121

@@ -37,7 +37,7 @@ def main() -> None:
3737
client = AuthForgeClient(
3838
app_id="YOUR_APP_ID",
3939
app_secret="YOUR_APP_SECRET",
40-
heartbeat_mode="SERVER",
40+
public_key="YOUR_PUBLIC_KEY", # required: base64 Ed25519 key from the dashboard
4141
on_failure=on_failure,
4242
)
4343
license_key = input("Enter license key: ").strip()
@@ -54,40 +54,55 @@ if __name__ == "__main__":
5454
main()
5555
```
5656

57+
This activates once and then runs through the grace period (no network) until the session TTL expires. To detect revocations quickly or catch concurrent use, add `online_heartbeat=True` (and optionally tune `heartbeat_interval`).
58+
5759
## Constructor parameters
5860

5961
| Parameter | Type | Required | Default | Description |
6062
|-----------|------|----------|---------|-------------|
61-
| `app_id` | `str` | yes | — | Application ID |
62-
| `app_secret` | `str` | yes | — | Application secret |
63-
| `heartbeat_mode` | `str` | yes | — | `"SERVER"` or `"LOCAL"` (case-insensitive) |
64-
| `heartbeat_interval` | `int` | no | `900` | Seconds between heartbeats (minimum `10`) |
63+
| `app_id` | `str` | yes | n/a | Application ID |
64+
| `app_secret` | `str` | yes | n/a | Application secret |
65+
| `public_key` | `str \| Sequence[str]` | yes | n/a | Base64 Ed25519 public key from the dashboard. Accepts one key, a list, or a comma-separated string; the SDK trusts a signature matching **any** entry (key rotation) |
66+
| `heartbeat_mode` | `str \| None` | no | `None` | Deprecated shim: `"LOCAL"` maps to the default (grace period), `"SERVER"` maps to `online_heartbeat=True`. Emits a `DeprecationWarning` when provided (case-insensitive) |
67+
| `heartbeat_interval` | `int` | no | `900` | Seconds between background checks (minimum `10`); applies to grace period checks and online check-ins |
6568
| `api_base_url` | `str` | no | `https://auth.authforge.cc` | API base URL |
66-
| `on_failure` | `Callable[[str, Optional[Exception]], None] \| None` | no | `None` | Called on login/heartbeat/network failure; if omitted, process exits via `os._exit(1)` (not used by `validate_license`) |
69+
| `on_failure` | `Callable[[str, Optional[Exception]], None] \| None` | no | `None` | Called on activation/check-in/network failure; if omitted, process exits via `os._exit(1)` (not used by `validate_license`) |
6770
| `request_timeout` | `int` | no | `15` | HTTP timeout (seconds) |
68-
| `ttl_seconds` | `int \| None` | no | `None` (server default: 86400) | Requested session token lifetime. Server clamps to `[3600, 604800]`; preserved across heartbeat refreshes. |
71+
| `ttl_seconds` | `int \| None` | no | `None` (server default: 86400) | The grace period duration: how long the app keeps running on the signed session without contacting AuthForge. Server clamps to `[3600, 604800]` (1h to 7d); preserved across check-in refreshes. |
6972
| `hwid_override` | `str \| None` | no | `None` | Optional custom HWID/subject string. When set to a non-empty value (for example `tg:123456789`), the SDK sends it instead of generating a machine fingerprint. |
73+
| `online_heartbeat` | `bool` (keyword-only) | no | `False` | Enable online check-ins: periodic `POST /auth/heartbeat` for fast revocation and concurrent-use detection |
7074

7175
For Telegram/Discord bot flows, prefer immutable IDs (`tg:<user_id>`, `discord:<user_id>`) instead of usernames.
7276

77+
## Migrating from heartbeat_mode
78+
79+
Earlier releases required `heartbeat_mode="LOCAL"` or `"SERVER"` as the 4th argument. It is now optional and deprecated (still accepted, but emits a `DeprecationWarning`):
80+
81+
- `heartbeat_mode="LOCAL"`: remove the argument; the grace period is the default.
82+
- `heartbeat_mode="SERVER"`: replace with `online_heartbeat=True`.
83+
84+
The attribute `client.heartbeat_mode` still exists for back-compat and reflects the effective policy (`"SERVER"` when online check-ins are enabled, `"LOCAL"` otherwise).
85+
7386
## Methods
7487

7588
| Method | Returns | Description |
7689
|--------|---------|-------------|
77-
| `login(license_key: str)` | `bool` | Validates license, verifies signatures, starts heartbeat thread |
78-
| `validate_license(license_key: str)` | `ValidateLicenseResult` | Same validate + signatures as login; no session persistence or heartbeat; **never** calls `on_failure` or `os._exit` |
79-
| `logout()` | `None` | Stops heartbeat and clears session state |
90+
| `login(license_key: str)` | `bool` | Activates: validates the license online, verifies signatures, starts the background check thread |
91+
| `validate_license(license_key: str)` | `ValidateLicenseResult` | Same validate + signatures as login; no session persistence or background checks; **never** calls `on_failure` or `os._exit` |
92+
| `logout()` | `None` | Stops background checks and clears session state |
8093
| `is_authenticated()` | `bool` | Whether a session token is present and marked authenticated |
8194
| `get_session_data()` | `dict \| None` | Decoded signed payload map |
8295
| `get_app_variables()` | `dict \| None` | App-scoped variables |
8396
| `get_license_variables()` | `dict \| None` | License-scoped variables |
8497

8598
## Error codes the server can return
8699

87-
invalid_app, invalid_key, expired, revoked, hwid_mismatch, no_credits, blocked, rate_limited, replay_detected, session_expired, app_disabled, bad_request
100+
Full set (`KNOWN_SERVER_ERRORS`): invalid_app, invalid_key, expired, revoked, hwid_mismatch, no_credits, app_burn_cap_reached, blocked, rate_limited, replay_detected, app_disabled, session_expired, revoke_requires_session, bad_request, malformed_request, system_error
88101

89102
Notes:
90103
- `replay_detected` is validate-only. `rate_limited` can be returned by `/auth/validate` and `/auth/heartbeat` (heartbeat is license-limited at 6/min and has no app-layer IP limit).
104+
- `app_burn_cap_reached` means the app's configured credit burn cap is hit; `revoke_requires_session` means a pre-session self-ban tried to revoke a license (only session-authenticated self-ban can revoke).
105+
- `session_expired` is also raised locally when the grace period (session TTL) runs out.
91106

92107
## Common patterns
93108

@@ -122,7 +137,7 @@ def on_failure(reason: str, exc: Optional[Exception]) -> None:
122137

123138
## Do NOT
124139

125-
- Do not hardcode the app secret as a plain string literal in source — use environment variables or encrypted config
126-
- Do not skip the `on_failure` callback — without it, heartbeat failures terminate the process via `os._exit(1)` without your cleanup
127-
- Do not call `login()` on every app action — call it once at startup; heartbeats handle the rest
128-
- Do not use `heartbeat_mode="LOCAL"` unless the app has no internet after initial auth
140+
- Do not hardcode the app secret as a plain string literal in source: use environment variables or encrypted config
141+
- Do not skip the `on_failure` callback: without it, background check failures terminate the process via `os._exit(1)` without your cleanup
142+
- Do not call `login()` on every app action: call it once at startup; the grace period (or online check-ins) handles the rest
143+
- Do not pass `heartbeat_mode` in new code: it is deprecated. Use the default grace period, or `online_heartbeat=True` when you need fast revocation or concurrent-use detection

0 commit comments

Comments
 (0)