Skip to main content

Migrate to the SubscriptionContractCalculation API

This guide walks you through migrating from the SubscriptionDraft API to the new SubscriptionContractCalculation API for managing subscription contracts.

Early access

This API is in early access. Send feedback to subscriptions-calculate-api-early-access@shopify.com.



Anchor to What's changing and whyWhat's changing and why

The new API integrates with Shopify's unified checkout and provides the following benefits:

  • Simplified API surface: Contract mutations are consolidated into three calculate operations plus a shared commit.
  • Consistent pricing: Tax and discount calculations align with checkout.
  • Function support: Cart transforms and delivery customizations. Other Shopify Functions will be supported in later releases.
  • Accurate cost breakdown: Totals for lines, delivery, taxes, duties, and discounts are available through the API.

The following table summarizes the main differences between SubscriptionDraft and the new SubscriptionContractCalculation API.

AspectSubscriptionDraftSubscriptionContractCalculation
ApproachMulti-step draft mutationsSingle contract calculation
ProcessingSynchronousAsynchronous (requires polling)
State managementServer maintains draft stateClient provides desired state; omitted fields are preserved on update
WebhooksNoneWebhooks on success or failure

Anchor to Understand the new API modelUnderstand the new API model

Anchor to From drafts to contract calculationsFrom drafts to contract calculations

With the current SubscriptionDraft API, you create a draft, apply changes through multiple mutations, and then commit the draft. With the new SubscriptionContractCalculation API, you submit the desired state in a single request. The server calculates pricing, applies taxes, executes functions, and returns an immutable result snapshot. On contract updates, any field you omit is left unchanged — you don't need to resend the entire contract.

Anchor to Asynchronous processingAsynchronous processing

Calculations run asynchronously. You can poll for results or subscribe to webhooks. Here's the flow:

  1. Call the calculate mutation.
  2. Receive a SubscriptionContractCalculationPending response.
  3. Poll the subscriptionContractCalculation query, or wait for a webhook.
  4. Receive a SubscriptionContractCalculationSuccess or SubscriptionContractCalculationFailure response when complete.
  5. Call subscriptionContractCalculationCommit to apply the changes.

The following Boolean fields control contract calculation behavior:

FieldLocationDescription
withMerchandiseCustomizationsTop-level create or update inputWhen set to true, enables cart transforms and other function-based merchandise modifications.
withDeliveryCustomizationsdeliveryMethod.fetchAvailableDeliveryOptionsWhen true (the default), runs delivery customization functions while fetching delivery options, so the returned options reflect the merchant's configured customizations. Set to false to bypass these functions and return the raw options.

Anchor to API migration referenceAPI migration reference

Old mutationNew approach
subscriptionContractCreateUse subscriptionContractCreateCalculate
subscriptionContractUpdateUse subscriptionContractUpdateCalculate
subscriptionContractAtomicCreateUse subscriptionContractCreateCalculate
subscriptionContractProductChangeUse subscriptionContractUpdateCalculate
subscriptionDraftLineAddInclude in lines[] input
subscriptionDraftLineUpdateInclude updated line with the same id in lines[]
subscriptionDraftLineRemoveOmit line from lines[]
subscriptionDraftDiscountAddInclude orderDiscount in manualDiscounts[] input
subscriptionDraftDiscountUpdateInclude updated orderDiscount with the same id in manualDiscounts[]
subscriptionDraftDiscountRemoveOmit discount from manualDiscounts[]
subscriptionDraftDiscountCodeApplyInclude in discountCodes[] input
subscriptionDraftFreeShippingDiscountAddInclude deliveryDiscount in manualDiscounts[]
subscriptionDraftFreeShippingDiscountUpdateInclude updated deliveryDiscount with the same id in manualDiscounts[]
subscriptionDraftUpdateRecalculate with additional input
subscriptionDraftCommitUse subscriptionContractCalculationCommit
subscriptionBillingCycleContractEditUse subscriptionBillingCycleContractEditCalculate
subscriptionBillingCycleContractDraftConcatenateNo calculate API equivalent yet; continue using the draft API
subscriptionBillingCycleContractDraftCommitUse subscriptionContractCalculationCommit

Anchor to Deprecated and renamed fieldsDeprecated and renamed fields

The SubscriptionContractCalculation API introduces changes to how certain contract attributes are managed. The following fields have been deprecated, renamed, or moved.

Contract status is now managed using dedicated mutations instead of being set through the draft or contract calculation input. This change decouples contract status from the contract versioning model.

Old approachNew approach
Set status field in SubscriptionDraftInput during commit.Use dedicated status mutations: subscriptionContractActivate, subscriptionContractPause, subscriptionContractCancel, subscriptionContractExpire, subscriptionContractFail.

The pricingPolicy field on subscription lines has been renamed to appManagedPricingPolicy. This name change clarifies that the field contains app-managed pricing data that isn't used during contract calculation. The SubscriptionContractCalculation API calculates prices based on the current catalog and any applied discounts.

Old fieldNew field
pricingPolicyappManagedPricingPolicy

Anchor to Billing configurationBilling configuration

The nextBillingDate, minCycles, and maxCycles fields have been moved into a new AppManagedBillingConfig object. This consolidates app-managed billing metadata that isn't used during contract calculation into a single location.

Old fieldsNew field
nextBillingDate, minCycles, maxCyclesappManagedBillingConfig.nextBillingDate, appManagedBillingConfig.minCycles, appManagedBillingConfig.maxCycles

Anchor to Multiple fulfillment configurationMultiple fulfillment configuration

Multi fulfillment is now optional

Passing multiFulfillment is only required for pre-paid subscriptions that have multiple deliveries per billing period. Omit the multi fulfillment input to use the billing cadence as the delivery cadence.

The SubscriptionContractCalculation API introduces explicit control over the number of fulfillments per billing cycle through a new multiFulfillment field. Previously, the number of deliveries per billing cycle was implicitly derived from the relationship between billing and delivery policy intervals. The new approach makes this configuration explicit.

The multiFulfillment field accepts a SubscriptionMultipleFulfillmentConfigInput with the following fields:

FieldTypeRequiredDescription
cadenceObjectYesThe cadence for calculating fulfillment dates.
numberOfFulfillmentsIntegerYesThe number of fulfillments per billing cycle. Must be at least 2.

Use the GraphQL Admin API docs to explore the new API mutations, input types, and return types.


Anchor to Create a subscription contractCreate a subscription contract

Anchor to Old approach (SubscriptionDraft)Old approach (SubscriptionDraft)

With the SubscriptionDraft API, creating a contract requires multiple sequential mutations:

  1. Create a draft for the new contract using subscriptionContractCreate.
  2. Add lines to the draft using subscriptionDraftLineAdd.
  3. Commit the draft to create the contract using subscriptionDraftCommit.

Anchor to New approach (SubscriptionContractCalculation)New approach (SubscriptionContractCalculation)

With the SubscriptionContractCalculation API, you submit the complete desired state for the new contract in a single mutation. The server calculates pricing, applies taxes, and returns an immutable result snapshot.

  1. Submit the new contract's desired state to create a contract calculation:

    POST https://{shop}.myshopify.com/admin/api/{api_version}/graphql.json

    Create contract calculation

    mutation CreateCalculation {
    subscriptionContractCreateCalculate(
    contractCreateInput: {
    # Control contract calculation behavior
    withMerchandiseCustomizations: true

    # Customer to create the contract for (must belong to the current shop)
    customerId: "gid://shopify/Customer/123"

    # Currency for the contract
    currencyCode: USD

    # Define all lines (at least one required)
    lines: [
    {
    productVariantLine: {
    productVariantId: "gid://shopify/ProductVariant/111"
    quantity: 1
    customAttributes: []
    discounts: [] # Line-scoped discounts; pass [] for none
    }
    }
    ]

    # Define billing policy
    billingPolicy: {
    cadence: {
    unit: MONTH
    count: 1
    }
    }

    # Define delivery policy
    deliveryPolicy: {
    multiFulfillment: {
    cadence: { unit: WEEK, count: 2 }
    numberOfFulfillments: 2
    }
    }

    # Define delivery method (use { none: true } for digital-only subscriptions)
    deliveryMethod: {
    shipping: {
    address: {
    firstName: "Quinn"
    lastName: "Ishida"
    address1: "123 Main St"
    city: "Toronto"
    provinceCode: "ON"
    countryCode: CA
    zip: "M5V 1A1"
    }
    deliveryPrice: {
    amount: "5.00"
    currencyCode: USD
    }
    }
    }

    # Define payment method (use { none: true } for no payment method)
    paymentMethod: {
    customerPaymentMethod: {
    id: "gid://shopify/CustomerPaymentMethod/456"
    }
    }

    # Define discount codes to apply (pass an empty array for none)
    discountCodes: []

    # Define manual discounts (pass an empty array for no manual discounts)
    # Supports two types: orderDiscount, deliveryDiscount
    manualDiscounts: []

    # Define custom attributes (pass an empty array for none)
    customAttributes: []
    }
    ) {
    subscriptionContractCalculation {
    ... on SubscriptionContractCalculationPending {
    id
    }
    }
    userErrors {
    field
    message
    code
    }
    }
    }

    JSON response

    {
    "data": {
    "subscriptionContractCreateCalculate": {
    "subscriptionContractCalculation": {
    "id": "gid://shopify/SubscriptionContractCalculation/789"
    },
    "userErrors": []
    }
    }
    }
  2. Poll for the contract calculation result:

    POST https://{shop}.myshopify.com/admin/api/{api_version}/graphql.json

    Poll for result

    query PollCalculation {
    subscriptionContractCalculation(
    id: "gid://shopify/SubscriptionContractCalculation/789"
    ) {
    __typename
    ... on SubscriptionContractCalculationPending {
    id
    }
    ... on SubscriptionContractCalculationSuccess {
    id
    calculatedContract {
    lines(first: 10) {
    edges {
    node {
    id
    variantId
    quantity
    }
    }
    }
    }
    }
    ... on SubscriptionContractCalculationFailure {
    id
    errors { code }
    }
    }
    }

    JSON response (pending)

    {
    "data": {
    "subscriptionContractCalculation": {
    "__typename": "SubscriptionContractCalculationPending",
    "id": "gid://shopify/SubscriptionContractCalculation/789"
    }
    }
    }
  3. Commit the contract calculation to create the contract:

    POST https://{shop}.myshopify.com/admin/api/{api_version}/graphql.json

    Commit contract calculation

    mutation CommitCalculation {
    subscriptionContractCalculationCommit(
    id: "gid://shopify/SubscriptionContractCalculation/789"
    ) {
    # The return type is a union: SubscriptionContract (create/update)
    # or SubscriptionBillingCycleEditedContract (billing cycle edit).
    contract {
    ... on SubscriptionContract {
    id
    status
    }
    }
    userErrors {
    field
    message
    }
    }
    }

    JSON response

    {
    "data": {
    "subscriptionContractCalculationCommit": {
    "contract": {
    "id": "gid://shopify/SubscriptionContract/999",
    "status": "ACTIVE"
    },
    "userErrors": []
    }
    }
    }

Anchor to Update a subscription contractUpdate a subscription contract

Anchor to Old approach (SubscriptionDraft)Old approach (SubscriptionDraft)

With the SubscriptionDraft API, updating a contract requires multiple sequential mutations:

  1. Create a draft from the existing contract using subscriptionContractUpdate.
  2. Add, update, or remove lines using subscriptionDraftLineAdd, subscriptionDraftLineUpdate, or subscriptionDraftLineRemove.
  3. Apply discounts using subscriptionDraftDiscountCodeApply, subscriptionDraftDiscountAdd, or subscriptionDraftFreeShippingDiscountAdd.
  4. Commit the draft to apply changes using subscriptionDraftCommit.

Anchor to New approach (SubscriptionContractCalculation)New approach (SubscriptionContractCalculation)

With the SubscriptionContractCalculation API, you submit only the fields you want to change in a single mutation. Omitted fields are left unchanged.

Fetch the existing SubscriptionContract state using the subscriptionContract query, then apply any changes before submitting an updated version of the SubscriptionContract attributes for contract calculation.

  1. Submit the desired state to create a contract calculation. On an update, only withMerchandiseCustomizations must be provided; all other fields are optional and omitted fields are left unchanged:

    POST https://{shop}.myshopify.com/admin/api/{api_version}/graphql.json

    Create contract calculation

    mutation UpdateCalculation {
    subscriptionContractUpdateCalculate(
    contractId: "gid://shopify/SubscriptionContract/123"
    contractUpdateInput: {
    # Control contract calculation behavior
    withMerchandiseCustomizations: true

    # Define all lines (replaces existing lines when provided)
    lines: [
    {
    productVariantLine: {
    # Include id to keep existing line
    id: "gid://shopify/SubscriptionLine/existing-line-uuid"
    productVariantId: "gid://shopify/ProductVariant/111"
    quantity: 1
    customAttributes: []
    discounts: [] # Line-scoped discounts; pass [] for none
    }
    }
    {
    productVariantLine: {
    # Omit id to add new line
    productVariantId: "gid://shopify/ProductVariant/789"
    quantity: 2
    customAttributes: []
    discounts: []
    }
    }
    ]

    # Define discount codes to apply (resolved into manual discounts; not persisted as codes)
    discountCodes: [
    { redeemCode: "SAVE10" }
    ]

    # Define all manual discounts (replaces existing manual discounts)
    # Supports two types: orderDiscount, deliveryDiscount
    manualDiscounts: [
    {
    orderDiscount: {
    # Include id to keep an existing order discount
    id: "gid://shopify/SubscriptionDiscount/existing-discount-uuid"
    title: "10% off subscription"
    value: {
    percentage: 10
    }
    recurringCycleLimit: 0 # 0 = no limit
    }
    }
    {
    deliveryDiscount: {
    # Omit id for a new delivery discount
    title: "Free shipping for 3 months"
    value: {
    percentage: 100
    }
    recurringCycleLimit: 3 # Apply for 3 billing cycles
    }
    }
    ]

    # Define billing policy (replaces existing billing policy)
    billingPolicy: {
    anchors: [
    {
    monthday: {
    dayOfMonth: 7
    }
    }
    ]
    cadence: {
    unit: MONTH
    count: 2
    }
    }

    # Define delivery policy (replaces existing delivery policy)
    deliveryPolicy: {
    anchors: {
    weekday: {
    dayOfWeek: TUESDAY
    }
    }
    multiFulfillment: {
    cadence: { unit: WEEK, count: 1 }
    numberOfFulfillments: 8
    }
    }

    # Define delivery method (replaces existing delivery method when provided)
    deliveryMethod:{
    pickup: {
    title: "Test",
    locationId: "gid://shopify/Location/1234",
    deliveryPrice: {
    amount: "0",
    currencyCode: GBP
    }
    }
    }

    # Define payment method (replaces existing payment method when provided)
    paymentMethod: {
    customerPaymentMethod: {
    id: "gid://shopify/CustomerPaymentMethod/1234"
    }
    }

    # Define note (replaces existing note)
    note: "Test"
    }
    ) {
    subscriptionContractCalculation {
    ... on SubscriptionContractCalculationPending {
    id
    }
    }
    userErrors {
    field
    message
    }
    }
    }

    JSON response

    {
    "data": {
    "subscriptionContractUpdateCalculate": {
    "subscriptionContractCalculation": {
    "id": "gid://shopify/SubscriptionContractCalculation/789"
    },
    "userErrors": []
    }
    }
    }
  2. Poll for the contract calculation result:

    POST https://{shop}.myshopify.com/admin/api/{api_version}/graphql.json

    Poll for result

    query PollCalculation {
    subscriptionContractCalculation(
    id: "gid://shopify/SubscriptionContractCalculation/789"
    ) {
    __typename
    ... on SubscriptionContractCalculationPending {
    id
    }
    ... on SubscriptionContractCalculationSuccess {
    id
    calculatedContract {
    lines(first: 10) {
    edges {
    node {
    id
    variantId
    quantity
    }
    }
    }
    }
    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 }
    }
    }
    }

    JSON response (pending)

    {
    "data": {
    "subscriptionContractCalculation": {
    "__typename": "SubscriptionContractCalculationPending",
    "id": "gid://shopify/SubscriptionContractCalculation/789"
    }
    }
    }
  3. Commit the contract calculation to apply changes:

    POST https://{shop}.myshopify.com/admin/api/{api_version}/graphql.json

    Commit contract calculation

    mutation CommitCalculation {
    subscriptionContractCalculationCommit(
    id: "gid://shopify/SubscriptionContractCalculation/789"
    ) {
    # The return type is a union: SubscriptionContract (create/update)
    # or SubscriptionBillingCycleEditedContract (billing cycle edit).
    contract {
    ... on SubscriptionContract {
    id
    status
    }
    }
    userErrors {
    field
    message
    }
    }
    }

    JSON response

    {
    "data": {
    "subscriptionContractCalculationCommit": {
    "contract": {
    "id": "gid://shopify/SubscriptionContract/123",
    "status": "ACTIVE"
    },
    "userErrors": []
    }
    }
    }

Anchor to Edit a single billing cycleEdit a single billing cycle

Apply a one-time change to a single billing cycle of a subscription without changing the recurring contract. Later cycles continue to bill from the underlying contract. A billing cycle can contain more than one delivery (for example, prepaid or multi-fulfillment subscriptions), and the edit applies to the whole cycle.

Anchor to Old approach (SubscriptionDraft)Old approach (SubscriptionDraft)

With the SubscriptionDraft object, editing one billing cycle requires multiple sequential mutations:

  1. Create a draft scoped to the target cycle using subscriptionBillingCycleContractEdit.
  2. Add, update, or remove lines and discounts on the draft using the subscriptionDraft* mutations.
  3. Commit the draft using subscriptionBillingCycleContractDraftCommit.

Anchor to New approach (SubscriptionContractCalculation)New approach (SubscriptionContractCalculation)

With the SubscriptionContractCalculation API, you submit the complete desired state for the cycle in a single mutation and identify the cycle with a selector, then poll and commit exactly as you do for a contract update.

  1. Submit the desired state for the target cycle to create a contract calculation, identifying the cycle with a billingCycleSelector. Select the cycle either:

    1. By its index:

      mutation {
      subscriptionBillingCycleContractEditCalculate(
      contractId: "gid://shopify/SubscriptionContract/1"
      billingCycleSelector: { index: 2 }
      billingCycleEditInput: {
      withMerchandiseCustomizations: true,
      # The complete desired state for this cycle (lines, discounts, delivery, and so on).
      lines: [
      {
      productVariantLine: {
      productVariantId: "gid://shopify/ProductVariant/1"
      quantity: 2
      customAttributes: []
      discounts: [] # Line-scoped discounts; pass [] for none
      }
      }
      ]
      }
      ) {
      subscriptionContractCalculation {
      ... on SubscriptionContractCalculationPending {
      id
      }
      }
      userErrors {
      field
      message
      }
      }
      }
    2. Or by a date that falls within the cycle:

      billingCycleSelector: { date: "2025-06-01T00:00:00Z" }
  2. Poll the calculation and commit it as described in Update a subscription contract. You wait for the calculation to succeed, then commit it with subscriptionContractCalculationCommit.

Committing creates and commits a new contract version scoped to that single billing cycle, leaving the recurring contract unchanged.


The SubscriptionContractCalculation API splits discounts into two separate input fields that replace the multiple draft-based discount mutations:

  • discountCodes[]: Discount codes to apply during the calculation. Applied codes are resolved into manual discounts on the resulting contract; codes aren't persisted as codes, so there's nothing to preserve or replace across calculations. Defaults to an empty array.
  • manualDiscounts[]: Manual order and delivery discounts. This is a @oneOf input — each entry is either an orderDiscount or a deliveryDiscount.

For line-scoped discounts (discounts that apply to a single line), use the discounts[] field on each line input. See Line-scoped discounts.

Manual discounts replacement behavior

The manualDiscounts array replaces all existing manual discounts on the contract. To keep an existing discount, include it in the array with its id. To remove a discount, omit it from the array. To add a new discount, include it without an id. To remove all manual discounts, pass an empty array ([]).

Discount typeOld mutation(s)New input fieldDescription
Code discountsubscriptionDraftDiscountCodeApplydiscountCodes[]Apply a discount code by its redeem code.
Order discountsubscriptionDraftDiscountAdd, subscriptionDraftDiscountUpdate, subscriptionDraftDiscountRemovemanualDiscounts[].orderDiscountApply a fixed or percentage discount to all lines.
Delivery discountsubscriptionDraftFreeShippingDiscountAdd, subscriptionDraftFreeShippingDiscountUpdatemanualDiscounts[].deliveryDiscountApply a discount to shipping or delivery charges.
Line-scoped discountsubscriptionDraftDiscountAdd (line-scoped)lines[].productVariantLine.discounts[]Apply a fixed or percentage discount to a single line.

Anchor to Apply a discount codeApply a discount code

To apply a discount code to an existing contract, include an entry in the discountCodes array:

POST https://{shop}.myshopify.com/admin/api/{api_version}/graphql.json

Apply code discount

mutation ApplyCodeDiscount {
subscriptionContractUpdateCalculate(
contractId: "gid://shopify/SubscriptionContract/123"
contractUpdateInput: {
discountCodes: [
{ redeemCode: "SAVE10" }
]
}
) {
subscriptionContractCalculation {
... on SubscriptionContractCalculationPending {
id
}
}
userErrors {
field
message
}
}
}

Anchor to Apply an order discountApply an order discount

Order discounts apply to all lines on the contract. They support percentage or fixed amount values:

POST https://{shop}.myshopify.com/admin/api/{api_version}/graphql.json

Percentage order discount

mutation ApplyOrderDiscount {
subscriptionContractUpdateCalculate(
contractId: "gid://shopify/SubscriptionContract/123"
contractUpdateInput: {
manualDiscounts: [
{
orderDiscount: {
title: "10% loyalty discount"
value: {
percentage: 10
}
recurringCycleLimit: 0 # 0 = applies indefinitely
}
}
]
}
) {
subscriptionContractCalculation {
... on SubscriptionContractCalculationPending {
id
}
}
userErrors {
field
message
}
}
}

POST https://{shop}.myshopify.com/admin/api/{api_version}/graphql.json

Fixed amount order discount

mutation ApplyFixedOrderDiscount {
subscriptionContractUpdateCalculate(
contractId: "gid://shopify/SubscriptionContract/123"
contractUpdateInput: {
manualDiscounts: [
{
orderDiscount: {
title: "$5 off subscription"
value: {
fixedAmount: {
appliesOnEachItem: true
amount: { amount: "5.00", currencyCode: USD }
}
}
recurringCycleLimit: 3 # Applies for 3 billing cycles
}
}
]
}
) {
subscriptionContractCalculation {
... on SubscriptionContractCalculationPending {
id
}
}
userErrors {
field
message
}
}
}

Anchor to Apply a delivery discountApply a delivery discount

Delivery discounts apply to shipping or delivery charges. For example, to offer free shipping:

POST https://{shop}.myshopify.com/admin/api/{api_version}/graphql.json

Free shipping discount

mutation ApplyDeliveryDiscount {
subscriptionContractUpdateCalculate(
contractId: "gid://shopify/SubscriptionContract/123"
contractUpdateInput: {
manualDiscounts: [
{
deliveryDiscount: {
title: "Free shipping"
value: {
percentage: 100
}
recurringCycleLimit: 0 # 0 = free shipping indefinitely
}
}
]
}
) {
subscriptionContractCalculation {
... on SubscriptionContractCalculationPending {
id
}
}
userErrors {
field
message
}
}
}

Anchor to Combine multiple discountsCombine multiple discounts

You can apply multiple discounts of different types in a single calculation. Include discount codes in discountCodes[] and manual discounts in manualDiscounts[]:

POST https://{shop}.myshopify.com/admin/api/{api_version}/graphql.json

Multiple discount types

mutation ApplyMultipleDiscounts {
subscriptionContractUpdateCalculate(
contractId: "gid://shopify/SubscriptionContract/123"
contractUpdateInput: {
discountCodes: [
{ redeemCode: "SAVE10" }
]
manualDiscounts: [
{
orderDiscount: {
title: "Loyalty reward"
value: { percentage: 5 }
recurringCycleLimit: 0
}
}
{
deliveryDiscount: {
title: "Free shipping promo"
value: { percentage: 100 }
recurringCycleLimit: 6
}
}
]
}
) {
subscriptionContractCalculation {
... on SubscriptionContractCalculationPending {
id
}
}
userErrors {
field
message
}
}
}

Anchor to Line-scoped discountsLine-scoped discounts

To apply a discount to a single line, include it in the discounts[] field on that line's input. Each line-scoped discount takes title, value, and recurringCycleLimit fields, and optionally an id to update an existing line discount:

POST https://{shop}.myshopify.com/admin/api/{api_version}/graphql.json

Line-scoped discount

mutation ApplyLineDiscount {
subscriptionContractUpdateCalculate(
contractId: "gid://shopify/SubscriptionContract/123"
contractUpdateInput: {
lines: [
{
productVariantLine: {
id: "gid://shopify/SubscriptionLine/line-uuid-1"
productVariantId: "gid://shopify/ProductVariant/111"
quantity: 1
customAttributes: []
discounts: [
{
title: "10% off this line"
value: { percentage: 10 }
recurringCycleLimit: 0
}
]
}
}
]
}
) {
subscriptionContractCalculation {
... on SubscriptionContractCalculationPending {
id
}
}
userErrors {
field
message
}
}
}

Anchor to Remove all discountsRemove all discounts

To remove all manual discounts from a contract, pass an empty manualDiscounts array and an empty discountCodes array:

POST https://{shop}.myshopify.com/admin/api/{api_version}/graphql.json

Remove all discounts

mutation RemoveAllDiscounts {
subscriptionContractUpdateCalculate(
contractId: "gid://shopify/SubscriptionContract/123"
contractUpdateInput: {
manualDiscounts: []
discountCodes: []
}
) {
subscriptionContractCalculation {
... on SubscriptionContractCalculationPending {
id
}
}
userErrors {
field
message
}
}
}

Anchor to Fetch delivery optionsFetch delivery options

The deliveryOptions field on SubscriptionContractCalculationSuccess returns the available delivery options that the calculation computed. This field replaces the SubscriptionDraft.deliveryOptions field from the SubscriptionDraft API, and returns all available options.

The SubscriptionContractCalculationSuccess type also exposes:

  • warnings: Non-fatal warnings produced during the calculation. The calculation still succeeded; warnings inform the merchant about issues that should be reviewed before committing.
  • projectedOrderTotals: Projected order totals (subtotal, total delivery, estimated tax, merchandise discounts, delivery discounts, and grand total) for the calculated contract. Null when the calculation only discovered delivery options for an address rather than calculating committed totals.

Each option is a SubscriptionContractCalculationDeliveryOption, which resolves to one of the following types:

  • SubscriptionContractCalculationShippingOption
  • SubscriptionContractCalculationLocalDeliveryOption
  • SubscriptionContractCalculationPickupOption

Fetching delivery options is opt-in. A calculation that sets or keeps a committed delivery method uses a faster path that doesn't look up the full set of rates, so it doesn't return the complete set of options. To fetch all available options for an address, provide fetchAvailableDeliveryOptions in the deliveryMethod input instead of a method. This fetches the options but leaves the contract's committed delivery method unchanged:

  • On a create, the calculation commits no method.
  • On an update, the calculation inherits the existing method.
Note

deliveryMethod is a @oneOf input. Provide exactly one of shipping, localDelivery, pickup, none, or fetchAvailableDeliveryOptions. Providing a committed method and fetchAvailableDeliveryOptions in the same calculation is rejected.

Anchor to Old approach (SubscriptionDraft)Old approach (SubscriptionDraft)

With the SubscriptionDraft API, fetching and setting a delivery option requires multiple sequential mutations and a separate asynchronous query:

  1. Create a draft using subscriptionContractUpdate (or subscriptionContractCreate).
  2. Query the deliveryOptions field on the SubscriptionDraft with a deliveryAddress, polling until it returns a non-null result. Delivery option lookup is asynchronous and returns null while pending.
  3. Set the selected method using subscriptionDraftUpdate with a deliveryMethod.
  4. Commit the draft using subscriptionDraftCommit.

Anchor to New approach (SubscriptionContractCalculation)New approach (SubscriptionContractCalculation)

With the SubscriptionContractCalculation API, you fetch delivery options with a calculation, read them off the result, then submit a second calculation that commits the selected method.

  1. Submit a contract calculation that fetches the delivery options by providing fetchAvailableDeliveryOptions in the deliveryMethod input:

    POST https://{shop}.myshopify.com/admin/api/{api_version}/graphql.json

    Fetch delivery options

    mutation FetchDeliveryOptions {
    subscriptionContractUpdateCalculate(
    contractId: "gid://shopify/SubscriptionContract/123"
    contractUpdateInput: {
    withMerchandiseCustomizations: true

    # Fetch delivery options for this address without committing a method
    deliveryMethod: {
    fetchAvailableDeliveryOptions: {
    # Run delivery customization functions while discovering options (default)
    withDeliveryCustomizations: true

    address: {
    firstName: "Quinn"
    lastName: "Ishida"
    address1: "123 Main St"
    city: "Toronto"
    provinceCode: "ON"
    countryCode: CA
    zip: "M5V 1A1"
    }
    }
    }
    }
    ) {
    subscriptionContractCalculation {
    ... on SubscriptionContractCalculationPending {
    id
    }
    }
    userErrors {
    field
    message
    }
    }
    }

    JSON response

    {
    "data": {
    "subscriptionContractUpdateCalculate": {
    "subscriptionContractCalculation": {
    "id": "gid://shopify/SubscriptionContractCalculation/789"
    },
    "userErrors": []
    }
    }
    }
  2. Poll for the contract calculation result (see the recommended polling strategy) and read the deliveryOptions. Each option type is part of a union, so use inline fragments to select its fields:

    POST https://{shop}.myshopify.com/admin/api/{api_version}/graphql.json

    Read delivery options

    query PollDeliveryOptions {
    subscriptionContractCalculation(
    id: "gid://shopify/SubscriptionContractCalculation/789"
    ) {
    __typename
    ... on SubscriptionContractCalculationPending {
    id
    }
    ... on SubscriptionContractCalculationSuccess {
    id
    deliveryOptions {
    __typename
    ... on SubscriptionContractCalculationShippingOption {
    title
    code
    price { amount currencyCode }
    }
    ... on SubscriptionContractCalculationLocalDeliveryOption {
    title
    code
    phoneRequired
    price { amount currencyCode }
    }
    ... on SubscriptionContractCalculationPickupOption {
    title
    code
    pickupTime
    price { amount currencyCode }
    location { id name }
    }
    }
    }
    ... on SubscriptionContractCalculationFailure {
    id
    errors { code }
    }
    }
    }

    JSON response (success)

    {
    "data": {
    "subscriptionContractCalculation": {
    "__typename": "SubscriptionContractCalculationSuccess",
    "id": "gid://shopify/SubscriptionContractCalculation/789",
    "deliveryOptions": [
    {
    "__typename": "SubscriptionContractCalculationShippingOption",
    "title": "Standard",
    "code": "Standard",
    "price": { "amount": "5.00", "currencyCode": "CAD" }
    },
    {
    "__typename": "SubscriptionContractCalculationShippingOption",
    "title": "Express",
    "code": "Express",
    "price": { "amount": "15.00", "currencyCode": "CAD" }
    }
    ]
    }
    }
    }
  3. Submit a second contract calculation that commits the selected option as the deliveryMethod. Map the chosen option's title and code directly, and map its price to the method's deliveryPrice field:

    POST https://{shop}.myshopify.com/admin/api/{api_version}/graphql.json

    Select a delivery option

    mutation SelectDeliveryOption {
    subscriptionContractUpdateCalculate(
    contractId: "gid://shopify/SubscriptionContract/123"
    contractUpdateInput: {
    withMerchandiseCustomizations: true

    # Commit the option chosen from deliveryOptions
    deliveryMethod: {
    shipping: {
    address: {
    firstName: "Quinn"
    lastName: "Ishida"
    address1: "123 Main St"
    city: "Toronto"
    provinceCode: "ON"
    countryCode: CA
    zip: "M5V 1A1"
    }
    title: "Standard"
    code: "Standard"
    deliveryPrice: {
    amount: "5.00"
    currencyCode: CAD
    }
    }
    }
    }
    ) {
    subscriptionContractCalculation {
    ... on SubscriptionContractCalculationPending {
    id
    }
    }
    userErrors {
    field
    message
    }
    }
    }
  4. Poll for the second contract calculation result, then commit it with subscriptionContractCalculationCommit to apply the selected delivery method to the contract. This follows the same poll-then-commit pattern as the create and update flows above.

Anchor to Delivery customization functionsDelivery customization functions

When a calculation fetches delivery options, the withDeliveryCustomizations field on fetchAvailableDeliveryOptions controls whether delivery customization functions run:

  • true (the default): Functions run, so the returned deliveryOptions reflect any options the merchant's customizations hide, rename, or re-order.
  • false: Functions are bypassed, and the raw delivery options are returned.

The SubscriptionDraft API doesn't run delivery customization functions when computing deliveryOptions. For shops with active delivery customizations, the set of options returned by the new API can therefore differ from the old field.

Note

withDeliveryCustomizations applies only to calculations that include fetchAvailableDeliveryOptions. Calculations that set a committed delivery method and recurring billing attempts always bypass delivery customization functions, because the contract's committed delivery method is fixed and re-running functions can remove it.


Anchor to Input types referenceInput types reference

Exploring input types

Explore the API to understand the full input schema, starting from the top-level input types.

Anchor to Top-level input typesTop-level input types

Input typeDescription
SubscriptionContractCalculationContractCreateInputInputs for creating a new subscription contract.
SubscriptionContractCalculationContractUpdateInputInput for updating an existing subscription contract.
SubscriptionContractCalculationBillingCycleEditInputInput for editing a single billing cycle of an existing contract.

Note

The inputs for lines, manualDiscounts, and deliveryMethod fields use the GraphQL @oneOf directive. You must provide exactly one of the available input options for each entry. discountCodes is a separate non-union array. Refer to the following sections for the specific options available for each field.

Anchor to [object Object]SubscriptionContractCalculationLineInput

Lines use a @oneOf input pattern. Provide exactly one of the following options:

  • productVariantLine: For product variant lines.
  • customLine: For custom lines without a variant.
  • productVariantParentLine: For product variant bundle parent lines (available when bundle support is enabled).
Lines replacement behavior

The lines array replaces all existing lines. Include all lines that you want to keep and omit any that you want to remove. Use line IDs to identify existing lines for updates.

Anchor to [object Object]SubscriptionContractCalculationProductVariantLineInput

Product variant lines accept the following fields:

FieldTypeRequiredDescription
idIDNoThe ID of an existing line to update. Omit for new lines.
productVariantIdIDYesThe ID of the product variant for this line.
quantityIntegerYesThe quantity of the product variant. Must be at least 1.
priceOverrideMoneyInputNoOverride price for this line item.
customAttributes[AttributeInput]Yes (default [])Custom attributes for this subscription line.
appManagedPricingPolicySubscriptionContractCalculationAppManagedPricingPolicyInputNoApp-managed pricing policy metadata for this line.
originSellingPlanIdIDNoThe ID of the selling plan this line was originally created with. Used to find the delivery profile.
sellingPlanNameStringNoThe selling plan name for this line. Defaults to the origin selling plan's current name.
discounts[SubscriptionContractCalculationLineDiscountInput]YesLine-scoped manual discounts. Pass [] for none.

Anchor to [object Object]SubscriptionContractCalculationManualDiscountInput

Manual discounts use a @oneOf input pattern. Provide exactly one of the following options:

  • orderDiscount: For order discounts that apply to all lines on the contract. Takes title, value, and recurringCycleLimit fields. Optionally takes an id to update an existing discount.
  • deliveryDiscount: For delivery or shipping discounts. Takes title, value, and recurringCycleLimit fields. Optionally takes an id to update an existing discount.
Manual discounts replacement behavior

The manualDiscounts array replaces all existing manual discounts. Include all discounts that you want to keep and omit any that you want to remove. Use discount IDs to identify existing discounts for updates.

Anchor to [object Object]SubscriptionContractCalculationDiscountCodeInput

Discount codes are provided in the separate discountCodes[] array (not part of manualDiscounts). Each entry takes:

  • redeemCode: The discount code string to apply.

Applied codes are resolved into manual discounts on the resulting contract; codes aren't persisted as codes, so there's nothing to preserve or replace across calculations.

Anchor to [object Object]SubscriptionContractCalculationDiscountValueInput

Discount values use a @oneOf input pattern. Provide exactly one of the following options:

  • percentage: An integer percentage value (0–100).
  • fixedAmount: A fixed money amount with appliesOnEachItem (boolean) and amount (MoneyInput).

Anchor to [object Object]SubscriptionContractCalculationDeliveryMethodInput

Delivery methods use a @oneOf input pattern. Provide exactly one of the following options:

  • shipping: For shipping deliveries.
  • localDelivery: For local delivery.
  • pickup: For pickup.
  • none: Explicitly set no delivery method (digital-only subscriptions). Must be true.
  • fetchAvailableDeliveryOptions: To fetch the available delivery options for an address without committing a delivery method. See Fetch delivery options.

Anchor to [object Object]SubscriptionContractCalculationPaymentMethodInput

Payment methods use a @oneOf input pattern. Provide exactly one of the following options:

  • customerPaymentMethod: A vaulted customer payment method. Takes an id field (ID) for the customer payment method.
  • none: Explicitly set no payment method. Must be true.

Anchor to [object Object]SubscriptionContractCalculationFetchDeliveryOptionsInput

Use this input under deliveryMethod.fetchAvailableDeliveryOptions to fetch available delivery options without setting a delivery method.

  • address: The delivery address to fetch options for.
  • withDeliveryCustomizations: Optional Boolean. When true (the default), delivery customization functions run while fetching options. When false, delivery customization functions are bypassed and the raw options are returned.

Anchor to Handle asynchronous resultsHandle asynchronous results

The SubscriptionContractCalculation API uses asynchronous processing because it aligns contract editing with all other checkout surfaces across Shopify, including subscription billing attempts, checkout, and draft orders.

Most contract calculations complete in less than one second, but the system needs to account for network latency and errors in third-party services. The asynchronous model handles these situations without exposing transient errors or imposing strict API request timeouts.

You can handle contract calculation results by polling the contract calculation query or subscribing to webhooks for event-driven processing.

Anchor to Contract calculation statesContract calculation states

A contract calculation progresses through several states from creation to completion. Understanding these states helps you build integrations that handle all possible outcomes.

StateDescriptionWebhook
InitiatedThe contract calculation is created and waiting to be processed.—
ProcessingThe contract calculation is running with functions and external services.—
SucceededThe contract calculation is ready for review and commit.subscription_contract_calculations/succeed
FailedThe contract calculation failed. Errors are available on the result.subscription_contract_calculations/fail
VoidedThe contract calculation wasn't processed due to infrastructure issues.—
CommittedThe contract calculation has been committed and is now active.subscription_contracts/create or subscription_contracts/update

The subscriptionContractCalculation query returns a union with three possible GraphQL types. The internal states map to these types as follows:

GraphQL typeInternal states
SubscriptionContractCalculationPendingInitiated, Processing
SubscriptionContractCalculationSuccessSucceeded, Committed
SubscriptionContractCalculationFailureFailed, Voided

After creating a contract calculation, poll the subscriptionContractCalculation query until processing completes.

POST https://{shop}.myshopify.com/admin/api/{api_version}/graphql.json

GraphQL query

query PollCalculation($id: ID!) {
subscriptionContractCalculation(id: $id) {
__typename
... on SubscriptionContractCalculationPending {
id
}
... on SubscriptionContractCalculationSuccess {
id
}
... on SubscriptionContractCalculationFailure {
id
}
}
}

  1. Initial wait: Wait 1 second after the mutation before the first poll.
  2. Poll interval: Poll every 500ms.
  3. Timeout: Stop polling after 30 seconds, or when the contract calculation result is in a failed state.

All contract calculation operations are safe to retry:

  • Calculate: Safe to retry. Creates a new contract calculation each time.
  • Commit: Safe to retry. If already committed, it returns success.
  • Polling: Safe to call as many times as needed.

Subscribe to webhooks for event-driven processing instead of polling.

Webhook topicDescription
subscription_contract_calculations/succeedFires when a contract calculation succeeds and the contract calculation is ready to commit.
subscription_contract_calculations/failFires when a contract calculation fails and errors are available on the result.

POST https://{shop}.myshopify.com/admin/api/{api_version}/graphql.json

Create webhook subscription

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

JSON response

{
"data": {
"webhookSubscriptionCreate": {
"webhookSubscription": {
"id": "gid://shopify/WebhookSubscription/123456789"
},
"userErrors": []
}
}
}

Webhook payload

{
"id": 123,
"admin_graphql_api_id": "gid://shopify/SubscriptionContractCalculation/123",
"state": "succeeded"
}

Anchor to Backwards compatibilityBackwards compatibility

The SubscriptionDraft API will remain available but will not support the new capabilities of the SubscriptionContractCalculation API.

Anchor to Cross-API compatibilityCross-API compatibility

Compatibility in progress

Compatibility between APIs is still being evaluated. This may work in one of the following ways:

  • Allow both the SubscriptionDraft and SubscriptionContractCalculation APIs to be used on the same contracts.
  • Prevent contracts with changes committed through the SubscriptionContractCalculation API from being edited using the SubscriptionDraft API.

Was this page helpful?