Developer Reference

License API

One licence key activates, validates and deactivates any product you build — over HTTPS, from any language, on any host. No database credentials, no shared hosting requirement.

https://shawonweb.com/api/v1 v2.0 Bearer auth JSON

How it works

Every purchase on Shawon Web issues one licence key in the form SW-XXXXX-XXXXX-XXXXX-XXXXX. That key is the API key. Your product sends it as a Bearer token and gets back everything it needs: whether it may run, which plan tier the customer is on, when the licence expires, and how many installations are already using it.

One credential

No separate API key, client id or HMAC secret to configure. The customer pastes the key once.

Runs anywhere

Plain HTTPS + JSON. Your product does not need to share our hosting or our database.

Re-activates itself

Renew or re-buy on the website and the installation comes back on its own — the key is never typed again.

One licence, one installation

By default every licence is permanently bound to the first installation that activates it. The same machine may reinstall, reactivate and renew as often as it likes. A second machine using the same key is refused with license_already_activated, and neither the product nor the customer can release the slot to free it up — /license/deactivate returns deactivation_not_permitted.

If the machine is genuinely gone — hardware failure, decommissioned server, a rebuild from scratch — the customer contacts Shawon Web support and we reopen the licence for exactly one new activation. The old installation is retired permanently at that point, so it can never take the slot back.

Some plans are sold as multi-seat instead. Those licences report seats.policy: "flexible" and seats.transferable: true, and there the customer can release an installation from their dashboard and deactivate works normally. Read seats.policy rather than assuming — if it is strict, do not show a "deactivate" or "move licence" button at all.

The one rule that makes re-activation automatic

Store the licence key locally the first time it is entered, and send the same instance_id on every call. The API remembers that installation forever. When a lapsed licence is renewed on the website, the next /license/validate returns valid: true and your product simply unlocks — no prompt, no re-entry, no support ticket.

Concepts

TermMeaning
license_keyThe customer-facing credential and the API key. Sent as Authorization: Bearer.
product_codeIdentifies your product (e.g. SW-ERP-001). Send it on every call — the API rejects a key issued for a different product, which is what makes one API safe to share across many products.
instance_idA stable id your product generates once per installation and keeps forever (a GUID in a config file, or a machine fingerprint). It is what an activation slot is keyed on.
instance_tokenReturned at activation. May be used in place of the licence key on later calls, so the raw key never has to be kept in memory.
signing_secretReturned once at activation. Used to verify offline tokens and webhook signatures. Never sent by your product as a credential.
Activation policystrict (default) binds the key to its first installation for good; only support can reopen it. flexible allows transferable seats. Reported as seats.policy.
SeatOne concurrent activation. A strict licence has exactly one and it is not transferable; a flexible licence has seats.max of them and deactivating frees one.
Grace periodDays after expiry during which the product keeps working (status: "grace") so a late renewal never locks a customer out mid-shift.

Authentication

Send the licence key as a Bearer token on every request. All traffic must use HTTPS.

header
Authorization: Bearer SW-A1B2C-D3E4F-5G6H7-8I9J0

If your HTTP stack cannot set Authorization, the API also accepts the X-License-Key header, or a license_key field in the JSON body. After activation you may send the instance_token instead of the key — the API detects which one you used from its shape.

Keep the key server-side. In a web product, call this API from your backend, never from browser JavaScript where a customer could read another tenant's key out of the page. In a desktop product, store it in the OS credential store or a file with restricted permissions.

Integration lifecycle

Four moments in the life of your product:

  1. 1

    First run — activate

    Ask for the licence key once. Call /license/activate. Save the key, the instance_id you generated, and the returned instance_token and signing_secret.

  2. 2

    Every launch — validate

    Call /license/validate and gate on data.license.valid. Cache the response for data.revalidate_after seconds (24h) so a restart loop does not hammer the API.

  3. 3

    Renewal — nothing to do

    When the customer renews or re-purchases on shawonweb.com, the same key gains a new expiry. The next scheduled validate returns valid: true and the product unlocks by itself. If the installation had been released, validate answers activation_required: true — re-call activate with the stored key and it slots straight back in.

  4. 4

    Uninstall or migration — deactivate

    Call /license/deactivate to free the seat so the customer can move to a new machine.

Request & response format

Requests are JSON (Content-Type: application/json); form-encoded bodies and query strings are accepted too. Every response uses the same envelope, so you can write one parser for the whole API.

json
{
  "success": true,
  "data":    { ... },
  "meta":    { "api_version": "2.0", "request_id": "...", "timestamp": "..." }
}

{
  "success": false,
  "error":   { "code": "license_expired", "message": "...", "details": { ... } },
  "meta":    { ... }
}

Branch on error.code, never on the message text — messages are written for humans and may be reworded. Every response carries an X-Request-Id header; include it if you contact support.

Endpoint summary

MethodPathPurpose
POST /license/activate Claim an activation slot for this installation.
POST /license/validate Check whether this installation may run right now.
POST /license/deactivate Release this installation's slot.
POST /license/heartbeat Lightweight liveness ping and status check.
GET /license/details Full licence record including billing and every activation.
GET /license/activations List every installation running on this key.
POST /license/offline-token Signed blob for verifying the licence without network access.
GET /ping Unauthenticated health check.

POST/license/activate

Claims an activation slot for this installation. Call it once, from your activation screen. Calling it again for an installation the API already knows is always safe: the existing slot is reclaimed, no extra seat is consumed, and the response carries activation.reactivated: true.

FieldRequiredNotes
instance_idRequired on strict licencesStable per-installation id — generate a GUID once, store it, never change it. On a strict licence, omitting it returns instance_id_required: we refuse to guess, because a derived id would drift when the customer's IP or hostname changes and lock them out of software they paid for. On flexible licences it falls back to domain + hostname + platform.
product_codeRecommendedRejects keys belonging to another product with product_mismatch.
device_nameOptionalLabel shown to the customer in their dashboard. Make it recognisable.
domainOptionalRequired if the customer locked the licence to a domain. Subdomains of the locked domain are allowed.
hostname, platformOptionalDiagnostics shown in the dashboard and in support tickets.
versionOptionalYour product version. Lets us tell a customer they are running something old.
environmentOptionalproduction (default), staging or development.
metadataOptionalAny JSON object you want stored against the installation.
curl
curl -X POST https://shawonweb.com/api/v1/license/activate \
  -H "Authorization: Bearer SW-A1B2C-D3E4F-5G6H7-8I9J0" \
  -H "Content-Type: application/json" \
  -d '{
        "product_code": "SW-ERP-001",
        "instance_id":  "9f2c1b7e-4d55-4a1e-9c0b-7f3a2e1d8c44",
        "device_name":  "Head office server",
        "domain":       "erp.acme-corp.com",
        "hostname":     "acme-prod-01",
        "platform":     "PHP 8.2 / Ubuntu 22.04",
        "version":      "3.4.1",
        "environment":  "production"
      }'

Response

http
HTTP/1.1 200 OK
X-Request-Id: 4f9a2c7b1e830d55
X-RateLimit-Limit: 30
X-RateLimit-Remaining: 29

{
  "success": true,
  "data": {
    "license": {
      "key": "SW-A1B2C-D3E4F-5G6H7-8I9J0",
      "status": "active",
      "valid": true,
      "message": null,
      "plan_type": "subscription",
      "perpetual": false,
      "issued_at": "2026-03-04T10:11:02+06:00",
      "expires_at": "2027-03-04T10:11:02+06:00",
      "grace_ends_at": "2027-03-11T10:11:02+06:00",
      "in_grace_period": false,
      "days_remaining": 203
    },
    "product": {
      "name": "DevSync Station",
      "slug": "devsync-station",
      "code": "SW-ERP-001",
      "latest_version": "3.5.0"
    },
    "package": { "name": "pro", "title": "Professional" },
    "entitlements": {
      "tier": "pro",
      "features": { "max_users": 25, "api_access": true, "white_label": false }
    },
    "activation": {
      "instance_id": "3b1f...c9a2",
      "instance_token": "8d41f0c9e6b24a7d93f5c1e0a8b6d4f27c93e150ab7d62f4e0c85b391df6a24c",
      "device_name": "Head office server",
      "domain": "erp.acme-corp.com",
      "environment": "production",
      "version": "3.4.1",
      "status": "active",
      "activated_at": "2026-08-13T09:22:41+06:00",
      "last_seen_at": "2026-08-13T09:22:41+06:00",
      "reactivated": false
    },
    "seats": { "max": 3, "used": 1, "unlimited": false },
    "customer": { "name": "Acme Corp", "email": "[email protected]" },
    "signing_secret": "5f2a...e91c",
    "offline": {
      "token": "eyJrIjoiU1ctQTFCMkMt....Q2ZmMg",
      "valid_until": "2026-08-20T09:22:41+06:00",
      "algorithm": "HMAC-SHA256"
    },
    "urls": {
      "renewal": "https://shawonweb.com/checkout/product/devsync-station/pro",
      "dashboard": "https://shawonweb.com/client/licenses",
      "support": "https://shawonweb.com/contact"
    },
    "revalidate_after": 86400,
    "server_time": "2026-08-13T09:22:41+06:00"
  },
  "meta": {
    "api_version": "2.0",
    "request_id": "4f9a2c7b1e830d55",
    "timestamp": "2026-08-13T09:22:41+06:00"
  }
}
signing_secret and activation.instance_token are returned by /license/activate only. Persist them at activation time — there is no endpoint that hands them out again, though re-activating the same instance returns them afresh.

POST/license/validate

The endpoint your product lives on. Returns the same object as activate. Gate your features on data.license.valid and nothing else — it already accounts for revocation, suspension, expiry, grace periods and failed payments.

curl
curl -X POST https://shawonweb.com/api/v1/license/validate \
  -H "Authorization: Bearer SW-A1B2C-D3E4F-5G6H7-8I9J0" \
  -H "Content-Type: application/json" \
  -d '{
        "product_code": "SW-ERP-001",
        "instance_id":  "9f2c1b7e-4d55-4a1e-9c0b-7f3a2e1d8c44",
        "version":      "3.4.1"
      }'

Two extra fields appear on this endpoint:

FieldMeaning
activation_requiredtrue when the key is valid but this instance_id does not hold a seat — either it has never activated, or it was released and no slot was free to restore it. Call /license/activate with the key you already hold; do not prompt the user. If activate then answers activation_limit_reached, show error.details.manage_url.
auto_reactivatedtrue when a previously released installation was silently restored because the licence is healthy again. Nothing for you to do; it is there so you can log it.

Caching. Honour data.revalidate_after (86 400 s). Between calls, trust your cached verdict. If the network is unreachable, keep running on the cache — a reasonable policy is to allow up to 7 days offline, then fail closed.

Status values. active and past_due and grace all carry valid: true; the last two mean you should show a non-blocking renewal banner using urls.renewal. expired, suspended and revoked carry valid: false.

POST/license/deactivate

Not available on strict licences. If seats.policy is strict — the default — this endpoint returns 403 deactivation_not_permitted, because a self-service release would turn the one-installation rule into "uninstall, then install anywhere". Check seats.transferable before offering the action in your UI.

On a flexible licence it frees the seat, and is idempotent — deactivating an already-released installation returns 200 with already_released: true, so an uninstaller that retries never reports a false failure.

curl
curl -X POST https://shawonweb.com/api/v1/license/deactivate \
  -H "Authorization: Bearer SW-A1B2C-D3E4F-5G6H7-8I9J0" \
  -H "Content-Type: application/json" \
  -d '{ "instance_id": "9f2c1b7e-4d55-4a1e-9c0b-7f3a2e1d8c44", "reason": "server decommissioned" }'

POST/license/heartbeat

A small status-only response for long-running services that want to notice a revocation sooner than the daily validate. It updates last seen but never restores a released slot.

curl
curl -X POST https://shawonweb.com/api/v1/license/heartbeat \
  -H "Authorization: Bearer SW-A1B2C-D3E4F-5G6H7-8I9J0" \
  -H "Content-Type: application/json" \
  -d '{ "instance_id": "9f2c1b7e-4d55-4a1e-9c0b-7f3a2e1d8c44" }'
json
{
  "success": true,
  "data": {
    "status": "active",
    "valid": true,
    "message": null,
    "expires_at": "2027-03-04T10:11:02+06:00",
    "days_remaining": 203,
    "revalidate_after": 86400,
    "server_time": "2026-08-13T09:31:00+06:00"
  }
}

GET/license/details

Everything on record: licence, product, package, entitlements, seats, customer, billing (subscription) and a list of every activation. Use it to build a "Licence" screen inside your product. POST works identically if you need to send an instance_id in a body.

curl
curl https://shawonweb.com/api/v1/license/details \
  -H "Authorization: Bearer SW-A1B2C-D3E4F-5G6H7-8I9J0"

GET/license/activations

Just the seat list — handy for an admin screen that shows which machines are using the licence. Instance tokens are never included.

json
{
  "success": true,
  "data": {
    "seats": { "max": 3, "used": 2, "unlimited": false },
    "activations": [
      {
        "instance_id": "3b1f...c9a2",
        "device_name": "Head office server",
        "domain": "erp.acme-corp.com",
        "environment": "production",
        "version": "3.4.1",
        "status": "active",
        "activated_at": "2026-08-13T09:22:41+06:00",
        "last_seen_at": "2026-08-13T09:31:00+06:00",
        "released_at": null
      }
    ]
  }
}

POST/license/offline-token

Returns a signed, self-contained token valid for 7 days. Your product verifies it locally with the signing_secret it received at activation, so an installation behind a firewall or on an intermittent connection keeps working without weakening the online check. Activate and validate already include a fresh token in their responses, so most products never need to call this endpoint directly.

php
<?php
/**
 * Verify an offline token without calling the API.
 * $secret is the `signing_secret` returned once at activation.
 */
function verifyOfflineToken(string $token, string $secret, string $productCode): array
{
    [$body, $sig] = array_pad(explode('.', $token, 2), 2, '');

    $expected = rtrim(strtr(base64_encode(
        hash_hmac('sha256', $body, $secret, true)
    ), '+/', '-_'), '=');

    if (!hash_equals($expected, $sig)) {
        throw new RuntimeException('Offline token signature is invalid');
    }

    $claims = json_decode(base64_decode(strtr($body, '-_', '+/')), true);

    if (($claims['pc'] ?? null) !== $productCode) {
        throw new RuntimeException('Token was issued for a different product');
    }
    if (time() > ($claims['vu'] ?? 0)) {
        throw new RuntimeException('Offline token has expired - reconnect to revalidate');
    }

    return $claims;   // ['s' => status, 'v' => valid, 'tier' => ..., 'exp' => ...]
}

Error codes

CodeHTTPWhat your product should do
missing_credentials 401 No key was sent. Add the Authorization header.
instance_id_required 400 A strict licence needs a stable instance_id on every call. Generate one per install and persist it — this is an integration bug, not a customer problem.
invalid_license_key 401 The key does not exist. Ask the customer to re-copy it from their dashboard.
license_already_activated 409 The key is already in use on another installation. Show error.details.support_url — the customer must contact support to have it reopened. Do not retry.
activation_revoked 403 This installation was retired by support. The replacement machine should activate instead. Do not retry on this machine.
deactivation_not_permitted 403 The licence is permanently bound and cannot be released by the product. Remove your deactivate call, or hide the button.
license_suspended 403 Suspended by an administrator. Stop the product and point the user at support.
license_revoked 403 Permanently revoked. Do not retry.
license_expired 403 Past its expiry and past the grace period. Show the renewal URL from error.details.
product_mismatch 403 The key belongs to a different product. Reject it in your activation UI.
domain_not_allowed 403 The licence is locked to another domain.
activation_limit_reached 409 All seats are in use on a flexible licence. Show error.details.manage_url so the user can free one.
instance_not_found 404 Nothing to deactivate for this instance_id.
not_activated 403 An offline token was requested before activation.
rate_limited 429 Back off for the number of seconds in the Retry-After header.
server_error 500 Our side failed. Retry with backoff and quote meta.request_id if it persists.
json
HTTP/1.1 409 Conflict

{
  "success": false,
  "error": {
    "code": "activation_limit_reached",
    "message": "This licence has reached its activation limit. Deactivate an existing installation, or upgrade the plan for more seats.",
    "details": {
      "max_activations": 3,
      "used_activations": 3,
      "manage_url": "https://shawonweb.com/client/licenses"
    }
  },
  "meta": {
    "api_version": "2.0",
    "request_id": "9c02fe41a7b3d688",
    "endpoint": "activate",
    "timestamp": "2026-08-13T09:24:10+06:00",
    "docs": "https://shawonweb.com/docs/license-api"
  }
}

Rate limits

Limits are per licence, per endpoint, in a fixed one-hour window. A separate per-IP limit guards unauthenticated traffic so nobody can brute-force keys. Every response carries X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset; a 429 also carries Retry-After.

EndpointLimit per licence
/license/validate240 / hour
/license/heartbeat120 / hour
/license/details120 / hour
/license/activations60 / hour
/license/activate, /license/deactivate30 / hour
/license/offline-token24 / hour

A correctly built integration uses a handful of calls a day. If you are anywhere near these numbers you are almost certainly not caching revalidate_after.

Webhooks

Set a webhook URL on the licence from your dashboard and we will POST license.activated and license.deactivated events to it. Each request is signed with the licence's signing secret in X-ShawonWeb-Signature. Only public HTTPS endpoints are called.

php
<?php
// Verify an incoming Shawon Web webhook.
$payload   = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_SHAWONWEB_SIGNATURE'] ?? '';   // "sha256=<hex>"
$expected  = 'sha256=' . hash_hmac('sha256', $payload, $signingSecret);

if (!hash_equals($expected, $signature)) {
    http_response_code(401);
    exit('bad signature');
}

$event = json_decode($payload, true);
// $event['event'] => license.activated | license.deactivated

Client libraries

Complete, copy-paste clients. Each one implements the whole lifecycle: activate once, cache for a day, tolerate being offline, and re-activate silently when the licence comes back to life.

php
<?php
/**
 * Minimal Shawon Web licence client. Drop into any PHP product.
 *
 * $lic = new ShawonLicense('SW-ERP-001', __DIR__ . '/storage/license.json');
 * if (!$lic->isActive()) { header('Location: /activate.php'); exit; }
 */
final class ShawonLicense
{
    private const BASE = 'https://shawonweb.com/api/v1';

    /** Last failure message, for your "licence problem" screen. */
    public ?string $lastError = null;

    public function __construct(
        private string $productCode,
        private string $storePath
    ) {}

    /** Called once, from your activation screen. */
    public function activate(string $licenseKey): array
    {
        $res = $this->call('POST', '/license/activate', $licenseKey, [
            'product_code' => $this->productCode,
            'instance_id'  => $this->instanceId(),
            'device_name'  => gethostname(),
            'domain'       => $_SERVER['HTTP_HOST'] ?? null,
            'hostname'     => gethostname(),
            'platform'     => 'PHP ' . PHP_VERSION . ' / ' . PHP_OS,
            'version'      => APP_VERSION,
            'environment'  => 'production',
        ]);

        if (!($res['success'] ?? false)) {
            throw new RuntimeException($res['error']['message'] ?? 'Activation failed');
        }

        // Persist the key. From here on the product re-checks by itself and the
        // customer never types the key again -- not after a renewal, not after
        // a re-purchase, not after a restart.
        $this->save([
            'license_key'    => $licenseKey,
            'instance_token' => $res['data']['activation']['instance_token'],
            'signing_secret' => $res['data']['signing_secret'],
            'state'          => $res['data'],
            'checked_at'     => time(),
        ]);

        return $res['data'];
    }

    /** Called on every boot. Cheap: it only hits the network once a day. */
    public function isActive(): bool
    {
        $store = $this->load();
        if (empty($store['license_key'])) {
            return false;
        }

        $age = time() - (int)($store['checked_at'] ?? 0);
        $ttl = (int)($store['state']['revalidate_after'] ?? 86400);

        if ($age < $ttl) {
            return (bool)($store['state']['license']['valid'] ?? false);
        }

        try {
            $res = $this->call('POST', '/license/validate', $store['license_key'], [
                'product_code' => $this->productCode,
                'instance_id'  => $this->instanceId(),
                'version'      => APP_VERSION,
            ]);
        } catch (Throwable $e) {
            // Offline: keep running until the cached result goes stale enough
            // to be untrustworthy, then fail closed.
            return $age < 7 * 86400 && ($store['state']['license']['valid'] ?? false);
        }

        // The key is valid but this machine holds no slot. Re-activate with the
        // key we already have -- never re-prompt the user. If the licence is
        // already claimed by another machine this fails, and it should: that is
        // the one-installation rule doing its job.
        if ($res['data']['activation_required'] ?? false) {
            try {
                $this->activate($store['license_key']);
                return true;
            } catch (Throwable $e) {
                $this->lastError = $e->getMessage();   // show this, with the support link
                return false;
            }
        }

        $store['state'] = $res['data'];
        $store['checked_at'] = time();
        $this->save($store);

        return (bool)($res['data']['license']['valid'] ?? false);
    }

    /** True only on flexible (multi-seat) licences. */
    public function canDeactivate(): bool
    {
        return (bool)($this->load()['state']['seats']['transferable'] ?? false);
    }

    public function deactivate(): void
    {
        $store = $this->load();
        if (empty($store['license_key'])) {
            return;
        }

        // A strict licence is permanently bound; the endpoint would return
        // deactivation_not_permitted. Hide the button instead of calling it.
        if (!$this->canDeactivate()) {
            throw new RuntimeException(
                'This licence is permanently bound to this installation. '
                . 'Contact Shawon Web support to move it to another machine.'
            );
        }

        $this->call('POST', '/license/deactivate', $store['license_key'], [
            'instance_id' => $this->instanceId(),
            'reason'      => 'uninstall',
        ]);
        @unlink($this->storePath);
    }

    public function feature(string $name, $default = null)
    {
        return $this->load()['state']['entitlements']['features'][$name] ?? $default;
    }

    /** Stable per-installation id. Generated once, then reused forever. */
    private function instanceId(): string
    {
        $store = $this->load();
        if (!empty($store['instance_id'])) {
            return $store['instance_id'];
        }
        $store['instance_id'] = bin2hex(random_bytes(16));
        $this->save($store);
        return $store['instance_id'];
    }

    private function call(string $method, string $path, string $key, array $body): array
    {
        $ch = curl_init(self::BASE . $path);
        curl_setopt_array($ch, [
            CURLOPT_CUSTOMREQUEST  => $method,
            CURLOPT_POSTFIELDS     => json_encode($body),
            CURLOPT_HTTPHEADER     => [
                'Authorization: Bearer ' . $key,
                'Content-Type: application/json',
            ],
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_TIMEOUT        => 15,
        ]);
        $raw = curl_exec($ch);
        if ($raw === false) {
            throw new RuntimeException('Licence server unreachable: ' . curl_error($ch));
        }
        curl_close($ch);

        return json_decode($raw, true) ?: [];
    }

    private function load(): array
    {
        return is_file($this->storePath)
            ? (json_decode(file_get_contents($this->storePath), true) ?: [])
            : [];
    }

    private function save(array $data): void
    {
        @mkdir(dirname($this->storePath), 0750, true);
        file_put_contents($this->storePath, json_encode($data), LOCK_EX);
        @chmod($this->storePath, 0600);
    }
}

Integration checklist

  • Generate an instance_id once per installation and never change it.
  • Send product_code on every call so another product's key can never activate yours.
  • Store the licence key locally after the first successful activation.
  • Gate features on data.license.valid, not on status strings.
  • Cache the validate response for revalidate_after seconds.
  • Handle activation_required: true by re-calling activate with the stored key — never by prompting the user.
  • Show a non-blocking renewal banner for grace and past_due, using urls.renewal.
  • Treat license_already_activated as final: show error.details.support_url and stop. Never retry it in a loop or with a fresh instance_id.
  • Check seats.transferable before showing any "deactivate" or "move licence" action — on a strict licence there is none.
  • Keep working for a bounded period when the API is unreachable, then fail closed.
  • Never ship the licence key in client-side JavaScript or a public repository.

Migrating from the old API

The previous endpoints under /api/license/* required an HMAC signature over a timestamp and a separate secret, and licences were bound to a single domain. Those endpoints still work and existing installs keep running — but they are frozen, and new integrations should use /api/v1.

OldNew
POST /api/license/verifyPOST /api/v1/license/validate
POST /api/license/validatePOST /api/v1/license/validate
POST /api/license/statusPOST /api/v1/license/heartbeat
POST /api/license/bindPOST /api/v1/license/activate
POST /api/license/unbindPOST /api/v1/license/deactivate
HMAC signature + timestampAuthorization: Bearer <license_key>
One domain per licenceSeats, each keyed on an instance_id
Direct database access from the productNo longer needed, or supported, for licensing

Licences already bound to a domain were migrated into an activation automatically, so those installs did not need to be re-activated.

Need a hand integrating?

Quote your X-Request-Id and the product code, and we can trace any call end to end.

Contact support