---
title: "Update a plan"
method: PATCH
path: "/v1/projects/{project}/plans/{plan}"
tags: ["Plans"]
---

# Update a plan

`PATCH /v1/projects/{project}/plans/{plan}`

## Request

- Base URL: `https://api.subscriby.net`
- URL: `https://api.subscriby.net/v1/projects/{project}/plans/{plan}`
- Auth: HTTP bearer

The same discriminated shape as create, with every field optional. Omit `kind` to keep the plan's current kind; a block you send must match it. Where a cross-field rule needs a value you did not send, the billing cycle behind a lifetime count check, the currency behind the price floor, the eligibility flags behind the exclusivity check, the plan's stored value stands in, so a partial update is validated against the plan it will actually produce.

- `resources` is optional; omitting it preserves the existing links, sending it replaces them.
- Every update of a plan on sale queues its push to the payment methods that keep a product catalogue, so a changed price or name reaches Stripe, PayPal, CoinPayments and Razorpay within a minute.
- `sales_cap` may be set, raised, lowered or cleared with `null`; any change restarts `sales_cap_sold` at `0`.
- `pass.slots` **replaces** the whole schedule and rebuilds unsold future windows; `pass.windows` **adds** dated windows and never replaces.
- Setting an active plan's `price` to `0` on a Free account is refused; editing a zero-priced plan that is **off sale** is allowed on any account.
- `sales_cap_sold`, `position` and `paused` are read-only.

Answers `200` with the whole plan after the change.

## Path parameters

- `project` string, uuid, required
- `plan` string, uuid, required

## Headers

- `Idempotency-Key` string, uuid, required

## Request body

- UpdatePlanRequest: The changes to a plan: any subset of the create shape. A block must match the plan's kind, and a field left out keeps its stored value.
  - `kind` 'subscription' | 'pass' | 'pass_series': What a purchase of a creator's plan actually buys. Plan shape had no discriminator before this. It was inferred from `pass_enabled` plus `recurring` plus `billing_cycle`, which is why a third shape kept falling through filters written as `where('pass_enabled', false)`: a boolean cannot express three things, so every query site had to remember which combination it meant and one of them always forgot. A `match` over this enum with no `default` is exhaustive, so a fourth shape later fails loudly at every site that has to care rather than silently taking whichever branch happened to be last. | | |---| | `subscription` <br/> Access starts at payment and runs on a billing cycle. | | `pass` <br/> One purchase buys one of the plan's own scheduled access windows. | | `pass_series` <br/> One purchase buys a curated slate of *other* plans' access windows. |
  - `name` string: The name buyers see, 5 to 255 characters, unique within the project without regard to case or surrounding spaces. Required on create.
  - `description` string, nullable: The pitch under the name, up to 1,000 characters. HTML is filtered to a safe subset before it is stored.
  - `currency_id` string: The id of a currency that at least one of the project's active payment methods can charge. Required on create.
  - `price` number: What one purchase costs: at least the $1.00 USD equivalent in `currency_id` at the current rate, or exactly `0` for a free plan, which only a Starter or Growth account may put on sale. Required on create.
  - `active` boolean: Whether the plan is on sale. Defaults to true on create; prefer the publish and unpublish endpoints to change it later.
  - `sales_cap` integer, nullable: Pause the plan after this many purchases, 1 to 100,000; null for no limit. Changing it restarts `sales_cap_sold` at 0.
  - `eligibility` object: Who may buy. At most one of the first three may be true, counting what the plan already holds on an update.
    - `newcomers_only` boolean: Only members who never held a subscription on the project.
    - `customers_only` boolean: Only members who currently hold one.
    - `churned_only` boolean: Only members who held one and let it lapse.
    - `single_use` boolean: Each member may buy the plan once.
    - `access_codes_only` boolean: The plan cannot be bought, only granted by redeeming an access code.
  - `resources` string[]: Ids of the project's resources the plan grants: at least one, required on create, except on `kind: pass_series`, where it is optional and names a lounge open for the whole span. Omit on an update to keep the existing links.
  - `billing` object: Cycle, trial and renewal. Send it on `kind: subscription` only; on any other kind it is refused.
    - `billing_cycle` 'day' | 'week' | 'month' | 'year' | 'lifetime': The unit a creator's own subscription plan renews in. Not the platform cycle: this is what a creator sells to their members, and it pairs with a count (`billing_cycle_count`) so "3 months" and "2 weeks" are both expressible. `Lifetime` is a one-time purchase that never renews.
    - `billing_cycle_count` integer: How many cycles one period spans, 1 to 99; must be 1 when the cycle is `lifetime`. Required on create.
    - `recurring` boolean: Whether the subscription renews itself. Refused as true when the currency is a crypto or platform currency.
    - `disabled_renewal` boolean: Charge once, then lapse at the end of the period instead of renewing.
    - `trial_days` integer: Free days before the first charge, 0 to 365.
    - `trial_cardless` boolean: Whether the trial starts without a payment method on file.
    - `trial_type` 'project' | 'plan'
  - `pass` object: The plan's own dated schedule. Send it on `kind: pass` only; on any other kind it is refused.
    - `timezone` 'GMT' | 'UTC' | 'Africa/Abidjan' | 'Africa/Accra' | 'Africa/Bamako' | 'Africa/Banjul' | 'Africa/Bissau' | 'Africa/Casablanca' | 'Africa/Conakry' | 'Africa/Dakar' | 'Africa/El_Aaiun' | 'Africa/Freetown' | 'Africa/Lome' | 'Africa/Monrovia' | 'Africa/Nouakchott' | 'Africa/Ouagadougou' | 'Africa/Sao_Tome' | 'Africa/Algiers' | 'Africa/Bangui' | 'Africa/Brazzaville' | 'Africa/Douala' | 'Africa/Kinshasa' | 'Africa/Lagos' | 'Africa/Libreville' | 'Africa/Luanda' | 'Africa/Malabo' | 'Africa/Ndjamena' | 'Africa/Niamey' | 'Africa/Porto-Novo' | 'Africa/Tunis' | 'Africa/Blantyre' | 'Africa/Bujumbura' | 'Africa/Ceuta' | 'Africa/Gaborone' | 'Africa/Harare' | 'Africa/Johannesburg' | 'Africa/Juba' | 'Africa/Khartoum' | 'Africa/Kigali' | 'Africa/Lubumbashi' | 'Africa/Lusaka' | 'Africa/Maputo' | 'Africa/Maseru' | 'Africa/Mbabane' | 'Africa/Tripoli' | 'Africa/Windhoek' | 'Africa/Addis_Ababa' | 'Africa/Asmara' | 'Africa/Cairo' | 'Africa/Dar_es_Salaam' | 'Africa/Djibouti' | 'Africa/Kampala' | 'Africa/Mogadishu' | 'Africa/Nairobi' | 'America/Adak' | 'America/Anchorage' | 'America/Juneau' | 'America/Metlakatla' | 'America/Nome' | 'America/Sitka' | 'America/Yakutat' | 'America/Creston' | 'America/Dawson' | 'America/Dawson_Creek' | 'America/Fort_Nelson' | 'America/Hermosillo' | 'America/Los_Angeles' | 'America/Mazatlan' | 'America/Phoenix' | 'America/Tijuana' | 'America/Vancouver' | 'America/Whitehorse' | 'America/Bahia_Banderas' | 'America/Belize' | 'America/Boise' | 'America/Cambridge_Bay' | 'America/Chihuahua' | 'America/Ciudad_Juarez' | 'America/Costa_Rica' | 'America/Denver' | 'America/Edmonton' | 'America/El_Salvador' | 'America/Guatemala' | 'America/Inuvik' | 'America/Managua' | 'America/Merida' | 'America/Mexico_City' | 'America/Monterrey' | 'America/Regina' | 'America/Swift_Current' | 'America/Tegucigalpa' | 'America/Atikokan' | 'America/Bogota' | 'America/Cancun' | 'America/Cayman' | 'America/Chicago' | 'America/Eirunepe' | 'America/Guayaquil' | 'America/Indiana/Knox' | 'America/Indiana/Tell_City' | 'America/Jamaica' | 'America/Lima' | 'America/Matamoros' | 'America/Menominee' | 'America/North_Dakota/Beulah' | 'America/North_Dakota/Center' | 'America/North_Dakota/New_Salem' | 'America/Ojinaga' | 'America/Panama' | 'America/Rankin_Inlet' | 'America/Resolute' | 'America/Rio_Branco' | 'America/Winnipeg' | 'America/Anguilla' | 'America/Antigua' | 'America/Aruba' | 'America/Barbados' | 'America/Blanc-Sablon' | 'America/Boa_Vista' | 'America/Campo_Grande' | 'America/Caracas' | 'America/Cuiaba' | 'America/Curacao' | 'America/Detroit' | 'America/Dominica' | 'America/Grand_Turk' | 'America/Grenada' | 'America/Guadeloupe' | 'America/Guyana' | 'America/Havana' | 'America/Indiana/Indianapolis' | 'America/Indiana/Marengo' | 'America/Indiana/Petersburg' | 'America/Indiana/Vevay' | 'America/Indiana/Vincennes' | 'America/Indiana/Winamac' | 'America/Iqaluit' | 'America/Kentucky/Louisville' | 'America/Kentucky/Monticello' | 'America/Kralendijk' | 'America/La_Paz' | 'America/Lower_Princes' | 'America/Manaus' | 'America/Marigot' | 'America/Martinique' | 'America/Montserrat' | 'America/Nassau' | 'America/New_York' | 'America/Port_of_Spain' | 'America/Port-au-Prince' | 'America/Porto_Velho' | 'America/Puerto_Rico' | 'America/Santo_Domingo' | 'America/St_Barthelemy' | 'America/St_Kitts' | 'America/St_Lucia' | 'America/St_Thomas' | 'America/St_Vincent' | 'America/Toronto' | 'America/Tortola' | 'America/Araguaina' | 'America/Argentina/Buenos_Aires' | 'America/Argentina/Catamarca' | 'America/Argentina/Cordoba' | 'America/Argentina/Jujuy' | 'America/Argentina/La_Rioja' | 'America/Argentina/Mendoza' | 'America/Argentina/Rio_Gallegos' | 'America/Argentina/Salta' | 'America/Argentina/San_Juan' | 'America/Argentina/San_Luis' | 'America/Argentina/Tucuman' | 'America/Argentina/Ushuaia' | 'America/Asuncion' | 'America/Bahia' | 'America/Belem' | 'America/Cayenne' | 'America/Coyhaique' | 'America/Fortaleza' | 'America/Glace_Bay' | 'America/Goose_Bay' | 'America/Halifax' | 'America/Maceio' | 'America/Moncton' | 'America/Montevideo' | 'America/Paramaribo' | 'America/Punta_Arenas' | 'America/Recife' | 'America/Santarem' | 'America/Santiago' | 'America/Sao_Paulo' | 'America/Thule' | 'America/St_Johns' | 'America/Miquelon' | 'America/Noronha' | 'America/Nuuk' | 'America/Scoresbysund' | 'America/Danmarkshavn' | 'Antarctica/Palmer' | 'Antarctica/Rothera' | 'Antarctica/Troll' | 'Antarctica/Syowa' | 'Antarctica/Mawson' | 'Antarctica/Vostok' | 'Antarctica/Davis' | 'Antarctica/Casey' | 'Antarctica/DumontDUrville' | 'Antarctica/Macquarie' | 'Antarctica/McMurdo' | 'Arctic/Longyearbyen' | 'Asia/Aden' | 'Asia/Amman' | 'Asia/Baghdad' | 'Asia/Bahrain' | 'Asia/Beirut' | 'Asia/Damascus' | 'Asia/Famagusta' | 'Asia/Gaza' | 'Asia/Hebron' | 'Asia/Jerusalem' | 'Asia/Kuwait' | 'Asia/Nicosia' | 'Asia/Qatar' | 'Asia/Riyadh' | 'Asia/Tehran' | 'Asia/Baku' | 'Asia/Dubai' | 'Asia/Muscat' | 'Asia/Tbilisi' | 'Asia/Yerevan' | 'Asia/Kabul' | 'Asia/Almaty' | 'Asia/Aqtau' | 'Asia/Aqtobe' | 'Asia/Ashgabat' | 'Asia/Atyrau' | 'Asia/Dushanbe' | 'Asia/Karachi' | 'Asia/Oral' | 'Asia/Qostanay' | 'Asia/Qyzylorda' | 'Asia/Samarkand' | 'Asia/Tashkent' | 'Asia/Yekaterinburg' | 'Asia/Colombo' | 'Asia/Kolkata' | 'Asia/Kathmandu' | 'Asia/Bishkek' | 'Asia/Dhaka' | 'Asia/Omsk' | 'Asia/Thimphu' | 'Asia/Urumqi' | 'Asia/Yangon' | 'Asia/Bangkok' | 'Asia/Barnaul' | 'Asia/Ho_Chi_Minh' | 'Asia/Hovd' | 'Asia/Jakarta' | 'Asia/Krasnoyarsk' | 'Asia/Novokuznetsk' | 'Asia/Novosibirsk' | 'Asia/Phnom_Penh' | 'Asia/Pontianak' | 'Asia/Tomsk' | 'Asia/Vientiane' | 'Asia/Brunei' | 'Asia/Hong_Kong' | 'Asia/Irkutsk' | 'Asia/Kuala_Lumpur' | 'Asia/Kuching' | 'Asia/Macau' | 'Asia/Makassar' | 'Asia/Manila' | 'Asia/Shanghai' | 'Asia/Singapore' | 'Asia/Taipei' | 'Asia/Ulaanbaatar' | 'Asia/Chita' | 'Asia/Dili' | 'Asia/Jayapura' | 'Asia/Khandyga' | 'Asia/Pyongyang' | 'Asia/Seoul' | 'Asia/Tokyo' | 'Asia/Yakutsk' | 'Asia/Ust-Nera' | 'Asia/Vladivostok' | 'Asia/Magadan' | 'Asia/Sakhalin' | 'Asia/Srednekolymsk' | 'Asia/Anadyr' | 'Asia/Kamchatka' | 'Atlantic/Bermuda' | 'Atlantic/Stanley' | 'Atlantic/South_Georgia' | 'Atlantic/Cape_Verde' | 'Atlantic/Azores' | 'Atlantic/Reykjavik' | 'Atlantic/St_Helena' | 'Atlantic/Canary' | 'Atlantic/Faroe' | 'Atlantic/Madeira' | 'Australia/Perth' | 'Australia/Eucla' | 'Australia/Darwin' | 'Australia/Brisbane' | 'Australia/Lindeman' | 'Australia/Adelaide' | 'Australia/Broken_Hill' | 'Australia/Hobart' | 'Australia/Lord_Howe' | 'Australia/Melbourne' | 'Australia/Sydney' | 'Europe/Dublin' | 'Europe/Guernsey' | 'Europe/Isle_of_Man' | 'Europe/Jersey' | 'Europe/Lisbon' | 'Europe/London' | 'Europe/Amsterdam' | 'Europe/Andorra' | 'Europe/Belgrade' | 'Europe/Berlin' | 'Europe/Bratislava' | 'Europe/Brussels' | 'Europe/Budapest' | 'Europe/Busingen' | 'Europe/Copenhagen' | 'Europe/Gibraltar' | 'Europe/Kaliningrad' | 'Europe/Ljubljana' | 'Europe/Luxembourg' | 'Europe/Madrid' | 'Europe/Malta' | 'Europe/Monaco' | 'Europe/Oslo' | 'Europe/Paris' | 'Europe/Podgorica' | 'Europe/Prague' | 'Europe/Rome' | 'Europe/San_Marino' | 'Europe/Sarajevo' | 'Europe/Skopje' | 'Europe/Stockholm' | 'Europe/Tirane' | 'Europe/Vaduz' | 'Europe/Vatican' | 'Europe/Vienna' | 'Europe/Warsaw' | 'Europe/Zagreb' | 'Europe/Zurich' | 'Europe/Athens' | 'Europe/Bucharest' | 'Europe/Chisinau' | 'Europe/Helsinki' | 'Europe/Istanbul' | 'Europe/Kirov' | 'Europe/Kyiv' | 'Europe/Mariehamn' | 'Europe/Minsk' | 'Europe/Moscow' | 'Europe/Riga' | 'Europe/Simferopol' | 'Europe/Sofia' | 'Europe/Tallinn' | 'Europe/Vilnius' | 'Europe/Volgograd' | 'Europe/Astrakhan' | 'Europe/Samara' | 'Europe/Saratov' | 'Europe/Ulyanovsk' | 'Indian/Antananarivo' | 'Indian/Comoro' | 'Indian/Mayotte' | 'Indian/Mahe' | 'Indian/Mauritius' | 'Indian/Reunion' | 'Indian/Kerguelen' | 'Indian/Maldives' | 'Indian/Chagos' | 'Indian/Cocos' | 'Indian/Christmas' | 'Pacific/Midway' | 'Pacific/Niue' | 'Pacific/Pago_Pago' | 'Pacific/Honolulu' | 'Pacific/Rarotonga' | 'Pacific/Tahiti' | 'Pacific/Marquesas' | 'Pacific/Gambier' | 'Pacific/Pitcairn' | 'Pacific/Galapagos' | 'Pacific/Easter' | 'Pacific/Palau' | 'Pacific/Chuuk' | 'Pacific/Guam' | 'Pacific/Port_Moresby' | 'Pacific/Saipan' | 'Pacific/Bougainville' | 'Pacific/Efate' | 'Pacific/Guadalcanal' | 'Pacific/Kosrae' | 'Pacific/Noumea' | 'Pacific/Pohnpei' | 'Pacific/Fiji' | 'Pacific/Funafuti' | 'Pacific/Kwajalein' | 'Pacific/Majuro' | 'Pacific/Nauru' | 'Pacific/Norfolk' | 'Pacific/Tarawa' | 'Pacific/Wake' | 'Pacific/Wallis' | 'Pacific/Apia' | 'Pacific/Auckland' | 'Pacific/Fakaofo' | 'Pacific/Kanton' | 'Pacific/Tongatapu' | 'Pacific/Chatham' | 'Pacific/Kiritimati' | 'null', nullable: The IANA zone the schedule is authored in. Legacy names are resolved, so `Asia/Calcutta` is stored as `Asia/Kolkata`. Required on create.
    - `schedule_mode` 'fixed' | 'repeating'
    - `recurrence` 'daily' | 'weekly' | 'monthly'
    - `recurrence_ends_at` string, date-time, nullable: When window generation stops; omit to keep generating.
    - `sales_cutoff_minutes` integer, nullable: Stop sales this many minutes before the moment `sales_cutoff_anchor` names; omit to sell until the window begins. With `before_end` it must be at least 5 and under the shortest slot's `duration_minutes`.
    - `sales_cutoff_anchor` 'before_start' | 'before_end' | 'null', nullable: `before_start` (the default) or `before_end`. `before_end` is refused when the shortest slot lasts 5 minutes or less. The series anchors `before_first_end` and `before_last_start` are refused on a pass.
    - `slots` object[]: The repeating schedule, at most 60 slots. Sending it replaces the whole schedule and rebuilds unsold future windows; windows a customer has bought keep their times.
      - `weekday` integer, nullable: Day of the week, 0 (Sunday) to 6, on a weekly recurrence.
      - `day_of_month` integer, nullable: Day of the month, 1 to 31, on a monthly recurrence; 29 to 31 skip the months that lack the day.
      - `start_time` string: Local wall-clock start in `pass.timezone`, `HH:MM`.
      - `duration_minutes` integer: How long the window stays open.
    - `windows` object[]: Explicitly dated windows for `schedule_mode: fixed`, at most 200 per call. Added to the schedule, never replacing it.
      - `starts_at` string, date-time: Local wall-clock start in `pass.timezone`.
      - `duration_minutes` integer: How long the window stays open.
  - `pass_series` object: The season ticket's slate, rules and seats. Send it on `kind: pass_series` only; on any other kind it is refused.
    - `prevent_overlaps` boolean: Refuse a window that clashes with one already on the slate. Defaults to true.
    - `sales_cutoff_minutes` integer, nullable: Stop sales this many minutes before the moment `sales_cutoff_anchor` names, measured against the whole season.
    - `sales_cutoff_anchor` 'before_start' | 'before_first_end' | 'before_last_start' | 'before_end': Which end of an access window the sales cutoff counts back from. This is more than a refinement of the cutoff number: it is the switch that decides whether a window can be bought once it has already opened. Anchoring to the start closes sales before anyone is let in, which is the only behaviour the product had. Anchoring to the end keeps the window on sale while it runs, so someone who notices a live session at 2 PM can buy in and be admitted straight away — with access still ending when that window closes, not later. A null column means the first-start anchor, so no plan authored before the anchor existed changes behaviour. Declaration order is the order every picker offers them in, because the two selects render `forSeries()` and `forPass()` directly. They therefore run from the earliest deadline to the latest — before it opens, before the first date ends, before the last one does — and a case declared out of that order shows up jumbled on screen while every test still passes. The case NAMES say first or last; the backing values do not. `before_start` and `before_end` predate the middle anchor, and they are written into rows on two tables, into a CHECK constraint, into the REST and MCP contracts and into published Zapier and n8n integrations. Renaming the values would break every one of those for a cosmetic gain, so only the PHP names were corrected. | | |---| | `before_start` <br/> Sales close a set number of minutes before the window opens. Nothing is sold once it has started. | | `before_first_end` <br/> Series only. Sales stay open into the opening session and close a set number of minutes before that FIRST date ends. The middle ground between the other two, and the one a season ticket actually wants: latecomers can still join on opening night, but once the first session is over the season is closed rather than being sold to someone who has already missed part of what they are paying for. Meaningless for a single window, where "before the first date ends" is simply {@see self::BeforeLastEnd} — which is why {@see self::forPass()} withholds it rather than the pickers filtering by hand. | | `before_last_start` <br/> Series only. Sales close a set number of minutes before the LAST date opens, so the season is buyable until its closing session begins. The widest anchor that still guarantees a buyer a whole session. Where {@see self::BeforeLastEnd} can sell a season with minutes of it left, this one stops while there is a complete date still to come — the difference between selling the remainder and selling the fag end of it. Meaningless for a single window, where the only date is both first and last, so this collapses into {@see self::BeforeFirstStart}. | | `before_end` <br/> Sales stay open through the window and close a set number of minutes before it ends. |
    - `seat_cap` integer, nullable: How many holders may hold the season at once, at least 1; null for unlimited. Zero is refused: take the plan off sale with unpublish instead.
    - `presale_hours` integer, nullable: How long the next season is offered to current holders only, 1 to 8760.
    - `successor_plan_id` string, nullable: Another `kind: pass_series` plan on the same project to offer holders when this one finishes. A series cannot lead to itself.
    - `window_ids` string[]: Ids of pass windows on this project to put on the slate, at most 120. A slate needs at least two unless a rule will supply them; with `prevent_overlaps` on, two that run at the same time are refused.
    - `blackout_window_ids` string[]: Windows a rule matches but you want permanently excluded.
    - `rules` object[]: Rules that keep absorbing matching windows into the slate, at most 20. A matching window scheduled later is added and granted to every current holder at no charge.
      - `source_plan_id` string: A `kind: pass` plan on this project to draw windows from.
      - `kind` 'date_range' | 'next_n': How an auto-include rule decides which dates to pull into a series. Rules exist because a weekly pass plan materialises its windows on a rolling horizon: a creator selling "every Sunday until December" would otherwise have to come back and hand-pick each new date as it appears, on a series people have already bought. | | |---| | `date_range` <br/> Every window of the source plan that starts between two dates. | | `next_n` <br/> The next N windows of the source plan, whenever they fall. |
      - `from_at` string, date-time, nullable: The earliest window start a `date_range` rule takes.
      - `to_at` string, date-time, nullable: The latest window start a `date_range` rule takes.
      - `take` integer, nullable: How many windows a `next_n` rule takes, 1 to 120; required when `kind` is `next_n`.

## Response `200`

The plan after the change.

- object
  - `data` PlanResource, required: A plan: what one purchase buys. `kind` names exactly one of `billing`, `pass` or `pass_series`; the other two are absent, not null.
    - `id` string, required: The plan's id.
    - `project_id` string, required: The project the plan belongs to.
    - `kind` string, required: `subscription`, `pass` or `pass_series`. Names the one block that accompanies it; the other two are absent, not null.
    - `name` string, required: The name buyers see, unique within the project without regard to case or surrounding spaces.
    - `description` string, nullable, required: The pitch under the name, HTML filtered to a safe subset; null when none was written.
    - `price` string, required: What one purchase costs, as a decimal string in `currency`. On a pass that buys one window; on a series it buys the whole slate, once. `"0.00"` is a free plan.
    - `currency` object, required: The currency the price is charged in.
      - `id` string, required: The currency's id, the value `currency_id` takes on a write.
      - `iso` string, required: The ISO 4217 code, such as `USD`.
      - `symbol` string, required: The symbol to print before the amount.
    - `active` boolean, required: Whether the plan is on sale. Flip it with the publish and unpublish endpoints, not with an update.
    - `sales_cap` integer, nullable, required: How many purchases the plan takes before pausing itself; null for no limit.
    - `sales_cap_sold` integer, required: Purchases counted against the cap since it was last changed or the plan was last published.
    - `position` integer, nullable, required: The plan's pinned place in the storefront order; null while it follows the built-in order (passes, then seasons, then subscriptions, cheapest first).
    - `paused` object, nullable, required: Why the plan took itself off sale; null while it is not paused.
      - `reason` string, required: `sales_cap_reached` when the cap filled, `seat_cap_reached` when a season filled its last seat.
      - `at` string, nullable, required: When it paused, ISO 8601.
    - `cadence` string, required: The duration in words the portal and the bot print, such as `Per Month`, `Per 3 Hours` or `For all 10 passes`. Use it rather than deriving one from the cycle fields.
    - `eligibility` object, required: Who may buy. The first three are mutually exclusive.
      - `newcomers_only` boolean, required: Only members who never held a subscription on the project.
      - `customers_only` boolean, required: Only members who currently hold one.
      - `churned_only` boolean, required: Only members who held one and let it lapse.
      - `single_use` boolean, required: Each member may buy the plan once.
      - `access_codes_only` boolean, required: The plan cannot be bought; it is granted by redeeming an access code.
    - `resources` object[]: The places the plan grants, present when the endpoint loaded them. On a series these are the lounge, open for the whole span, not the thing being sold.
      - `id` string, required: The resource's id.
      - `name` string, required: The resource's title.
      - `kind` string, required: `manual` for a perk the creator hands over themselves, or `<connector>:<kind>` for a place a connector hosts.
      - `connector` string, nullable, required: The connector that hosts the place; null for a manual resource.
    - `billing` object: Cycle, trial and renewal. Present only on `kind: subscription`.
      - `billing_cycle` union, required: `day`, `week`, `month`, `year` or `lifetime`.
        - string
        - 'day' | 'week' | 'month' | 'year' | 'lifetime': The unit a creator's own subscription plan renews in. Not the platform cycle: this is what a creator sells to their members, and it pairs with a count (`billing_cycle_count`) so "3 months" and "2 weeks" are both expressible. `Lifetime` is a one-time purchase that never renews.
      - `billing_cycle_count` integer, required: How many cycles one period spans, 1 to 99; always 1 when the cycle is `lifetime`.
      - `recurring` boolean, required: Whether the subscription renews itself. Never true for a crypto or platform currency.
      - `disabled_renewal` boolean, required: Charges once, then lapses at the end of the period instead of renewing.
      - `trial_days` integer, required: Free days before the first charge, 0 to 365.
      - `trial_cardless` boolean, required: Whether the trial starts without a payment method on file.
      - `trial_type` union, required: Whose trial rule applies: `project` for the project's trial settings, `plan` for this plan's own.
        - string
        - 'project' | 'plan'
    - `pass` object: The plan's own dated schedule. Present only on `kind: pass`.
      - `timezone` string, nullable, required: The IANA zone the schedule was authored in. Render window times in it, never in UTC.
      - `schedule_mode` string, nullable, required: `repeating` for windows generated from `slots`, `fixed` for explicitly dated windows.
      - `recurrence` string, nullable, required: `daily`, `weekly` or `monthly`; null in `fixed` mode.
      - `recurrence_ends_at` string, nullable, required: When window generation stops, ISO 8601; null for no end.
      - `sales_cutoff_minutes` integer, nullable, required: Sales stop this many minutes before the moment `sales_cutoff_anchor` names; null to sell until the window begins.
      - `sales_cutoff_anchor` string, required: `before_start` or `before_end`. Never null: a plan that never set one reads as `before_start`.
      - `slots` object[]: The repeating schedule, present when the endpoint loaded it. Each slot carries its own length, so one plan can mix a 3-hour and a 14-hour window.
        - `weekday` integer, nullable, required: Day of the week, 0 (Sunday) to 6, on a weekly recurrence; null otherwise.
        - `day_of_month` integer, nullable, required: Day of the month, 1 to 31, on a monthly recurrence; null otherwise. 29 to 31 skip the months that lack the day.
        - `start_time` string, required: Local wall-clock start in `timezone`, `HH:MM`.
        - `duration_minutes` integer, required: How long the window stays open.
      - `upcoming_windows` object[]: The next windows, present only where the endpoint loaded them. Timestamps are UTC.
        - `id` string, required: The window's id, the value a series names in `pass_series.window_ids`.
        - `starts_at` string, required: When the window opens, UTC.
        - `ends_at` string, required: When it closes, UTC.
        - `status` string, required: `scheduled`, `open`, `closed` or `canceled`.
    - `pass_series` union: The season ticket's slate, the rules that grow it and its seats. Present only on `kind: pass_series`.
      - object
        - `timezone` string, required: Borrowed from the source plans; a series has no zone of its own. Null while the slate is empty.
        - `prevent_overlaps` boolean, required: Whether a window clashing with one already on the slate is refused.
        - `sales_cutoff_minutes` integer, required: Sales stop this many minutes before the moment `sales_cutoff_anchor` names, measured against the whole season.
        - `sales_cutoff_anchor` string, required: Earliest deadline first: `before_start` closes before the first window opens, `before_first_end` during that opening window, `before_last_start` as the last window opens, `before_end` as it ends.
        - `seat_cap` integer, nullable, required: How many holders may hold the season at once; null for unlimited.
        - `seats_taken` integer, required: Holders currently counted against the cap.
        - `seats_remaining` integer, nullable, required: Seats still open; null when uncapped.
        - `starts_at` string, nullable, required: The first window's start, UTC; null while the slate is empty.
        - `ends_at` string, nullable, required: The last window's end, UTC; null while the slate is empty.
        - `window_count` integer, required: How many windows the slate holds, at most 120.
        - `successor_plan_id` string, nullable, required: The next season, offered to holders first when this one finishes; null when none is set.
        - `presale_hours` integer, nullable, required: How long that offer is held for holders only, 1 to 8760; null when none is set.
        - `windows` object[], required: The slate. Each entry names the plan the window belongs to, because a series can mix several.
          - `id` string, required: The window's id.
          - `plan_id` string, required: The pass plan the window belongs to.
          - `plan_name` string, required: That plan's name.
          - `starts_at` string, required: When the window opens, UTC.
          - `ends_at` string, required: When it closes, UTC.
          - `status` string, required: `scheduled`, `open`, `closed` or `canceled`.
          - `added_by_rule` boolean, required: True when a rule absorbed it rather than the creator picking it by hand.
        - `rules` object[], required: The rules that keep absorbing matching windows into the slate.
          - `source_plan_id` string, required: The pass plan the rule draws windows from.
          - `kind` string, required: `date_range` takes every window between `from_at` and `to_at`; `next_n` takes the next `take` windows.
          - `from_at` string, nullable, required: The earliest window start the rule takes, UTC; null for no bound.
          - `to_at` string, nullable, required: The latest window start the rule takes, UTC; null for no bound.
          - `take` integer, nullable, required: How many windows a `next_n` rule takes; null on a `date_range` rule.
        - `blackout_window_ids` object, required: Windows a rule matches but the creator has permanently excluded.
      - object
        - `timezone` unknown, required
        - `prevent_overlaps` boolean, required: Whether a window clashing with one already on the slate is refused.
        - `sales_cutoff_minutes` integer, required: Sales stop this many minutes before the moment `sales_cutoff_anchor` names, measured against the whole season.
        - `sales_cutoff_anchor` string, required: Earliest deadline first: `before_start` closes before the first window opens, `before_first_end` during that opening window, `before_last_start` as the last window opens, `before_end` as it ends.
        - `seat_cap` unknown, required
        - `seats_taken` integer, required: Holders currently counted against the cap.
        - `seats_remaining` unknown, required
        - `starts_at` unknown, required
        - `ends_at` unknown, required
        - `window_count` integer, required: How many windows the slate holds, at most 120.
        - `successor_plan_id` unknown, required
        - `presale_hours` unknown, required
        - `windows` string[], required: The slate. Each entry names the plan the window belongs to, because a series can mix several.
        - `rules` string[], required: The rules that keep absorbing matching windows into the slate.
        - `blackout_window_ids` string[], required: Windows a rule matches but the creator has permanently excluded.
    - `created_at` string, nullable, required: When the plan was created, ISO 8601.
    - `updated_at` string, nullable, required: When it last changed, ISO 8601.

## Other responses

- `400`: `IDEMPOTENCY_KEY_MISSING`: every write needs an `Idempotency-Key` header. Send a fresh UUID per distinct operation.
- `401`: `AUTHENTICATION_REQUIRED`: the request carries no bearer token, or one that is revoked, malformed, or minted for another environment (an `sbt_test_` token on production).
- `403`: `TOKEN_MISSING_ABILITY`: the token is valid but does not carry the ability this endpoint requires; `error.context.required_ability` names the one to grant. An endpoint that also checks who owns a row or which tier the account is on answers `FORBIDDEN`, `TEAM_TIER_REQUIRED` or `CONNECTOR_TIER_REQUIRED` with the same status, and says so in its own description. On this endpoint: `TEAM_TIER_REQUIRED`: when the change needs an entitlement the project owner's account lacks, such as passes on a plan without them.
- `404`: `RESOURCE_NOT_FOUND`: an id in the path names nothing the token can see. `TENANT_MISMATCH`: the project sits outside the token's `scope:project:` allow-list, or the token carries no team scope. Both answer 404 rather than 403 so that existence outside the token's scope cannot be inferred.
- `409`: `IDEMPOTENCY_KEY_REUSED`: the key was already used in the last 24 hours with a different request body.
- `422`: `VALIDATION_FAILED`: the payload broke a rule, and `error.fields` maps each offending key to its messages. A refusal from the domain, such as a plan that cannot go on sale or a member who cannot be removed, uses the same code with `error.message` saying why and no `fields`. On this endpoint: `VALIDATION_FAILED`: when a block does not match the plan's kind, `name` is taken by another live plan, `price` is below the minimum or is `0` on an active plan of a Free account, two eligibility flags end up set, or a per-kind rule is broken; `error.fields` names the key.
- `425`: `IDEMPOTENCY_REPLAY_IN_PROGRESS`: the first request with this key is still running; retry in a few seconds and the original response is replayed.
- `429`: `RATE_LIMITED`: the token has spent its 300 requests a minute or 10,000 an hour; `Retry-After` says when the next one is accepted.

---

[API](https://skmtc.dev/memberpass/apis/subscriby-rest-api.md) · [All operations](https://skmtc.dev/memberpass/apis/subscriby-rest-api/llms.txt) · [OpenAPI document](https://skmtc.dev/memberpass/apis/subscriby-rest-api/revisions/59c4df91d764?raw)
