DrPlugin

Licensing REST API

Updated October 24, 2026

Activate, validate and deactivate license keys from your own software, and authenticate management requests with API keys or Application Passwords.

Base URL and authentication

The licensing routes are public REST routes under your store's tiermint/v1 namespace, at https://yourstore.example/wp-json/tiermint/v1.

Any HTTP client can call them: a WordPress plugin, a desktop app or a script. They take no account, password or API token. The license key, together with the URL of the site it is used on, is the credential.

Every request names:

Parameter Meaning
license_key The key TierMint issued at purchase. License keys and activations describes the format.
site_url The site or install the request comes from. TierMint normalizes it, so https://www.Example.com/ and example.com are the same site. Activations lists the rules.

Two more public routes, /update-check and /package/download, deliver new versions. The update server page covers them.

Activate a license

Send POST /wp-json/tiermint/v1/license/activate when a customer enters their key in your software, with this body:

{
  "license_key": "7F3A9C21-04BD8E5F-A1C2D3E4-9B8F7A60",
  "site_url": "https://www.example.com/",
  "product_slug": "orbit-forms",
  "environment": { "php": "8.2.20", "wp": "6.8.1", "product_version": "2.0.1" }
}
Field Required Meaning
license_key Yes The license key, up to 64 characters.
site_url Yes The site's URL, up to 200 characters.
product_slug No The slug of the product the key should belong to, up to 200 characters: the product's catalog slug, as in its permalink. When you send it and it does not match, the request fails with tiermint_key_mismatch. This catches a customer who pastes a key for another product.
fingerprint No An opaque device id of up to 64 characters, for software with no stable URL. It becomes part of the site's identity. Keep it stable between runs. A WordPress site leaves it out.
environment No A flat map of details such as the PHP, WordPress and product versions. TierMint stores up to 20 entries with the activation for diagnostics, shows them on the license screen and does not act on them.

A successful activation returns 200:

{
  "success": true,
  "license": {
    "status": "active",
    "product_slug": "orbit-forms",
    "expires_at": "2027-10-12T00:00:00Z",
    "activation_limit": 5,
    "activation_count": 2,
    "customer_email": "a***@example.com"
  },
  "activation": {
    "id": 4812,
    "site_url": "example.com",
    "activated_at": "2026-10-13T09:14:03Z",
    "is_local": false
  }
}

Anyone with the key can call this route, so customer_email is always masked. is_local is true for a local or staging site, which does not use a slot. expires_at is null for a lifetime license and activation_limit is null for an unlimited one.

Activating a site that is already active succeeds again while the license is active, and does not use a second slot. An expired or disabled license refuses it.

When every slot is taken, the route returns 403 with the sites that hold the slots, so your software can offer to deactivate one:

{
  "code": "tiermint_activation_limit_reached",
  "message": "This license key has reached its activation limit (3 sites).",
  "data": {
    "status": 403,
    "activation_limit": 3,
    "activation_count": 3,
    "activations": [
      { "id": 4801, "site_url": "old-site.example" },
      { "id": 4802, "site_url": "shop.example" },
      { "id": 4803, "site_url": "example.com" }
    ]
  }
}

An expired license returns 403 tiermint_license_expired with the expiry date in data.expires_at. The message reads "This license has expired. To activate new sites, buy a new license and use its key." A disabled license returns 403 tiermint_license_disabled.

Check a license

Send GET /wp-json/tiermint/v1/license/validate from software that is already activated, to confirm the license is still good and to pick up a new expiry date. The check never activates or deactivates anything.

curl "https://yourstore.example/wp-json/tiermint/v1/license/validate?license_key=7F3A9C21-04BD8E5F-A1C2D3E4-9B8F7A60&site_url=example.com&product_slug=orbit-forms"

license_key is required. site_url, product_slug and fingerprint are optional and work as they do on activate.

A well-formed request always gets 200, whatever the license's state. An expired, disabled or unknown key is part of the answer, not an HTTP error. Build your client on that: a 200 with "valid": false is how an expired license looks, while a timeout or a 5xx means your store could not answer.

An active license:

{
  "valid": true,
  "status": "active",
  "site_active": true,
  "product_slug": "orbit-forms",
  "expires_at": "2027-10-12T00:00:00Z",
  "activation_limit": 5,
  "activation_count": 2,
  "checked_at": "2026-10-13T09:20:11Z",
  "renewal_url": "https://yourstore.example/checkout/?tiermint_renew=7F3A9C21-04BD8E5F-A1C2D3E4-9B8F7A60"
}

An expired license:

{
  "valid": false,
  "status": "expired",
  "site_active": true,
  "product_slug": "orbit-forms",
  "expires_at": "2026-09-12T00:00:00Z",
  "activation_limit": 1,
  "activation_count": 1,
  "checked_at": "2026-10-13T09:20:11Z",
  "renewal_url": "https://yourstore.example/checkout/?tiermint_renew=5068A1B2-C3D4E5F6-A7B8C9D0-E1F20F44"
}
Field Meaning
valid True only when status is active.
status active, expired, disabled, invalid (no license has this key) or key_mismatch (the key belongs to another product than product_slug). For invalid and key_mismatch, the product, date and count fields are null.
site_active Whether this site holds an active activation. Null when you send no site_url, and also null when from_cache is true. A site that was never activated reads false.
expires_at The expiry date in UTC, or null for a lifetime license.
activation_limit, activation_count The limit (null for unlimited) and the slots in use. Local and staging sites are not counted.
checked_at When the store answered.
renewal_url A link to your checkout that sells a new license. The name is kept for compatibility: the free plugin does not extend a license, so the customer gets a new key. It appears when your store has a Checkout page. Renew a license explains what the customer sees.
from_cache Present and true when your store's database failed and it answered from the last good result for this key, kept for 72 hours. The answer still holds, but site_active is null.

When the database fails and no good result is stored, the route returns 500 tiermint_server_error. That is the case your client's own grace period covers.

TierMint Pro can add a signature to this response. The advanced licensing page explains how to verify it.

Release an activation

Send POST /wp-json/tiermint/v1/license/deactivate when a customer moves the license off a site, often to free a slot for a new one. Send this body:

{
  "license_key": "7F3A9C21-04BD8E5F-A1C2D3E4-9B8F7A60",
  "site_url": "example.com"
}

license_key and site_url are required. If the site was activated with a fingerprint, send the same one. The route also works for an expired or disabled license, so a customer can always clear old sites.

A successful call returns 200 with the slots still in use:

{ "success": true, "activation_count": 1 }

Deactivating the same site twice succeeds both times, so a client that failed halfway can retry. A site that never had an activation on the key returns 404 tiermint_activation_not_found.

Errors

Errors use the WordPress REST error format:

{
  "code": "tiermint_activation_not_found",
  "message": "The site example.com is not activated for this license.",
  "data": { "status": 404 }
}

Match on code in your client. The message is for people and may be translated.

Code Status Meaning
tiermint_invalid_params 400 A required field is missing, or the site URL could not be read. data.errors lists the messages.
rest_invalid_param 400 A value is longer than its limit (license_key 64, site_url 200, product_slug 200, fingerprint 64), or environment is not an object. WordPress core returns this code, not a TierMint one.
tiermint_invalid_key 404 Activate or deactivate: no license has this key.
tiermint_key_mismatch 404 Activate: the key belongs to another product than product_slug.
tiermint_license_expired 403 Activate: the license has expired.
tiermint_license_disabled 403 Activate: the license is disabled.
tiermint_activation_limit_reached 403 Activate: every slot is in use.
tiermint_activation_not_found 404 Deactivate: the site has no activation on this key.
tiermint_rate_limited 429 Too many requests. Wait for the seconds in the Retry-After header.
tiermint_server_error 500 Something failed inside the store. Retry later.

An unknown key and a key for another product both return 404 on activate, so a script that guesses keys cannot tell them apart. Your own client still gets the exact code.

/license/validate returns license problems in a 200 answer, as Check a license shows. Its errors are only for a bad request, a rate limit or a store failure.

Rate limits

Scope Limit
Per license key 60 requests an hour
Per IP address 300 requests an hour

The limits cover /license/validate, /license/activate, /license/deactivate, /update-check, /package/download and /order/download. All of these share one window per IP address. A request over either limit gets 429 tiermint_rate_limited with a Retry-After header.

TierMint reads the IP address from the web server's REMOTE_ADDR. If your store sits behind a proxy or CDN that does not pass the visitor's address on, every request shares one address and one window. Developers can change the limits with the tiermint_rest_rate_limits filter:

add_filter(
	'tiermint_rest_rate_limits',
	function ( $limits ) {
		if ( isset( $limits['license_ip'] ) ) {
			$limits['license_ip']['limit'] = 1000; // Requests per window.
		}
		return $limits;
	}
);

Each entry has a limit and a window in seconds. The licensing scopes are license_key and license_ip.

A busy store can also limit these routes at the edge, for example in its CDN or web server.

How a client should cache answers

A client that validates on every page load wastes requests and can reach the rate limit. A good pattern:

  1. Keep a successful validation answer for about 12 hours, and do not call the route again in that time.
  2. If your store cannot be reached when the 12 hours are up (a timeout, a connection error or a 5xx), keep using the last valid answer for up to 72 hours from when you fetched it.
  3. After 72 hours without an answer, stop treating the license as checked until your store answers again.
  4. When your store answers, use that answer right away, even if it says the license is no longer valid. The grace period covers only a store that cannot be reached.

For updates in a WordPress plugin, the update client already caches its update checks this way.

Management API keys

The routes above need only a license key. The management routes, such as products, orders, licenses, discounts and releases, need a signed-in user with the right capability. Scripts and other servers can authenticate in two ways.

TierMint API keys

A TierMint API key has a public part, tmk_ followed by 32 hex characters, and a secret, tms_ followed by 43 characters. Send both in the Authorization header:

curl -H "Authorization: Bearer tmk_d9eeedf9827564d7d69c0984b3460e8d:tms_FqzFzDyqlQ57ZcEKieeBBR-N75LV78NaoqVo4cuG5rM" \
  https://yourstore.example/wp-json/tiermint/v1/licenses

A key works on REST API requests only. Sent to a page, admin-ajax.php or admin-post.php, it does not sign anyone in. It also works only on TierMint's own routes, under tiermint/v1. A request with a key to any other REST route, such as WordPress's /wp/v2/users, gets a 403 error.

There is no API keys screen in wp-admin. You create, list and revoke keys through the /api-keys REST routes. They need the manage_tiermint_settings capability and a signed-in session or an Application Password. An API key can list keys (a read key) and revoke them (a write key) when its owner has that capability. An API key cannot create keys.

Route What it does
GET /tiermint/v1/api-keys Lists keys with their id, owner, label, public key, permission, status, last use and creation date. Secrets are never listed.
POST /tiermint/v1/api-keys Creates a key owned by the current user. Send label (required, up to 200 characters) and permissions (read or write, default read).
DELETE /tiermint/v1/api-keys/{id} Revokes a key. It stops working, and its row stays for the record.

To create a key with an Application Password, send POST /wp-json/tiermint/v1/api-keys:

curl -u admin:"abcd efgh ijkl mnop qrst uvwx" \
  -H "Content-Type: application/json" \
  -d '{"label":"Release pipeline","permissions":"write"}' \
  https://yourstore.example/wp-json/tiermint/v1/api-keys

The response is 201 and carries the full credential in bearer:

{
  "id": 3,
  "user_id": 1,
  "label": "Release pipeline",
  "public_key": "tmk_d9eeedf9827564d7d69c0984b3460e8d",
  "permissions": "write",
  "status": "active",
  "last_used_at": null,
  "created_at": "2026-10-13T09:00:00Z",
  "bearer": "tmk_d9eeedf9827564d7d69c0984b3460e8d:tms_FqzFzDyqlQ57ZcEKieeBBR-N75LV78NaoqVo4cuG5rM"
}

A key acts as the user who created it, with that user's capabilities. A read key can only send GET, HEAD and OPTIONS requests. Anything else gets 403 tiermint_forbidden.

TierMint also protects keys in two ways:

  • After 20 failed key logins from one IP address in an hour, every key request from that address gets 429 until the hour is up, even with a correct key.
  • One key can make 600 management requests an hour. Past that it gets 429 with a Retry-After header.

Both limits use the tiermint_rest_rate_limits filter, with the scopes auth_fail and management_api. A key with a wrong secret gets 401 tiermint_unauthorized.

WordPress Application Passwords

Application Passwords, under Users → Profile → Application Passwords, also work on every management route. If you already use them for other integrations, you do not need a TierMint key.

Neither method applies to the licensing routes on this page. Activate, validate and deactivate never ask for an API key or an Application Password.

Common questions

Why does activate return key_mismatch?

The key exists, but it was issued for another product than the product_slug you sent. Usually a customer owns two of your products and pasted the wrong key, or the slug in your software has a typo. Send the product's catalog slug, as it appears in the product's permalink.

A customer reached the activation limit. Do they need to contact you?

No. The 403 tiermint_activation_limit_reached answer lists the active sites in data.activations. Your software can show that list and let the customer deactivate one before it tries again. Customers can also do this in their customer dashboard.

Do local and staging sites use a slot?

No. TierMint recognizes local and staging hosts from the site_url you send and does not count them. Your client does nothing extra. Activations lists the hosts.

What is the difference between expired and disabled?

expired means the expiry date has passed. The customer needs a new license: the link in renewal_url sells one, with a new key. disabled means you switched the license off, or a full refund did. Both return "valid": false from validate, and status tells them apart, so your software can show a different message for each.