---
title: Manage billing cycle contracts
description: >-
  Learn how to use the subscriptions billing cycle API to edit the contract
  information on a billing cycle.
source_url:
  html: >-
    https://shopify.dev/docs/apps/build/purchase-options/subscriptions/billing-cycles/manage-billing-cycle-contracts
  md: >-
    https://shopify.dev/docs/apps/build/purchase-options/subscriptions/billing-cycles/manage-billing-cycle-contracts.md
api_name: admin
---

# Manage billing cycle contracts

Billing cycle contracts inherit the contract lines, pricing policy, and delivery policy from the billing cycle's source subscription contract. You might want to edit the contract information on a single billing cycle to make a temporary change, such as replacing an out-of-stock product for one cycle.

This guide shows you how to submit a single-cycle edit to [`subscriptionBillingCycleContractEditCalculate`](https://shopify.dev/docs/api/admin-graphql/2026-10/mutations/subscriptionBillingCycleContractEditCalculate), review Shopify's calculated result, and commit it to the targeted cycle. Fields that you omit remain unchanged.

**Available in the 2026-10 release candidate:**

The `SubscriptionContractCalculation` API is available in the GraphQL Admin API `2026-10` release candidate. If your app uses the `SubscriptionDraft` API, refer to the [legacy billing cycle edit guide](https://shopify.dev/docs/apps/build/purchase-options/subscriptions/billing-cycles/manage-billing-cycle-contracts-with-subscription-draft).

**Combining contracts on the calculate API:**

To combine contracts, see [Combine subscription contracts](https://shopify.dev/docs/apps/build/purchase-options/subscriptions/contracts/combine-subscription-contracts), which uses `subscriptionBillingCycleContractConcatenateCalculate`. `subscriptionBillingCycleContractEditCalculate` rejects a cycle that's already part of a concatenation with an `INVALID` user error that explains the cycle is concatenated.

***

## Requirements

**Note:**

* Most subscriptions, pre-order and try before you buy apps need to request API access through the [Partner Dashboard](https://partners.shopify.com/current/apps). We give API access to apps that are designed according to our [principles for subscriptions, pre-order and TBYB apps](https://shopify.dev/docs/apps/build/purchase-options#shopifys-principles).
* Public apps that use subscriptions, pre-order or TBYB need to meet [specific requirements](https://shopify.dev/docs/apps/launch/shopify-app-store/app-store-requirements#purchase-option) to be published on the Shopify App Store.
* Custom apps [created in the Shopify admin](https://shopify.dev/docs/apps/build/authentication-authorization/legacy/admin-custom-apps) can't use subscriptions, pre-order or TBYB because these apps can't use extensions or request access to protected scopes. If you're building a solution for a single store, then [create your custom app in the Dev Dashboard](https://shopify.dev/docs/apps/build/dev-dashboard/create-apps-using-dev-dashboard).

- Your app can make [authenticated requests](https://shopify.dev/docs/api/admin-graphql#authentication) to the GraphQL Admin API.
- Your app has the `read_own_subscription_contracts` and `write_own_subscription_contracts` [access scopes](https://shopify.dev/docs/api/usage/access-scopes#authenticated-access-scopes). Learn how to [configure your access scopes using Shopify CLI](https://shopify.dev/docs/apps/build/cli-for-apps/app-configuration).
- You've created [products](https://shopify.dev/docs/api/admin-graphql/latest/mutations/productcreate) and [product variants](https://shopify.dev/docs/api/admin-graphql/latest/mutations/productVariantsBulkCreate) in your development store.

* Use the GraphQL Admin API version `2026-10` or later.
* Create an active [subscription contract](https://shopify.dev/docs/apps/build/purchase-options/subscriptions/contracts/build-a-subscription-contract) with a recurring billing and delivery policy.
* Familiarize yourself with [billing cycles](https://shopify.dev/docs/apps/build/purchase-options/subscriptions/billing-cycles).

***

## How billing cycle calculation works

A single-cycle edit uses the same asynchronous calculate and commit flow as a contract update, scoped to one billing cycle:

1. Query the billing cycle and its current contract state.
2. Submit the fields that you want to change for the targeted cycle.
3. Poll the calculation or wait for a webhook. Shopify emits `subscription_contract_calculations/succeed` when a calculation succeeds and `subscription_contract_calculations/fail` when it fails or is voided.
4. Review the calculated contract, projected totals, warnings, or errors.
5. Commit a successful calculation.

Committing applies the edit to the targeted cycle only. It doesn't change the source contract or other cycles, and it doesn't modify orders that have already been created.

**Note:**

You can only edit a current or future cycle on a non-prepaid contract that isn't part of a concatenation. `subscriptionBillingCycleContractEditCalculate` rejects prepaid contracts, past cycles, and concatenated cycles with a user error.

***

## Step 1: Query the billing cycle

Query the billing cycle before calculating an edit so you have its current line IDs. A cycle that's already been edited exposes its current lines on `editedContract`; a cycle that hasn't returns `null` for `editedContract`, so read the lines from `sourceContract` instead. The calculation compounds on the edited contract when one exists, and otherwise on the source contract. When you rebuild the `lines` array, a line's `variantId` is the value that you pass as `productVariantId` in the input. Select the cycle by `index` or `date`:

## POST https://{shop}.myshopify.com/admin/api/2026-10/graphql.json

## GraphQL query

```graphql
query {
  subscriptionBillingCycle(
    billingCycleInput: {
      contractId: "gid://shopify/SubscriptionContract/123"
      selector: { date: "2026-01-10T00:00:00Z" }
    }
  ) {
    cycleIndex
    edited
    sourceContract {
      lines(first: 50) {
        nodes {
          id
          quantity
          variantId
          currentPrice {
            amount
            currencyCode
          }
        }
      }
    }
    editedContract {
      lines(first: 50) {
        nodes {
          id
          quantity
          variantId
          currentPrice {
            amount
            currencyCode
          }
        }
      }
    }
  }
}
```

***

## Step 2: Calculate the edit

Call `subscriptionBillingCycleContractEditCalculate` with the contract ID, a `billingCycleSelector`, and the fields to change for that cycle in `billingCycleEditInput`. Contract-level attributes such as the billing policy, delivery policy, and payment method can't be changed by a billing cycle edit. The following example updates an existing line's quantity and adds a new line:

## POST https://{shop}.myshopify.com/admin/api/2026-10/graphql.json

## GraphQL mutation

```graphql
mutation EditBillingCycleContract {
  subscriptionBillingCycleContractEditCalculate(
    contractId: "gid://shopify/SubscriptionContract/123"
    billingCycleSelector: { date: "2026-01-10T00:00:00Z" }
    billingCycleEditInput: {
      withMerchandiseCustomizations: true
      lines: [
        {
          productVariantLine: {
            id: "gid://shopify/SubscriptionLine/existing-line-uuid"
            productVariantId: "gid://shopify/ProductVariant/111"
            quantity: 2
            customAttributes: []
            discounts: []
          }
        }
        {
          productVariantLine: {
            productVariantId: "gid://shopify/ProductVariant/222"
            quantity: 1
            customAttributes: []
            discounts: []
          }
        }
      ]
    }
  ) {
    subscriptionContractCalculation {
      ... on SubscriptionContractCalculationPending {
        id
      }
    }
    userErrors {
      field
      message
      code
    }
  }
}
```

## JSON response

```json
{
  "data": {
    "subscriptionBillingCycleContractEditCalculate": {
      "subscriptionContractCalculation": {
        "id": "gid://shopify/SubscriptionContractCalculation/789"
      },
      "userErrors": []
    }
  }
}
```

If the mutation returns a user error, then correct the input before polling. Shopify doesn't create an asynchronous calculation when input validation fails.

**Collections replace existing values:**

When you provide `lines` or `manualDiscounts`, the array replaces the complete existing collection for the targeted cycle. Include the IDs and values for every existing item that you want to keep. Omit an existing item to remove it, and omit `id` from a new item.

***

## Step 3: Poll for the calculation result

The calculate mutation returns a pending calculation. Poll for the result and review it before committing.

Contract calculations run asynchronously. Most calculations finish in less than three seconds, but Functions and external services can increase processing time. Use the calculation ID returned by the calculate mutation to query [`subscriptionContractCalculation`](https://shopify.dev/docs/api/admin-graphql/2026-10/queries/subscriptionContractCalculation):

## POST https://{shop}.myshopify.com/admin/api/2026-10/graphql.json

## GraphQL query

```graphql
query PollSubscriptionContractCalculation($id: ID!) {
  subscriptionContractCalculation(id: $id) {
    __typename
    ... on SubscriptionContractCalculationPending {
      id
    }
    ... on SubscriptionContractCalculationSuccess {
      id
      calculatedContract {
        lines(first: 10) {
          nodes {
            id
            quantity
            title
            variantId
          }
        }
      }
      warnings {
        code
        message
      }
      projectedOrderTotals {
        subtotal {
          amount
          currencyCode
        }
        totalDelivery {
          amount
          currencyCode
        }
        totalTax {
          amount
          currencyCode
        }
        totalMerchandiseDiscounts {
          amount
          currencyCode
        }
        totalDeliveryDiscounts {
          amount
          currencyCode
        }
        total {
          amount
          currencyCode
        }
      }
    }
    ... on SubscriptionContractCalculationFailure {
      id
      errors {
        code
        message
      }
    }
  }
}
```

## Variables

```json
{
  "id": "gid://shopify/SubscriptionContractCalculation/789"
}
```

## Pending response

```json
{
  "data": {
    "subscriptionContractCalculation": {
      "__typename": "SubscriptionContractCalculationPending",
      "id": "gid://shopify/SubscriptionContractCalculation/789"
    }
  }
}
```

The query returns one of the following types:

| Type | Meaning | Action |
| - | - | - |
| `SubscriptionContractCalculationPending` | Shopify initiated or is processing the calculation. | Continue polling. |
| `SubscriptionContractCalculationSuccess` | The calculated contract is ready to review and commit. | Review the result before committing. |
| `SubscriptionContractCalculationFailure` | The calculation failed (processed with errors) or Shopify voided it (never processed, for example, because of an infrastructure issue). | If it failed, read `errors` and correct the input before calculating again. If it was voided, `errors` is empty and there's nothing to fix in the input, so retry the calculation. |

Wait two seconds before the first poll, poll every second, and stop after 30 seconds. If the calculation is still pending, then retry later or wait for a webhook instead of increasing the polling frequency.

### Review the calculated contract

Before committing, inspect the following fields on `SubscriptionContractCalculationSuccess`:

* `calculatedContract`: The complete contract snapshot that the commit applies.
* `projectedOrderTotals`: The projected merchandise, delivery, discount, tax, and total amounts.
* `warnings`: Existing-data or compatibility problems that don't prevent the calculation from succeeding.

A successful calculation is immutable. If you need to change the input, then start a new calculation and commit only the result that you want to make active.

**Review warnings:**

A successful calculation can contain warnings. Review them before committing because they can identify disabled currencies, delivery configuration problems, or other existing contract data that Shopify preserved instead of blocking the update.

### Use webhooks instead of polling

For event-driven processing, subscribe to the following webhook topics:

| Topic | Description |
| - | - |
| `subscription_contract_calculations/succeed` | The calculation succeeded and is ready to review and commit. |
| `subscription_contract_calculations/fail` | The calculation failed or Shopify voided it. A failed calculation has `errors` to query; a voided one has empty `errors`, so retry it. |

Create a webhook subscription with [`webhookSubscriptionCreate`](https://shopify.dev/docs/api/admin-graphql/2026-10/mutations/webhookSubscriptionCreate):

## POST https://{shop}.myshopify.com/admin/api/2026-10/graphql.json

## GraphQL mutation

```graphql
mutation CreateCalculationWebhook {
  webhookSubscriptionCreate(
    topic: SUBSCRIPTION_CONTRACT_CALCULATIONS_SUCCEED
    webhookSubscription: {
      uri: "https://example.com/webhooks/calculation-success"
      format: JSON
    }
  ) {
    webhookSubscription {
      id
    }
    userErrors {
      field
      message
    }
  }
}
```

## Webhook payload

```json
{
  "id": 789,
  "admin_graphql_api_id": "gid://shopify/SubscriptionContractCalculation/789",
  "state": "succeeded"
}
```

Use `admin_graphql_api_id` to query the completed calculation. Webhook delivery doesn't commit the result automatically.

***

## Step 4: Commit the calculation

Committing applies the calculated contract to the targeted billing cycle. Call [`subscriptionContractCalculationCommit`](https://shopify.dev/docs/api/admin-graphql/2026-10/mutations/subscriptionContractCalculationCommit). A billing cycle edit commits an ephemeral contract, so the committed `contract` returns as a `SubscriptionBillingCycleEditedContract`, not a `SubscriptionContract`. Select that fragment to read the edited cycle's fields:

## POST https://{shop}.myshopify.com/admin/api/2026-10/graphql.json

## GraphQL mutation

```graphql
mutation CommitBillingCycleEditCalculation {
  subscriptionContractCalculationCommit(
    id: "gid://shopify/SubscriptionContractCalculation/789"
  ) {
    contract {
      ... on SubscriptionBillingCycleEditedContract {
        lines(first: 10) {
          nodes {
            id
            quantity
            variantId
          }
        }
      }
    }
    userErrors {
      field
      message
      code
    }
  }
}
```

## JSON response

```json
{
  "data": {
    "subscriptionContractCalculationCommit": {
      "contract": {
        "lines": {
          "nodes": [
            {
              "id": "gid://shopify/SubscriptionLine/existing-line-uuid",
              "quantity": 2,
              "variantId": "gid://shopify/ProductVariant/111"
            },
            {
              "id": "gid://shopify/SubscriptionLine/new-line-uuid",
              "quantity": 1,
              "variantId": "gid://shopify/ProductVariant/222"
            }
          ]
        }
      },
      "userErrors": []
    }
  }
}
```

Handle commit user errors by `code`:

| Code | Meaning | Action |
| - | - | - |
| `NOT_READY_TO_COMMIT` | The calculation hasn't succeeded yet. | Continue polling before retrying the commit. |
| `CALCULATION_NOT_FOUND` | The calculation ID doesn't exist or isn't available to the app. | Verify the ID and app access. |
| `STALE_CONTRACT` | The contract changed after this calculation started. | Query the latest contract state and calculate again. |
| `INVALID` | Shopify can't commit the calculation. | Use `field` and `message` to correct the request. |

You can safely retry polling and commit requests. Retrying a calculate mutation creates a new calculation, so retain the ID for the result that you intend to commit. Retrying a commit for an already committed calculation returns the committed result.

***

## Step 5: Fetch the edited contract

After committing, you can fetch the edited contract on the billing cycle with the [`subscriptionBillingCycle`](https://shopify.dev/docs/api/admin-graphql/2026-10/queries/subscriptionBillingCycle) query:

## POST https://{shop}.myshopify.com/admin/api/2026-10/graphql.json

## GraphQL query

```graphql
query {
  subscriptionBillingCycle(
    billingCycleInput: {
      contractId: "gid://shopify/SubscriptionContract/123"
      selector: { index: 5 }
    }
  ) {
    cycleIndex
    editedContract {
      lines(first: 10) {
        nodes {
          id
          quantity
          variantId
        }
      }
    }
  }
}
```

***

## Step 6: Create an order

When the billing date comes, use the [`subscriptionBillingCycleCharge`](https://shopify.dev/docs/api/admin-graphql/2026-10/mutations/subscriptionBillingCycleCharge) mutation to create an order for the cycle. This mutation allows a single successful charge per billing cycle, and only charges cycles with a billing attempt expected date in the past or within the next 24 hours.

Billing attempts are processed asynchronously, so the resulting order isn't available right away. Fetch the billing attempt with the [`subscriptionBillingAttempt`](https://shopify.dev/docs/api/admin-graphql/latest/queries/subscriptionBillingAttempt) query and check its [`state`](https://shopify.dev/docs/api/admin-graphql/latest/objects/SubscriptionBillingAttempt#field-SubscriptionBillingAttempt.fields.state). `SubscriptionBillingAttemptPendingState` means the attempt is still processing, `SubscriptionBillingAttemptSuccessState` contains the order, `SubscriptionBillingAttemptFailedState` contains the error, and `SubscriptionBillingAttemptActionRequiredState` means the customer needs to complete a 3D Secure challenge. The challenge URL is in the `nextActionUrl` field of its `action`.

## POST https://{shop}.myshopify.com/admin/api/2026-10/graphql.json

## GraphQL mutation

```graphql
mutation {
  subscriptionBillingCycleCharge(
    subscriptionContractId: "gid://shopify/SubscriptionContract/123"
    billingCycleSelector: { date: "2026-01-10T00:00:00Z" }
  ) {
    subscriptionBillingAttempt {
      id
      state {
        __typename
        ... on SubscriptionBillingAttemptSuccessState {
          order {
            id
          }
        }
        ... on SubscriptionBillingAttemptFailedState {
          error {
            __typename
            ... on SubscriptionBillingAttemptPaymentError {
              code
            }
          }
        }
      }
    }
  }
}
```

***

## Billing cycle edit input

Use the following semantics when preparing `billingCycleEditInput`:

| Input | Behavior |
| - | - |
| `withMerchandiseCustomizations` | Required. Set to `true` to run Shopify Functions that customize merchandise. Set to `false` to bypass them. |
| `lines` | Replaces all lines for the cycle. Include an `id` to keep or update an existing line. Omit `id` to add a line. Omit the field to leave all lines unchanged. At least one line is required. |
| `manualDiscounts` | Replaces all manual order and delivery discounts for the cycle. Pass `[]` to remove all manual discounts. |
| `discountCodes` | Applies codes during this calculation. Shopify resolves them into manual discounts on the cycle instead of storing the codes. |
| `deliveryMethod` | Replaces the delivery method for the cycle. Pass `none` to remove it, or `fetchAvailableDeliveryOptions` to discover options without changing the committed method. The resulting discovery calculation can't be committed. |
| `note` | Replaces the note. Pass `null` to clear it. |
| `customAttributes` | Replaces all attributes for the cycle. Pass `[]` to remove them. |

***

## Next steps

* Learn how to [bulk charge multiple contracts at the same time](https://shopify.dev/docs/apps/build/purchase-options/subscriptions/billing-cycles/bulk-billing-cycles).
* Refer to the [`SubscriptionContractCalculationBillingCycleEditInput`](https://shopify.dev/docs/api/admin-graphql/2026-10/input-objects/SubscriptionContractCalculationBillingCycleEditInput) fields.
* Use the [legacy `SubscriptionDraft` billing cycle edit guide](https://shopify.dev/docs/apps/build/purchase-options/subscriptions/billing-cycles/manage-billing-cycle-contracts-with-subscription-draft) while maintaining an older integration.

***
