Update a subscription

Update subscription-level settings, manage items (add/remove/modify), adjust quantities and prices, with control over proration timing and behavior for mid-cycle changes.

View as Markdown

Update subscription fields

Update metadata, payment method, trial period, or discount on an active subscription without changing line items.

$curl -X PATCH https://api.paymentkit.com/api/{account_id}/subscriptions/{subscription_id} \
>-H "Authorization: Bearer sk_live_..." \
>-H "Content-Type: application/json" \
>-d '{
> "description": "Enterprise plan - annual",
> "default_payment_method_id": "pm_newcard456"
>}'

Updatable fields

FieldDescription
descriptionFree-text description of the subscription.
default_payment_method_idPayment method used for future collections. Must belong to the subscription’s customer.
cancel_at_period_endSet to true to cancel at the end of the current period instead of renewing.
trial_endExtend or shorten the trial period. Mutually exclusive with trial_period_days. Only valid when subscription is in TRIALING state.
trial_period_daysSet trial length in days from now. Mutually exclusive with trial_end. Only valid when subscription is in TRIALING state.
coupon_idAttach a coupon. Pass "" to remove an existing coupon. Mutually exclusive with promotion_code.
promotion_codeApply a promotion code. Mutually exclusive with coupon_id.
collection_methodSet to charge_automatically or send_invoice. Requires a payment method when set to charge_automatically.
net_dPayment terms in days (Net X). Must be 0 or greater. Set to 0 for due immediately.
metadataKey-value pairs. Replaces existing metadata entirely. Pass an empty object {} to clear all metadata.

Lifecycle impact: Changing cancel_at_period_end, trial_end, or trial_period_days triggers a re-evaluation of the subscription lifecycle and may reschedule automated actions.

Invoice impact: Changing coupon_id or promotion_code voids any pending renewal invoices to ensure correct pricing on the next billing cycle.

Update subscription items

Add, remove, or change items on a subscription. This is how you handle quantity changes, add-ons, and price swaps within the same billing interval.

$curl -X PATCH https://api.paymentkit.com/api/{account_id}/subscriptions/{subscription_id}/items \
>-H "Authorization: Bearer sk_live_..." \
>-H "Content-Type: application/json" \
>-d '{
> "items": [
> {"id": "si_existing123", "quantity": 10},
> {"price_id": "price_addon_storage", "quantity": 1},
> {"id": "si_old_addon", "deleted": true}
> ],
> "proration_behavior": "always_invoice"
>}'

Item operations

OperationPayloadNotes
Add new item{"price_id": "price_xxx", "quantity": 2}Quantity defaults to 1 if omitted
Add new item at period end{"price_id": "price_xxx", "quantity": 2, "start_at_end": true}Created inactive, activates at next renewal
Update quantity{"id": "si_xxx", "quantity": 10}
Swap price{"id": "si_xxx", "price_id": "price_yyy"}Keeps existing quantity if not specified
Update price and quantity{"id": "si_xxx", "price_id": "price_yyy", "quantity": 5}
Remove item now{"id": "si_xxx", "deleted": true}
Schedule removal{"id": "si_xxx", "drop_at_end": true}Item removed at next renewal
Cancel scheduled removal{"id": "si_xxx", "drop_at_end": false}

Combine multiple operations in a single request. All changes are applied atomically.

Proration behavior

The proration_behavior field is required and controls how mid-cycle changes are billed:

  • always_invoice: Creates an invoice immediately and attempts payment. Use when you want the customer to pay for the upgrade right away.
  • create_prorations: Creates floating items (invoice_id=NULL) that are collected on the next renewal invoice. Use for deferred billing.
  • none: No proration charges or credits are created. Items are updated immediately but no billing adjustment is made for the mid-cycle change.

Response fields

The response includes:

  • subscription_id: The updated subscription’s external ID
  • invoice_id: Created invoice ID (if proration_behavior=always_invoice)
  • payment_status: Payment result ("paid", "processing", "requires_action", "failed", "no_payment_method", "skipped")
  • payment_error: Error message if payment failed
  • floating_items_created: Count of floating items (if proration_behavior=create_prorations)
  • proration_amount_atom: Net proration amount in atoms
  • voided_invoice_ids: External IDs of voided pending renewal invoices
  • new_renewal_invoice_id: External ID of replacement renewal invoice (if a voided invoice had active dunning)
  • new_invoice_payment_status: Payment status of the replacement renewal invoice

Items must have the same billing interval as the subscription. To change a customer from monthly to annual billing, use the change plan endpoint instead.

Proration behavior

When items change mid-cycle, PaymentKit calculates the prorated difference. Control how proration is handled with the proration_behavior field:

BehaviorDescription
always_invoiceCreate a proration invoice immediately and attempt payment. Use this for instant upgrades where the customer should be charged right away.
create_prorationsCreate prorated line items that are included on the next renewal invoice.
noneNo proration. Changes take effect at the next renewal with no credit or charge for the remainder of the current period.

Example: Immediate upgrade

Upgrade a customer from a basic plan to a pro plan and charge the prorated difference immediately:

$curl -X PATCH https://api.paymentkit.com/api/{account_id}/subscriptions/{subscription_id}/items \
>-H "Authorization: Bearer sk_live_..." \
>-H "Content-Type: application/json" \
>-d '{
> "items": [
> {"id": "si_basic_plan", "price_id": "price_pro_plan"}
> ],
> "proration_behavior": "always_invoice"
>}'

The response includes the created invoice ID and payment status:

1{
2 "subscription_id": "sub_abc123",
3 "invoice_id": "in_proration456",
4 "payment_status": "paid",
5 "proration_amount_atom": 1500,
6 "voided_invoice_ids": [],
7 "new_renewal_invoice_id": null,
8 "new_invoice_payment_status": null
9}

Payment status values:

  • paid: Payment succeeded
  • processing: Async payment (ACH/SEPA) submitted, awaiting confirmation
  • requires_action: Customer action required (e.g., 3D Secure authentication)
  • failed: Payment failed
  • no_payment_method: No payment method available
  • skipped: Payment processing disabled for this account

If payment fails, the payment_error field contains the error message.

Invoice voiding: When immediate updates are made, any pending renewal invoices (DRAFT, OPEN, or PAST_DUE) are automatically voided to ensure correct billing. The voided_invoice_ids field contains a list of the voided invoices. If the voided invoice had active dunning, a new renewal invoice is created with updated items, and its ID is returned in new_renewal_invoice_id.

Example: Deferred billing

Add an add-on that will be billed on the next renewal:

$curl -X PATCH https://api.paymentkit.com/api/{account_id}/subscriptions/{subscription_id}/items \
>-H "Authorization: Bearer sk_live_..." \
>-H "Content-Type: application/json" \
>-d '{
> "items": [
> {"price_id": "price_addon_seats", "quantity": 3}
> ],
> "proration_behavior": "create_prorations"
>}'

The response includes the count of floating items created:

1{
2 "subscription_id": "sub_abc123",
3 "invoice_id": null,
4 "payment_status": null,
5 "floating_items_created": 1,
6 "proration_amount_atom": 4500,
7 "voided_invoice_ids": [],
8 "new_renewal_invoice_id": null,
9 "new_invoice_payment_status": null
10}

Prorated line items are stored as floating items (with invoice_id set to null) and automatically included in the next renewal invoice.

Change subscription plan

Use the change-plan endpoint when you need to move items to different billing intervals or contract terms. This is the recommended approach for upgrades and downgrades that change the billing cycle.

$curl -X POST https://api.paymentkit.com/api/{account_id}/subscriptions/{subscription_id}/change-plan \
>-H "Authorization: Bearer sk_live_..." \
>-H "Content-Type: application/json" \
>-d '{
> "items": [
> {"action": "update", "subscription_item_id": "si_basic_monthly", "new_price_id": "price_pro_annual"}
> ],
> "proration_behavior": "always_invoice"
>}'

Request fields

FieldTypeRequiredDescription
itemsarrayYesList of item changes (min 1 item required)
proration_behaviorstringYesHow to handle proration: always_invoice, create_prorations, or none
reasonstringNoReason for the plan change (default: "change_plan")
pay_before_changebooleanNoIf true (default), payment must succeed before the plan change commits. On payment failure (including 3D Secure), the operation rolls back. If false, the plan change commits regardless of payment status, and new subscriptions may be left in ‘incomplete’ state.
metadataobjectNoAdditional metadata to add to new subscription(s)

Proration behavior options

  • always_invoice: Create invoice immediately and attempt payment now. Use when you want the customer to pay for the upgrade immediately.
  • create_prorations: Create floating items (invoice_id=NULL) that will be collected at the next renewal. Use for deferred billing.
  • none: No proration, changes apply at next renewal only. Items are updated but no proration charges/credits are created.

How term-based splitting works

When items change to prices with different billing terms, PaymentKit automatically creates new subscriptions grouped by their terms:

Items are grouped by:

  • Billing interval (month, year, etc.)
  • Billing interval count (every 1 month, every 3 months, etc.)
  • Contract length (12 cycles, indefinite, etc.)
  • Auto-renew setting

Item actions

Each item mapping requires an action field and additional fields depending on the action:

ActionRequired fieldsForbidden fieldsDescription
addnew_price_idsubscription_item_idAdd a new item to the subscription. quantity is optional and defaults to 1 if not specified.
updatesubscription_item_id, new_price_id-Change an existing item’s price and/or quantity. quantity is optional; if omitted, keeps existing quantity.
deletesubscription_item_idnew_price_idRemove an item from the subscription.

Items NOT included in the request stay unchanged on the original subscription.

Example: Monthly to annual upgrade

Upgrade a customer from monthly (100/mo)toannual(100/mo) to annual (1,000/yr) billing with immediate payment:

$curl -X POST https://api.paymentkit.com/api/{account_id}/subscriptions/{subscription_id}/change-plan \
>-H "Authorization: Bearer sk_live_..." \
>-H "Content-Type: application/json" \
>-d '{
> "items": [
> {
> "action": "update",
> "subscription_item_id": "si_monthly_plan",
> "new_price_id": "price_annual_plan"
> }
> ],
> "proration_behavior": "always_invoice"
>}'

The response includes details about the plan change:

1{
2 "original_subscription_id": "sub_abc123",
3 "original_cancelled": true,
4 "original_items_remaining": 0,
5 "original_subscription_updated_at": "2026-03-10T15:30:00Z",
6 "created_subscriptions": [
7 {
8 "subscription_id": "sub_xyz789",
9 "state": "active",
10 "billing_interval": "year",
11 "billing_interval_count": 1,
12 "items_count": 1,
13 "total_billing_cycles": null,
14 "contract_auto_renew": false
15 }
16 ],
17 "items_added": 0,
18 "proration_credit_atom": -5000,
19 "proration_charge_atom": 100000,
20 "net_amount_atom": 95000,
21 "invoice_id": "in_change123",
22 "payment_status": "paid",
23 "voided_invoice_ids": []
24}

Response fields

FieldTypeDescription
original_subscription_idstringID of the original subscription
original_cancelledbooleanTrue if original was cancelled (all items moved/deleted)
original_items_remainingintegerNumber of active items still on original subscription
original_subscription_updated_atdatetimeLast update timestamp of the original subscription
created_subscriptionsarrayNew subscriptions created (one per unique billing terms)
items_addedintegerNumber of new items created via ADD action
proration_credit_atomintegerCredit for unused time on moved/deleted items (negative, in atomic units)
proration_charge_atomintegerCharge for new subscription periods (positive, in atomic units)
net_amount_atomintegerNet proration amount (credit + charge, in atomic units)
invoice_idstringID of proration invoice (if always_invoice behavior)
payment_statusstringPayment status: paid, processing, failed, requires_action, no_payment_method, or pending
voided_invoice_idsarrayIDs of voided pending renewal invoices

Created subscription fields

Each subscription in created_subscriptions includes:

FieldTypeDescription
subscription_idstringID of the new subscription
statestringState of the new subscription (active, incomplete). Incomplete means payment failed or requires action (e.g., 3D Secure).
billing_intervalstringBilling interval (month, year, etc)
billing_interval_countintegerInterval count (1, 3, etc)
items_countintegerNumber of items on this subscription
total_billing_cyclesintegerContract length in billing cycles (null = indefinite)
contract_auto_renewbooleanWhether contract auto-renews at end of term

In this example:

  • The customer receives a $50 credit for unused time on the monthly plan
  • The customer is charged $1,000 for the annual plan
  • The net invoice is $950
  • The original subscription is cancelled (all items moved)
  • A new annual subscription is created

Example: Add items during plan change

Add a new item while upgrading to a different plan:

$curl -X POST https://api.paymentkit.com/api/{account_id}/subscriptions/{subscription_id}/change-plan \
>-H "Authorization: Bearer sk_live_..." \
>-H "Content-Type: application/json" \
>-d '{
> "items": [
> {"action": "update", "subscription_item_id": "si_basic", "new_price_id": "price_pro_annual"},
> {"action": "add", "new_price_id": "price_addon_support", "quantity": 1}
> ],
> "proration_behavior": "always_invoice",
> "pay_before_change": false
>}'

Example: Partial plan change

Change only some items while keeping others unchanged:

$curl -X POST https://api.paymentkit.com/api/{account_id}/subscriptions/{subscription_id}/change-plan \
>-H "Authorization: Bearer sk_live_..." \
>-H "Content-Type: application/json" \
>-d '{
> "items": [
> {"action": "update", "subscription_item_id": "si_storage", "new_price_id": "price_storage_annual"}
> ],
> "proration_behavior": "always_invoice",
> "pay_before_change": false
>}'

Items not included in the request remain on the original subscription with their current billing terms.

Example: Deferred billing with create_prorations

Upgrade a plan but defer billing to the next renewal:

$curl -X POST https://api.paymentkit.com/api/{account_id}/subscriptions/{subscription_id}/change-plan \
>-H "Authorization: Bearer sk_live_..." \
>-H "Content-Type: application/json" \
>-d '{
> "items": [
> {"action": "update", "subscription_item_id": "si_basic", "new_price_id": "price_pro"}
> ],
> "proration_behavior": "create_prorations",
> "pay_before_change": false
>}'

With create_prorations, proration amounts are calculated but stored as floating line items (invoice_id=NULL). They will be included in the next renewal invoice rather than creating an immediate invoice.

Example: Pay-before-change flow

Require payment before applying the plan change. This ensures the customer stays on their original plan if payment fails:

$curl -X POST https://api.paymentkit.com/api/{account_id}/subscriptions/{subscription_id}/change-plan \
>-H "Authorization: Bearer sk_live_..." \
>-H "Content-Type: application/json" \
>-d '{
> "items": [
> {"action": "update", "subscription_item_id": "si_basic", "new_price_id": "price_enterprise"}
> ],
> "proration_behavior": "always_invoice",
> "pay_before_change": true
>}'

With pay_before_change: true:

  1. New subscriptions are created in incomplete state - items are added but not yet active
  2. Original subscription remains unchanged - items are NOT deactivated and the subscription is NOT cancelled until payment succeeds
  3. A 402 Payment Required response is returned with the invoice_id to collect
  4. Collect the invoice separately to complete the plan change:
    • If payment succeeds: Items on the original subscription are deactivated, the original subscription is cancelled (if empty), and new subscriptions are activated automatically
    • If payment fails or requires action (e.g., 3D Secure): The customer stays on their original subscription with no disruption

pay_before_change: true requires proration_behavior: "always_invoice". Without an invoice, there is nothing to collect payment on.

Pending invoice handling

When you modify a subscription, PaymentKit automatically voids any pending renewal invoices to ensure the next invoice reflects the updated configuration.

What gets voided:

  • Only renewal invoices (billing_reason: "subscription_cycle")
  • With status draft, open, or past_due
  • Proration invoices (billing_reason: "subscription_update") are never voided

The response includes the voided invoice IDs:

1{
2 "subscription_id": "sub_abc123",
3 "voided_invoice_ids": ["in_pending456"]
4}

Handling active dunning:

If a voided invoice has active dunning (failed payment with scheduled retries), PaymentKit uses a two-phase approach to ensure the replacement invoice has the correct subscription items:

  1. Phase 1 - Void old invoice:

    • Marks the old invoice as void
    • Marks the dunning state as invoice_voided
  2. Phase 2 - Create replacement invoice (after item changes are applied):

    • Creates a new renewal invoice with the updated subscription items
    • Finalizes the invoice to open status
    • The dunning system automatically picks up the new invoice for collection

This ensures customers are always billed the correct amount for their current subscription configuration, even when dunning is in progress.

Preview upcoming invoice

Before making changes, preview what the next invoice will look like:

$curl -X GET https://api.paymentkit.com/api/{account_id}/subscriptions/{subscription_id}/preview \
>-H "Authorization: Bearer sk_live_..."

This is a read-only operation that computes what the next renewal invoice will be without creating anything in the database. No invoice numbers are claimed, no billing cycles are decremented, and no events are emitted.

The response includes:

  • subscription: Complete subscription details including all items and pricing
  • upcoming_invoice: Preview of the next billing cycle invoice with:
    • Line items (descriptions, quantities, amounts, period dates)
    • Amount breakdowns (subtotal, tax, total, due amount)
    • Due date (calculated from net_d payment terms)
    • Billing period (period_start, period_end)
    • Currency and collection method

Example response:

1{
2 "subscription": {
3 "id": "sub_abc123",
4 "customer_id": "cus_xyz789",
5 "state": "active",
6 "currency": "usd",
7 "current_period_start": "2026-02-10T00:00:00Z",
8 "current_period_end": "2026-03-10T00:00:00Z",
9 "items": [
10 {
11 "id": "si_item1",
12 "price_id": "prc_monthly_plan",
13 "quantity": 1
14 }
15 ]
16 },
17 "upcoming_invoice": {
18 "id": "preview",
19 "customer_id": "cus_xyz789",
20 "status": "draft",
21 "currency": "usd",
22 "subtotal_amount_atom": 2000,
23 "tax_amount_atom": 0,
24 "total_amount_atom": 2000,
25 "due_amount_atom": 2000,
26 "paid_amount_atom": 0,
27 "remaining_amount_atom": 2000,
28 "period_start": "2026-03-10T00:00:00Z",
29 "period_end": "2026-04-10T00:00:00Z",
30 "due_date": "2026-04-10T00:00:00Z",
31 "billing_reason": "subscription_cycle",
32 "items": [
33 {
34 "id": "preview",
35 "description": "Monthly Plan",
36 "quantity": 1,
37 "amount": 2000,
38 "period_start": "2026-03-10T00:00:00Z",
39 "period_end": "2026-04-10T00:00:00Z"
40 }
41 ]
42 }
43}