Early bird discounts live! Claim your offer

create_plan

Create a subscription, a time-limited pass or a pass series on a project — one tool, tagged by kind.

Purpose

Create a plan on a project. Delegates to an Action that enforces resource-sync, the plan policy, and cache invalidation before a plan.created event fires.

Every plan has a kind, and the kind decides which one nested block you send with it:

kindSendSells
subscriptionbillingAccess that begins at payment and renews on a cycle. The default.
passpassOne dated access window per purchase.
pass_seriespass_seriesA slate of other pass plans' windows, sold once — a season ticket.

Sending the wrong block is refused

A pass carrying billing, or a subscription carrying pass_series, fails with VALIDATION_FAILED naming the offending key. It is not silently ignored — that would let you believe you had set a billing cycle on a pass, where a cycle means nothing.

The following rules are enforced — identical inputs are rejected by the dashboard too:

  • name is 5–255 chars and unique per project.
  • currency_id must be supported by an active payment method on the project.
  • price must be at least the $1.00 USD equivalent in the plan currency. 0 publishes a free plan, which only the Starter and Growth plans can sell; on Free the call is rejected with TEAM_TIER_REQUIRED.
  • eligibility.newcomers_only, eligibility.customers_only and eligibility.churned_only are mutually exclusive.
  • resources must link to at least one existing project resource — except on kind: pass_series, where it is optional. Each window in a series grants that window's own plan's resources, so a series with none still delivers what was sold; anything linked there is a lounge open for the whole span. Never pass a resource one of the slate's own windows opens: a lounge is granted at purchase with no window and kept all season, so the holder gets that channel permanently and the date it was scheduled for stops gating anything. Pass a holders-only room, or nothing.
  • kind: pass and kind: pass_series both require Time-Limited Passes — bundled with Growth, or the Passes Addon on Free and Starter. On any other tier the call is rejected.

Required ability

project-subscription-plan:create

Input schema

{
  "type": "object",
  "required": ["project_id", "kind", "name", "currency_id", "price"],
  "properties": {
    "project_id": {
      "type": "string",
      "description": "UUID of the parent project."
    },
    "kind": {
      "type": "string",
      "description": "subscription, pass or pass_series. Defaults to subscription. Decides which nested block is required."
    },
    "name": {
      "type": "string",
      "description": "Plan name (min 5, max 255 chars, unique per project)."
    },
    "currency_id": {
      "type": "string",
      "description": "UUID of the currency. Must be supported by an active payment method on the project."
    },
    "price": {
      "type": "number",
      "description": "What one purchase costs. On a pass this buys one window; on a pass_series it buys the whole slate, once."
    },
    "resources": {
      "type": "array",
      "description": "UUIDs of project resources. Required on subscription and pass; optional on pass_series, where it means a lounge."
    },
    "description": {
      "type": "string",
      "description": "Optional. Max 1000 chars, HTML filtered."
    },
    "active": {
      "type": "boolean",
      "description": "Defaults to true. Set false to create in draft state."
    },
    "sales_cap": {
      "type": "integer",
      "description": "Optional. Pause the plan automatically after this many successful purchases (1-100000); the creator publishes it again for another batch. Omit or null for no limit."
    },
    "eligibility": {
      "type": "object",
      "description": "{newcomers_only?, customers_only?, churned_only?, single_use?, access_codes_only?}"
    },
    "billing": {
      "type": "object",
      "description": "Required when kind=subscription, refused otherwise."
    },
    "pass": {
      "type": "object",
      "description": "Required when kind=pass, refused otherwise."
    },
    "pass_series": {
      "type": "object",
      "description": "Required when kind=pass_series, refused otherwise."
    }
  }
}

Output shape

{
  "data": {
    "id": "c4e82f16-93a7-4d5b-b81c-6e0f27a94d3b",
    "kind": "subscription",
    "name": "Premium Monthly",
    "project_id": "7f3d1c92-8b45-4e6a-9d21-5c8e0a4b6f13",
    "price": "29.00",
    "cadence": "Per Month",
    "active": true
  }
}

cadence is the human-readable duration string the portal and the bot show — "Per Month", "Per 3 Hours", "For all 10 passes". Use it rather than deriving one yourself.

kind: subscription

{
  "kind": "subscription",
  "billing": {
    "billing_cycle": "months",
    "billing_cycle_count": 1,
    "recurring": true,
    "trial_days": 7
  }
}
FieldTypeNotes
billing.billing_cyclestringdays, weeks, months, years, lifetime.
billing.billing_cycle_countinteger1–99. Forced to 1 when billing_cycle is lifetime.
billing.recurringbooleanRejected as true for crypto or platform currencies.
billing.disabled_renewalbooleanCharges once, then lapses.
billing.trial_daysinteger0–365.
billing.trial_cardlessbooleanWhether the trial starts without a payment method.
billing.trial_typestringA TrialModeType value.

kind: pass

Sells one scheduled access window per purchase. The plan owns its own windows and generates them from a recurrence.

{
  "kind": "pass",
  "pass": {
    "timezone": "America/New_York",
    "schedule_mode": "repeating",
    "recurrence": "weekly",
    "sales_cutoff_minutes": 60,
    "sales_cutoff_anchor": "before_start",
    "slots": [
      { "weekday": 4, "start_time": "19:00", "duration_minutes": 180 },
      { "weekday": 0, "start_time": "09:00", "duration_minutes": 840 }
    ]
  }
}
FieldTypeNotes
pass.timezonestringIANA zone, e.g. America/New_York. Slot times are local wall-clock in this zone and survive daylight saving. Legacy names are resolved, so Asia/Calcutta is stored as Asia/Kolkata. Required.
pass.schedule_modestringrepeating (generate from slots) or fixed. Defaults to repeating.
pass.recurrencestringdaily, weekly or monthly. Decides which slot fields apply.
pass.recurrence_ends_attimestampOptional. When window generation stops.
pass.sales_cutoff_minutesinteger | nullStop selling a window this many minutes before the moment sales_cutoff_anchor names. Omit to sell until the window begins.
pass.sales_cutoff_anchorstringbefore_start (default) closes sales before a window opens and nothing is sold once it is running. before_end keeps it on sale while it runs, so a buyer can join a session already in progress. before_end requires at least 5, must be under the shortest slot duration_minutes, and is refused when that shortest slot is 5 or less.
pass.slots[]arrayWindow definitions. Replaces the whole schedule on update.
pass.slots[].weekdayinteger | null06, 0 = Sunday. Weekly recurrence only.
pass.slots[].day_of_monthinteger | null131. Monthly only. Values of 29–31 skip months that lack the day.
pass.slots[].start_timestring HH:MMLocal wall-clock start in pass.timezone.
pass.slots[].duration_minutesintegerEach slot has its own, so one plan can mix a 3-hour and a 14-hour window.
pass.windows[]arrayExplicitly dated windows for fixed mode. Added, never replacing. Each: {starts_at, duration_minutes}.

Updating pass.slots rebuilds future windows. Windows a customer has already bought keep their original times and are never moved or deleted; only unsold future windows are regenerated.

kind: pass_series

Sells a curated slate of other pass plans' windows for one payment — a season ticket. It owns no windows of its own, which is the whole distinction from kind: pass.

Get the window ids first

pass_series.window_ids names windows that already exist. Call list_pass_windows to find them — there is no other way to obtain a window UUID, so a series cannot be authored without it.

{
  "kind": "pass_series",
  "pass_series": {
    "window_ids": ["3d5a8c72-b016-4e94-8fa7-61c209d4e738", "psw_01HY..."],
    "prevent_overlaps": true,
    "seat_cap": 50,
    "presale_hours": 48,
    "rules": [
      {
        "source_plan_id": "pln_01HZ...",
        "kind": "date_range",
        "from_at": "2026-09-01T00:00:00Z",
        "to_at": "2026-12-01T00:00:00Z"
      }
    ]
  }
}
FieldTypeNotes
pass_series.window_ids[]arrayPass-window UUIDs from list_pass_windows. Max 120. Needs at least two, unless a rule will supply them.
pass_series.rules[]arrayAutomatic inclusion. Max 20. See below.
pass_series.blackout_window_ids[]arrayWindows a rule matches but you want permanently excluded.
pass_series.prevent_overlapsbooleanDefaults true. Refuses to absorb a window clashing with one already on the slate.
pass_series.seat_capinteger | nullConcurrent holder limit. null is unlimited; 0 is refused — take a plan off sale with publish_plan instead.
pass_series.sales_cutoff_minutesintegerMeasured against the whole season.
pass_series.sales_cutoff_anchorstringbefore_start closes sales before the first window opens. before_first_end keeps the season on sale into that opening window and closes at least 5 minutes before it ends, so a latecomer is still admitted to it. before_end keeps the season on sale until the last window ends. All three charge full price for whatever remains. Series-only values are refused on a pass plan.
pass_series.successor_plan_idstring | nullAnother kind: pass_series plan on the same project. A series cannot lead to itself.
pass_series.presale_hoursinteger | null1–8760. How long the successor is held for this season's holders before general sale.

Rules keep working after the save

A rule describes windows rather than naming them, and it keeps matching: a window scheduled later is absorbed into the slate and granted to everyone already holding the series, at no extra charge. That is the behaviour handpicked window_ids deliberately do not have. They compose — most real seasons use both.

Rule kindTakes
date_rangeEvery window on source_plan_id starting between from_at and to_at. Either bound may be omitted.
next_nThe next take windows, then stops. Never tops itself back up — a window freeing up later does not make it reach for another.

take is required when kind is next_n. A count rule with no count is incomplete, not "all of them", and is refused.

source_plan_id must be a kind: pass plan on the same project.

Example prompts

"Create a $29/month Premium plan on project 7f3d1c92-8b45-4e6a-9d21-5c8e0a4b6f13 with a 7-day trial, linked to resource rsc_01HY...."

"Add a one-time 'Starter Access' plan priced at $49 with lifetime duration, linked to the Onboarding Pack resource."

"Create a weekly Sunday pass on project 7f3d1c92-8b45-4e6a-9d21-5c8e0a4b6f13 at $15, 9am New York, three hours long."

"Build a season ticket from the next ten Match Day windows and price it at $99 with 50 seats."

Failure modes

  • VALIDATION_FAILED — a nested block that does not match kind; missing resources on a kind that requires them; lifetime-count mismatch; mutual-exclusion violated; a next_n rule with no take; a slate under two windows; a seat cap of 0; a successor that is not another series; or the underlying Action policy rejecting (name collision, unsupported currency, recurring-vs-crypto conflict).
  • RESOURCE_NOT_FOUNDproject_id doesn't exist in your token's scope.
  • TOKEN_MISSING_ABILITY — token lacks project-subscription-plan:create.
  • TEAM_TIER_REQUIREDkind: pass or kind: pass_series without Time-Limited Passes, or a free plan on the Free tier.

How is this guide?

On this page

Subscriby is a product designed by you — for you.

No boardroom full of executives deciding what we ships next. Our roadmap always shaped by you with your feedback.

Share feedback or a request