License Verification Server licenseverificationserver.com
Sign In Get Started

Integration Guide

The LVS Agent runs on your Linux server and sits between your application and the License Verification Server. Your code makes a single local HTTP call to check whether a user is licensed — the agent handles all LVS communication, 24-hour offline caching, and automatic grant refresh. Integration takes three steps.

Your App
PHP · Python · Node.js
nginx · Drupal
LVS Agent
localhost:8788
grant cache + offline
LVS Server
licenseverificationserver.com
Ed25519 signed tokens

The agent binds to 127.0.0.1 only — it is never exposed to the network.

1
Step 1

Get your license key

Your license key identifies your account to the LVS Agent and tells it which user grants to enforce. Find it in your portal under Licenses.

  1. Sign up for a free account (or sign in)
  2. Navigate to Licenses in the left sidebar.
  3. Find or create the license for your site — copy the key.
Free tier included. The free plan supports up to 10 end-users at no cost — no card required. View all plans →
2
Step 2

Install the agent

Run this one command on your Linux server as root. The installer detects your distro, creates a dedicated service account, starts the agent, and prints a local token you'll need in the next step.

curl --proto '=https' --tlsv1.2 -fsSL \ https://raw.githubusercontent.com/JeremiahButtler/lvs-agent/main/install.sh \ | sudo bash

The installer will prompt for:

  • Your LVS server URL https://www.licenseverificationserver.com (default, press Enter)
  • Your license key from Step 1
  • Port for the local API (default 8788, press Enter)

The installer will automatically:

  • Create a dedicated lvs-agent system user (no shell, no home directory)
  • Install and enable the agent as a systemd service
  • Generate a secret local token — save it immediately

⚑ Save your local token

After installation completes, the installer prints your LVS_LOCAL_TOKEN. This token authenticates your app to the agent — without it, any process on the server could consume your licensed seats.

Add it to your app's environment now:

# Add to your server's environment, .env file, or secrets manager export LVS_LOCAL_TOKEN=paste_your_token_here

The token is stored in /opt/lvs-agent/config.json (mode 600) if you ever need to retrieve it.

Verify the agent is running:

curl -s http://127.0.0.1:8788/health # → {"status":"ok","version":"1.1.0"}
3
Step 3

Check authorization in your code

Call the agent before serving any licensed content. It returns a JSON response with a status field — check for "granted". If the agent is unreachable, all examples fail closed.

<?php // Requires LVS_LOCAL_TOKEN in your environment (from the installer) define('LVS_LOCAL_TOKEN', getenv('LVS_LOCAL_TOKEN') ?: ''); function lvs_authorize(string $user_id, string $kind = 'user'): bool { $headers = ['Content-Type: application/json', 'Authorization: Bearer ' . LVS_LOCAL_TOKEN]; $ch = curl_init('http://127.0.0.1:8788/authorize'); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_POSTFIELDS => json_encode(['external_user_id' => $user_id, 'kind' => $kind]), CURLOPT_HTTPHEADER => $headers, CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 2, ]); $body = curl_exec($ch); curl_close($ch); if ($body === false) return false; // agent unreachable — fail closed return (json_decode($body, true)['status'] ?? '') === 'granted'; } // Check before serving licensed content if (!lvs_authorize((string) $current_user_id)) { http_response_code(403); exit('License limit reached or service unavailable.'); }
import os import requests # Requires LVS_LOCAL_TOKEN in your environment (from the installer) LVS_LOCAL_TOKEN = os.getenv("LVS_LOCAL_TOKEN", "") def lvs_authorize(user_id: str, kind: str = "user") -> bool: """Returns True if the user holds a valid license grant.""" try: r = requests.post( "http://127.0.0.1:8788/authorize", json={"external_user_id": str(user_id), "kind": kind}, headers={"Authorization": f"Bearer {LVS_LOCAL_TOKEN}"}, timeout=2, ) return r.json().get("status") == "granted" except Exception: return False # agent unreachable — fail closed # Check before serving licensed content if not lvs_authorize(current_user_id): raise HTTPException(status_code=403, detail="License limit reached.")
const http = require('http'); // Requires LVS_LOCAL_TOKEN in your environment (from the installer) const LVS_LOCAL_TOKEN = process.env.LVS_LOCAL_TOKEN || ''; function lvsAuthorize(userId, kind = 'user') { return new Promise((resolve) => { const body = JSON.stringify({ external_user_id: String(userId), kind }); const req = http.request({ host: '127.0.0.1', port: 8788, path: '/authorize', method: 'POST', headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body), 'Authorization': `Bearer ${LVS_LOCAL_TOKEN}`, }, }, (res) => { let data = ''; res.on('data', chunk => data += chunk); res.on('end', () => { try { resolve(JSON.parse(data).status === 'granted'); } catch { resolve(false); } }); }); req.setTimeout(2000, () => { req.destroy(); resolve(false); }); req.on('error', () => resolve(false)); req.write(body); req.end(); }); } // Usage (Express middleware example) app.use('/members', async (req, res, next) => { const granted = await lvsAuthorize(req.user.id); if (!granted) return res.status(403).json({ error: 'License limit reached.' }); next(); });

Gate an entire URL path without touching application code. The auth_request module reads the HTTP status from the agent — 200 allows, 403 denies.

# Add inside your site's server {} block location /members/ { auth_request /lvs-auth; error_page 403 = /license-required; # ... your proxy_pass or root directive here } location = /lvs-auth { internal; proxy_pass http://127.0.0.1:8788/authorize; proxy_pass_request_body on; proxy_set_header Content-Type application/json; proxy_set_header Authorization "Bearer YOUR_LOCAL_TOKEN"; proxy_no_cache 1; }
Replace YOUR_LOCAL_TOKEN with the token from Step 2. After editing, run nginx -t && systemctl reload nginx.

Agent response reference

Status JSON body Meaning
200 {"status":"granted","cached":true,"expires_at":"…"} User is licensed — grant access
403 {"status":"rejected","reason":"license_limit_reached","used":N,"limit":N} Over user limit — show upgrade prompt
503 {"status":"error","reason":"lvs_unreachable"} Agent can't reach LVS; still uses the 24-hour offline cache if a prior grant exists

Revoking a user

When a user's subscription ends or they're removed from your system, revoke their grant so the seat is freed. The agent clears its local cache and notifies LVS.

curl -s -X POST http://127.0.0.1:8788/revoke \ -H 'Authorization: Bearer YOUR_LOCAL_TOKEN' \ -H 'Content-Type: application/json' \ -d '{"external_user_id":"user-123"}' # → {"status":"revoked"}
🟦

Running Drupal?

The LVS License Service Drupal module handles all of this automatically — no agent installation needed. It gates node access and user roles by license level, integrates with Drupal's permission system, and caches grants in Drupal's cache API.

View the Drupal module on GitHub →

Removing the Agent

The LVS Agent can be removed from your server at any time. Once removed, the agent's local HTTP API is no longer available and license restrictions are no longer enforced — all users will have unrestricted access to your application. Before removing, update your application code to remove calls to the local /authorize endpoint (or change your logic to allow all users).

Option 1 — Automated uninstall

Run the bundled uninstall script as root. It stops the service, removes all installed files, and cleans up the systemd unit in one step.

sudo bash /opt/lvs-agent/uninstall.sh

Option 2 — Manual removal

Run each command in order:

  1. sudo systemctl stop lvs-agent
  2. sudo systemctl disable lvs-agent
  3. sudo rm -rf /opt/lvs-agent
  4. sudo rm -f /etc/systemd/system/lvs-agent.service
  5. sudo systemctl daemon-reload
  6. (Optional) sudo rm -f /var/log/lvs-agent.log
After removing the agent, any application code that calls http://127.0.0.1:8788/authorize will receive a connection-refused error. Remove those calls or replace them with unconditional access before uninstalling.

Frequently asked questions

Your app is either missing the Authorization: Bearer … header or using the wrong token. Verify LVS_LOCAL_TOKEN matches the value in /opt/lvs-agent/config.json. You can check with:
sudo python3 -c "import json; d=json.load(open('/opt/lvs-agent/config.json')); print(d['local_token'])"
Check the service status and recent logs:
systemctl status lvs-agent journalctl -u lvs-agent -n 30 --no-pager
Common causes: wrong Python path (which python3 should be /usr/bin/python3), permission error on the config file, or port 8788 already in use.
Any string that uniquely identifies the user in your application — your database user ID, a UUID, or a hashed email address. LVS uses this to track which of your users have been granted access under your license. It is stored on the LVS server; do not use a value that could expose sensitive personal information (use an internal ID, not a full email).
The agent uses a 24-hour offline grace window. If LVS cannot be reached, any user who was successfully granted access within the past 24 hours is still allowed through. New users who have never been granted receive a 503 response. This window is configurable via offline_grace_seconds in /opt/lvs-agent/config.json.
Re-run the installer — it handles existing installations gracefully (the service user already exists, || true suppresses that). Your config.json is preserved because the installer only overwrites agent.py and the systemd unit, not the config.
curl --proto '=https' --tlsv1.2 -fsSL \ https://raw.githubusercontent.com/JeremiahButtler/lvs-agent/main/install.sh \ | sudo bash
No — and it shouldn't be. The agent is hardcoded to bind to 127.0.0.1 only. Your site backend and nginx are the only callers; both run on the same server. Never proxy the agent port through a firewall or nginx externally — the internal; directive on the /lvs-auth location enforces this for nginx.

Need the full REST API? View the API Reference →  ·  Contact us