Licensing API

Activate, validate, and deactivate product licenses using canonical website identity

Overview

The Licensing API allows plugins, themes, and other clients to activate, validate, and deactivate WooNooW product licenses, while providing authenticated administrative and customer endpoints for license management and OAuth verification.

Base URL: https://your-store.com/wp-json/woonoow/v1

Public license operations (/licenses/activate, /licenses/validate, /licenses/deactivate) use JSON POST requests without WordPress user credentials; identity and license status authorize each operation. Merchant administrative routes require manage_woocommerce capabilities, while customer and OAuth verification routes require user authentication (is_user_logged_in()) and enforce license ownership.

Read Website Identity for identity requirements and License Entitlements for the lifecycle, update, and support entitlement model before implementing a client.

Required website identity

A website is identified by this pair:

text
persistent installation UUID + normalized domain

Both values are required for activation and public validation. There is no domain-only, UUID-only, or machine_id fallback. Read Website Identity before implementing a client.


Activate a license

http
POST /wp-json/woonoow/v1/licenses/activate
Content-Type: application/json

Request

json
{
  "license_key": "XXXX-YYYY-ZZZZ-WWWW",
  "domain": "https://customer-site.com",
  "installation_id": "550e8400-e29b-41d4-a716-446655440000"
}
Body parameterTypeRequiredDescription
license_keystringYesProduct license key
domainstringYesCurrent site URL or host
installation_idUUIDYesPersistent canonical installation UUID
machine_idstringNoMetadata only; not part of identity
return_urlURLFor OAuthCallback URL on the requesting domain
activation_tokenstringOAuth callback onlyShort-lived token returned after approval

Success response

json
{
  "success": true,
  "activation_id": 123,
  "activations_remaining": 2,
  "product_id": 42,
  "variation_id": 0
}

Save activation_id when possible so the installation can later be deactivated precisely. activations_remaining is the remaining seat count; it is -1 when activation_limit is 0 (unlimited). product_id is the canonical parent WooCommerce product ID, and variation_id is the purchased variation ID (0 when unlicensed or simple product).

Repeating this request with an already-active UUID + domain pair is idempotent. WooNooW returns the existing activation without creating another record or consuming another slot:

json
{
  "success": true,
  "activation_id": 123,
  "activations_remaining": 2,
  "message": "Already activated",
  "product_id": 42,
  "variation_id": 0
}

OAuth-required response (HTTP 200)

The merchant configures the activation method globally or per product. A client does not force OAuth with an activation_mode request field.

When the license requires customer account approval, the endpoint returns an HTTP 200 OK response (not a 4xx client error) with success: false and code: "oauth_required":

json
{
  "success": false,
  "code": "oauth_required",
  "message": "This license requires account verification. You will be redirected to complete activation.",
  "redirect_url": "https://your-store.com/my-account/license-connect/?license_key=XXXX-YYYY-ZZZZ-WWWW&site_url=https%3A%2F%2Fcustomer-site.com&return_url=https%3A%2F%2Fcustomer-site.com%2Fcallback&installation_id=550e8400-e29b-41d4-a716-446655440000&state=eyJhbGci...&nonce=7a8b9c0d1e"
}

Open redirect_url in the user's browser and follow the OAuth Activation Flow.


Validate a license

http
POST /wp-json/woonoow/v1/licenses/validate
Content-Type: application/json

Use this endpoint to verify whether a license is valid and active for a specific installation.

Request

json
{
  "license_key": "XXXX-YYYY-ZZZZ-WWWW",
  "domain": "https://customer-site.com",
  "installation_id": "550e8400-e29b-41d4-a716-446655440000"
}
Body parameterTypeRequiredDescription
license_keystringYesProduct license key
domainstringYesCurrent site URL or host
installation_idUUIDYesPersistent canonical installation UUID

Valid response (200)

When the license is active, unexpired, and activated for the requesting identity, the endpoint returns HTTP 200:

json
{
  "valid": true,
  "error": null,
  "message": null,
  "effective_status": "valid",
  "license_key": "XXXX-YYYY-ZZZZ-WWWW",
  "product_id": 42,
  "variation_id": 0,
  "status": "active",
  "activation_limit": 3,
  "activation_count": 1,
  "activations_remaining": 2,
  "expires_at": "2027-09-08 12:00:00",
  "usage_expires_at_utc": "2027-09-08T12:00:00Z",
  "is_expired": false,
  "subscription_status": null,
  "subscription_active": true,
  "domain_active": true,
  "entitlements": {
    "license_active": true,
    "update_entitled": true,
    "support_active": null,
    "usage_expires_at": "2027-09-08 12:00:00",
    "usage_expires_at_utc": "2027-09-08T12:00:00Z",
    "updates_expires_at": "2027-09-08 12:00:00",
    "support_expires_at": null,
    "policy_mode": "legacy",
    "historical_downloads": false,
    "history_access": false,
    "updates_mode": "license",
    "support_mode": "unspecified",
    "usage_expiry_valid": true,
    "policy_valid": true,
    "policy_error": null,
    "issued_at": "2026-09-08 10:00:00",
    "subscription_status": null,
    "subscription_active": true,
    "subscription_linked": false
  }
}

Do not decide validity from status === "active" alone. Check valid: true and effective_status: "valid". activations_remaining is -1 when the license has unlimited activations (activation_limit: 0). usage_expires_at_utc provides the canonical RFC 3339 UTC timestamp, while expires_at is preserved for backward compatibility. product_id and variation_id identify the associated WooCommerce product and variation.

The nested entitlements object evaluates independent rights. In the example above, policy_mode: "legacy" reflects a license without an explicit snapshot, where update rights follow usage expiry and support is unspecified. For explicit policies, policy_mode is "explicit" with dedicated cutoff timestamps. A fail-closed incomplete license read can report policy_mode: "missing", policy_valid: false, and policy_error: "invalid_entitlement_snapshot"; update and support rights remain unavailable until the complete license schema/record is loaded. Read License Entitlements for full entitlement semantics.

Invalid validation response (403)

When validation fails, WooNooW returns HTTP 403 with valid: false (not HTTP 200).

Domain not activated (403)

When the license exists and is active, but has not been activated for the supplied UUID + domain pair:

json
{
  "valid": false,
  "error": "domain_not_activated",
  "effective_status": "domain_not_activated",
  "message": "License is not activated for this installation_id and domain pair.",
  "domain_active": false,
  "product_id": 42,
  "variation_id": 0,
  "entitlements": {
    "license_active": true,
    "update_entitled": true,
    "support_active": null,
    "usage_expires_at": "2027-09-08 12:00:00",
    "usage_expires_at_utc": "2027-09-08T12:00:00Z",
    "updates_expires_at": "2027-09-08 12:00:00",
    "support_expires_at": null,
    "policy_mode": "legacy",
    "historical_downloads": false,
    "history_access": false,
    "updates_mode": "license",
    "support_mode": "unspecified",
    "usage_expiry_valid": true,
    "policy_valid": true,
    "policy_error": null,
    "issued_at": "2026-09-08 10:00:00",
    "subscription_status": null,
    "subscription_active": true,
    "subscription_linked": false
  }
}

Non-existent license key (403)

json
{
  "valid": false,
  "error": "invalid_license",
  "effective_status": "invalid_license",
  "message": "Invalid license key",
  "entitlements": null
}

Expired, revoked, or subscription-inactive license (403)

When the license is expired, revoked, or its linked subscription has lapsed, the endpoint returns HTTP 403 with effective_status set to expired, revoked, subscription_inactive, license_inactive, or invalid_usage_expiry, accompanied by the evaluated entitlements object.

A missing or malformed identity returns a REST error with HTTP 400 (missing_key, missing_identity, or invalid_identity).


Deactivate a license

http
POST /wp-json/woonoow/v1/licenses/deactivate
Content-Type: application/json

Use one of the following request forms.

By activation ID

json
{
  "license_key": "XXXX-YYYY-ZZZZ-WWWW",
  "activation_id": 123
}

The activation ID must belong to the supplied license.

By website identity

json
{
  "license_key": "XXXX-YYYY-ZZZZ-WWWW",
  "domain": "https://customer-site.com",
  "installation_id": "550e8400-e29b-41d4-a716-446655440000"
}

Domain-only deactivation is not supported. When activation_id is omitted, both identity fields are required.

Success response

json
{
  "success": true
}

Deactivating an already-deactivated activation is idempotent and frees no additional slots.


Admin licensing endpoints

Merchant administrative endpoints require the manage_woocommerce WordPress capability. Browser and SPA requests authenticate using WordPress session cookies and the REST nonce (X-WP-Nonce); external API clients authenticate with Application Passwords or basic auth.

List all licenses

http
GET /wp-json/woonoow/v1/licenses

Query parameters

ParameterTypeRequiredDescription
searchstringNoSearch string for license key or customer
statusstringNoFilter by base status: active or revoked
product_idintegerNoFilter by parent product ID
user_idintegerNoFilter by WordPress customer ID
pageintegerNoPage number (default: 1)
per_pageintegerNoRecords per page (default: 50)

Success response (200)

json
{
  "licenses": [
    {
      "id": 1,
      "license_key": "XXXX-YYYY-ZZZZ-WWWW",
      "product_id": 42,
      "variation_id": 0,
      "product_name": "WooNooW Pro",
      "variation_name": "",
      "order_id": 1050,
      "order_item_id": 312,
      "user_id": 15,
      "user_email": "customer@example.com",
      "user_name": "Jane Doe",
      "status": "active",
      "activation_limit": 3,
      "activation_count": 1,
      "activations_remaining": 2,
      "expires_at": "2027-09-08 12:00:00",
      "usage_expires_at_utc": "2027-09-08T12:00:00Z",
      "is_expired": false,
      "entitlement_policy": "{\"updates\":{\"mode\":\"days\",\"days\":365}}",
      "entitlements": {
        "license_active": true,
        "update_entitled": true,
        "support_active": null,
        "usage_expires_at": "2027-09-08 12:00:00",
        "updates_expires_at": "2027-09-08 12:00:00",
        "support_expires_at": null,
        "policy_mode": "explicit",
        "historical_downloads": false,
        "history_access": false,
        "updates_mode": "days",
        "support_mode": "unspecified",
        "usage_expiry_valid": true,
        "policy_valid": true,
        "policy_error": null,
        "issued_at": "2026-09-08 12:00:00",
        "subscription_status": null,
        "subscription_active": true,
        "subscription_linked": false
      },
      "created_at": "2026-09-08 12:00:00",
      "updated_at": "2026-09-08 12:00:00"
    }
  ],
  "total": 1,
  "page": 1,
  "per_page": 50
}

Get a single license

http
GET /wp-json/woonoow/v1/licenses/{id}

Returns the enriched license object above plus an activations array containing all recorded activations:

json
{
  "id": 1,
  "license_key": "XXXX-YYYY-ZZZZ-WWWW",
  "product_id": 42,
  "variation_id": 0,
  "product_name": "WooNooW Pro",
  "variation_name": "",
  "order_id": 1050,
  "order_item_id": 312,
  "user_id": 15,
  "user_email": "customer@example.com",
  "user_name": "Jane Doe",
  "status": "active",
  "activation_limit": 3,
  "activation_count": 1,
  "activations_remaining": 2,
  "expires_at": "2027-09-08 12:00:00",
  "usage_expires_at_utc": "2027-09-08T12:00:00Z",
  "is_expired": false,
  "activations": [
    {
      "id": 123,
      "license_id": 1,
      "identity_key": "a1b2c3...",
      "domain": "https://customer-site.com",
      "domain_normalized": "customer-site.com",
      "installation_id": "550e8400-e29b-41d4-a716-446655440000",
      "ip_address": "192.0.2.1",
      "machine_id": null,
      "user_agent": "WooCommerce/9.0 WordPress/6.6",
      "status": "active",
      "activated_at": "2026-09-08 12:30:00",
      "deactivated_at": null,
      "activation_revision": 1,
      "updated_at": "2026-09-08 12:30:00"
    }
  ]
}

Revoke a license

http
DELETE /wp-json/woonoow/v1/licenses/{id}

Sets stored base status to revoked. Immediately revokes update and download authorization across all active activations and prevents future activations.

Response (200):

json
{
  "success": true
}

List activations for a license

http
GET /wp-json/woonoow/v1/licenses/{id}/activations

Returns the array of activation records for license {id}.


Customer licensing endpoints

Customer endpoints require user authentication (is_user_logged_in()). Customers can view only licenses owned by their account (license.user_id === current_user_id).

Get customer licenses

http
GET /wp-json/woonoow/v1/account/licenses

Returns an array of enriched license objects belonging to the authenticated customer, including product metadata, remaining seats, evaluated entitlements, and the customer's activation records.

Customer deactivate an activation

http
POST /wp-json/woonoow/v1/account/licenses/{id}/deactivate
Content-Type: application/json

Allows a customer to release an activation slot directly from their account dashboard. Verifies that license {id} exists and belongs to the authenticated customer (user_id === current_user_id), returning 404 not_found if not owned by the user.

Request payload

By activation ID:

json
{
  "activation_id": 123
}

Or by website identity:

json
{
  "domain": "https://customer-site.com",
  "installation_id": "550e8400-e29b-41d4-a716-446655440000"
}

Success response (200)

json
{
  "success": true
}

OAuth backend endpoints

The OAuth flow uses two authenticated backend endpoints to validate connection requests and complete activation tokens. Both endpoints require is_user_logged_in() and verify license ownership against get_current_user_id().

Validate OAuth request

http
GET /wp-json/woonoow/v1/licenses/oauth/validate?license_key={key}&state={state}

Validates the license key, verifies account ownership, verifies the signed state token, and resolves the requesting website identity.

Success response (200)

json
{
  "license_key": "XXXX-YYYY-ZZZZ-WWWW",
  "product_id": 42,
  "variation_id": 0,
  "product_name": "WooNooW Pro",
  "variation_name": "",
  "status": "active",
  "activation_limit": 3,
  "activation_count": 1,
  "expires_at": "2027-09-08 12:00:00",
  "usage_expires_at_utc": "2027-09-08T12:00:00Z"
}

Returns 400 missing_license_key or missing_state, 404 license_not_found, 403 unauthorized (if license does not belong to the logged-in user), or 400 invalid_state.

Confirm OAuth activation

http
POST /wp-json/woonoow/v1/licenses/oauth/confirm
Content-Type: application/json

Confirms approval of the requesting installation, creates a single-use activation token bound to the approved identity in the signed state, and constructs the return URL.

Request payload

json
{
  "license_key": "XXXX-YYYY-ZZZZ-WWWW",
  "state": "...",
  "nonce": "7a8b9c0d1e"
}

Success response (200)

json
{
  "success": true,
  "redirect_url": "https://customer-site.com/callback?activation_token=tok_abc123...&license_key=XXXX-YYYY-ZZZZ-WWWW&nonce=7a8b9c0d1e",
  "activation_token": "tok_abc123..."
}

License status model

WooNooW distinguishes three separate layers of state:

  1. Stored base status:
    • active — the license is administratively active;
    • revoked — the merchant explicitly revoked the license.
  2. Effective lifecycle status: Evaluates administrative status, usage expiry (expires_at), linked subscription status, and installation activation:
Base statusUsage expiredSubscription validIdentity activeEffective status
activeNoYes or unlinkedYesvalid
activeYesAnyAnyexpired
activeNoNoAnysubscription_inactive
activeInvalid formatAnyAnyinvalid_usage_expiry
activeNoYes or unlinkedNodomain_not_activated
revokedAnyAnyAnyrevoked
  1. Entitlement rights: Software update rights (update_entitled) and customer support rights (support_active) operate on their own schedules determined at license issuance. An expired update window does not alter effective usage status. Read License Entitlements for details.

Activation limits

An activation limit of 0 means unlimited. Positive values are the maximum number of active combined identities for the license.

For variable products, activation limit is resolved in this order:

text
variation override → parent product value → global default

An empty variation value inherits from the parent. A variation value of 0 explicitly means unlimited.

Error codes

Handle the machine-readable error code instead of matching the human-readable message.

HTTPCodeMeaning
400missing_key / missing_license_keylicense_key was not supplied
400missing_stateOAuth state parameter was omitted
400missing_paramsMissing required parameters on OAuth confirmation
400missing_identityDomain or installation UUID is missing
400invalid_identityUUID or domain is malformed
400invalid_stateOAuth state signature is invalid, tampered with, or expired
400missing_identifierDeactivation has neither activation ID nor complete identity
400missing_return_urlOAuth activation requires a callback URL
400invalid_return_urlOAuth callback domain does not match the requesting domain
401rest_not_logged_inAuthentication required for customer or admin endpoint
403unauthorizedCurrent user does not own the requested license
403invalid_licenseLicense key is invalid or does not exist
403license_inactive / revokedLicense is not active or has been revoked
403license_expired / expiredLicense is past its usage expiry
403subscription_inactiveLinked subscription is not active
403invalid_usage_expiryLicense usage expiry timestamp format is invalid
403domain_not_activatedUUID + domain pair has no active activation
403activation_limit_reachedNo activation slot is available
403invalid_tokenOAuth activation token is invalid or expired
403token_consumedOAuth activation token was already used
403deactivation_disabledMerchant disabled customer deactivation
404not_found / license_not_foundLicense record was not found
404no_activationMatching activation record was not found
500activation_transaction_failedDatabase could not begin transaction for activation
500activation_read_failedDatabase read error during activation
500activation_write_failedDatabase write error during activation
500activation_commit_failedDatabase commit error during activation
500deactivation_transaction_failedDatabase could not begin transaction for deactivation
500deactivation_read_failedDatabase read error during deactivation
500deactivation_write_failedDatabase write error during deactivation
500deactivation_commit_failedDatabase commit error during deactivation
500revoke_failedFailed to update license revocation status in database

Client rules

  • Persist one UUID per installation; never generate one per request.
  • Send the same UUID and current domain on every operation.
  • Do not log full license keys or OAuth activation tokens.
  • Treat network/server failures as temporary; do not erase the key or replace the UUID automatically.
  • Deactivate the old identity before a planned domain migration when the old activation slot should be released.

Last updated Sep 8, 2026