Version
DESTRUCTIVE

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.

The token behind the MCP session must hold it, or the call is refused with TOKEN_MISSING_ABILITY.

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

DestructiveIdempotentOpen world

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*string

UUID of the plan to change.

namestringoptional

Plan name (min 5, max 255 chars, unique per project).

descriptionstringoptional

Plan description. Max 1000 chars, HTML is filtered. Pass an empty string to clear.

pricenumberoptional

What 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_idstringoptional

UUID of the currency. Must be supported by an active payment method on the project.

activebooleanoptional

true publishes the plan, false takes it off sale; existing subscriptions keep running either way.

sales_capintegeroptional

Pause the plan automatically after this many successful purchases (1-100000). Changing it restarts the count; null removes the limit.

eligibilityobjectoptional

Audience restrictions: {newcomers_only?: bool, customers_only?: bool, churned_only?: bool, single_use?: bool, access_codes_only?: bool}. The first three are mutually exclusive.

resourcesarrayoptional

The 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.

billingobjectoptional

Only 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.

passobjectoptional

Only 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_seriesobjectoptional

Only 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    }  }}

The full get_plan row, re-read after the write so a schedule or slate change is reflected in the block. price is a decimal string. The block present depends on the plan's kind; see get_plan for the pass and pass_series shapes.

How it fails

TEAM_TIER_REQUIRED

the change needs a tier the project owner lacks: a price of 0 without

VALIDATION_FAILED

one entry per offending field in error.context: a kind argument that

RESOURCE_NOT_FOUND

unknown plan_id, a plan on another team's project, one outside the

AUTHENTICATION_REQUIRED

no authenticated user on the request.

TOKEN_MISSING_ABILITY

token lacks project-subscription-plan:update.

How is this guide?

Last updated on