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:
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
POST /wp-json/woonoow/v1/licenses/activate
Request
{
"license_key": "XXXX-YYYY-ZZZZ-WWWW",
"domain": "https://customer-site.com",
"installation_id": "550e8400-e29b-41d4-a716-446655440000"
}
| Body parameter | Type | Required | Description |
|---|
license_key | string | Yes | Product license key |
domain | string | Yes | Current site URL or host |
installation_id | UUID | Yes | Persistent canonical installation UUID |
machine_id | string | No | Metadata only; not part of identity |
return_url | URL | For OAuth | Callback URL on the requesting domain |
activation_token | string | OAuth callback only | Short-lived token returned after approval |
Success response
{
"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:
{
"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":
{
"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
POST /wp-json/woonoow/v1/licenses/validate
Use this endpoint to verify whether a license is valid and active for a specific installation.
Request
{
"license_key": "XXXX-YYYY-ZZZZ-WWWW",
"domain": "https://customer-site.com",
"installation_id": "550e8400-e29b-41d4-a716-446655440000"
}
| Body parameter | Type | Required | Description |
|---|
license_key | string | Yes | Product license key |
domain | string | Yes | Current site URL or host |
installation_id | UUID | Yes | Persistent canonical installation UUID |
Valid response (200)
When the license is active, unexpired, and activated for the requesting identity, the endpoint returns HTTP 200:
{
"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:
{
"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)
{
"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
POST /wp-json/woonoow/v1/licenses/deactivate
Use one of the following request forms.
By activation ID
{
"license_key": "XXXX-YYYY-ZZZZ-WWWW",
"activation_id": 123
}
The activation ID must belong to the supplied license.
By website identity
{
"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
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
GET /wp-json/woonoow/v1/licenses
Query parameters
| Parameter | Type | Required | Description |
|---|
search | string | No | Search string for license key or customer |
status | string | No | Filter by base status: active or revoked |
product_id | integer | No | Filter by parent product ID |
user_id | integer | No | Filter by WordPress customer ID |
page | integer | No | Page number (default: 1) |
per_page | integer | No | Records per page (default: 50) |
Success response (200)
{
"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
GET /wp-json/woonoow/v1/licenses/{id}
Returns the enriched license object above plus an activations array containing all recorded activations:
{
"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
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):
List activations for a license
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
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
POST /wp-json/woonoow/v1/account/licenses/{id}/deactivate
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:
Or by website identity:
{
"domain": "https://customer-site.com",
"installation_id": "550e8400-e29b-41d4-a716-446655440000"
}
Success response (200)
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
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)
{
"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
POST /wp-json/woonoow/v1/licenses/oauth/confirm
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
{
"license_key": "XXXX-YYYY-ZZZZ-WWWW",
"state": "...",
"nonce": "7a8b9c0d1e"
}
Success response (200)
{
"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:
- Stored base status:
active — the license is administratively active;
revoked — the merchant explicitly revoked the license.
- Effective lifecycle status:
Evaluates administrative status, usage expiry (
expires_at), linked subscription status, and installation activation:
| Base status | Usage expired | Subscription valid | Identity active | Effective status |
|---|
active | No | Yes or unlinked | Yes | valid |
active | Yes | Any | Any | expired |
active | No | No | Any | subscription_inactive |
active | Invalid format | Any | Any | invalid_usage_expiry |
active | No | Yes or unlinked | No | domain_not_activated |
revoked | Any | Any | Any | revoked |
- 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:
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.
| HTTP | Code | Meaning |
|---|
| 400 | missing_key / missing_license_key | license_key was not supplied |
| 400 | missing_state | OAuth state parameter was omitted |
| 400 | missing_params | Missing required parameters on OAuth confirmation |
| 400 | missing_identity | Domain or installation UUID is missing |
| 400 | invalid_identity | UUID or domain is malformed |
| 400 | invalid_state | OAuth state signature is invalid, tampered with, or expired |
| 400 | missing_identifier | Deactivation has neither activation ID nor complete identity |
| 400 | missing_return_url | OAuth activation requires a callback URL |
| 400 | invalid_return_url | OAuth callback domain does not match the requesting domain |
| 401 | rest_not_logged_in | Authentication required for customer or admin endpoint |
| 403 | unauthorized | Current user does not own the requested license |
| 403 | invalid_license | License key is invalid or does not exist |
| 403 | license_inactive / revoked | License is not active or has been revoked |
| 403 | license_expired / expired | License is past its usage expiry |
| 403 | subscription_inactive | Linked subscription is not active |
| 403 | invalid_usage_expiry | License usage expiry timestamp format is invalid |
| 403 | domain_not_activated | UUID + domain pair has no active activation |
| 403 | activation_limit_reached | No activation slot is available |
| 403 | invalid_token | OAuth activation token is invalid or expired |
| 403 | token_consumed | OAuth activation token was already used |
| 403 | deactivation_disabled | Merchant disabled customer deactivation |
| 404 | not_found / license_not_found | License record was not found |
| 404 | no_activation | Matching activation record was not found |
| 500 | activation_transaction_failed | Database could not begin transaction for activation |
| 500 | activation_read_failed | Database read error during activation |
| 500 | activation_write_failed | Database write error during activation |
| 500 | activation_commit_failed | Database commit error during activation |
| 500 | deactivation_transaction_failed | Database could not begin transaction for deactivation |
| 500 | deactivation_read_failed | Database read error during deactivation |
| 500 | deactivation_write_failed | Database write error during deactivation |
| 500 | deactivation_commit_failed | Database commit error during deactivation |
| 500 | revoke_failed | Failed 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.