Skip to main content
PUT
Plan Transition Policies
Base URL: https://api.nozle.app/core/api/v1. Auth: secret key only. When your organization has API key permissions, reads need plan_transition_policy:read and changes need plan_transition_policy:write. Keys that existed before this permission have the same access to it that they have to plan. A plan transition policy tells Nozle how to move a subscription from one plan (the source) to another (the target). Policies are stored per pair of plans: starter to growth and growth to starter are two separate policies. Nozle does not infer the direction from the prices; you set it. Changing an existing subscription’s plan needs a policy for that exact pair. Without one, these calls return 422 plan_transition_policy_required: Withdrawing a pending change does not need a policy. Subscription options leave out every target plan that has no policy, so an empty eligible_plans list usually means no policy is configured. A subscription on an override plan (a plan made from a catalog plan for one customer, for example a negotiated price or a subscription created from a quote) uses the policies of the catalog plan it was made from. Its options list the same eligible plans as a subscription on the catalog plan, and preview, checkout and transitions use the catalog plan’s policy. The change replaces the override plan with the target catalog plan, so the negotiated price and other overrides do not carry over. A self-serve change from an override plan to its own catalog plan returns 422 plan_already_active; a merchant plan change through POST /subscriptions can still replace it with the catalog plan.
One fallback exists. When the current plan is free and the target plan is paid, Nozle uses the organization’s free-to-paid policy (Settings, General, “Checkout & billing policy” in the dashboard) as an upgrade. Every other pair, including paid to paid, paid to free, and free to free, needs its own policy. The dashboard sets only the free-to-paid default; create pair policies with this API.

Create or update a policy

PUT creates the policy for the pair, or updates the existing one. It returns 200 in both cases. On a new policy, fields you leave out take their defaults. On an existing policy, fields you leave out keep their current values. To return a field to its default, send the default value. PATCH on the same path does the same thing. Validation rules:
  • end_of_period timing needs proration_behavior: "none" and credit_action: "none". Because proration_behavior defaults to prorate_immediately, send "proration_behavior": "none" with every end_of_period policy.
  • reset_anchor needs immediate timing (reset_requires_immediate_timing).
  • refund_mode: "full" needs credit_action: "refund" (requires_refund_action).
  • credit_action: "offset" needs final_invoice_action: "generate" (must_generate_for_offset).
  • Source and target must differ (must_be_different).
A violation returns 422 with code: "validation_errors" and the failing field in error_details:
The two end_of_period rules currently return a long untranslated message in error_details.proration_behavior and error_details.credit_action instead of a short code. Check which field is present rather than matching the text.
An unknown target plan returns 404 plan_not_found. An invalid enum value returns 422 with value_is_invalid for that field.

Response

Read, list, and delete

Common setups

Upgrade now, keep the renewal day, credit the unused time:
Downgrade at the end of the period. Subscription options list a downgrade only when it uses end_of_period timing and keep_anchor:

Proration on a mid-cycle upgrade

When a subscription moves to another plan in the middle of its billing period, the policy for that pair decides what is charged:
  • timing: "immediate" applies the change now. With end_of_period, the change waits for the next period and nothing is prorated (proration_behavior and credit_action must be none).
  • proration_behavior: "prorate_immediately", the default, prorates the unused part of the current period. none does not prorate it.
  • credit_action decides what happens to that unused amount of the current plan: credit credits it, refund refunds it (refund_mode prorated refunds the unused part, full the whole amount), offset needs final_invoice_action: "generate", and none, the default, does nothing with it.
  • billing_anchor: "keep_anchor" keeps the current renewal day. reset_anchor starts a new period on the change date.
  • An immediate upgrade with proration_behavior: "none" and billing_anchor: "keep_anchor" still bills the current plan for its period up to the change, as a cancellation does: its usage in arrears and, for a plan paid in arrears, its base fee for the days used. A self-serve checkout charges that invoice. The new plan is not charged or credited for the rest of the period, and no credit note is issued; its base fee starts at the next billing anchor. Before, such a change billed nothing, so that usage and fee were never invoiced. The same applies to POST /subscriptions with proration_behavior: none.
  • An immediate upgrade is payment-gated: checkout returns a provider result when money is due, or type: "completed" when nothing is due.
To see the exact amounts before applying a change, preview it with POST /subscriptions/preview. The preview returns credit_amount_cents, debit_amount_cents, net_amount_cents, amount_due_now_cents and the policy it applied.

How a policy shapes checkout

When a customer with an active subscription checks out a different plan, Nozle resolves the policy for the pair and then: Other 422 responses from plan changes: A new scheduled change while a change is already pending replaces the pending change and returns a new pending subscription ID. Withdrawing the old ID then returns 409 pending_subscription_changed. A paid upgrade checkout does not replace or withdraw a pending downgrade: it is refused with 409 subscription_change_pending. Withdraw the pending change first. Scheduling a change cancels an open upgrade checkout. See Paid upgrades: current limits.