Claude Code LemonSqueezy License Key (2026)
Claude Code LemonSqueezy License Key Validation Workflow
Software licensing is a critical component of any commercial application. Whether you’re selling a desktop app, a SaaS product, or a plugin, you need a reliable way to validate that users have purchased valid licenses. LemonSqueezy provides an excellent merchant-of-record solution for digital product sales, complete with license key management. Combined with Claude Code’s agentic capabilities, you can build a powerful, automated validation workflow that integrates smoothly into your development process.
This guide walks you through creating a practical license key validation system using Claude Code skills and LemonSqueezy’s API. from basic key checking to full activation management, caching strategies, and server-side proxy patterns.
Understanding the LemonSqueezy License API
LemonSqueezy generates unique license keys for each purchase. These keys can be validated against their API to check:
- Whether the license is active and not expired
- How many activation limits have been used
- The license type and associated product
- Which variant (plan tier) the customer purchased
The primary endpoint for license validation is POST /v1/licenses/validate, which checks a key against a specific instance identifier. There’s also GET /v1/licenses/{license_key} for reading raw license data.
LemonSqueezy License States
Understanding the possible states a license can be in helps you write correct validation logic:
| Status | Meaning | Recommended action |
|---|---|---|
active |
License is valid and within activation limits | Allow access |
inactive |
License exists but no active activations | Prompt user to activate |
expired |
License period has ended | Prompt renewal |
disabled |
Manually disabled by seller | Block access, contact support |
invalid |
Key does not exist | Show error, prompt purchase |
Always handle all five states rather than assuming anything that isn’t active is a generic failure.
Building the Validation Skill
Create a new Claude Code skill for license validation. This skill will handle the communication with LemonSqueezy’s API and provide clear responses about license status.
---
name: license-validator
description: "Validate LemonSqueezy license keys and check activation status"
---
License Key Validator
This skill validates LemonSqueezy license keys and provides detailed license status information.
Usage
When asked to validate a license key or check license status, use the following process:
1. Extract the license key from the user's request
2. Call the LemonSqueezy API to validate the key
3. Parse the response and provide clear feedback
API Call Format
Use this curl command to validate a license:
```bash
curl -s -X GET "https://api.lemonsqueezy.com/v1/licenses/{license_key}" \
-H "Accept: application/vnd.api+json" \
-H "Content-Type: application/vnd.api+json" \
-H "Authorization: Bearer {api_key}"
Replace {license_key} with the user’s license key and {api_key} with your LemonSqueezy API key.
Once this skill file is committed to your `.claude/skills/` directory, Claude Code can answer questions like "Is license key XXXX-YYYY valid?" directly within your terminal workflow. This is especially useful during support conversations or internal tooling where you want to check a customer's license without opening the LemonSqueezy dashboard.
## Implementing the Validation Function
Beyond the skill, you can create a reusable validation function that your applications can call. Here's a practical example in JavaScript that you can integrate into any project:
```javascript
const LEMONSQUEEZY_API_URL = 'https://api.lemonsqueezy.com/v1/licenses';
async function validateLicense(licenseKey, apiKey) {
try {
const response = await fetch(`${LEMONSQUEEZY_API_URL}/${licenseKey}`, {
method: 'GET',
headers: {
'Accept': 'application/vnd.api+json',
'Content-Type': 'application/vnd.api+json',
'Authorization': `Bearer ${apiKey}`
}
});
if (!response.ok) {
return { valid: false, error: 'License check failed' };
}
const data = await response.json();
const license = data.data.attributes;
return {
valid: license.status === 'active',
status: license.status,
expiresAt: license.expires_at,
usageCount: license.usage_count,
limit: license.usage_limit,
productName: license.product_name,
variantName: license.variant_name
};
} catch (error) {
return { valid: false, error: error.message };
}
}
This function returns a clear object with all the information your application needs to make licensing decisions. Note that the status field is included so callers can distinguish between expired, disabled, and inactive states rather than treating all non-active responses identically.
Python Version
For Python-based desktop apps, CLI tools, or backend services:
import requests
LEMONSQUEEZY_API_URL = "https://api.lemonsqueezy.com/v1/licenses"
def validate_license(license_key: str, api_key: str) -> dict:
"""
Validate a LemonSqueezy license key.
Returns a dict with 'valid' bool and license details.
"""
headers = {
"Accept": "application/vnd.api+json",
"Content-Type": "application/vnd.api+json",
"Authorization": f"Bearer {api_key}",
}
try:
response = requests.get(
f"{LEMONSQUEEZY_API_URL}/{license_key}",
headers=headers,
timeout=10,
)
response.raise_for_status()
data = response.json()
attrs = data["data"]["attributes"]
return {
"valid": attrs["status"] == "active",
"status": attrs["status"],
"expires_at": attrs.get("expires_at"),
"usage_count": attrs.get("usage_count"),
"usage_limit": attrs.get("usage_limit"),
"product_name": attrs.get("product_name"),
"variant_name": attrs.get("variant_name"),
}
except requests.exceptions.Timeout:
return {"valid": False, "error": "Request timed out"}
except requests.exceptions.RequestException as e:
return {"valid": False, "error": str(e)}
Building a Server-Side Proxy
Never call the LemonSqueezy API directly from client-side JavaScript or a desktop app binary. The API key would be exposed in network traffic or decompilation. Instead, route validation through a lightweight server-side proxy.
Here’s a minimal Express proxy:
import express from 'express';
import fetch from 'node-fetch';
const app = express();
app.use(express.json());
const LEMON_API_KEY = process.env.LEMON_API_KEY;
const LEMON_API_URL = 'https://api.lemonsqueezy.com/v1/licenses';
// Simple in-memory cache: licenseKey -> { result, cachedAt }
const cache = new Map();
const CACHE_TTL_MS = 60 * 60 * 1000; // 1 hour
app.post('/api/validate-license', async (req, res) => {
const { licenseKey } = req.body;
if (!licenseKey || typeof licenseKey !== 'string') {
return res.status(400).json({ valid: false, error: 'License key required' });
}
// Check cache
const cached = cache.get(licenseKey);
if (cached && Date.now() - cached.cachedAt < CACHE_TTL_MS) {
return res.json({ ...cached.result, cached: true });
}
try {
const response = await fetch(`${LEMON_API_URL}/${licenseKey}`, {
headers: {
'Accept': 'application/vnd.api+json',
'Authorization': `Bearer ${LEMON_API_KEY}`,
},
});
const data = await response.json();
if (!response.ok) {
const result = { valid: false, status: 'invalid' };
cache.set(licenseKey, { result, cachedAt: Date.now() });
return res.json(result);
}
const attrs = data.data.attributes;
const result = {
valid: attrs.status === 'active',
status: attrs.status,
expiresAt: attrs.expires_at,
usageCount: attrs.usage_count,
usageLimit: attrs.usage_limit,
productName: attrs.product_name,
variantName: attrs.variant_name,
};
cache.set(licenseKey, { result, cachedAt: Date.now() });
return res.json(result);
} catch (err) {
return res.status(500).json({ valid: false, error: 'Validation service unavailable' });
}
});
app.listen(3000, () => console.log('License proxy running on port 3000'));
Clients now call /api/validate-license with their key. The server holds the secret API key and handles caching.
Automated License Checking in CI/CD
One powerful use case is integrating license validation into your continuous integration pipeline. You can create a Claude Code workflow that checks licenses during deployment:
name: License Check
on:
workflow_dispatch:
inputs:
license_key:
description: 'License key to validate'
required: true
jobs:
validate:
runs-on: ubuntu-latest
steps:
- name: Validate License
run: |
response=$(curl -s -X GET "https://api.lemonsqueezy.com/v1/licenses/${{ github.event.inputs.license_key }}" \
-H "Accept: application/vnd.api+json" \
-H "Content-Type: application/vnd.api+json" \
-H "Authorization: Bearer ${{ secrets.LEMON_API_KEY }}")
status=$(echo $response | jq -r '.data.attributes.status')
if [ "$status" != "active" ]; then
echo "License is not active: $status"
exit 1
fi
echo "License validated successfully"
This workflow can be triggered manually or as part of your release process to ensure only valid licenses are activated.
Creating an Activation/Deactivation System
For desktop applications that need to track activations (like limiting usage to a specific number of machines), you can implement an activation system:
async function activateLicense(licenseKey, machineId, apiKey) {
const response = await fetch(`${LEMONSQUEEZY_API_URL}/${licenseKey}/activations`, {
method: 'POST',
headers: {
'Accept': 'application/vnd.api+json',
'Content-Type': 'application/vnd.api+json',
'Authorization': `Bearer ${apiKey}`
},
body: JSON.stringify({
data: {
type: 'license-activations',
attributes: {
machine_id: machineId
}
}
})
});
return response.json();
}
async function deactivateLicense(licenseKey, activationId, apiKey) {
const response = await fetch(
`${LEMONSQUEEZY_API_URL}/${licenseKey}/activations/${activationId}`,
{
method: 'DELETE',
headers: {
'Accept': 'application/vnd.api+json',
'Authorization': `Bearer ${apiKey}`
}
}
);
return response.ok;
}
This allows users to transfer their license between machines by deactivating on the old machine and activating on the new one.
Generating a Stable Machine ID
A machine ID needs to be deterministic (same machine always gets the same ID) but not personally identifiable. A common approach is to hash a combination of hardware identifiers:
import { machineIdSync } from 'node-machine-id';
import crypto from 'crypto';
function getStableMachineId(appName) {
const rawId = machineIdSync(true); // `true` = hashed by the library
// Namespace the ID to your app so it's unique per app, not globally shared
return crypto
.createHash('sha256')
.update(`${appName}:${rawId}`)
.digest('hex')
.slice(0, 32);
}
Store the returned activation_id from LemonSqueezy after a successful activation. you’ll need it later for deactivation.
Handling Offline Grace Periods
A common UX problem is that license validation requires a network call. If your user is on a plane or in a spotty-coverage area, don’t lock them out. Implement a grace period using locally cached validation results:
import Store from 'electron-store'; // or any persistent local store
const store = new Store();
const GRACE_PERIOD_DAYS = 7;
async function checkLicense(licenseKey) {
const lastValid = store.get('licenseLastValidated');
const lastStatus = store.get('licenseStatus');
// If we validated recently and it was active, allow offline access
if (lastValid && lastStatus === 'active') {
const daysSinceCheck = (Date.now() - lastValid) / (1000 * 60 * 60 * 24);
if (daysSinceCheck < GRACE_PERIOD_DAYS) {
return { valid: true, source: 'cache', daysUntilRecheck: GRACE_PERIOD_DAYS - daysSinceCheck };
}
}
// Attempt live validation
try {
const result = await validateLicense(licenseKey, process.env.LEMON_API_KEY);
store.set('licenseLastValidated', Date.now());
store.set('licenseStatus', result.status);
return { ...result, source: 'live' };
} catch {
// Network failure. fall back to cached status if within grace period
if (lastStatus === 'active') {
return { valid: true, source: 'offline-fallback', warning: 'Could not reach license server' };
}
return { valid: false, source: 'offline', error: 'No cached license and network unavailable' };
}
}
A 7-day grace period is a reasonable default. Adjust it based on your product’s connectivity expectations.
Using Claude Code to Scaffold the Validation Workflow
Claude Code can generate the entire validation stack from a single prompt. A precise prompt produces better results:
Build a LemonSqueezy license validation system for an Electron app with:
- Server-side proxy in Express (hides API key)
- In-memory cache with 1-hour TTL
- Activation/deactivation support with machine ID hashing
- 7-day offline grace period using electron-store
- Full TypeScript types for all response shapes
- Error handling for network timeouts and invalid keys
Claude Code will scaffold the proxy server, the client-side SDK, the TypeScript interfaces, and wire everything together. You then review the output, drop in your LemonSqueezy API key, and have a working system in minutes rather than days.
Best Practices for Production Systems
When implementing license validation in production, keep these principles in mind:
| Practice | Implementation |
|---|---|
| Never expose API keys client-side | Use a server proxy for all API calls |
| Cache validation results | 1-hour TTL reduces API load and improves resilience |
| Handle all license states | Don’t treat non-active as a single “invalid” case |
| Implement offline grace periods | Avoid locking out users during network outages |
| Store activation IDs persistently | Required for deactivation flows |
| Log all validation events | Build an audit trail for abuse detection |
| Use stable machine IDs | Hash hardware identifiers, namespace per app |
| Set request timeouts | Default 10 seconds; fall back to cache on timeout |
Never expose your API key in client-side code. Always validate licenses server-side where possible. If you must validate client-side, use a proxy server to hide your API credentials.
Cache validation results intelligently. License status doesn’t change frequently, so implement caching with a reasonable TTL to reduce API calls while still maintaining security.
Handle network failures gracefully. If LemonSqueezy’s API is unavailable, decide whether to grant temporary access or block the user. Most applications choose to allow access with a warning for a defined grace period.
Log validation attempts. Keep track of when and where licenses are validated to detect potential abuse or piracy. Unusual patterns. like the same key being validated from 50 different machines in an hour. are worth alerting on.
Conclusion
Building a license validation workflow with Claude Code and LemonSqueezy combines the best of both worlds: Claude Code’s agentic capabilities for automation and LemonSqueezy’s solid licensing infrastructure. Whether you’re validating licenses in a CLI tool, a web application, or a desktop app, the patterns outlined in this guide provide a solid foundation.
Start by creating the Claude Code skill to handle validation conversations, then implement the proxy server to keep your API key safe. Add caching and offline grace periods to handle the real-world conditions your users will encounter. With proper error handling, activation management, and audit logging, you’ll have a production-ready system that protects your software while providing a smooth experience for legitimate customers.
Related Reading
- Using Claude Code for Data Quality Validation Workflow
- Claude Code for Dependency License Audit Workflow
- Claude Code for License Compatibility Workflow Guide
- Claude Code For Lemonsqueezy — Complete Developer Guide
- Claude Code for Pandera Dataframe Validation Workflow
Built by theluckystrike. More at zovo.one
Find the right skill → Browse 155+ skills in our Skill Finder.
See Also
Try it: Paste your error into our Error Diagnostic for an instant fix.