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!