Skip to content

License API reference

Complete request and response contract for all four license endpoints.

3 min read

Four endpoints, all under /api/v1, all POST, all rate limited to 60 requests per minute. No authentication token — the license key is the credential.

POST /api/v1/license/verify#

The endpoint your software calls to check entitlement. If a domain is supplied it is registered as a side effect, so most integrations never need to call anything else.

Parameters#

Parameter Required Notes
license_key yes The customer’s key.
domain no Up to 255 characters. Supplying it activates the domain, or refreshes its last-seen timestamp if already active.
product_slug no Up to 255 characters. When present, the key must belong to that product or the request fails.
current_version no Up to 64 characters. The version currently installed.

Request#

curl -X POST https://your-panel.example/api/v1/license/verify 
  -H "Content-Type: application/json" 
  -H "Accept: application/json" 
  -d '{
    "license_key": "MP-XXXXXXXXXXXXXXXXXXXXXXXXXXXX",
    "domain": "customer-site.com",
    "product_slug": "my-plugin"
  }'

Success — 200#

{
  "valid": true,
  "license_key": "MP-XXXXXXXXXXXXXXXXXXXXXXXXXXXX",
  "status": "active",
  "plan": "Agency",
  "update_entitled": true,
  "update_access_until": "2027-03-14",
  "update_expires_at": "2027-03-14T00:00:00.000000Z",
  "domain_limit": 5,
  "domains_used": 2,
  "domains_remaining": 3,
  "activated_domains": ["customer-site.com", "another-site.com"]
}

Response fields#

Field Meaning
valid The key exists and the subscription is active.
status One of active, canceled, past_due, paused.
plan Plan name, useful for unlocking tier-specific features.
update_entitled Whether the update window is still open.
update_access_until Update expiry as a plain date.
update_expires_at The same moment as a full timestamp.
domain_limit Concurrent domain cap, or null when unlimited.
domains_used How many are currently active.
domains_remaining Slots left, or null when unlimited.
activated_domains Every currently active domain.

Failure — 403#

{
  "valid": false,
  "reason": "invalid_or_inactive"
}
reason Meaning
invalid_or_inactive No such key, or the subscription is not active.
product_mismatch The key is real but belongs to a different product than the product_slug sent.
domain_limit_exceeded The key is valid but every domain slot is already taken.
domain_invalid The domain could not be parsed.

POST /api/v1/license/activate#

Behaves identically to verify and returns the same payload. It exists so integration code can read clearly: call activate when the customer first enters their key, verify on every later check.

POST /api/v1/license/update-check#

A narrower answer to one question: may this installation take a newer build?

Takes the same parameters as verify.

Entitled — 200#

{
  "valid": true,
  "update_available": true,
  "update_expires_at": "2027-03-14T00:00:00.000000Z"
}

Window closed — 200#

{
  "valid": true,
  "update_available": false,
  "reason": "update_window_expired",
  "update_expires_at": "2026-03-14T00:00:00.000000Z"
}

Note the status is 200 and valid is still true. A lapsed customer has a working license — they are simply not entitled to newer releases. An invalid key returns the 403 shape above instead.

POST /api/v1/license/download#

Streams a release archive, so your software can update itself without sending the customer to the panel.

Parameters#

Parameter Required Notes
license_key yes The customer’s key.
version one of the two Version number, such as 1.4.2.
version_id one of the two The version’s identifier, if you already have it.
domain no Activated or refreshed as with verify.

Success#

The archive itself, as a file stream. The download is recorded against the subscription, version, user, IP, and user agent.

Failures#

Errors return a JSON body with a message field, not the reason shape used by the other endpoints.

Status Condition
403 Invalid or inactive subscription.
403 The update window has expired.
403 Domain limit exceeded.
403 The requested version was released after the update window closed.
404 No such version for this product.
404 The archive is missing from storage.

Integration notes#

  • Cache the result. Verify once or twice a day, not on every page load. The rate limit is 60 per minute per client and a busy site will hit it.
  • Fail open on network errors. Treat a timeout as “unknown”, not as “invalid”. Your customer’s site must not break because your panel had an outage.
  • Fail closed only on an explicit 403. That is the panel actually telling you the key is not good.
  • Send product_slug so a key for one of your products cannot be used to unlock another.
  • Skip local hostnames so staging copies do not eat domain slots.
  • Show the customer the reason. domain_limit_exceeded and invalid_or_inactive need very different messages, and surfacing the difference prevents most support tickets.