Add-on Activation REST API
Powers the per-show on/off control on every entitled card in Settings → Add-ons.
The distinction this API exists to hold apart is entitlement versus activation. Entitlement is what a license grants and is decided entirely by the license server. Activation is the podcaster’s own choice about which of their shows actually runs an add-on they already own. This API writes the second and never the first — it never calls the license server.
Purchase is license-wide; activation is per show. A Multi-Show customer who owns an add-on can run it on shows A and B and leave it off on C, with no second purchase.
A third, read-only route, GET /addons, lists every add-on in the catalogue with the state its card shows.
GET /addons
Returns every add-on in Benecaster’s pricing catalogue, with the state its card on Settings → Add-ons shows. It is the same data the Add-ons screen renders, so a script or dashboard reading it sees exactly what the screen does.
Auth: manage_options plus a valid wp_rest nonce. Read-only.
It never contacts Benecaster.com. It answers from what the site has already cached. If the site holds no copy of the catalogue, the answer is { "catalog_available": false, "currency": null, "items": [] }, and this route does not fetch one.
Response (200):
{
"catalog_available": true,
"currency": "usd",
"items": [
{
"slug": "email-editor",
"entitlement_slug": "benecaster-addon-email-editor",
"name": "Email Editor",
"note": null,
"phase": 1,
"state": "active",
"coming_soon": false,
"free": false,
"price": { "annual_price": 49, "annual_price_cents": 4900, "currency": "usd" },
"scope": "show",
"licensed_show_ids": [ 42, 43 ],
"active_show_ids": [ 42 ],
"installed": true,
"version": "1.2.0",
"update_available": false,
"settings_url": "https://example.com/wp-admin/admin.php?page=benecaster#/email-editor"
},
{
"slug": "guest-manager",
"entitlement_slug": "benecaster-addon-guest-manager",
"name": "Guest Manager",
"note": null,
"phase": 1,
"state": "purchase",
"coming_soon": false,
"free": false,
"price": { "annual_price": 49, "annual_price_cents": null, "currency": "usd" },
"scope": "show",
"licensed_show_ids": [],
"active_show_ids": [],
"installed": false,
"version": null,
"update_available": null,
"purchase_url": "https://benecaster.com/shop/add-ons/guest-manager/"
}
]
}
| Field | Description |
|---|---|
slug / entitlement_slug |
The add-on’s short slug, and the slug a license grants it under. |
state |
active: owned and installed. install: owned, not installed. no_entitlement: installed, but nobody on this site owns it. purchase: neither owned nor installed. Owned means some published show’s license grants it. A free add-on is always owned. |
coming_soon |
true for an add-on that is not released yet. It keeps its state and price but carries no purchase or install link. |
free |
true only when the catalogue prices the add-on at exactly 0. A missing price is unknown, not free. |
price |
The yearly price as the catalogue gives it: annual_price in major units, annual_price_cents when present, and the currency. Never pre-formatted. |
scope |
show for an add-on switched on and off per show, site for one that runs site-wide. |
licensed_show_ids |
Published shows whose license grants the add-on. A show with no license connected is never listed. |
active_show_ids |
Those of licensed_show_ids where it is not switched off. For a site add-on the two lists are the same. |
installed / version |
Whether the add-on plugin is installed, and its version. version is null when it is not installed. |
update_available |
From WordPress’s own update check: null until WordPress has checked, or when the add-on is not installed. Benecaster never installs or updates an add-on itself. |
Links follow the state. Each row carries only the link that makes sense for it:
| Link | On |
|---|---|
purchase_url |
purchase and no_entitlement rows only. An add-on someone already owns never carries one. |
install_action |
install rows only: install when the Add-on Manager is installed and you can install plugins, otherwise download, with downloads_url pointing at your account’s Downloads page on Benecaster.com. |
settings_url |
active rows whose add-on has its own settings screen. |
A coming_soon row carries neither purchase_url nor install_action.
GET /addons/activation
Returns per-show activation state for the Add-ons screen.
Auth: manage_options plus a valid wp_rest nonce.
Body: none.
Response (200): one row per published show.
{
"shows": [
{
"id": 42,
"uuid": "8f3c1a90-...",
"title": "The Example Show",
"granted": [ "email-editor", "analytics-dashboard" ],
"disabled": [ "analytics-dashboard" ]
}
]
}
| Field | Type | Description |
|---|---|---|
id |
int | Show post ID |
uuid |
string | The show’s _benecaster_show_uuid |
title |
string | Show title |
granted |
string[] | Slugs this show’s license entitles, decided the same way as benecaster_addon_is_active(): the license’s add-on list when it sent one, the all-add-ons plan flag only when it sent none |
disabled |
string[] | Slugs the podcaster has switched off for this show |
Read disabled, not an enabled list. Only switch-offs are stored, so absence means on. That is why a newly granted add-on is active the moment the grant arrives, with no migration and no backfill.
Shows whose license does not grant an add-on are still listed, with the add-on simply absent from granted. The screen renders them with the reason — “Not on this show’s license” — rather than hiding them. A missing control on one show of three reads as a bug rather than a license boundary, and that is the failure this control exists to prevent.
POST /addons/activation
Switches one add-on on or off for one show.
Auth: manage_options plus a valid wp_rest nonce.
Body
| Name | Type | Description |
|---|---|---|
show_id |
int | The show to change |
addon_slug |
string | The add-on to switch |
enabled |
bool | true to switch on, false to switch off |
Response (200): the rebuilt row set, in the same shape as GET.
The write returns the full refreshed state rather than an acknowledgement, so the screen renders confirmed server state instead of its own optimistic guess. A client that patches its local copy on a bare 200 will eventually disagree with the server, and the disagreement shows up only after a save.
Errors (400)
| Code | Meaning |
|---|---|
invalid_show_id |
show_id missing, non-positive, or not a benecaster_show post |
missing_addon_slug |
addon_slug empty after sanitisation |
no_show_uuid |
the show has no _benecaster_show_uuid |
addon_not_granted |
this show’s license does not grant the slug |
addon_not_granted is a refusal, not a validation nicety. A stored “off” for an add-on that was never granted is indistinguishable from a deliberate choice on the day the license does grant it — the customer would buy the add-on and find it already switched off on that one show, with nobody having switched it off.
What a successful write fires
do_action( 'benecaster_addon_activation_changed', string $addon_slug, int $show_id, bool $enabled );
This is an activation change, never an entitlement change. The customer still owns the add-on and is expected to switch it back on. A listener may drop per-show caches, transients and scheduled tasks — but must not destroy anything the customer authored or paid for, because the next switch-on cannot undo a delete.
See benecaster_addon_activation_changed.
Related
Reading activation state in code is benecaster_addon_is_active(), which takes a required show context. Its install-wide companion, benecaster_addon_is_active_for_any_show(), answers a different question and must not be substituted for it. benecaster_addon_is_active() answers false for a show that is not connected to a license, and the install-wide one answers false when nothing on the install is connected — on every plan, including one that includes every add-on.
See Also
Need this built rather than just documented? See our services →