Skip to content

Verify API Reference

Use this endpoint inside the products you sell to confirm that a customer's license key is valid and permitted to run in the current environment.

Endpoint

POST /api/v1/licenses/verify

Full URL example:

https://your-store.example.com/api/v1/licenses/verify

No authentication is required — the endpoint is intentionally public so your customers' installed software can call it without needing a user account or API token.

Request

Headers

Content-Type: application/json
Accept: application/json

Body

json
{
  "license_key": "CP-AB12CD-34EF56-GH78IJ-90KLMN-XYZ123",
  "domain": "customer-site.example.com",
  "ip": "203.0.113.42"
}
FieldTypeRequiredDescription
license_keystringYesThe full license key as issued to the customer.
domainstringYesThe domain where your product is installed, e.g. example.com.
ipstring (valid IP)YesA syntactically valid IP address. The server uses the actual connection IP for its checks — the value you send here is validated for format only. Pass the server's real IP or any valid placeholder.

Responses

Success — HTTP 200

json
{
  "valid": true,
  "product": "Acme Theme Pro",
  "expiry": "2026-12-31",
  "lifetime": false
}
FieldTypeDescription
validbooleanAlways true on a 200 response.
productstringThe product name stored on the license at the time it was issued.
expirystring | nullThe expiry date in YYYY-MM-DD format, or null if the key has no expiry.
lifetimebooleantrue when the key never expires (expiry will be null).

Failure — HTTP 403 or 404

json
{
  "valid": false,
  "reason": "License has been revoked."
}
HTTPreasonWhen this happens
403Invalid license format.The key doesn't match the CP-... key shape.
404License not found.No key with that value exists in the store.
403License has been revoked.The store owner or a refund has revoked this key.
403License expired.The key has passed its expiry date, or the subscription it was tied to has ended.
403Max activations exceeded or unrecognized environment.The domain/IP isn't in the key's allow-list and all activation slots are filled.

Validation error — HTTP 422

If any required field is missing or the ip value isn't a valid IP address, Laravel returns a standard 422 validation response:

json
{
  "message": "The given data was invalid.",
  "errors": {
    "ip": ["The ip must be a valid IP address."]
  }
}

Code examples

curl

bash
curl -X POST https://your-store.example.com/api/v1/licenses/verify \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
    "license_key": "CP-AB12CD-34EF56-GH78IJ-90KLMN-XYZ123",
    "domain": "customer-site.example.com",
    "ip": "203.0.113.42"
  }'

PHP (Guzzle / Laravel Http)

php
use Illuminate\Support\Facades\Http;

$response = Http::post('https://your-store.example.com/api/v1/licenses/verify', [
    'license_key' => $licenseKey,
    'domain'      => request()->getHost(),
    'ip'          => request()->ip(),
]);

if ($response->successful() && $response->json('valid')) {
    // License is valid — activate the product
    $product = $response->json('product');
    $expiry  = $response->json('expiry'); // null if lifetime
} else {
    $reason = $response->json('reason', 'Verification failed.');
    // Handle failure: show $reason to the customer
}

JavaScript (fetch)

js
const res = await fetch('https://your-store.example.com/api/v1/licenses/verify', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' },
  body: JSON.stringify({
    license_key: licenseKey,
    domain: window.location.hostname,
    ip: '0.0.0.0', // server will use the real connection IP; any valid IP passes validation
  }),
});

const data = await res.json();

if (data.valid) {
  console.log('Valid license for:', data.product);
} else {
  console.warn('Verification failed:', data.reason);
}

Node.js (axios)

js
import axios from 'axios';

try {
  const { data } = await axios.post(
    'https://your-store.example.com/api/v1/licenses/verify',
    {
      license_key: licenseKey,
      domain: 'customer-site.example.com',
      ip: '0.0.0.0',
    },
    { headers: { Accept: 'application/json' } }
  );

  if (data.valid) {
    console.log('Valid license for:', data.product);
    console.log('Expires:', data.lifetime ? 'Never' : data.expiry);
  }
} catch (err) {
  const reason = err.response?.data?.reason ?? 'Verification failed.';
  console.error('License error:', reason);
}

Python (requests)

python
import requests
import socket

payload = {
    "license_key": license_key,
    "domain": "customer-site.example.com",
    "ip": socket.gethostbyname(socket.gethostname()),  # or pass a known IP
}

response = requests.post(
    "https://your-store.example.com/api/v1/licenses/verify",
    json=payload,
    headers={"Accept": "application/json"},
    timeout=10,
)

data = response.json()

if response.ok and data.get("valid"):
    print("Valid license for:", data["product"])
    print("Expires:", "Never" if data["lifetime"] else data["expiry"])
else:
    print("Verification failed:", data.get("reason", "Unknown error"))

Ruby (Net::HTTP)

ruby
require 'net/http'
require 'json'
require 'uri'

uri = URI('https://your-store.example.com/api/v1/licenses/verify')

response = Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
  request = Net::HTTP::Post.new(uri)
  request['Content-Type'] = 'application/json'
  request['Accept']       = 'application/json'
  request.body = {
    license_key: license_key,
    domain:      'customer-site.example.com',
    ip:          '203.0.113.42'
  }.to_json
  http.request(request)
end

body = JSON.parse(response.body)

if response.is_a?(Net::HTTPSuccess) && body['valid']
  puts "Valid license for: #{body['product']}"
  puts "Expires: #{body['lifetime'] ? 'Never' : body['expiry']}"
else
  puts "Verification failed: #{body['reason']}"
end

Go

go
package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"net/http"
)

type verifyRequest struct {
	LicenseKey string `json:"license_key"`
	Domain     string `json:"domain"`
	IP         string `json:"ip"`
}

type verifyResponse struct {
	Valid    bool    `json:"valid"`
	Product  string  `json:"product"`
	Expiry   *string `json:"expiry"`
	Lifetime bool    `json:"lifetime"`
	Reason   string  `json:"reason"`
}

func verifyLicense(licenseKey, domain, ip string) (*verifyResponse, error) {
	body, _ := json.Marshal(verifyRequest{LicenseKey: licenseKey, Domain: domain, IP: ip})

	resp, err := http.Post(
		"https://your-store.example.com/api/v1/licenses/verify",
		"application/json",
		bytes.NewBuffer(body),
	)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	var result verifyResponse
	if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
		return nil, err
	}
	return &result, nil
}

func main() {
	result, err := verifyLicense("CP-AB12CD-34EF56-GH78IJ-90KLMN-XYZ123", "customer-site.example.com", "203.0.113.42")
	if err != nil {
		fmt.Println("Request error:", err)
		return
	}
	if result.Valid {
		fmt.Println("Valid license for:", result.Product)
	} else {
		fmt.Println("Verification failed:", result.Reason)
	}
}

C# (HttpClient)

csharp
using System.Net.Http;
using System.Net.Http.Json;
using System.Text.Json.Serialization;

public record VerifyRequest(
    [property: JsonPropertyName("license_key")] string LicenseKey,
    [property: JsonPropertyName("domain")]      string Domain,
    [property: JsonPropertyName("ip")]          string Ip
);

public record VerifyResponse(
    [property: JsonPropertyName("valid")]    bool    Valid,
    [property: JsonPropertyName("product")]  string? Product,
    [property: JsonPropertyName("expiry")]   string? Expiry,
    [property: JsonPropertyName("lifetime")] bool    Lifetime,
    [property: JsonPropertyName("reason")]   string? Reason
);

// --- Usage ---
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("Accept", "application/json");

var payload = new VerifyRequest(licenseKey, "customer-site.example.com", "203.0.113.42");
var httpResponse = await client.PostAsJsonAsync(
    "https://your-store.example.com/api/v1/licenses/verify",
    payload
);

var result = await httpResponse.Content.ReadFromJsonAsync<VerifyResponse>();

if (result?.Valid == true)
{
    Console.WriteLine($"Valid license for: {result.Product}");
    Console.WriteLine($"Expires: {(result.Lifetime ? "Never" : result.Expiry)}");
}
else
{
    Console.WriteLine($"Verification failed: {result?.Reason}");
}

Verifying that a key belongs to a specific product

The endpoint validates the key itself — its format, existence, revocation status, expiry, and whether the calling environment is allowed. It does not enforce that a key was issued for a particular product.

If your integration needs to confirm a key belongs to the product you expect, compare the product field in the success response against the name you assigned to your product in the store:

php
$response = Http::post(..., [...]);

if ($response->json('valid') && $response->json('product') === 'Acme Theme Pro') {
    // Key is valid AND issued for this specific product
}

The product value is a name snapshot taken at the time the key was issued, so it will match the product name as it appeared in your store at the time of purchase.

Caching recommendations

Each call from a new domain or IP address consumes one activation slot. To avoid depleting a customer's activation allowance or adding unnecessary load to your store:

  • Cache a successful verification response locally (e.g. in a transient, file cache, or database) for at least 24 hours.
  • Only re-verify when the cached result expires or when a customer explicitly triggers a re-check.
  • Do not call the endpoint on every page load.

Released under the Commercial License.