Skipping a billing cycle discards a previously-set billingAttemptExpectedDate, and the value is unrecoverable

If a billing cycle of a subscription contract has a custom billingAttemptExpectedDate set via subscriptionBillingCycleScheduleEdit, skipping that cycle overwrites the date with cycleEndAt. Un-skipping does not restore it, and I can’t find a way to recover the original value. Is this intended?

Reproduced on Admin GraphQL 2025-10, on a dev store, with both anchored and non-anchored contracts.

Repro. A MONTH/1 contract, cycle 5 spanning 2026-12-18T02:00:01Z … 2027-01-18T02:00:00Z, defaulting to billingAttemptExpectedDate = 2027-01-18T02:00:00Z.

  1. Move the billing date inside the cycle:
mutation ScheduleEdit($contractId: ID!, $index: Int!, $input: SubscriptionBillingCycleScheduleEditInput!) {
  subscriptionBillingCycleScheduleEdit(
    billingCycleInput: { contractId: $contractId, selector: { index: $index } }
    input: $input
  ) {
    billingCycle { cycleIndex billingAttemptExpectedDate skipped edited }
    userErrors { field message }
  }
}

with input: { billingDate: "2026-12-28T00:00:00Z", reason: MERCHANT_INITIATED } →

billingAttemptExpectedDate = 2026-12-28T00:00:00Z   skipped = false   edited = true
  1. subscriptionBillingCycleSkip on the same cycle →
billingAttemptExpectedDate = 2027-01-18T02:00:00Z   skipped = true    edited = true
                             ^ back to cycleEndAt, custom date gone
  1. subscriptionBillingCycleUnskip →
billingAttemptExpectedDate = 2027-01-18T02:00:00Z   skipped = false   edited = true
                             ^ still cycleEndAt, not restored

Two things that make it hard to work around:

  • billingDate is rejected on a skipped cycle — input.billingDate: Billing date cannot be set on skipped billing cycle. It’s also rejected when sent together with skip: true on a cycle that isn’t skipped yet, and with skip: false on one that is. So there’s no single call that skips-and-preserves or unskips-and-restores.
  • Nothing on SubscriptionBillingCycle exposes the previous value, so an app that didn’t snapshot it first can’t recover it. subscriptionBillingCycleScheduleEdit with { skip: true } behaves the same as the dedicated mutation.

This matters for any app that moves a cycle’s billing date off the default — migrating contracts in from another subscription platform, or honouring a customer’s preferred delivery date. One skip from the customer account UI wipes it, with no error, and the resulting schedule is internally consistent so nothing surfaces the problem afterwards.

I couldn’t find this documented on the skip/unskip mutations, on subscriptionBillingCycleScheduleEdit, or on the SubscriptionBillingCycle object. Is skip meant to discard the schedule edit? If so it’d be worth documenting; if not, leaving billingAttemptExpectedDate alone on skip would fix it, and un-skip would then need no special handling.

Hi @mgtmn, thanks for the clear repro steps! I confirmed the behavior you’re seeing. When a cycle is skipped, a previously set custom billingAttemptExpectedDate is replaced and the cycle reports cycleEndAt.

SubscriptionBillingCycle only exposes the current billingAttemptExpectedDate, so there’s no documented way to read back a previous value.

The billing-cycles guide covers rescheduling and skipping separately, and the subscriptionBillingCycleScheduleEdit reference documents the mutation itself, but neither outlines how skip interacts with an existing custom date.

For now, keep a record of any custom billing date your app sets. If a cycle gets skipped, call subscriptionBillingCycleUnskip first, then reschedule with billingDate in a second call.

I’ve raised whether skip should preserve (or unskip should restore) the original date with the relevant team internally, and I’ll follow up here once I hear back. Thanks for raising this here!

Hi @mgtmn, apologies for the very delayed follow up here but I’ve heard back from the team, and yes, this is intended. A skipped cycle can’t hold a custom billing date, which is the same rule behind the BILLING_DATE_SET_ON_SKIPPED error you hit. Skipping clears the edited date and unskipping doesn’t bring it back.

On “unrecoverable”: confirmed, there’s no way to read the previous value back through the API. If your app didn’t snapshot the date before skipping, it’s gone. If it did, the path is unskip first, then subscriptionBillingCycleScheduleEdit with billingDate in a second call.

I’ve also flagged that this skip/date interaction should be spelled out in the docs so it doesn’t catch the next person by surprise. Thanks for your patience on this!