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.

Field clearing on lifecycle actions

When subscriptions transition between states, certain fields are automatically cleared to maintain data consistency. Understanding this behavior is important when building integrations that depend on these fields.

ActionFields ClearedFields Preserved
Pausecancel_at_period_end, cancel_at, cancellation_refund_option
Resumepaused_at, resumes_at, pause_at_end, pause_for_cycles
Cancelpaused_at, resumes_at, pause_at_end, pause_for_cycles, cancel_at_period_end
Activate (from paused/past_due/trialing)paused_at, resumes_at, pause_at_end, pause_for_cyclescancel_at_period_end, cancel_at, cancellation_refund_option

Why activate preserves scheduled cancellation: A customer may schedule a cancellation during their trial or while recovering from a failed payment. When the subscription activates (trial converts or payment succeeds), the scheduled cancellation should persist—it’s a deliberate user action that shouldn’t be silently discarded.

Why pause clears scheduled cancellation: Pausing supersedes any scheduled cancellation. The customer explicitly chose to pause instead, and can re-schedule cancellation after resuming if desired. This prevents edge cases where a cancel_at date passes while paused, causing unexpected immediate cancellation upon resume.

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.

You can optionally specify proration_date to set a custom date for proration calculation (defaults to now).

Response fields

The response includes:

  • subscription_id: The updated subscription’s external ID
  • invoice_id: Created invoice ID (if proration_behavior=always_invoice with upgrade)
  • credit_note_id: Created credit note ID (if proration_behavior=always_invoice with downgrade)
  • 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 subscription change requests workflow instead (recommended) or the legacy change plan endpoint (deprecated).

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 charges or credits are created. Items are updated immediately but no billing adjustment is made for the mid-cycle change.

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": "inv_proration456",
4 "payment_status": "paid",
5 "payment_error": null,
6 "floating_items_created": 0,
7 "proration_amount_atom": 1500,
8 "voided_invoice_ids": [],
9 "new_renewal_invoice_id": null,
10 "new_invoice_payment_status": null
11}

Payment status values:

  • paid: Payment succeeded
  • processing: Async payment (ACH/SEPA/BACS) submitted, awaiting webhook 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

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.

Payment error responses

When payment fails with proration_behavior: "always_invoice", the update uses the Charge First pattern — payment is attempted before applying any item changes. If payment fails, the API returns a 402 Payment Required error and the subscription remains unchanged:

Card declined (402 response):

1{
2 "error": "Payment failed for subscription update",
3 "error_code": "PAYMENT_FAILED",
4 "payment_status": "failed",
5 "payment_error": "Your card was declined. Please try a different payment method.",
6 "orchestrator_summary": "Card declined by issuer (insufficient_funds)"
7}

The subscription items are NOT updated. The customer must update their payment method and retry the request.

No payment method (402 response):

1{
2 "error": "Cannot charge subscription without a payment method. Please add a payment method before making changes.",
3 "error_code": "PAYMENT_FAILED",
4 "payment_status": "no_payment_method",
5 "payment_error": "no_payment_method"
6}

Charge First guarantees: With proration_behavior: "always_invoice", payment is collected BEFORE any subscription changes are applied. If payment fails:

  • ✅ Subscription items remain unchanged
  • ✅ Original subscription state preserved
  • ✅ No incomplete subscriptions created
  • ✅ Safe to retry without double-charging

This is the same Charge First pattern used by the subscription change requests workflow.

3D Secure handling:

If payment requires 3D Secure authentication, the MIT orchestrator will attempt to handle it automatically. If the orchestrator returns requires_action, you’ll need to collect the payment separately using the invoice collection endpoint.

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 "payment_error": null,
6 "floating_items_created": 1,
7 "proration_amount_atom": 4500,
8 "voided_invoice_ids": [],
9 "new_renewal_invoice_id": null,
10 "new_invoice_payment_status": null
11}

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

To move items to different billing intervals or contract terms (e.g., monthly to annual), use the subscription change requests workflow:

The subscription change requests workflow provides:

  • Preview before applying — show customers exact proration amounts before committing
  • Charge First pattern — guarantee payment succeeds before modifying the subscription
  • Idempotent retry — built-in retry safety prevents double-charging
  • Status tracking — DRAFT → READY → APPLIED state machine
  • Cross-interval support — handles monthly to annual and other billing term changes
  • Term-based splitting — items automatically group into new subscriptions by billing terms

Both Update Items (this endpoint) and Change Requests use the Charge First pattern with proration_behavior: "always_invoice". The key difference is that Change Requests provide explicit preview and multi-step workflow for user confirmation.

Legacy endpoint (deprecated)

The change subscription plan endpoint is a single-call approach that is planned for deprecation. New integrations should use subscription change requests instead.

View legacy change plan endpoint details

The change plan endpoint provides:

  • Term-based subscription splitting — items automatically group into new subscriptions by billing interval and contract terms
  • Proration — credits for unused time, charges for new periods
  • Scheduled changes — defer execution to period end with effective_at: "period_end"
  • Pay-before-change — require payment success before committing changes
  • Lineage tracking — trace new subscriptions back to the original

See change subscription plan documentation for full details (deprecated).

Pending invoice handling

When you modify a subscription with immediate changes, 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

When voiding occurs:

Pending invoices are only voided for immediate changes that affect current-period billing:

  • Adding items (without start_at_end: true)
  • Updating items (quantity or price changes)
  • Deleting items

Scheduled changes do not trigger voiding:

  • Adding items with start_at_end: true
  • Scheduling item removal with drop_at_end: true

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}