---
title: "Reconcile a subscription against a DESIRED item list"
description: "Where …/change speaks in DELTAS against item ids the caller must already know, this takes the desired end state (\"plan X plus two of add-on Y\") and works out the swap/quantity/add/remove mix itself…"
canonical_url: "https://twominutebilling.com/help/developers/api-reference/subscriptions/reconcile-a-subscription-against-a-desired-item"
---
# Reconcile a subscription against a DESIRED item list

> Where …/change speaks in DELTAS against item ids the caller must already know, this takes the desired end state ("plan X plus two of add-on Y") and works out the swap/quantity/add/remove mix itself…

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

Where `…/change` speaks in DELTAS against item ids the caller must already know, this takes the desired end state ("plan X plus two of add-on Y") and works out the swap/quantity/add/remove mix itself — so an integrator holding a configuration does not have to read the subscription, diff it, and re-implement that logic client-side. Non-interactive by design: the API sibling of the change-mode checkout, for a caller acting on the customer's behalf rather than a buyer at a page. It takes no payment.

## 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>
        items
      </code>
    </td>
    
    <td>
      <code>
        array of object
      </code>
    </td>
    
    <td>
      <strong>
        yes
      </strong>
    </td>
    
    <td>
      The DESIRED items, not a delta. Anything already on the subscription and absent here is left alone unless replace_items_list or mandatory_items_to_remove says otherwise.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        items.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>
        items.quantity
      </code>
    </td>
    
    <td>
      <code>
        integer
      </code>
    </td>
    
    <td>
      no
    </td>
    
    <td>
      The quantity.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        replace_items_list
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      no
    </td>
    
    <td>
      Treat <code>
        items
      </code>
      
       as exhaustive and drop unlisted NON-PLAN items.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        mandatory_items_to_remove
      </code>
    </td>
    
    <td>
      <code>
        array of string
      </code>
    </td>
    
    <td>
      no
    </td>
    
    <td>
      Drop these PRICE ids whatever the mode.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        prorate
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      no
    </td>
    
    <td>
      False suppresses the credit/charge a mid-term change would raise — what a TRIAL upgrade needs, since nothing should be charged before the trial converts. There is no separate no-proration mode: false is applied by deferring the money (invoice_immediately=false), and how callers send it. Omitted → the engine's normal proration.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        invoice_immediately
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      no
    </td>
    
    <td>
      Where any prorated charge lands. Ignored when prorate=false. Omitted resolves the business setting <code>
        default_invoice_immediately
      </code>
      
      , as /change does.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        reset_term
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      no
    </td>
    
    <td>
      Restart the term at this change rather than keeping the current period boundary.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        metadata
      </code>
    </td>
    
    <td>
      <code>
        object
      </code>
    </td>
    
    <td>
      no
    </td>
    
    <td>
      Metadata — REPLACED wholesale, and applied inside this change's own transaction so it commits with the item operations or not at all. For an edit where the items and a flag are one decision (a destination switch bills for one thing and serves what this names): landing them separately means the customer pays for one and receives the other. null clears; omit to leave it untouched.
    </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.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        id
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      The subscription's id (sub_…).
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        status
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      The subscription's status. One of <code>
        future
      </code>
      
      , <code>
        in_trial
      </code>
      
      , <code>
        active
      </code>
      
      , <code>
        non_renewing
      </code>
      
      , <code>
        paused
      </code>
      
      , <code>
        cancelled
      </code>
      
      .
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        currency
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      The subscription's currency (ISO-4217 code).
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        current_period_end
      </code>
    </td>
    
    <td>
      <code>
        string · ISO-8601
      </code>
    </td>
    
    <td>
      The current billing term end. NULL while <code>
        in_trial
      </code>
      
       — a trial is not a term, so a trialling subscription reports <code>
        trial_end
      </code>
      
       instead and this is null (one or the other is reported, never both). Use <code>
        next_billing_at
      </code>
      
       for "when is this customer charged again?", which answers across every status.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        next_billing_at
      </code>
    </td>
    
    <td>
      <code>
        string · ISO-8601
      </code>
    </td>
    
    <td>
      When the next invoice will be raised: trial end while in_trial, the scheduled start while future, the first renewal not skipped while active (or the next monthly usage invoice, when it is earlier); null once non_renewing/paused/cancelled, where no further invoice is due
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        bill_usage_monthly
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      Metered usage is invoiced every month inside the billing period.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        current_sub_period_start
      </code>
    </td>
    
    <td>
      <code>
        string · ISO-8601
      </code>
    </td>
    
    <td>
      Monthly usage billing: usage before this instant is billed. Null when monthly usage billing is off.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        current_sub_period_end
      </code>
    </td>
    
    <td>
      <code>
        string · ISO-8601
      </code>
    </td>
    
    <td>
      Monthly usage billing: when the next monthly usage invoice is raised. Null when off.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        commitment
      </code>
    </td>
    
    <td>
      <code>
        object
      </code>
    </td>
    
    <td>
      Minimum commitment for one full billing period, or null. See GET /subscriptions/{id}/commitment for the counted fees.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        skipped_renewals
      </code>
    </td>
    
    <td>
      <code>
        array of string · ISO-8601
      </code>
    </td>
    
    <td>
      Upcoming renewals scheduled to be skipped, as the instants the clock renews at. See <code>
        skip_renewal
      </code>
      
      .
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        recurring_amount_minor
      </code>
    </td>
    
    <td>
      <code>
        integer
      </code>
    </td>
    
    <td>
      What one billing period charges for the recurring items, in minor units, net of discounts that last more than one period and before tax; null when cancelled or when an item recurs on a different cadence than the subscription
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        revenue_to_date_minor
      </code>
    </td>
    
    <td>
      <code>
        integer
      </code>
    </td>
    
    <td>
      DERIVED, read-only: cash collected on this subscription's invoices, in its currency — succeeded payments minus succeeded refunds. Credits applied to invoices are excluded. Emitted by GET (detail) only.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        revenue_paid_invoices_count
      </code>
    </td>
    
    <td>
      <code>
        integer
      </code>
    </td>
    
    <td>
      How many of this subscription's invoices received a succeeded payment.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        revenue_since
      </code>
    </td>
    
    <td>
      <code>
        string · ISO-8601
      </code>
    </td>
    
    <td>
      The first succeeded payment on this subscription; null when nothing was collected.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        custom_fields
      </code>
    </td>
    
    <td>
      <code>
        object
      </code>
    </td>
    
    <td>
      Stored custom-field values; also flattened onto the object as cf_<name>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        shipping_address_id
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      The shipping address this subscription is set to ship to; null when it ships to the customer's default. Emitted by GET (detail), create, and an update that sends it.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        shipping_address
      </code>
    </td>
    
    <td>
      <code>
        object
      </code>
    </td>
    
    <td>
      Where this subscription ships now: its own address, else the customer's default, else null. Invoices record a copy when they are issued.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        applied
      </code>
    </td>
    
    <td>
      <code>
        object
      </code>
    </td>
    
    <td>
      The operations that ran.
    </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>
      Reconciled. The FULL subscription — items, customer, discount and default payment method, exactly as <code>
        GET /subscriptions/{sub}
      </code>
      
       returns it — plus <code>
        applied
      </code>
      
      : <code>
        changed
      </code>
      
       / <code>
        added
      </code>
      
       counts and the <code>
        removed
      </code>
      
       price ids, so a caller can assert what actually happened instead of diffing the subscription (a removal the mandatory-items guard REFUSED would otherwise look identical to one that was never requested). <code>
        items
      </code>
      
       is included because it is what this endpoint changed; it was omitted until 2026-09-08, which left callers re-reading the subscription to see the result of their own write.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        400
      </code>
    </td>
    
    <td>
      The desired list could not be reconciled (unknown price, a removal that cannot be credited, or an invalid option combination)
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        404
      </code>
    </td>
    
    <td>
      No such subscription or business
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        409
      </code>
    </td>
    
    <td>
      A concurrent write invalidated the proration this change priced — retry; or the Idempotency-Key was reused with a different payload
    </td>
  </tr>
</tbody>
</table>
</template>

<template v-slot:request="">

```bash [Request]
curl -X POST "$TMBILLING_URL/v1/businesses/biz_XXXXXXXXXXXX01/subscriptions/sub_XXXXXXXXXXXX01/update_for_items" \
  -H "Authorization: Bearer $TMBILLING_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "items": [
      {
        "price": "pro-monthly-usd",
        "quantity": 1
      }
    ]
  }'
```

</template>

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

```json [200 OK]
{
  "object": "subscription_reconciled",
  "id": "sub_XXXXXXXXXXXX01",
  "status": "active",
  "currency": "USD",
  "current_period_end": "2026-04-14T09:30:00Z",
  "next_billing_at": "2026-04-14T09:30:00Z",
  "bill_usage_monthly": false,
  "current_sub_period_start": "2026-03-14T09:30:00Z",
  "current_sub_period_end": "2026-04-14T09:30:00Z",
  "commitment": {
    "amount_minor": 2900
  },
  "skipped_renewals": [
    "2026-04-14T09:30:00Z"
  ],
  "recurring_amount_minor": 2900,
  "revenue_to_date_minor": 2900,
  "revenue_paid_invoices_count": 1,
  "revenue_since": "2026-03-14T09:30:00Z",
  "custom_fields": {
    "cf_region": "West"
  },
  "shipping_address_id": "Shipping address id",
  "shipping_address": {
    "object": "shipping_address",
    "id": "addr_XXXXXXXXXXXX01",
    "customer_id": "cus_XXXXXXXXXXXX01",
    "label": "Example",
    "first_name": "Maya",
    "last_name": "Castillo",
    "company": "Brightwater Analytics",
    "phone": "+1 415 555 0100",
    "line1": "500 Market Street",
    "line2": "Suite 400",
    "line3": "Line3",
    "city": "San Francisco",
    "state": "CA",
    "state_code": "CA",
    "postal_code": "94105",
    "country": "US",
    "is_default": false,
    "archived": false,
    "archived_at": null,
    "created_at": "2026-03-14T09:30:00Z",
    "updated_at": "2026-03-14T09:30:00Z"
  },
  "applied": {
    "changed": 1,
    "added": 0,
    "removed": [
      "extra-seat-monthly-usd"
    ]
  }
}
```

```json [400 Bad Request]
{
  "error": {
    "type": "invalid_request_error",
    "message": "The desired list could not be reconciled (unknown price, a removal that cannot be credited, or an invalid option combination)"
  }
}
```

```json [404 Not Found]
{
  "error": {
    "type": "invalid_request_error",
    "message": "No such subscription or business"
  }
}
```

```json [409 Conflict]
{
  "error": {
    "type": "idempotency_error",
    "message": "A concurrent write invalidated the proration this change priced — retry; or the Idempotency-Key was reused with a different payload"
  }
}
```

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


## Sitemap

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