---
title: "Start the next season"
method: POST
path: "/v1/projects/{project}/plans/{plan}/successor"
tags: ["Plans"]
---

# Start the next season

`POST /v1/projects/{project}/plans/{plan}/successor`

## Request

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

```bash
curl -X POST https://api.subscriby.net/v1/projects/7f3d1c92-8b45-4e6a-9d21-5c8e0a4b6f13/plans/$SERIES_PLAN/successor \
  -H "Authorization: Bearer sbt_..." \
  -H "Idempotency-Key: $(uuidgen)"
```

The plan list's "start next season" button. For a `kind: pass_series` plan only: creates a **new** `pass_series` plan copied from the one named, its price, currency, description, eligibility, linked resources, overlap rule, sales cutoff and seat cap, with two deliberate differences: it is created **inactive**, and its slate is **empty**, because a season that went straight on sale with last year's dates would be selling something that has already happened. Rules are carried forward with their date bounds shifted by the length of the finished season; the slate itself and any blackouts are not, since both name windows that have run.

The old season's `successor_plan_id` is pointed at the new plan (and a presale window is set if the old season had none), which is what lets current holders be offered the next season first. Answers `201` with the new plan in the same discriminated shape as any other read and emits `plan.created` for it. Compose it with a `PATCH` (`pass_series.window_ids` or `pass_series.rules`) and put it on sale with publish.

> **Not idempotent across calls. Call it once per season.** Every call creates another plan, named after the source with a season number appended, and re-points the old season's successor at the newest one. Read the source plan first: if `pass_series.successor_plan_id` is already set, the next season exists. The `Idempotency-Key` protects a retry of the same call, not a second call with a new key.

## Path parameters

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

## Headers

- `Idempotency-Key` string, uuid, required

## Response `201`

The new season, 201, inactive with an empty slate.

- 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.
- `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`: when the plan is not a pass series; `error.context.plan_id` names it.
- `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)
