Activation Guide#
This page explains how to activate Lactuca after obtaining a license key or trial key.
How activation works#
On the first import lactuca, the library checks for a valid license in the following
order:
Environment variable
LACTUCA_LICENSE_KEY— if set, the key is validated online and stored automatically (no prompt).Local license file
license.json— if present and valid, the library loads silently.Interactive prompt — if neither of the above is found, you are asked to enter your key.
Activation contacts the Lactuca license server to validate the key and bind it to the
current device (hardware fingerprint). A license.json file is written to your
configuration directory for subsequent offline use.
Verification checks performed on license.json#
On each subsequent import lactuca, the library verifies the local file in the
following order:
Expiry check —
expires_atmust be in the future.Device fingerprint check — the fingerprint in the file must match the current machine.
MAC integrity check — a device-bound HMAC-SHA256 code covers
expires_at,tier,last_validated_at, andlast_process_heartbeat. A missingmacfield raisesLicenseTamperedError[LAC-3003] (auto-recoverable); a mismatch raises [LAC-3004] (auto-recoverable).Clock rollback check — the system clock must not be more than 60 seconds behind
last_validated_at. A larger rollback raisesLicenseTamperedError[LAC-3005] (requires restoring the system clock).Ed25519 signature verification — the server-issued cryptographic signature is verified last. A missing or invalid signature raises
LicenseTamperedError[LAC-3001] (auto-recoverable) or [LAC-3002] (auto-recoverable).
license.json is device-bound and cannot be shared between machines. Copying it to
another device triggers [LAC-3004] or [LAC-2002] (fingerprint mismatch).
Recovery using the stored key applies only on the same device; on a new machine,
activate independently or use the device transfer procedure.
License CLI quick reference#
Use the license CLI when you want explicit, script-friendly checks without waiting for the next automatic revalidation window.
python -m lactuca # defaults to activate (same as the line below)
python -m lactuca activate
python -m lactuca license refresh [--json]
python -m lactuca license status [--json]
python -m lactuca license doctor [--json]
When to use each command#
python -m lactuca(no subcommand): identical topython -m lactuca activate— theactivatesubcommand is assumed when omitted.activate: verify an existing license (same checks asimport lactuca) and print a single status line to stdout when the license is already valid; otherwise open the interactive menu to enter a key or request a trial.license refresh: force online synchronization now (recommended after support confirms a server-side adjustment).license status: inspect local state only (no network call).license doctor: run guided diagnostics (local checks + connectivity).
Import vs CLI — where messages appear#
Action |
Already-valid license |
First activation / re-activation |
|---|---|---|
|
Silent on stdout; expiry reminder on stderr when ≤30 days remain |
Interactive menu ( |
|
One consolidated status line on stdout (tier, expiry date, renewal URL) |
Interactive menu, then confirmation on stdout |
The CLI skips the import-time license gate so verification runs once, in the
activate handler. Expiry reminders are not duplicated on stderr and stdout in the
same command.
Non-interactive CLI:
python -m lactuca activateworks without a TTY whenlicense.jsonalready exists orLACTUCA_LICENSE_KEYis set. Without either, the command exits with code1and instructions to set the environment variable.
Exit codes — python -m lactuca / activate#
The activate command uses a simplified exit scheme for interactive use (not the
structured codes used by license subcommands):
Code |
Meaning |
|---|---|
|
License verified or activated; user cancelled or declined activation ( |
|
Any other licensing error ( |
On success with an already active license, the command exits 0 after printing
a status line to stdout (expiry date and renewal URL when applicable).
On success after entering a new key interactively, the command exits 0 after
printing the activation confirmation to stdout.
Exit codes — python -m lactuca license …#
Structured exit codes for license refresh, license status, and license doctor:
Code |
Meaning |
|---|---|
|
Success |
|
Network error (server unreachable) |
|
No key available |
|
License expired |
|
License revoked, suspended, or permanently deleted on the license server |
|
Invalid/unsupported server status |
|
License command usage error |
|
Unexpected internal error |
Example outputs#
Text mode — license already active (≤30 days remaining):
[Lactuca] License active (individual). Expires in 12 days (2026-06-30). Renew at: https://www.lactuca.io/pricing
Text mode — license already active (≤7 days remaining; urgent wording):
[Lactuca] License active (individual). WARNING: expires in 5 day(s) (2026-06-22). Renew now: https://www.lactuca.io/pricing
Text mode — interactive activation just completed:
License activated. Lactuca is ready.
Text mode — license refresh:
License synchronized successfully.
Doctor offline:
Could not reach license server during diagnosis.
JSON mode:
{"status": "network_error", "code": 2, "message": "Could not reach license server during diagnosis.", "expires_at": null, "tier": null, "recommendation": "Check network access and run 'python -m lactuca license doctor' again.", "checks": [{"check": "license_file", "result": "ok", "detail": "license.json found"}]}
Step-by-step: first activation#
If you already have a license key, skip to Entering your key. If you need a trial key, see Requesting a free trial below.
Requesting a free trial#
Run import lactuca without a key. When the activation prompt appears, select
option [T] to request a free 30-day trial. You will be asked for your email
address and your name (optional). A confirmation step lets you correct the email
before the request is sent. Before the request is submitted, the EULA agreement
URL is displayed — submitting your email constitutes acceptance.
The trial key is sent to your inbox within a few minutes.
Device limit: a Trial license can be activated on 1 device only. If you need to move the trial to a different machine, contact support@lactuca.io.
Note: The
[T]option only appears on first install (when no prior activation has occurred on this machine). If you are re-activating an existing license, the prompt shows[K/Q]only — enter your existing key using option[K].
Once you have your key, re-run import lactuca and choose [K] to enter it.
Common trial errors#
A trial already exists for this email or device
A 30-day trial has already been issued to this email address or to this machine. Check your inbox for the original trial key email. If you cannot find it, contact support or purchase a license.
This device has an active paid license
This machine already has an active paid Lactuca license associated with it. Choose option [K] and enter your license key instead of requesting a trial.
The license on this device has been revoked or suspended
The license associated with this machine has been suspended or revoked. Contact support@lactuca.io for assistance.
Entering your key#
Windows#
Open a terminal (PowerShell, Command Prompt, or Git Bash) and activate your Python environment.
Run
python -m lactuca(orimport lactucain a Python session).When the activation prompt appears, select the option to enter an existing key and paste your license key. The license file is written to
%LOCALAPPDATA%\lactuca\license.json.Subsequent
import lactucacalls are silent on stdout; usepython -m lactucato print the current license status.
macOS#
Open Terminal and activate your Python environment.
Run
python -m lactuca(orimport lactucain a Python session).When the activation prompt appears, select the option to enter an existing key and paste your license key. The license file is written to
~/Library/Application Support/lactuca/license.json.Subsequent
import lactucacalls are silent on stdout; usepython -m lactucato print the current license status.
Linux#
Open a terminal and activate your Python environment.
Run
python -m lactuca(orimport lactucain a Python session).When the activation prompt appears, select the option to enter an existing key and paste your license key. The license file is written to
~/.config/lactuca/license.json.Subsequent
import lactucacalls are silent on stdout; usepython -m lactucato print the current license status.
Jupyter notebook or JupyterLab#
In a notebook cell, run
import lactuca.The activation menu appears directly in the cell output — no terminal needed.
Select
[T]for a free trial or[K]to enter an existing key.After activation, subsequent cells import silently.
License file locations#
OS |
Default path |
|---|---|
Windows |
|
macOS |
|
Linux |
|
You can override the directory with the LACTUCA_CONFIG_DIR environment variable.
Server mode and CI/CD#
For shared servers (JupyterHub, HPC clusters) or automated pipelines where interactive
prompts are not available, set the LACTUCA_LICENSE_KEY environment variable before
importing Lactuca:
export LACTUCA_LICENSE_KEY="XXXX-XXXX-XXXX-XXXX"
python -c "import lactuca"
The library validates and stores the key automatically on first run, then uses the
local license.json on subsequent imports (no network call unless the 30-day
revalidation period elapses).
CI/CD (GitHub Actions example):
- name: Run actuarial tests
env:
LACTUCA_LICENSE_KEY: ${{ secrets.LACTUCA_LICENSE_KEY }}
run: pytest
Store your key in the repository’s secret store, never in plain text.
Shared server (admin sets once, all users benefit):
# Admin: activate once with a shared config directory
export LACTUCA_CONFIG_DIR=/etc/lactuca
export LACTUCA_LICENSE_KEY="XXXX-XXXX-XXXX-XXXX"
python -c "import lactuca" # writes /etc/lactuca/license.json
chmod 644 /etc/lactuca/license.json
# All users: set LACTUCA_CONFIG_DIR to point to the shared location
# (add to /etc/environment or equivalent)
LACTUCA_CONFIG_DIR=/etc/lactuca
One activation is consumed for the server host, regardless of the number of concurrent users. Ensure the device limit of your tier covers the number of server hosts.
Offline grace period#
Lactuca enforces two independent offline grace periods:
License validation (revalidation every 30 days)
The license is revalidated online every 30 days. If the network is unavailable
at revalidation time, the library continues operating — printing a warning to
stderr on each import lactuca startup — until the license’s local expiry date.
There is no additional offline cutoff beyond expires_at; the binding limit is your
subscription or trial end date.
If a prior license refresh or license doctor run recorded that the license was
revoked or permanently deleted on the server, the next import lactuca forces an
immediate online check and blocks access when the network is available — even if
the 30-day interval has not elapsed.
If the expiry date is reached while offline, the library raises a
LicenseExpiredError and stops. Renew at the Pricing page.
Process heartbeat (keep-alive every 10–20 minutes)
Each running Lactuca process sends a keep-alive heartbeat to the license server approximately every 10 minutes on single-seat tiers (Trial, Individual, Academic) and every 20 minutes on Team and Enterprise. The interval is derived from the license server’s process policy (minimum 5 minutes). OEM licenses do not use floating-seat heartbeats under the normal contractual setup (see the Licensing FAQ — OEM and third-party distribution).
A running process is never interrupted by network unavailability. However,
new startups on finite-pool tiers are permitted offline for up to 3 days
from the last successful server contact. After 3 days, a new import lactuca
raises LicenseSeatExhaustedError until connectivity is restored. In practice,
the 3-day heartbeat limit is the binding constraint for most users.
Expiry warnings#
When a valid license is approaching its expiry date, the Software reminds you to renew. Where the message appears depends on how you start Lactuca:
Startup |
Channel |
Wording |
|---|---|---|
|
stderr |
Short reminder ( |
|
stdout |
Consolidated status line ( |
There are two urgency levels (consistent with section 11.2 of the EULA):
30 days or fewer remaining — informational reminder.
7 days or fewer remaining — escalated to an urgent
WARNING(CLI status line and import-time stderr).
Normal computation continues unaffected in both cases. Renew through the Lemon Squeezy customer portal (link in your subscription confirmation email) or at the Pricing page.
Jupyter and non-interactive environments#
The activation menu ([T/K/Q] or [K/Q]) appears in any environment that
supports input(): terminal, Jupyter, JupyterLab, VS Code Notebooks, Spyder,
and PyCharm. No special setup is required; simply run import lactuca and follow
the on-screen prompt.
In headless environments (CI, Docker, stdin redirected to /dev/null), the
menu is printed but input() fails immediately with EOFError. The library
converts this to an ActivationRequiredError with an actionable message:
First install (no prior activation on this machine): message includes the free-trial URL and instructions to set
LACTUCA_LICENSE_KEY.Re-activation (configuration directory exists but
license.jsonis missing): message instructs to setLACTUCA_LICENSE_KEYor runpython -m lactuca activate.
For CI/CD pipelines, always set LACTUCA_LICENSE_KEY as a secret — see
Server mode and CI/CD.
Troubleshooting#
LicenseInvalidError — device not recognised
The hardware fingerprint stored in license.json does not match this device.
This happens when the file is copied from another machine or the hardware
changes significantly. Delete license.json and re-activate on this device
(consumes one device slot on your license), or use the
device transfer procedure.
LicenseInvalidError — activation limit reached
Your license has reached the maximum number of activated devices. See the device transfer FAQ for instructions on deactivating an unused device, or contact support@lactuca.io.
LicenseSeatExhaustedError
All concurrent session slots for your license are in use. This can happen for two reasons:
Too many parallel processes: another machine or process has consumed all available session slots. Wait for a session to close, or see the FAQ for options.
Offline too long: no successful server contact in the last 3 days. Restore network connectivity and try again.
LicenseTamperedError
The license file has been modified manually or is corrupted. The library
attempts automatic online recovery by re-validating your stored key with the
license server and rewriting license.json. No manual action is required in most
cases.
If recovery fails (for example, no network connection), run:
python -m lactuca license doctor
python -m lactuca license refresh
If recovery still fails, delete license.json manually and re-activate. On
re-import, the activation prompt shows [K/Q] (no trial option, since a prior
activation exists on this machine). Enter your license key to restore access.
LicenseRevokedError
Your license has been suspended, revoked, or permanently deleted on the license
server. Contact support@lactuca.io if you believe this
is an error. Run python -m lactuca license refresh to confirm the server
status before contacting support.
LicenseExpiredError
Your license or trial has expired. Renew your subscription or purchase a new plan at the Pricing page.
Error reference#
For a complete list of license-related errors and their meaning, see Error Reference.