Black Friday: 79% off every plugin Shop the sale →

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.

ParameterTypeRequiredDescription
license_keystringyesThe customer’s key.
product_slugstringyesSlug of the product the key must belong to.
domainstringyesThe 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.

ParameterTypeRequired
license_keystringyes
product_slugstringyes
domainstringyes

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#

ParameterTypeRequiredDescription
license_keystringyes
product_slugstringyes
current_versionstringnoInstalled version. Defaults to 0.0.0.
domainstringyesMust 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#

ParameterTypeRequired
tokenstringyes

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#

EndpointLimit per IP
/license/activate, /license/deactivate30 per minute
/license/status, /update/check, /update/download60 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"}'
Was this article helpful?