---
title: "Change items"
description: "Change/add/remove in one atomic call (immediate proration)."
canonical_url: "https://twominutebilling.com/help/developers/api-reference/subscriptions/change-items"
---
# Change items

> Change/add/remove in one atomic call (immediate proration).

<api-operation method="POST" path="/v1/businesses/{biz}/subscriptions/{sub}/change">
<template v-slot:docs="">

## Path parameters

<table>
<thead>
  <tr>
    <th>
      Parameter
    </th>
    
    <th>
      Type
    </th>
    
    <th>
      Required
    </th>
    
    <th>
      Description
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        biz
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <strong>
        yes
      </strong>
    </td>
    
    <td>
      Business id (biz_…)
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        sub
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <strong>
        yes
      </strong>
    </td>
    
    <td>
      Subscription id (sub_…)
    </td>
  </tr>
</tbody>
</table>

## Headers

<table>
<thead>
  <tr>
    <th>
      Parameter
    </th>
    
    <th>
      Type
    </th>
    
    <th>
      Required
    </th>
    
    <th>
      Description
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        Idempotency-Key
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      no
    </td>
    
    <td>
      Safely retry writes — repeats return the original response. If a write FAILED after it had already taken effect, that failure is what repeats: the key is spent, and a new attempt needs a new key.
    </td>
  </tr>
</tbody>
</table>

## Body

The request body is **required**.

<table>
<thead>
  <tr>
    <th>
      Field
    </th>
    
    <th>
      Type
    </th>
    
    <th>
      Required
    </th>
    
    <th>
      Description
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        coupon_code
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      no
    </td>
    
    <td>
      A coupon to apply AS PART OF this change. Applied inside the change's own transaction, BEFORE its proration invoice is raised — so the proration is discounted, which is the intended behaviour. Posting it as a separate call afterwards bills the proration at list price. <code>
        POST …/estimate
      </code>
      
       takes the same field and previews the same result.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        coupon_ids
      </code>
    </td>
    
    <td>
      <code>
        array of string
      </code>
    </td>
    
    <td>
      no
    </td>
    
    <td>
      The same coupon as <code>
        coupon_code
      </code>
      
      , as a list. A subscription carries ONE discount at a time, so more than one entry is refused (400), as is a <code>
        coupon_code
      </code>
      
       naming a different coupon.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        manual_discount
      </code>
    </td>
    
    <td>
      <code>
        object
      </code>
    </td>
    
    <td>
      no
    </td>
    
    <td>
      A manual discount to apply as part of this change — same shape <code>
        POST …/subscriptions/{sub}/manual_discount
      </code>
      
       takes, minus <code>
        note
      </code>
      
      , so one screen sends one body shape to the preview and the booking. <code>
        duration
      </code>
      
       defaults to <code>
        forever
      </code>
      
      .
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        manual_discount.type
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <strong>
        yes
      </strong>
    </td>
    
    <td>
      The discount type. One of <code>
        percent
      </code>
      
      , <code>
        amount
      </code>
      
      .
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        manual_discount.value
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <strong>
        yes
      </strong>
    </td>
    
    <td>
      1–100 for <code>
        percent
      </code>
      
      ; minor units for <code>
        amount
      </code>
      
      .
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        manual_discount.duration
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      no
    </td>
    
    <td>
      How long the discount lasts. One of <code>
        one_time
      </code>
      
      , <code>
        forever
      </code>
      
      .
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        remove_discount
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      no
    </td>
    
    <td>
      Remove the subscription's active discount (coupon or manual) as part of this change, inside its transaction and BEFORE the proration invoice is raised — so that invoice bills without it. With <code>
        coupon_code
      </code>
      
       or <code>
        manual_discount
      </code>
      
       it swaps the discount in one edit. On the estimate it prices the same order.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        changes
      </code>
    </td>
    
    <td>
      <code>
        array of object
      </code>
    </td>
    
    <td>
      no
    </td>
    
    <td>
      Existing items to change.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        changes.item
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <strong>
        yes
      </strong>
    </td>
    
    <td>
      The subscription item's id (si_…).
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        changes.price
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      no
    </td>
    
    <td>
      The new price's id (a slug id, e.g. <code>
        pro-monthly-usd
      </code>
      
      ).
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        changes.quantity
      </code>
    </td>
    
    <td>
      <code>
        integer
      </code>
    </td>
    
    <td>
      no
    </td>
    
    <td>
      The new quantity.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        changes.unit_price_minor
      </code>
    </td>
    
    <td>
      <code>
        integer
      </code>
    </td>
    
    <td>
      no
    </td>
    
    <td>
      Custom price override (flat/per-unit only), minor units
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        adds
      </code>
    </td>
    
    <td>
      <code>
        array of object
      </code>
    </td>
    
    <td>
      no
    </td>
    
    <td>
      Items to ATTACH by this same call — priced exactly as <code>
        POST …/items
      </code>
      
       prices them, including plan-scoped differential resolution. A recurring add is prorated to the remaining term (the full term when this edit resets it); a one-time add is charged in full. Cadence is validated against the POST-change plan cycle, so an added yearly add-on is legal in the same call that moves the plan to its yearly price.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        adds.price
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <strong>
        yes
      </strong>
    </td>
    
    <td>
      The price's id (a slug id, e.g. <code>
        pro-monthly-usd
      </code>
      
      ).
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        adds.quantity
      </code>
    </td>
    
    <td>
      <code>
        integer
      </code>
    </td>
    
    <td>
      no
    </td>
    
    <td>
      The quantity. Defaults to <code>
        1
      </code>
      
      .
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        adds.unit_price_minor
      </code>
    </td>
    
    <td>
      <code>
        integer
      </code>
    </td>
    
    <td>
      no
    </td>
    
    <td>
      Custom price override (flat/per-unit only), minor units
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        removes
      </code>
    </td>
    
    <td>
      <code>
        array of string
      </code>
    </td>
    
    <td>
      no
    </td>
    
    <td>
      Subscription item ids to DETACH by this same call — each credits the unused time exactly as <code>
        DELETE …/items/:id
      </code>
      
       does (a one-time item is credited nothing), into the same document as the rest of the edit. No line ever bills an item the same call removes. A mandatory add-on of the POST-change plan is refused; the same item may not appear in both <code>
        changes
      </code>
      
       and <code>
        removes
      </code>
      
      .
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        at
      </code>
    </td>
    
    <td>
      <code>
        string · ISO-8601
      </code>
    </td>
    
    <td>
      no
    </td>
    
    <td>
      Backdate the change (proration as of this instant)
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        reset_term
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      no
    </td>
    
    <td>
      Restart the term today and charge the FULL new-term price. Default false, and a SAME-CADENCE plan swap does NOT reset without it — a billing-frequency change always does, with or without it.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        prorate
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      no
    </td>
    
    <td>
      False suppresses the PRORATED credit and charge. On a term reset the full new-term charge is not a prorated artefact and still happens — only the unused-time credit is dropped, so there is no free term. Omitted = the business rule for the change's direction (<code>
        billing_rules_config.upgrade_proration
      </code>
      
       or <code>
        downgrade_proration
      </code>
      
      ; unset, prorated). A value sent here applies to this change in either direction. A longer billing period is an upgrade and a shorter one a downgrade; on the same period the recurring amount per month decides.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        timing
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      no
    </td>
    
    <td>
      <code>
        now
      </code>
      
       applies the change; <code>
        end_of_term
      </code>
      
       schedules it for the current term end (the trial end in a trial) and returns <code>
        scheduled_change
      </code>
      
      . Omitted = the business rule for the change's direction (<code>
        billing_rules_config.upgrade_timing
      </code>
      
       or <code>
        downgrade_timing
      </code>
      
      ); unset, <code>
        now
      </code>
      
      . Omitted with any of <code>
        at
      </code>
      
      , <code>
        reset_term
      </code>
      
      , <code>
        prorate
      </code>
      
      , <code>
        invoice_immediately
      </code>
      
      , <code>
        metadata
      </code>
      
       or a discount = <code>
        now
      </code>
      
       when the rule allows it. A timing the rule does not allow is refused (400). A change at the end of the term cannot carry those fields, or <code>
        on_charge_failure
      </code>
      
      , <code>
        coupons
      </code>
      
      , <code>
        downgrade_credit
      </code>
      
      , <code>
        plan_credits
      </code>
      
       or <code>
        allowances
      </code>
      
       (400 names them): it follows the business rules when it lands. One of <code>
        now
      </code>
      
      , <code>
        end_of_term
      </code>
      
      .
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        invoice_immediately
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      no
    </td>
    
    <td>
      Invoicing options: true raises the proration invoice now and collects it; false queues the same lines as unbilled charges, folded into the next renewal invoice (where tax and account credits are applied). OMITTED resolves the business setting <code>
        default_invoice_immediately
      </code>
      
       (Settings > Billing LogIQ > Update subscription), which is off by default — so omitting this queues unbilled charges unless the business turned it on. Pass true explicitly to invoice now regardless of that setting. Credits (downgrade / term-reset) are issued immediately either way.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        on_charge_failure
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      no
    </td>
    
    <td>
      What a failed charge on this change's invoice does, for this call only. <code>
        start_dunning
      </code>
      
      : dunning retries it now, as any failed invoice. <code>
        next_renewal
      </code>
      
      : dunning waits for the term end (not after a term reset). Omitted: the business rule (<code>
        billing_rules_config.failed_change_charge
      </code>
      
      ). One of <code>
        start_dunning
      </code>
      
      , <code>
        next_renewal
      </code>
      
      .
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        coupons
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      no
    </td>
    
    <td>
      What this plan change does with the subscription's coupons, for this call only. <code>
        keep
      </code>
      
      : every coupon stays. <code>
        drop
      </code>
      
      : every coupon is removed before the change bills. <code>
        keep_if_valid
      </code>
      
      : a coupon that names plans and does not cover the new plan is removed. Manual discounts always stay. Omitted: the business rule (<code>
        billing_rules_config.plan_change_coupons
      </code>
      
      ). One of <code>
        keep
      </code>
      
      , <code>
        drop
      </code>
      
      , <code>
        keep_if_valid
      </code>
      
      .
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        downgrade_credit
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      no
    </td>
    
    <td>
      Where a credit from this change goes, for this call only. <code>
        credit_balance
      </code>
      
      : it stays as account credit. <code>
        refund
      </code>
      
      : it is refunded to the card or bank payments that funded the customer's invoices, as far as they can take it; the rest stays as credit, and the response carries <code>
        credit_refunds
      </code>
      
      . Omitted: the business rule (<code>
        billing_rules_config.downgrade_credit
      </code>
      
      ; <code>
        customer_chooses
      </code>
      
       keeps the credit for a call made here). One of <code>
        credit_balance
      </code>
      
      , <code>
        refund
      </code>
      
      .
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        plan_credits
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      no
    </td>
    
    <td>
      How a plan that grants credits per cycle grants them at this change, for this call only. <code>
        full
      </code>
      
      : the new plan's credits for the current window now. <code>
        prorated
      </code>
      
      : the part of them that matches the rest of the window (upgrade only; 400 on a downgrade). <code>
        next_window
      </code>
      
      : the old grant runs to its window end, and the new plan grants from the next window. On an upgrade it replaces <code>
        billing_rules_config.plan_credits.on_upgrade
      </code>
      
      , on a downgrade <code>
        on_downgrade
      </code>
      
      . Omitted: the business rule. One of <code>
        full
      </code>
      
      , <code>
        prorated
      </code>
      
      , <code>
        next_window
      </code>
      
      .
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        allowances
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      no
    </td>
    
    <td>
      How a raised meter allowance counts after this upgrade, for this call only. <code>
        new_window
      </code>
      
      : usage counts from the change. <code>
        keep_used
      </code>
      
      : usage before the change still counts. <code>
        prorated
      </code>
      
      : the limit is prorated over the window. Upgrade only: 400 on a downgrade. Omitted: the business rule (<code>
        billing_rules_config.allowances.on_upgrade
      </code>
      
      ). One of <code>
        new_window
      </code>
      
      , <code>
        keep_used
      </code>
      
      , <code>
        prorated
      </code>
      
      .
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        priced_at
      </code>
    </td>
    
    <td>
      <code>
        string · ISO-8601
      </code>
    </td>
    
    <td>
      no
    </td>
    
    <td>
      <code>
        priced_at
      </code>
      
       from <code>
        POST …/estimate
      </code>
      
      . Send it only with <code>
        quote
      </code>
      
      , on a change applied now without <code>
        at
      </code>
      
       (else 400). The change is booked at this instant when it is at most 15 minutes old and no more than 5 seconds ahead of the server clock, so the unused-time credit is the one the estimate showed; otherwise it is priced at the moment of the call.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        quote
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      no
    </td>
    
    <td>
      <code>
        quote
      </code>
      
       from <code>
        POST …/estimate
      </code>
      
      . Only on a change applied now (<code>
        timing: now
      </code>
      
      , or omitted when the business rule resolves to now) without <code>
        at
      </code>
      
      ; otherwise 400. The change is priced again with this body and refused (409, <code>
        param: quote
      </code>
      
      ) when any amount differs from the estimate, or when the subscription renewed after <code>
        priced_at
      </code>
      
      . Nothing is booked. Omitted, no check is made.
    </td>
  </tr>
</tbody>
</table>

## Response

<table>
<thead>
  <tr>
    <th>
      Field
    </th>
    
    <th>
      Type
    </th>
    
    <th>
      Description
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        object
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      The object type; always <code>
        subscription_change
      </code>
      
      . One of <code>
        subscription_change
      </code>
      
      .
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        subscription
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      The subscription id.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        proration_invoice
      </code>
    </td>
    
    <td>
      <code>
        object
      </code>
    </td>
    
    <td>
      The invoice that the change raised, or null if it raised none.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        credit_note
      </code>
    </td>
    
    <td>
      <code>
        object
      </code>
    </td>
    
    <td>
      The credit note that the change raised, or null if it raised none.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        unbilled_charges
      </code>
    </td>
    
    <td>
      <code>
        array of object
      </code>
    </td>
    
    <td>
      Charges that were queued instead of invoiced, or null if none were queued.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        timing
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      When the change takes effect. One of <code>
        now
      </code>
      
      , <code>
        end_of_term
      </code>
      
      .
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        credit_refunds
      </code>
    </td>
    
    <td>
      <code>
        array of object
      </code>
    </td>
    
    <td>
      Only with <code>
        billing_rules_config.downgrade_credit: refund
      </code>
      
       and a credit to refund.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        scheduled_change
      </code>
    </td>
    
    <td>
      <code>
        object
      </code>
    </td>
    
    <td>
      The scheduled change when <code>
        timing
      </code>
      
       is <code>
        end_of_term
      </code>
      
      , else null.
    </td>
  </tr>
</tbody>
</table>

## Status codes

<table>
<thead>
  <tr>
    <th>
      Status
    </th>
    
    <th>
      Meaning
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        200
      </code>
    </td>
    
    <td>
      Applied (with proration invoice if any), or scheduled for the term end (<code>
        scheduled_change
      </code>
      
      )
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        400
      </code>
    </td>
    
    <td>
      Invalid change
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        409
      </code>
    </td>
    
    <td>
      Refused by the business's plan-change gate (<code>
        error.code
      </code>
      
       = <code>
        invoices_unpaid
      </code>
      
      ), or a concurrent write
    </td>
  </tr>
</tbody>
</table>
</template>

<template v-slot:request="">

```bash [Request]
curl -X POST "$TMBILLING_URL/v1/businesses/biz_XXXXXXXXXXXX01/subscriptions/sub_XXXXXXXXXXXX01/change" \
  -H "Authorization: Bearer $TMBILLING_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "coupon_code": "welcome-10",
    "remove_discount": false
  }'
```

</template>

<template v-slot:response="">
<code-group>

```json [200 OK]
{
  "object": "subscription_change",
  "subscription": "sub_XXXXXXXXXXXX01",
  "proration_invoice": {
    "id": "inv_XXXXXXXXXXXX01",
    "status": "paid",
    "total_minor": 4900
  },
  "credit_note": null,
  "unbilled_charges": [
    {
      "id": "uch_XXXXXXXXXXXX01",
      "description": "Prorated charge for 5 seats",
      "amount_minor": 2500,
      "currency": "USD",
      "entity_type": "addon"
    }
  ],
  "timing": "now",
  "credit_refunds": [
    {
      "credit_note": "cn_XXXXXXXXXXXX01",
      "refunded_minor": 1500,
      "kept_minor": 0,
      "reason": "Requested by the customer."
    }
  ],
  "scheduled_change": {
    "object": "scheduled_change",
    "id": "sch_XXXXXXXXXXXX01",
    "type": "cancel",
    "effective_at": "2026-03-14T09:30:00Z",
    "payload": {},
    "status": "scheduled",
    "applied_at": "2026-03-14T09:30:00Z",
    "attempts": 0,
    "last_error": null,
    "failed_at": null,
    "created_at": "2026-03-14T09:30:00Z"
  }
}
```

```json [400 Bad Request]
{
  "error": {
    "type": "invalid_request_error",
    "message": "Invalid change"
  }
}
```

```json [409 Conflict]
{
  "error": {
    "type": "conflict_error",
    "message": "Refused by the business's plan-change gate (error.code = invoices_unpaid), or a concurrent write"
  }
}
```

</code-group>
</template>
</api-operation>


## Sitemap

See the full [sitemap](https://twominutebilling.com/sitemap.md) for all pages.
