Skip to content

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
validbooleanWhether the license is valid and active
license_typestring|nullstandard
licensed_tostring|nullName of the license holder
domainstring|nullRegistered domain (if activated)
expires_atISO8601|nullLicense expiry date (null = perpetual)
support_expires_atISO8601|nullSupport/updates expiry date
support_activebooleanWhether support is still active
failure_reasonstring|nullReason 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:

  1. Step 1 — License Input: Show a form asking for the license key (and optionally the buyer's AppTrovo username).
  2. Step 2 — Server-Side Verification: Call the verification API from your backend (never from client-side JavaScript in production).
  3. Step 3 — Domain Activation: Pass the installation domain in the domain parameter to bind the license to this domain.
  4. Step 4 — Store Locally: Cache the verification result locally (database or file) so the app works offline.
  5. 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 domain parameter for domain locking
  • Handle network failures gracefully
  • Check activations_used / activations_limit for slot info
  • Check support_active before 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.

Sign in to get a sandbox license

How to get one (for an existing product)

  1. Go to Author Dashboard → My Products → Edit on the product you want to test.
  2. Scroll to the Developer Test License card at the bottom.
  3. Click Generate. A UUID key is issued instantly, bound to your user + product.
  4. Copy the key into your installer and run the 6 verification scenarios below.
  5. 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 domain parameter 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/verify includes "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 domainFresh key, domain=localhostInstall proceeds, cache written
Valid key + already-activated domainSame key, same domain, re-runInstall proceeds, no double-activation
Slots exhaustedActivate 5 distinct local domains, try a 6thClear "deactivate existing" error
Invalid / unknown keyUse a random UUIDClear error, installer stays on License step
Expired keyLet the 30-day dev key lapse, or use an expired purchased keyClear "License has expired" error
Network failurePoint apptrovo.com to 127.0.0.1 in /etc/hostsFriendly error + retry — not a white screen
Before you ship: switch the base URL in your installer config from 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 Documentation

Installer SDK Package

Ready-to-use PHP installer with license verification, step-by-step wizard, and customizable branding.

Download SDK

What's in the SDK

File Copy To Purpose
AppTrovoLicense.phpapp/Services/License verification SDK with caching, offline support
InstallController.phpapp/Http/Controllers/5-step installer (requirements, license, database, setup, done)
CheckLicenseMiddleware.phpapp/Http/Middleware/Runtime license check with auto-reverification
views/*.blade.phpresources/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

1 Requirements
2 License
3 Database
4 Setup
5 Complete
  1. Step 1 — Requirements: Checks PHP version, extensions (BCMath, cURL, GD, PDO, etc.) and folder permissions (storage/, bootstrap/cache).
  2. 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.
  3. Step 3 — Database: Buyer enters MySQL credentials. The installer tests the connection before proceeding.
  4. Step 4 — Setup: Set app name, URL, and create the admin account. Runs migrations, seeds data, generates app key.
  5. 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.