License REST API
Endpoints for reading and managing the plugin’s license state. All admin license endpoints require manage_options and a valid X-WP-Nonce header. The subscriber-count endpoint uses a separate Bearer token authentication scheme — it is called by the license server, not the WordPress admin.
GET /benecaster/v1/license
Returns the current license status and all associated metadata.
Auth: manage_options + X-WP-Nonce
Response:
{
"status": "active",
"plan": "growth",
"expires_at": "2027-05-17",
"site_url": "https://yoursite.com",
"activated_at": "2025-03-15T09:00:00",
"last_validated_at": "2026-05-17T08:00:00",
"validation_failure_count": 0,
"subscriber_count": 41,
"subscriber_limit": 250,
"overage_up_count": 0,
"upgrade_scheduled_at": null,
"upgrade_target_tier": null,
"downgrade_lock_active": false,
"downgrade_lock_until": null,
"active_addons": ["email-editor", "blocks-library"],
"show_limit": 1,
"active_show_count": 1,
"all_addons_included": false,
"priority_support": false,
"beta_access": false,
"license_key_masked": "XXXX-XXXX-XXXX-••••",
"activation_count": 1,
"activation_limit": 1
}
Field notes:
| Field | Notes |
|---|---|
status |
active, grace_period, expired, cancelled, suspended, invalid, or none |
subscriber_limit |
10 for Launch plan; 50 for Starter; 250 for Growth; null for Pro, Multi-Show, Studio (unlimited) |
show_limit |
null for Multi-Show and Studio (unlimited). 1 for all other plans. |
all_addons_included |
true for Studio only — all add-on entitlements granted regardless of active_addons |
license_key_masked |
Last 4 characters of the license key, rest replaced with bullet characters. null when no key stored. |
activation_count |
null when absent from license server response |
activation_limit |
null for unlimited-activation plans (Studio). When null, the Account tab hides the “Sites: N / M” display. |
downgrade_lock_active |
true for one full billing period after a Launch→Starter automatic upgrade |
downgrade_lock_until |
ISO date when the downgrade lock expires; null when not locked |
validation_failure_count |
Consecutive days of failed validation attempts |
POST /benecaster/v1/license/activate
Activates a license key for this site. Calls the external Benecaster license server.
Auth: manage_options + X-WP-Nonce
Body:
{
"license_key": "XXXX-XXXX-XXXX-XXXX"
}
Response:
{
"status": "activated",
"plan": "growth",
"expires_at": "2027-05-17",
"active_addons": []
}
Errors:
| Code | HTTP | Meaning |
|---|---|---|
missing_license_key |
400 | No license_key in request body |
license_not_found |
404 | Key not recognized by the license server |
site_mismatch |
409 | Key is already registered to a different domain |
license_expired |
403 | Key exists but the subscription has lapsed |
activation_limit_reached |
403 | All activation slots in use; fail-open — feeds on existing sites continue, only new activations are blocked; ActivationLimitNotice fires in wp-admin and clears automatically on next successful activation |
license_server_unreachable |
503 | Outbound HTTPS connection to benecaster.com failed |
POST /benecaster/v1/license/deactivate
Deactivates the license on this site and clears all locally stored license state.
Auth: manage_options + X-WP-Nonce
Response:
{
"deactivated": true
}
Calling this endpoint on a site with no active license returns 200 with { "deactivated": true } — it is idempotent.
POST /benecaster/v1/license/validate
Forces an immediate validation check against the Benecaster license server. Validation normally happens on a daily WP-Cron schedule; this endpoint triggers it on demand.
Auth: manage_options + X-WP-Nonce
Response: Updated license status object — same shape as GET /license.
Use case: After making a payment or upgrading a plan on benecaster.com, call this endpoint to pull in the updated plan details without waiting for the next daily cron run.
GET /benecaster/v1/license/subscriber-count
Returns the current paying subscriber count and total token count for this site. Used by the Benecaster license server to independently verify subscriber counts against the plugin’s telemetry payload.
Auth: Bearer token — Authorization: Bearer {license_key}
This endpoint does not accept X-WP-Nonce or cookie authentication. The intended caller is the Benecaster license server, not a WordPress admin. The license key in the Authorization header must exactly match the value stored in benecaster_license_key; mismatches return 401.
Response:
{
"paying_subscriber_count": 41,
"total_token_count": 167
}
| Field | Definition |
|---|---|
paying_subscriber_count |
Count of active tokens joined to benecaster_tier_map rows where is_free_tier = 0 |
total_token_count |
Count of all active tokens regardless of tier type (paying + free combined) |
Errors:
| Code | HTTP | Meaning |
|---|---|---|
rest_forbidden |
401 | Authorization header absent or license key mismatch |
Security rationale: Reading counts directly from the database at this layer means manipulation requires database access, not just PHP access. The endpoint intentionally bypasses WordPress authentication to prevent a compromised admin account from blocking the license server’s independent count.
Daily Validation Payload
The plugin sends a POST request to the Benecaster license server once per day via WP-Cron (LicenseValidationCron). This is separate from the admin-triggered POST /benecaster/v1/license/validate endpoint above — that endpoint triggers the cron to run immediately. The payload is constructed by TelemetryCalculator immediately before the outbound request.
Core fields (always sent):
| Field | Type | Description |
|---|---|---|
license_key |
string |
The stored license key. |
site_url |
string |
get_site_url() — the WordPress site URL. |
subscriber_count |
int |
Paying subscriber count. On staging with a subscriber cap (Free/Starter/Growth plans): min(actual_count, staging_cap). On staging with an unlimited plan (Pro/Multi-Show/Studio, where staging_cap is null): actual count sent uncapped. |
total_token_count |
int |
All active tokens (paying + free tiers combined). |
plugin_version |
string |
Installed Benecaster plugin version. |
wp_version |
string |
WordPress version. |
php_version |
string |
PHP version. |
active_bridge_slugs |
string[] |
Slugs of active subscription bridge plugins across all shows (e.g. ["memberpress"]). Always sent — software metadata, not subscriber data. |
is_staging |
bool |
true when StagingManager::is_staging() is active. This field is omitted entirely in production. Absence of the field is the canonical signal that a ping is from a production site. |
Telemetry fields (sent only when telemetry_opted_in = true):
| Field | Type | Description |
|---|---|---|
geo_distribution |
object |
Nested JSON of token counts by country and region: {"US": {"TX": 45, "CA": 32}, "GB": {"ENG": 67}}. |
listening_hour_distribution |
object |
Download counts by UTC hour over the last 30 days: {"0": 8, "6": 22, ...}. |
listening_dow_distribution |
object |
Download counts by day of week (1=Sunday) over the last 30 days. |
app_distribution |
object |
Download counts by resolved podcast app name over the last 30 days. |
subscriber_tenure_distribution |
object |
Active token counts by tenure bucket: {"0-30d": N, "31-90d": N, "91-365d": N, "365d+": N}. |
Staging-mode payload adjustments:
When is_staging: true is present, the license server:
- Excludes the ping from tier upgrade logic (subscriber count changes on staging do not trigger upgrades or overage counters).
- Does not increment the site’s activation count.
- Excludes the snapshot row from benchmark aggregation and announcement estimated-reach queries.
See Staging Mode Internals for the StagingManager API and the constant that controls staging subscriber count capping.
WP-Option State
The license endpoints read from and write to the following wp_options. These are documented here for diagnostic and test purposes — external code should call the REST endpoints rather than reading options directly.
| Option | Type | Description |
|---|---|---|
benecaster_license_key |
string | The stored license key |
benecaster_license_status |
string | active, grace_period, expired, cancelled, suspended, invalid, or none |
benecaster_license_plan |
string | Plan slug from last validation response |
benecaster_license_expires_at |
string | ISO 8601 expiry date |
benecaster_license_validated_at |
string | ISO 8601 datetime of last successful validation |
benecaster_validation_failure_days |
int | Consecutive days of failed validation |
benecaster_validation_first_failure_at |
string | ISO 8601 datetime of first failure in current streak |
benecaster_activation_count |
int | Site activation count from license server |
benecaster_activation_limit |
int|null | Site activation limit; null when unlimited |
benecaster_activation_limit_reached |
bool | Set when activation rejected at limit; cleared on successful activation |
benecaster_license_activated_at |
string | ISO 8601 datetime of activation; cleared on deactivation |
benecaster_is_new_activation |
bool | Set on activation; deleted after first validation response |