import hashlib
def generate_machine_fingerprint(mac: str, hostname: str, machine_id: str) -> str:
payload = f"{mac}{hostname}{machine_id}"
return hashlib.sha256(payload.encode("utf-8")).hexdigest()
License Validation API Integration Guide
Technical reference for external client applications integrating with the JCP-VISION licensing platform. This page mirrors the required activation, validation, cache, outage, and rebind behavior.
Base URL and Endpoints
Set your base URL to the deployed License Validation API.
- POST /client/activate
- POST /client/validate
- POST /client/fingerprint/generate
- GET /health
Fingerprint and Request Contract
Use the same payload for activate and validate.
{
"app_name": "scanstock",
"license_key": "LICS-JCV-1234-ABCD",
"machine_fingerprint": "7a2b7d3d5c0f6d9e4cc7bdb7f45d2f0dc7f9b1197a68fd4d8db03337df2ab4c0",
"hostname": "workstation-01",
"app_version": "1.4.0"
}
Activation response behavior
Activation success returns HTTP 200 with status=success and activation_id. Activation business failures return HTTP 400 for invalid/expired/revoked/inactive licenses, blocked machines, or max instance limits.
Validation response behavior
Validation business outcomes return HTTP 200 always. Gate your logic with is_valid and reason_code. status is success when valid and error when invalid.
Runtime Decision Logic
Apply this sequence exactly in the client runtime.
First run on machine
- Generate fingerprint using local system values.
- Activate using POST /client/activate.
- Validate immediately after successful activation.
- Cache policy if is_valid=true.
- Deny execution if validation returns invalid.
Startup and periodic checks
- Call validate directly on each check cycle.
- Update cache when valid.
- Enforce deny path when invalid.
- Offline fallback only on transport-level failures.
Reason codes to handle
Valid and executable
- validation_ok
- license_active
- license_expired_in_grace
License-level deny
- license_expired
- license_not_found
- license_revoked
- license_inactive
- license_invalid_expiry
Machine-level
- machine_not_activated
- machine_blocked
Caching, Offline, and Recovery
Confirmed outage logic and safe rebind behavior.
Minimum cache fields
Persist accepted_machine_fingerprint, checked_at, policy_updated_at, license_state, machine_state, reason_code, full license_snapshot, and full environment_updates after successful validation.
Offline execution conditions
Allow offline execution only when validate failed on transport, /health is unreachable, allow_offline is true, cached fingerprint matches, and last known state is executable (active or grace).
Grace behavior
When is_valid=true and reason_code=license_expired_in_grace, allow app execution, show user warning, and continue periodic validation.
Rebind behavior
For machine_not_activated, perform one controlled activate retry, then validate once more. If still invalid, deny execution. Do not loop activation indefinitely.
startup_or_periodic_check():
fp = generate_machine_fingerprint(local_mac, local_hostname, local_machine_id)
try:
result = POST /client/validate with fp
except network_error:
if GET /health succeeds:
deny("validation transport failed while service is reachable")
else:
return offline_decision(fp, cached_state)
if result.is_valid:
cache(result, fp)
if result.license_state == "grace":
show_warning(result.reason)
allow()
return
if result.reason_code == "machine_not_activated":
act = POST /client/activate with fp
if act succeeds:
retry = POST /client/validate with fp
if retry.is_valid:
cache(retry, fp)
allow()
return
deny(result.reason_code + ": " + result.reason)
Developer Acceptance Checklist
Integration is complete when all checks pass.
- First run activates then validates.
- Normal startup uses validate only.
- Grace state allows app with warning.
- Blocked machine always denies execution.
- Not-activated machine triggers one rebind attempt.
- Transport failure with reachable /health does not use offline mode.
- Confirmed outage uses cached allow_offline plus fingerprint match.
- Cached environment_updates are persisted and applied.
- reason_code drives all decision branches.
- No infinite activation retry loops.