update_plan
Change one or more fields of a plan. Partial by design — anything omitted keeps its stored value; the kind is fixed. Emits plan.updated.
Change an existing plan without restating it. Only the arguments present in the call reach the
write, the way the REST PATCH behaves, so repricing a plan is one field, not a re-creation. The
shared fields are checked the way create_plan checks them, and the one
nested block the plan's kind names is shaped exactly as create_plan shapes it.
Synchronous — the row after the change comes back, and
plan.updated emits once with the fields that actually moved. A
call whose arguments change nothing returns the current row and emits nothing.
The kind is fixed, and so is which block you may send
A plan's kind is set at creation and cannot change; a kind argument that
disagrees with the stored one is refused rather than converting a subscription
into a pass — create a new plan instead. The same rule decides the nested
block: billing only on a subscription, pass only on a pass,
pass_series only on a pass_series. A block of another kind fails with
VALIDATION_FAILED naming the stray key; it is never silently ignored.
`resources` replaces, it does not append
Send the full list of resource UUIDs the plan should grant. Whatever is
missing from the list is unlinked. A subscription or pass must keep at
least one; a pass_series may be empty, because each window in a series
grants its own plan's resources.
Flipping `active` here is not the same as publish_plan
active is accepted, and it works, but it is announced as a field inside
plan.updated. publish_plan flips the same flag
with its own plan.activated / plan.deactivated event, which is what an
automation reacting to plans going on or off sale should key on.
Two tiers of entitlement are checked on the way in: a price of 0 makes the plan free, which
needs the Starter or Growth tier, and anything touching a pass or pass_series needs Time-Limited
Passes. Either failing surfaces as TEAM_TIER_REQUIRED with nothing written.
Requires ability
The token behind the MCP session must hold it, or the call is refused with TOKEN_MISSING_ABILITY.
Runs the same action as
The REST endpoint and this tool share one action, so validation, permissions and events are identical.
Fires one event
Delivered to every endpoint subscribed to it once the change is made.
Annotations
A client that honours annotations asks a person before running it. Sending the same arguments twice changes nothing the second time. It reaches beyond Subscriby: a connector, a provider or a member.
Arguments
plan_id*stringUUID of the plan to change.
namestringoptionalPlan name (min 5, max 255 chars, unique per project).
descriptionstringoptionalPlan description. Max 1000 chars, HTML is filtered. Pass an empty string to clear.
pricenumberoptionalWhat one purchase costs, as a decimal. At least the $1.00 USD equivalent unless 0, which makes the plan free and requires the Starter or Growth tier.
currency_idstringoptionalUUID of the currency. Must be supported by an active payment method on the project.
activebooleanoptionaltrue publishes the plan, false takes it off sale; existing subscriptions keep running either way.
sales_capintegeroptionalPause the plan automatically after this many successful purchases (1-100000). Changing it restarts the count; null removes the limit.
eligibilityobjectoptionalAudience restrictions: {newcomers_only?: bool, customers_only?: bool, churned_only?: bool, single_use?: bool, access_codes_only?: bool}. The first three are mutually exclusive.
resourcesarrayoptionalThe full list of project_resources UUIDs this plan grants access to; replaces the current list. Must not be empty on kind subscription or kind pass.
billingobjectoptionalOnly on a plan of kind subscription. {billing_cycle: days|weeks|months|years|lifetime, billing_cycle_count: int 1-99, recurring?: bool, disabled_renewal?: bool, trial_days?: int 0-365, trial_cardless?: bool, trial_type?: string}. lifetime requires billing_cycle_count=1.
passobjectoptionalOnly on a plan of kind pass. {timezone: IANA zone, schedule_mode?: repeating|fixed, recurrence?: daily|weekly|monthly, recurrence_ends_at?: ISO-8601, sales_cutoff_minutes?: int, sales_cutoff_anchor?: before_start|before_end, slots?: array, windows?: array}; the same shape `create_plan` documents.
pass_seriesobjectoptionalOnly on a plan of kind pass_series. {window_ids?: array of pass-window UUIDs, prevent_overlaps?: bool, sales_cutoff_minutes?: int, sales_cutoff_anchor?: string, seat_cap?: int, successor_plan_id?: UUID, presale_hours?: int, blackout_window_ids?: array, rules?: array}; the same shape `create_plan` documents. Window ids come from `list_pass_windows`.
What it returns
{ "data": { "id": "c4e82f16-93a7-4d5b-b81c-6e0f27a94d3b", "project_id": "7f3d1c92-8b45-4e6a-9d21-5c8e0a4b6f13", "name": "Premium Monthly", "description": "All-access pass", "price": "34.00", "currency": "USD", "kind": "subscription", "cadence": "Per Month", "active": true, "created_at": "2026-05-18T10:05:00+00:00", "billing": { "billing_cycle": "months", "billing_cycle_count": 1, "recurring": true, "trial_days": 14, "trial_cardless": false } }}How it fails
TEAM_TIER_REQUIREDthe change needs a tier the project owner lacks: a price of 0 without
VALIDATION_FAILEDone entry per offending field in error.context: a kind argument that
RESOURCE_NOT_FOUNDunknown plan_id, a plan on another team's project, one outside the
AUTHENTICATION_REQUIREDno authenticated user on the request.
TOKEN_MISSING_ABILITYtoken lacks project-subscription-plan:update.
Related
How is this guide?
Last updated on
start_next_season
Duplicate a finished pass series into its next season — a new inactive pass_series plan with the same settings, an empty slate, and the old season linked to it. Not idempotent.
Project Tools
A project is the container for one membership business: its plans, members, payment methods, resources and connectors.