Create a plan
/v1/projects/{project}/plans in the Plans API.
POST takes the same shape it returns: kind plus the one matching block. Sending a block that does not match the kind is refused with VALIDATION_FAILED naming the offending key; silently ignoring it would let a caller believe they had set a billing cycle on a pass. Answers 201 with the plan in the same discriminated shape the read endpoint shows for each kind. A plan created on sale (active: true, the default) is queued for the push to the payment methods that keep a product catalogue (Stripe, PayPal, CoinPayments, Razorpay), so it is sellable through them within a minute; a draft is pushed when it is published.
Create a subscription
curl -X POST https://api.subscriby.net/v1/projects/$PROJECT_ID/plans \ -H "Authorization: Bearer $SUBSCRIBY_TOKEN" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "kind": "subscription", "name": "Premium Monthly", "currency_id": "8f27a0d4-63be-4915-8c07-1a5d9e34b628", "price": 29.00, "resources": ["b73c5f21-9d80-4a6e-8215-4f70ce13a9d6"], "billing": { "billing_cycle": "month", "billing_cycle_count": 1, "trial_days": 7 } }'Create a pass
curl -X POST https://api.subscriby.net/v1/projects/$PROJECT_ID/plans \ -H "Authorization: Bearer $SUBSCRIBY_TOKEN" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "kind": "pass", "name": "Sunday Slate Pass", "currency_id": "8f27a0d4-63be-4915-8c07-1a5d9e34b628", "price": 15.00, "resources": ["b73c5f21-9d80-4a6e-8215-4f70ce13a9d6"], "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 } ] } }'Sending pass.slots replaces the whole schedule and 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. pass.windows is different: it adds explicitly dated windows for schedule_mode: fixed and never replaces anything.
Create a pass series
A series needs window ids, and those come from windows that already exist. Read them from pass.upcoming_windows on the source plan, from the pass windows endpoints, or from the list_pass_windows MCP tool.
curl -X POST https://api.subscriby.net/v1/projects/$PROJECT_ID/plans \ -H "Authorization: Bearer $SUBSCRIBY_TOKEN" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "kind": "pass_series", "name": "Autumn Season Ticket", "currency_id": "8f27a0d4-63be-4915-8c07-1a5d9e34b628", "price": 99.00, "pass_series": { "prevent_overlaps": true, "seat_cap": 50, "presale_hours": 48, "window_ids": ["3d5a8c72-b016-4e94-8fa7-61c209d4e738", "9c1f4e27-5a8b-4d63-b2e0-8f7a6c5d4e31"], "rules": [ { "source_plan_id": "0b8e6a2f-4c1d-4e3a-9f52-7d6c1b2a3e45", "kind": "date_range", "from_at": "2026-09-01T00:00:00Z", "to_at": "2026-12-01T00:00:00Z" } ] } }'
resourcesis optional on a series only. Every other kind must link at least one resource, or a purchase buys nothing. A series is the exception: each of its windows grants that window's own plan's resources, so a series with none still delivers exactly what was sold. Anything you do link here is a lounge, open for the whole span.
Never put a slate window's resource in a series
resources. A lounge is granted at purchase with no window and kept until the season ends. Send a resource that one of your slate windows already opens and every holder is handed it permanently the moment they pay: the dates it was scheduled for stop gating anything, and a season ticket becomes a permanent key to that channel. The API accepts it, because a genuinely permanent room is a legitimate thing to sell. It is simply almost never what was meant. Keepresourcesfor a holders-only room nothing on the slate opens; the slate's own channels are already granted by their own pass plans on their own dates. The dashboard warns when the two overlap. Over the API, checking is yours to do.
Rules keep working after the save: a matching window scheduled later is absorbed into the slate and granted to everyone already holding the series, at no charge. That emits pass_series.leg_added. Handpicked window_ids never grow on their own. The two compose; most real seasons use both.
Validation
The field-by-field rules are on each request field below. The rules that cross fields:
namemust be unique within the project, compared without regard to case or surrounding spaces; a duplicate is refused onname.pricemust be at least the $1.00 USD equivalent incurrency_id, converted at the current rate. Payment providers reject dust amounts, so anything below that is unbuyable and is refused; the error names the minimum in both the plan's currency and USD.priceof exactly0publishes a free plan. Plans priced at zero can only be sold by a Starter or Growth account, never on Free: the platform fee is a share of what you charge, so a zero-priced plan earns nothing to share. On Free the call is refused onprice. The rule enforced is that a plan may not be simultaneously active and priced at0on a creator whose plan cannot sell one, so an update that leaves a zero-priced plan off sale is allowed.currency_idmust be supported by at least one active payment method on the project.- At most one of
eligibility.newcomers_only,eligibility.customers_onlyandeligibility.churned_onlymay betrue. resourcesis required with at least one id, except onkind: pass_series, where it is optional and means a lounge.billing.billing_cycle_countmust be1whenbilling.billing_cycleislifetime;billing.recurringis refused astruewhen the currency is a crypto or platform currency.pass.sales_cutoff_anchor: before_endrequirespass.sales_cutoff_minutesof at least5and under the shortest slot'sduration_minutes, and is refused outright when that shortest slot is5minutes or less.before_first_endandbefore_last_startare series-only anchors and are rejected on a pass plan, where a lone window is both the first and the last.pass_series.window_idsneeds at least two windows unless a rule will supply them; every id must belong to a pass plan on this project; withprevent_overlapson, two windows that run at the same time are refused.pass_series.rules[].takeis required whenkindisnext_n, because a count rule with no count is incomplete rather than "all of them".pass_series.successor_plan_idmust be anotherkind: pass_seriesplan on the same project.
Requires ability
The token must hold this ability, or the call is refused with 403.
Fires one event
Delivered to every endpoint subscribed to it once the change is made.
MCP tool
Runs the same action from an agent, behind the same ability.
Idempotent
Send the header on every call; the same key replays the original response for 24 hours.
Authorization
bearerToken A personal access token minted on the dashboard under Settings, then Tokens, sent as Authorization: Bearer sbt_live_…. The token carries the abilities each endpoint lists under Requires ability and is frozen to one team.
In: header
Path Parameters
The project, resolved by the route binder.
uuidHeader Parameters
A key unique to this operation, such as a fresh UUID. The same key replays the original 2xx response for 24 hours (with Idempotent-Replay: true), so a retry after a timeout never repeats the write; the same key with a different body is refused with 409.
uuidRequest body
JSONWhat the request carriesRequiredapplication/json
A plan to create: kind plus the one block it names, in the shape a plan is read back in.
Responses
201Createdapplication/json
The new plan.
400Bad requestIDEMPOTENCY_KEY_MISSINGapplication/json
Every write needs an Idempotency-Key header. Send a fresh UUID per distinct operation.
401UnauthorizedAUTHENTICATION_REQUIREDapplication/json
The request carries no bearer token, or one that is revoked, malformed, or minted for another environment (an sbt_test_ token on production).
403ForbiddenTOKEN_MISSING_ABILITYapplication/json
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 kind is pass or pass_series and the project owner's account lacks Time-Limited Passes, bundled with Growth or available as the Passes add-on.
404Not foundRESOURCE_NOT_FOUNDapplication/json
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.
409ConflictIDEMPOTENCY_KEY_REUSEDapplication/json
The key was already used in the last 24 hours with a different request body.
422Validation failedVALIDATION_FAILEDapplication/json
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 kind, name is taken, price is below the minimum or is 0 on a Free account, currency_id has no active payment method, two eligibility flags are set, or a per-kind rule above is broken; error.fields names the key.
425Too earlyIDEMPOTENCY_REPLAY_IN_PROGRESSapplication/json
The first request with this key is still running; retry in a few seconds and the original response is replayed.
429Too many requestsRATE_LIMITEDapplication/json
The token has spent its 300 requests a minute or 10,000 an hour; Retry-After says when the next one is accepted.
How is this guide?
Last updated on