License Verification & Integration Guide
Protect your products sold on AppTrovo by integrating our license verification API into your application installer or admin panel.
Overview
Every product purchased on AppTrovo is issued a unique license key (UUID format). Authors can verify these keys using our public REST API to ensure buyers have a valid, active license before granting access to features, updates, or support.
Base URL
https://apptrovo.com/api/v1/licenses/verify
Quick Start
A single GET request is all you need. product_slug is optional — omit it to let the API resolve the product from the key and return it in the response.
GET https://apptrovo.com/api/v1/licenses/verify?license_key=YOUR_KEY
Response (valid):
{
"valid": true,
"license_id": 142,
"product_id": 28,
"product": {
"id": 28,
"slug": "your-product-slug",
"name": "Your Product",
"retired": false
},
"license_type": "standard",
"licensed_to": "Acme Corp",
"domain": "acme.com",
"expires_at": null,
"support_expires_at": "2027-04-08T00:00:00Z",
"support_active": true,
"activations_used": 1,
"activations_limit": 3,
"failure_reason": null
}
Response (invalid):
{
"valid": false,
"failure_reason": "Invalid license key."
}
For authors using the Laravel SDK
Buyers never type a product slug. You set product_slug once in
config/apptrovo.php before zipping your product — the SDK reads
it from there. Your buyers only enter their license key on /install
and are done.
Request Parameters
| Parameter | Required | Type | Description |
|---|---|---|---|
license_key |
Yes | string (max 64) | The UUID license key provided to the buyer after purchase |
product_slug |
No | string (max 255) | Optional. When sent, enforces the license belongs to this product (recommended for author-side defense). Omit to let the server resolve the product from the key. |
domain |
No | string (max 255) | Domain to validate against the license registration (optional) |
Response Fields
| Field | Type | Description |
|---|---|---|
valid | boolean | Whether the license is valid and active |
license_type | string|null | standard |
licensed_to | string|null | Name of the license holder |
domain | string|null | Registered domain (if activated) |
expires_at | ISO8601|null | License expiry date (null = perpetual) |
support_expires_at | ISO8601|null | Support/updates expiry date |
support_active | boolean | Whether support is still active |
failure_reason | string|null | Reason for failure (only when valid=false) |
Failure Reasons
| Reason | Cause |
|---|---|
Product not found. | The product_slug doesn't match any product |
Invalid license key for this product. | Key doesn't exist or doesn't belong to this product |
License has been deactivated. | Admin or system deactivated the license |
License has expired. | The expires_at date has passed |
License is registered to a different domain. | Domain mismatch (only when both request and license have domains) |
Product Lifecycle — product.retired
Every verify response carries a product.retired flag. This exists because a
buyer's license is a permanent sale record — it must keep verifying indefinitely, even if
the product later disappears from the marketplace.
false(normal) — the product is still for sale on AppTrovo. Business as usual.true— the product has been retired (unlisted) by the author or admin, but the buyer's license is still valid. The API resolves the product identity from a permanent snapshot captured when the license was issued, so verification keeps succeeding.
Use this flag to render a soft banner in your product's admin UI instead of blocking access:
$cached = $license->checkLocal();
if (! empty($cached['product']['retired'])) {
// Render a dismissible notice:
// "This product has been retired on AppTrovo. Your license remains
// valid — no further updates are expected."
}
For authors — Retire vs Delete
Once a product has issued at least one license, it can no longer be hard-deleted — the sale record must survive. The strongest action available is Retire, which unlists the product from the marketplace but leaves every existing license fully functional. This matches how CodeCanyon / Envato handle unlisted items.
Rate Limiting
The API allows 120 requests per minute per IP address. Cache verification results locally to avoid hitting limits.
Integration Examples
PHP (Laravel / Plain PHP)
/**
* Verify a license key against AppTrovo API.
*/
function verifyLicense(string $licenseKey, string $productSlug, ?string $domain = null): array
{
$params = [
'license_key' => $licenseKey,
'product_slug' => $productSlug,
];
if ($domain) {
$params['domain'] = $domain;
}
$url = 'https://apptrovo.com/api/v1/licenses/verify?' . http_build_query($params);
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 10,
CURLOPT_SSL_VERIFYPEER => true,
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode !== 200 || !$response) {
return ['valid' => false, 'failure_reason' => 'Unable to reach license server.'];
}
return json_decode($response, true);
}
// Usage in your installer or admin panel:
$result = verifyLicense(
$_POST['license_key'],
'my-awesome-saas-script',
$_SERVER['HTTP_HOST'] ?? null
);
if ($result['valid']) {
// License is valid — proceed with installation
// Optionally store license info locally for offline checks
file_put_contents('storage/license.json', json_encode([
'license_key' => $_POST['license_key'],
'license_type' => $result['license_type'],
'licensed_to' => $result['licensed_to'],
'verified_at' => date('c'),
]));
} else {
// Show error to user
die('License verification failed: ' . $result['failure_reason']);
}
JavaScript / Node.js
async function verifyLicense(licenseKey, productSlug, domain = null) {
const params = new URLSearchParams({
license_key: licenseKey,
product_slug: productSlug,
});
if (domain) params.set('domain', domain);
try {
const res = await fetch(
`https://apptrovo.com/api/v1/licenses/verify?${params}`
);
return await res.json();
} catch (err) {
return { valid: false, failure_reason: 'Unable to reach license server.' };
}
}
// Usage
const result = await verifyLicense('your-uuid-key', 'my-product-slug', 'client-domain.com');
if (result.valid) {
console.log(`Licensed to: ${result.licensed_to}, Type: ${result.license_type}`);
} else {
console.error(`Invalid: ${result.failure_reason}`);
}
Python
import requests
def verify_license(license_key: str, product_slug: str, domain: str = None) -> dict:
params = {"license_key": license_key, "product_slug": product_slug}
if domain:
params["domain"] = domain
try:
r = requests.get("https://apptrovo.com/api/v1/licenses/verify", params=params, timeout=10)
return r.json()
except Exception:
return {"valid": False, "failure_reason": "Unable to reach license server."}
# Usage
result = verify_license("your-uuid-key", "my-product-slug", "client-domain.com")
if result["valid"]:
print(f"Licensed to: {result['licensed_to']}")
else:
print(f"Invalid: {result['failure_reason']}")
Installer Integration Pattern
Here's the recommended flow for integrating license verification into your product's web installer:
- Step 1 — License Input: Show a form asking for the license key (and optionally the buyer's AppTrovo username).
- Step 2 — Server-Side Verification: Call the verification API from your backend (never from client-side JavaScript in production).
- Step 3 — Domain Activation: Pass the installation domain in the
domainparameter to bind the license to this domain. - Step 4 — Store Locally: Cache the verification result locally (database or file) so the app works offline.
- Step 5 — Periodic Re-check: Re-verify the license periodically (e.g., once per week) to catch deactivations or expirations.
Example: Laravel Installer Middleware
The Laravel Installer SDK ships a
ready-made middleware. You set your product slug once in config/apptrovo.php
— buyers don't touch .env at all.
// config/apptrovo.php (author, committed to repo)
<?php
return [
'product_slug' => 'your-product-slug',
];
// app/Http/Middleware/VerifyLicense.php
class VerifyLicense
{
public function handle($request, Closure $next)
{
$license = cache()->remember('apptrovo_license', 86400, function () use ($request) {
$stored = json_decode(file_get_contents(storage_path('license.json')), true);
if (!$stored) return null;
// Slug is read from config/apptrovo.php automatically.
$result = verifyLicense($stored['license_key'], null, $request->getHost());
return $result['valid'] ? $result : null;
});
if (!$license) {
return redirect('/install'); // Redirect to license activation page
}
return $next($request);
}
}
Best Practices
Do
- Verify server-side (never client-only)
- Cache results locally for offline operation
- Re-verify periodically (weekly)
- Use the
domainparameter for domain locking - Handle network failures gracefully
- Check
activations_used/activations_limitfor slot info - Check
support_activebefore allowing update checks
Don't
- Don't verify on every page load (use caching)
- Don't hardcode your product slug client-side
- Don't block the app entirely on network failure
- Don't store the license key in public JavaScript
- Don't skip SSL verification in production
- Don't ignore the rate limit (120/min)
Feature Gating & Activation Info
// Check activation slots and support status
$license = json_decode(file_get_contents(storage_path('license.json')), true);
echo "Domains used: {$license['activations_used']} / {$license['activations_limit']}";
if ($license['support_active']) {
// Allow automatic update checks
// Show support ticket form
} else {
// Show "Renew support" prompt
}
Checking for Product Updates
Use the public product API to check for newer versions. Combine this with license verification to ensure only valid, supported licenses receive updates.
// Check for updates via the public product API
$product = json_decode(
file_get_contents('https://apptrovo.com/api/v2/products/your-product-slug'),
true
);
$latestVersion = $product['data']['current_version'] ?? null;
$installedVersion = config('app.version'); // Your local version
if ($latestVersion && version_compare($latestVersion, $installedVersion, '>')) {
// New version available — prompt for update
// Only allow download if support_active is true
}
Testing Your Integration
Use the test environment to verify your integration before going live:
Test API: https://mp.chetsapp.de/api/v1/licenses/verify
Production API: https://apptrovo.com/api/v1/licenses/verify
Developer Test License
Before submitting a product for review, you need to test that your installer handles the full license-verification flow end-to-end — but you don't have a paid license for your own product yet. Every author can issue themselves a developer test license for any of their own products, for free.
No product yet? Use the SDK sandbox.
Brand-new authors can exercise the verify flow against a shared sandbox product before creating their first listing.
You'll test end-to-end now, then do a one-line swap of the product_slug when your real product goes live.
How to get one (for an existing product)
- Go to Author Dashboard → My Products → Edit on the product you want to test.
- Scroll to the Developer Test License card at the bottom.
- Click Generate. A UUID key is issued instantly, bound to your user + product.
- Copy the key into your installer and run the 6 verification scenarios below.
- Re-click Extend 30 days at any time to roll the expiry forward — the key stays the same.
Limitations (by design)
- Only verifies when the
domainparameter is a local hostname:localhost,127.0.0.1,::1, or ends in.test,.local,.localhost. Hitting the API with a real public domain returns a clear rejection — so the key can't be used in production. - 30-day expiry, extendable on demand.
- Up to 5 activation slots (enough for a few concurrent local test environments).
- One dev license per
(author, product)pair — re-clicking rotates the expiry, it doesn't issue a new key. - Response from
/api/v1/licenses/verifyincludes"license_type": "developer"so your SDK can log/branch on it if you want.
Quick smoke test
# Valid local domain — should return "valid": true
curl "https://apptrovo.com/api/v1/licenses/verify?license_key=YOUR_DEV_KEY&product_slug=your-product&domain=localhost"
# Real domain — must reject because this is a dev license
curl "https://apptrovo.com/api/v1/licenses/verify?license_key=YOUR_DEV_KEY&product_slug=your-product&domain=example.com"
# → "valid": false, "failure_reason": "This is a developer license and can only activate on local domains..."
The 6 scenarios to run before submitting
| Scenario | How | Expected UX |
|---|---|---|
| Valid key + new local domain | Fresh key, domain=localhost | Install proceeds, cache written |
| Valid key + already-activated domain | Same key, same domain, re-run | Install proceeds, no double-activation |
| Slots exhausted | Activate 5 distinct local domains, try a 6th | Clear "deactivate existing" error |
| Invalid / unknown key | Use a random UUID | Clear error, installer stays on License step |
| Expired key | Let the 30-day dev key lapse, or use an expired purchased key | Clear "License has expired" error |
| Network failure | Point apptrovo.com to 127.0.0.1 in /etc/hosts | Friendly error + retry — not a white screen |
https://mp.chetsapp.de (test) to https://apptrovo.com (production),
and remove or guard any APPTROVO_DEV_BYPASS-style flags you may have added during development.
Laravel Installer SDK (Drop-in Package)
We provide a complete, ready-to-use installer package for Laravel products. It includes a 5-step web installer, license verification, and runtime middleware.
Download the Installer SDK
Contains: License SDK class, InstallController, CheckLicenseMiddleware, 5 Blade views, README.
View SDK DocumentationInstaller SDK Package
Ready-to-use PHP installer with license verification, step-by-step wizard, and customizable branding.
What's in the SDK
| File | Copy To | Purpose |
|---|---|---|
AppTrovoLicense.php | app/Services/ | License verification SDK with caching, offline support |
InstallController.php | app/Http/Controllers/ | 5-step installer (requirements, license, database, setup, done) |
CheckLicenseMiddleware.php | app/Http/Middleware/ | Runtime license check with auto-reverification |
views/*.blade.php | resources/views/installer/ | Beautiful installer UI with Tailwind CSS |
Installation Steps (5 minutes)
Step 1: Copy the SDK files into your Laravel project:
cp AppTrovoLicense.php your-app/app/Services/AppTrovoLicense.php
cp InstallController.php your-app/app/Http/Controllers/InstallController.php
cp CheckLicenseMiddleware.php your-app/app/Http/Middleware/CheckLicenseMiddleware.php
cp -r views/ your-app/resources/views/installer/
Step 2: Create config/apptrovo.php with your product slug (author-baked, committed to your repo — buyers never edit this):
<?php
return [
// Your product's URL slug on AppTrovo
'product_slug' => 'your-product-slug',
// Re-verify with the license API every N days (default: 7)
'recheck_days' => 7,
// Keep running offline for N days after the last successful verify
// (default: 30). Set to 0 to disable offline mode entirely.
'offline_grace_days' => 30,
];
The API URL is hardcoded in the SDK — no APPTROVO_API_URL env var needed.
The only env var the buyer ever sees is APPTROVO_LICENSE_KEY, which the
installer writes automatically after a successful verify.
Step 3: Add installer routes to routes/web.php:
use App\Http\Controllers\InstallController;
Route::middleware('web')->prefix('install')->group(function () {
Route::get('/', [InstallController::class, 'requirements'])->name('install.requirements');
Route::get('/license', [InstallController::class, 'license'])->name('install.license');
Route::post('/license', [InstallController::class, 'verifyLicense'])->name('install.verifyLicense');
Route::get('/database', [InstallController::class, 'database'])->name('install.database');
Route::post('/database',[InstallController::class, 'saveDatabase'])->name('install.saveDatabase');
Route::get('/setup', [InstallController::class, 'setup'])->name('install.setup');
Route::post('/setup', [InstallController::class, 'install'])->name('install.install');
Route::get('/complete', [InstallController::class, 'complete'])->name('install.complete');
});
Step 4: Register the middleware in bootstrap/app.php:
->withMiddleware(function (Middleware $middleware) {
$middleware->alias([
'license' => \App\Http\Middleware\CheckLicenseMiddleware::class,
]);
})
Step 5: Protect your app routes:
// All your application routes behind license check
Route::middleware('license')->group(function () {
Route::get('/', [HomeController::class, 'index']);
// ... rest of your routes
});
Installer Flow
- Step 1 — Requirements: Checks PHP version, extensions (BCMath, cURL, GD, PDO, etc.) and folder permissions (storage/, bootstrap/cache).
- Step 2 — License: Buyer enters their AppTrovo license key. The SDK calls our API to verify it and binds the license to the installation domain.
- Step 3 — Database: Buyer enters MySQL credentials. The installer tests the connection before proceeding.
- Step 4 — Setup: Set app name, URL, and create the admin account. Runs migrations, seeds data, generates app key.
- Step 5 — Complete: Success page with post-install checklist (remove installer routes, set up cron, etc.).
Runtime License Middleware
The CheckLicenseMiddleware runs on every request and handles:
- Checks local cache file (
storage/.apptrovo_license) - If cache is fresh (< 7 days) — allows request immediately
- If cache is stale (> 7 days) — re-verifies with API in background
- If API confirms invalid — blocks access and clears cache
- If API is unreachable — allows request (graceful offline support)
- Shares license data with all views via
$apptrovo_license
Feature Gating in Your Product
use App\Services\AppTrovoLicense;
// No arguments — the SDK reads config/apptrovo.php automatically
$license = new AppTrovoLicense();
// Check activation slots
$cached = $license->checkLocal();
echo "Domains: {$cached['activations_used']} / {$cached['activations_limit']}";
// Gate updates by support status
if ($license->isSupportActive()) {
// Allow: auto-update checks, support ticket form, priority support
} else {
// Show: "Renew your support" banner with link to AppTrovo
}
// Use in Blade templates (shared by middleware)
// @if($apptrovo_license['support_active'])
// <!-- Show update checker -->
// @endif
Need Help?
If you need assistance integrating license verification into your product, visit our Help Center or contact support@apptrovo.com.