REST API Reference
Base URL and conventions#
All routes live under the license-cart/v1 namespace:
https://your-site.com/wp-json/license-cart/v1
Public endpoints need no authentication. They accept JSON bodies or form parameters for POST and query parameters for GET, and always return JSON. Every public response has the shape:
{ "success": true, "message": "License activated successfully.", "data": { … } }
Failures return success: false with a human-readable message and HTTP 400 (validation), 403 (not allowed), 404 (not found) or 429 (rate-limited).
Domains are canonicalised on the server (lower-case, www. stripped, default ports removed, IDN converted). Send home_url('/') or the bare host; both work.
POST /license/activate#
Adds the domain to the key’s activations.
| Parameter | Type | Required | Description |
|---|---|---|---|
license_key | string | yes | The customer’s key. |
product_slug | string | yes | Slug of the product the key must belong to. |
domain | string | yes | The site being activated. |
Possible messages: “Invalid license key.”, “This license is not valid for the requested product.”, “License has expired.”, “License is revoked.” / “License is inactive.”, “Activation limit reached (N).”, “Domain is already activated.” (returned with success: true).
The data object on success:
{
"status": "active",
"product_slug": "my-plugin",
"domain_active": true,
"activation_limit": 3,
"activations_count": 1,
"expires_at": "2027-10-30 12:00:00",
"created_at": "2026-10-30 12:00:00"
}
expires_at is null for lifetime keys. Activations are performed inside a database transaction with a row lock, so concurrent requests cannot exceed the limit.
POST /license/deactivate#
Removes the domain from the key’s activations. Same parameters as activate. Returns “Domain is not activated with this license.” if the domain was not present.
GET /license/status#
Returns the current status without changing anything.
| Parameter | Type | Required |
|---|---|---|
license_key | string | yes |
product_slug | string | yes |
domain | string | yes |
The request fails (success: false) when the product does not match or the domain is not activated, so a stolen key cannot be queried from an unrelated site. data has the same shape as activate; check data.status === "active" and data.domain_active === true before enabling premium features. Expired subscription keys are flipped to expired by this call.
POST /update/check#
| Parameter | Type | Required | Description |
|---|---|---|---|
license_key | string | yes | |
product_slug | string | yes | |
current_version | string | no | Installed version. Defaults to 0.0.0. |
domain | string | yes | Must be an activated domain. |
Responses:
{ "update_available": false, "message": "You are running the latest version.", "version": "1.2.0" }
{
"update_available": true,
"version": "1.3.0",
"download_url": "https://your-site.com/wp-json/license-cart/v1/update/download?token=…",
"changelog": "- Fixed …",
"requires_php": "7.4",
"requires_wp": "5.8",
"product_name": "My Plugin",
"released_at": "2026-10-30 09:00:00"
}
HTTP 403 with update_available: false is returned for invalid, inactive, expired or revoked keys, a product mismatch, or a domain that is not activated. HTTP 404 means the product slug does not exist.
GET /update/download#
| Parameter | Type | Required |
|---|---|---|
token | string | yes |
Streams the release ZIP. Tokens are issued by update/check and the portal, are signed with the site’s auth salt and expire after 300 seconds. Errors: invalid_download_token, expired_download_token, license_download_denied, license_domain_denied, update_file_missing.
POST /paddle/webhook#
Receiver for Paddle Billing notifications. Requires a valid Paddle-Signature header. Not for use by your client plugins.
Admin endpoints#
The dashboard uses a set of /admin/* routes (stats, licenses, products, products/{id}/prices, prices, customers, updates, upload-release, settings, logs, backups, backups/restore, r2/test-connection, integration, integration/mcp-config). They require a logged-in administrator (manage_options) and the X-WP-Nonce header, and are not a stable public API.
Rate limits#
| Endpoint | Limit per IP |
|---|---|
/license/activate, /license/deactivate | 30 per minute |
/license/status, /update/check, /update/download | 60 per minute |
/paddle/webhook (failed signatures only) | 20 per 10 minutes |
A limited request returns HTTP 429 with error code license_cart_rate_limited and data.retry_after in seconds.
cURL examples#
# Activate
curl -X POST https://your-site.com/wp-json/license-cart/v1/license/activate \
-H 'Content-Type: application/json' \
-d '{"license_key":"A1B2-…","product_slug":"my-plugin","domain":"customer-site.com"}'
# Status
curl 'https://your-site.com/wp-json/license-cart/v1/license/status?license_key=A1B2-…&product_slug=my-plugin&domain=customer-site.com'
# Update check
curl -X POST https://your-site.com/wp-json/license-cart/v1/update/check \
-H 'Content-Type: application/json' \
-d '{"license_key":"A1B2-…","product_slug":"my-plugin","current_version":"1.0.0","domain":"customer-site.com"}'
