Skip to navigation

Manage Plan API

Programmatically manage a company via the Manage Plan API.

The Manage Plan API lets you programmatically set a company’s full plan state, including the base plan, add-ons, credit bundles, and pay-in-advance entitlements. It handles both Stripe-linked and non-billing plans in a single call.

This is a replace-style (PUT-like) API, not a patch. The request body represents the desired end state of the company’s plan configuration.

Anything you don’t include will be removed. That means:

  • An add-on omitted from add_on_selections will be removed from the company.
  • A pay-in-advance entitlement (e.g. user seats) omitted from pay_in_advance_entitlements will be removed, dropping the company’s quantity to zero.
  • A base plan omitted (or set to null) will be cleared, falling back to the default plan if one is configured.

When upgrading or making any change, you almost always want to read the company’s current state first and re-send the existing add-ons and pay-in-advance entitlements alongside whatever you’re changing. See Common patterns for examples.

Credit bundles are the exception. Unlike the other fields, credit_bundles is not part of the replace-style behavior. Any bundles you include are purchased immediately, added to the company’s credit ledger, and persist through future plan changes. Omitting them in a later request does not remove previously purchased credits. To send a request that doesn’t purchase any new credits, pass an empty array.

Endpoints

EndpointPurpose
POST /manage-planApply a plan change
POST /manage-plan/previewPreview the financial impact of a plan change without applying it. Useful for custom checkout scenarios.
POST /manage-plan/subscription/cancelCancel a company’s active subscription

All endpoints require API key authentication.

How it works

When you call POST /manage-plan, you provide the complete desired plan state for a company. Schematic then:

  1. Determines which plans are billing-linked (connected to Stripe) and which are not
  2. For billing-linked plans, updates the Stripe subscription accordingly
  3. For non-billing plans, applies the assignment directly
  4. Returns the updated company object

Because this is a replace-style API, you must always include the full desired state. The two fields most commonly missed:

  • add_on_selections replaces the whole set. If a company currently has add-ons A and B and you want to add C, include all three, because sending only C removes A and B.
  • pay_in_advance_entitlements works the same way. If a company has 10 seats today and you make any plan change, include { price_id, quantity: 10 } in this array, or their seat count drops to zero.

The safest pattern is: fetch the company’s current state, build the request from it, then mutate only what you intend to change.

Plan version behavior

When specifying a base plan or add-on:

  • If you provide an explicit version_id, that version is used
  • If the company is already on that plan and you omit the version, the company’s current version is preserved
  • If it’s a new plan and you omit the version, the latest published version is used

Default (fallback) plan

If you clear all plans from a company (by omitting base_plan_id), Schematic will assign the configured default/fallback plan if one exists.

Proration

When changing plans on a billing-linked subscription, Schematic uses Stripe’s proration logic to refund the unused portion of the current plan’s base cost and charge the prorated cost of the new plan. The same applies to add-on changes, credit bundles, and pay-in-advance quantity changes.

Upgrade vs. downgrade behavior differs:

  • Upgrades keep the current billing period (e.g. if you’re 15 days in, you stay on day 15 after the upgrade), and accrued usage on usage-based features carries over to the new plan
  • Downgrades reset the billing period (the new plan starts on day 1), which also resets usage counters for usage-based features

If your Schematic catalog has scheduled downgrades enabled, downgrades are deferred to the end of the current billing period instead of being applied immediately, and no proration is applied.

Use POST /manage-plan/preview to see the exact prorated charges and credits before applying a change.

Manage Plan

POST /manage-plan

Request body

{
"company_id": "comp_abc123",
"base_plan_id": "plan_xyz789",
"base_plan_price_id": "bprice_def456",
"base_plan_version_id": "planv_ghi012",
"add_on_selections": [
{
"plan_id": "plan_addon1",
"price_id": "bprice_jkl345",
"version_id": "planv_mno678"
}
],
"credit_bundles": [
{
"bundle_id": "bundle_pqr901",
"quantity": 5
}
],
"pay_in_advance_entitlements": [
{
"price_id": "bprice_stu234",
"quantity": 10
}
],
"coupon_external_id": "DISCOUNT20",
"promo_code": "LAUNCH2024",
"payment_method_external_id": "pm_stripe_abc",
"billing_entity_id": "comp_vwx567",
"trial_end": "2024-12-31T23:59:59Z"
}

Field reference

company_id
stringRequired

The ID of the company whose plan you want to manage.

base_plan_id
string

The plan to assign as the company’s base plan. Omit or set to null to clear the base plan (the company will fall back to the default plan if one is configured).

base_plan_price_id
string

The specific price to use for the base plan. Required for billing-linked plans with multiple price options (e.g. monthly vs. yearly).

base_plan_version_id
string

A specific version of the base plan. If omitted, the company’s current version is preserved (same plan) or the latest published version is used (new plan).

add_on_selections
array

The complete list of add-ons the company should have. Each entry requires a plan_id. Billing-linked add-ons also require a price_id. An optional version_id can pin a specific version.

This is replace-style. Any add-on the company currently has that is not included in this array will be removed. To preserve existing add-ons across a plan change, you must re-send them here.

credit_bundles
array

Credit bundles to purchase as part of this request. Each entry requires a bundle_id and quantity.

Unlike the other fields, this is not replace-style: bundles listed here are charged immediately and added to the company’s credit ledger. Previously purchased credits are not removed if you omit them. Pass an empty array to make a request that doesn’t purchase new credits.

pay_in_advance_entitlements
array

Pay-in-advance quantities (e.g. user seats). Each entry requires a price_id and quantity.

This is replace-style. Any pay-in-advance entitlement not included in this array will be set to zero. If a company currently has 10 seats and you want to keep them, you must re-send { price_id, quantity: 10 } on every request, even if you’re only changing something else (like the base plan or an add-on).

coupon_external_id
string

A Stripe coupon ID to apply to the subscription. On an existing subscription, omitting this field keeps whatever discount the subscription already has, and sending one replaces the subscription’s discounts with what you send in this request.

promo_code
string

A Stripe promotion code to apply to the subscription. On an existing subscription, omitting this field keeps whatever discount the subscription already has, and sending one replaces the subscription’s discounts with what you send in this request.

payment_method_external_id
string

A Stripe payment method ID to use for this subscription.

billing_entity_id
string

The company that pays for this subscription, when it is different from company_id. The billing entity must already have a Stripe customer. Only honored when starting a new subscription; sending it for a company that already has an active subscription returns a 400. See Billing Entities.

trial_end
datetime

An ISO 8601 timestamp for when the trial should end. Must be in the future. To be eligible for a trial the plan must be marked as trialable, and cannot contain pay-as-you-go, overage, or tiered entitlements, billed credit grants, or credit grants that top up or bill in arrears. Pay-in-advance entitlements are allowed, and their pay_in_advance quantities bill nothing until the trial ends. Additionally, new add-ons cannot be added when putting a company onto a trial. See What a plan with a trial can include.

collection_method
string

How the subscription is paid. charge_automatically charges the payment method on file. send_invoice raises a Stripe invoice with payment terms instead, so the customer pays from the invoice. When you omit this field on a new subscription, Schematic uses the collection method on the company’s billing profile, so a company whose profile is set to invoice gets an invoiced subscription. A company with no billing profile gets charge_automatically. Send the field explicitly whenever the deal needs a specific method. Invoicing can only be set up when starting a new subscription; sending send_invoice for a company that already has an active subscription returns a 400. An invoiced subscription cannot start with trial_end. See Invoicing a subscription.

billing_email
string

The address Stripe sends the invoice to. Required when collection_method is send_invoice, unless the company’s Stripe customer already has an email address, which is used when this field is omitted.

days_until_due
integer

Payment terms for an invoiced subscription: the number of days after the invoice date that payment is due, from 1 to 365. Defaults to the terms saved on the company’s billing profile, or 30 if it has none. Only applies when collection_method is send_invoice.

billing_cycle_anchor
datetime

An ISO 8601 timestamp for the date the subscription renews on, for example the renewal date in a signed order form. Must be in the future, and cannot be combined with trial_end. Only honored when starting a new subscription; sending it for a company that already has an active subscription returns a 400. The time between the subscription starting and this date becomes a shortened first period, and prorate_first_period decides whether that period is billed.

prorate_first_period
boolean

Whether the shortened first period created by billing_cycle_anchor is billed. Defaults to true, which bills that period pro rata straight away. Set it to false to make the period free: no invoice is raised until the anchor date, and the first invoice covers the first full period. Only applies alongside billing_cycle_anchor; sending it without one returns a 400.

paid_outside_stripe
boolean

Set to true when the customer has already paid outside Stripe, for example by bank transfer against a quote. Schematic finalizes the first invoice without emailing it, records it in Stripe as paid out of band, and the company gets the plan straight away. Requires collection_method to be send_invoice. Cannot be combined with activate_on_payment, with prorate_first_period: false, or with credit_bundles. Defaults to false.

activate_on_payment
boolean

If true, the company only gets the plan once the first invoice is paid, and keeps whatever plan and add-ons it has today until then. Only applies when collection_method is send_invoice. Cannot be combined with prorate_first_period: false, because a free first period raises no invoice to pay, or with credit_bundles, because credits are granted immediately. Defaults to false. Read Activation on payment before turning this on: an invoice that goes past due cancels the subscription.

send_invoice
boolean

Whether Stripe emails the invoice to billing_email when it is finalized. Set it to false when you send invoices from your own system and only need Stripe to track the payment. Only applies when collection_method is send_invoice. Defaults to true.

Finding price IDs

The base_plan_price_id and add-on price_id fields are Schematic billing product price IDs (prefixed bprice_), not Stripe price IDs.

To find them, fetch a plan via GET /plans/:id (or list plans via GET /plans). The response includes a billing_product object with a prices array. Each entry has an id along with metadata like interval (month, year, one_time) and currency, so you can pick the right one.

{
"id": "plan_xyz789",
"name": "Pro",
"billing_product": {
"prices": [
{
"id": "bprice_def456",
"interval": "month",
"currency": "usd",
"price": 4900
},
{
"id": "bprice_ghi789",
"interval": "year",
"currency": "usd",
"price": 49000
}
]
}
}

Response

{
"data": {
"company": { ... },
"success": true
}
}

The response includes the full updated company object reflecting the new plan state.

Preview Manage Plan

POST /manage-plan/preview

Use this endpoint to preview the financial impact of a plan change before applying it, most commonly while implementing your own custom checkout. The request body is identical to POST /manage-plan.

Response

{
"data": {
"subscription_change_preview": {
...
}
}
}

The preview includes details like prorated charges, credits, and the new subscription amount. This is useful for showing users what a plan change will cost before they confirm.

Cancel subscription

POST /manage-plan/subscription/cancel

Cancels a company’s active Stripe subscription. This only applies to companies with billing-linked plans.

Request body

{
"company_id": "comp_abc123",
"cancel_immediately": true,
"prorate": true
}
company_id
stringRequired

The ID of the company whose subscription you want to cancel.

cancel_immediately
boolean

If false, the subscription cancels at the end of the current billing period instead of immediately. Defaults to true.

prorate
boolean

If true and cancel_immediately is true, a prorated credit is issued for the unused portion of the billing period. Defaults to true.

Response

Same format as POST /manage-plan, returning the updated company object.

Common patterns

Upgrading a plan

To upgrade a company from one plan to another, send the new plan ID along with all existing add-ons and pay-in-advance entitlements you want to preserve. Schematic handles proration and Stripe subscription updates automatically.

In the example below, the company already has the team-collab add-on and 10 user seats. Both are re-sent on the request so they carry through the upgrade.

{
"company_id": "comp_abc123",
"base_plan_id": "plan_pro",
"base_plan_price_id": "bprice_pro_monthly",
"add_on_selections": [
{ "plan_id": "plan_team_collab", "price_id": "bprice_team_collab" }
],
"pay_in_advance_entitlements": [
{ "price_id": "bprice_seats", "quantity": 10 }
]
}

If you omit add_on_selections or pay_in_advance_entitlements (or send them empty), the company’s existing add-ons will be removed and their pay-in-advance quantities will drop to zero. Always re-send existing values when upgrading.

Adding an add-on while preserving existing state

To add an add-on without changing anything else, you must re-send the current base plan, all existing add-ons, and all existing pay-in-advance entitlements alongside the new add-on.

In the example below, the company is on the pro plan, has the team-collab add-on, and 10 user seats. We’re adding the priority-support add-on. The existing pro plan, team-collab add-on, and 10 seats are re-sent so they aren’t removed.

{
"company_id": "comp_abc123",
"base_plan_id": "plan_pro",
"base_plan_price_id": "bprice_pro_monthly",
"add_on_selections": [
{ "plan_id": "plan_team_collab", "price_id": "bprice_team_collab" },
{ "plan_id": "plan_priority_support", "price_id": "bprice_priority_support" }
],
"pay_in_advance_entitlements": [
{ "price_id": "bprice_seats", "quantity": 10 }
]
}

Forgetting any of base_plan_id, the existing add_on_selections, or pay_in_advance_entitlements here would silently change the company’s state. Omitting base_plan_id clears the plan (falling back to the default), omitting an existing add-on removes it, and omitting pay_in_advance_entitlements drops the seat count to zero. Always re-send everything you want to preserve.

Changing a pay-in-advance quantity (e.g. adding a seat)

To change just one pay-in-advance quantity, you still need to re-send the full plan state alongside it. Below, the company is going from 10 seats to 11.

{
"company_id": "comp_abc123",
"base_plan_id": "plan_pro",
"base_plan_price_id": "bprice_pro_monthly",
"add_on_selections": [
{ "plan_id": "plan_existing_addon", "price_id": "bprice_existing" }
],
"pay_in_advance_entitlements": [
{ "price_id": "bprice_seats", "quantity": 11 }
]
}

Invoicing a subscription

A new subscription follows the collection method on the company’s billing profile, and a company with no billing profile is charged to the payment method on file. For a sales-led deal, set collection_method to send_invoice and Schematic raises a Stripe invoice with payment terms instead. Invoicing can only be set up when the subscription starts, so a company that already has an active subscription needs that subscription cancelled first. The same options are available in the app’s Manage Plan panel, and Invoicing a new subscription covers when to choose each one.

Invoiced plan with net terms

The request below puts a company on the Enterprise plan with 25 seats, invoiced on net 45 terms. Stripe emails the invoice to the billing contact, the company gets the plan straight away, and the subscription renews a year from today.

{
"company_id": "comp_abc123",
"base_plan_id": "plan_enterprise",
"base_plan_price_id": "bprice_enterprise_annual",
"add_on_selections": [],
"pay_in_advance_entitlements": [
{ "price_id": "bprice_seats", "quantity": 25 }
],
"collection_method": "send_invoice",
"billing_email": "ap@example.com",
"days_until_due": 45
}

Omit days_until_due to use the terms on the company’s billing profile, or 30 days if it has none.

Customer who has already paid

Some deals are paid before the subscription exists, for example by bank transfer against a quote. Set paid_outside_stripe: true and Schematic raises the first invoice, records it in Stripe as paid out of band without emailing it, and gives the company the plan straight away. Finance then sees the payment in Stripe alongside every other invoice.

{
"company_id": "comp_abc123",
"base_plan_id": "plan_enterprise",
"base_plan_price_id": "bprice_enterprise_annual",
"add_on_selections": [],
"pay_in_advance_entitlements": [],
"collection_method": "send_invoice",
"billing_email": "ap@example.com",
"paid_outside_stripe": true
}

Renewal date from an order form

When the order form fixes a renewal date and the customer has already paid for the time until then, pin the date with billing_cycle_anchor and make that time free with prorate_first_period: false. Stripe raises no invoice until the renewal date, and that first invoice covers the first full period.

{
"company_id": "comp_abc123",
"base_plan_id": "plan_enterprise",
"base_plan_price_id": "bprice_enterprise_annual",
"add_on_selections": [],
"pay_in_advance_entitlements": [],
"collection_method": "send_invoice",
"billing_email": "ap@example.com",
"billing_cycle_anchor": "2027-01-01T00:00:00Z",
"prorate_first_period": false
}

A free first period raises no invoice before the renewal date, so Stripe has no record of the earlier payment, and paid_outside_stripe cannot be added to this request. If finance needs that payment in Stripe, leave prorate_first_period at its default instead: the first invoice bills the period up to the renewal date, and paid_outside_stripe: true marks it paid.

Activation on payment

Add activate_on_payment: true to an invoiced request to hold the plan back until the first invoice is paid. Until then the company keeps whatever plan and add-ons it has today.

If the invoice goes past due before it is paid, Schematic cancels the subscription and voids the invoice, and the company never receives the plan. Leave activate_on_payment off for customers who need a grace period after the due date. Without it the company gets the plan when the subscription starts, and an overdue invoice follows your Stripe settings for late invoices instead.

Error cases

The Manage Plan API validates the full request before applying any changes. If validation fails, the entire request is rejected and the company’s state is left untouched. Common error cases include:

  • Externally managed subscription: If the company’s subscription is managed outside of Schematic (e.g. directly in Stripe without the Schematic integration), the API will return an error. You must manage the subscription through whichever system owns it.
  • Invalid trial configuration: Trials require a trialable plan with no add-ons, no pay-as-you-go, overage, or tiered entitlements, and no credit grants that bill or charge on usage. A plan that is not trialable returns The selected plan does not allow trials. Add-ons return A trial can't be combined with add-ons. A pay-as-you-go, overage, or tiered entitlement, or a billed credit grant, returns A trial can't be combined with pay-as-you-go, overage, or tiered entitlements, or with billed credit grants. A credit grant with automatic or user-controlled top-ups, or one that bills in arrears, returns A trial can't be combined with credit grants that bill in arrears or top up automatically. Pay-in-advance quantities are accepted. The trial_end timestamp must also be in the future.
  • Missing price for billing-linked add-on: Billing-linked add-ons require a price_id. Look up the available prices via GET /plans/:id.
  • Price specified for non-billing add-on: You cannot specify a price_id for an add-on that is not linked to a billing product. Omit the price_id for these add-ons.
  • Plan not found or not available to company: The base plan or an add-on does not exist, or the company is not eligible for it.
  • Billing entity on an existing subscription: billing_entity_id can only be set when a subscription is created. Sending it for a company that already has an active subscription returns billing entity can only be set when starting a new subscription. The billing entity must also already have a Stripe customer.
  • Invoice terms on an existing subscription: collection_method: send_invoice and billing_cycle_anchor can only be set when a subscription is created. Sending send_invoice for a company that already has an active subscription returns Payment by invoice can only be set up when a subscription starts. This company already has a subscription, so cancel it first to start a new one paid by invoice. Sending billing_cycle_anchor returns The billing period renewal date can only be set when a subscription starts. This company already has a subscription, so its renewal date can't be changed here.
  • Invoicing without a billed base plan: An invoiced subscription needs a base plan that is linked to a billing product. Without one, the request returns Payment by invoice requires a base plan that has billing configured. A request that sets billing_cycle_anchor while removing all of the company’s plans returns The billing period renewal date can't be set on a request that removes the company's plans.
  • Conflicting invoice terms: Trials cannot be combined with collection_method: send_invoice or billing_cycle_anchor. prorate_first_period needs billing_cycle_anchor, and prorate_first_period: false cannot be combined with activate_on_payment or paid_outside_stripe, since a free first period raises no invoice before the renewal date. Any two of paid_outside_stripe, activate_on_payment, and credit_bundles together are rejected. Each conflict returns a 400 whose message names the conflict.
  • Missing billing email: An invoiced subscription needs a billing_email, or a Stripe customer that already has one. Without either, the request returns A billing email is required to send an invoice. POST /manage-plan/preview skips this check, so a preview can succeed for a request that the apply call then rejects.
  • Stripe errors: Any error returned by Stripe during the subscription update (e.g. payment method declined, customer not found, currency mismatch) will be surfaced back to you. If the Stripe call fails, the company’s state in Schematic is left unchanged.

For all errors, the response will include a descriptive message indicating what went wrong. We recommend using POST /manage-plan/preview before applying changes in production flows so users can be shown the financial impact and any validation issues up front.