> ## Documentation Index
> Fetch the complete documentation index at: https://waffo.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Subscription Amounts & Per-Period Scheduling FAQ

> What scheduledAmounts means, how Waffo picks the amount for each billing period, what /subscription/update can and cannot change (it lowers amounts; raising needs a plan change or a seat add-on top-up), the amount-update webhook, and why there is no proration field.

This page covers **how much is charged in each billing period**: configuring
different amounts per period, changing amounts on a live subscription, and what
Waffo does (and does not) do about mid-period price changes.

For creation-time validation and status values see
[Subscription Creation & Validation FAQ](/docs/en/support/subscription-creation-validation-faq).
For deferred starts, payment methods and retries see
[Subscription Lifecycle & Payment Method Management](/docs/en/support/subscription-lifecycle-management)
and the
[Subscription FAQ](/docs/en/support/subscription-faq).

## What does `scheduledAmounts` mean in the subscription request?

`scheduledAmounts` is an optional list inside `productInfo` that lets you charge
a **different amount in specific billing periods** instead of the same `amount`
every period. Each entry has exactly two fields:

| Field | Type | Meaning |
| - | - | - |
| `period` | String | Which billing period this amount applies to. A positive integer, `1` = the subscription's **first** period. |
| `amount` | String | The amount to charge in that period, in the same currency as the subscription. |

```json theme={null}
{
  "amount": "99.90",
  "productInfo": {
    "periodType": "MONTHLY",
    "periodInterval": "1",
    "numberOfPeriod": "12",
    "scheduledAmounts": [
      { "period": "1", "amount": "9.90" },
      { "period": "2", "amount": "49.90" }
    ]
  }
}
```

That subscription charges 9.90 in period 1, 49.90 in period 2, and 99.90 in
every period from 3 to 12. Periods you do not list simply fall back to `amount`,
and the listed periods do **not** have to be consecutive — listing only periods
1 and 5 is valid.

You can set `scheduledAmounts` when you create the subscription and change it
later with `/api/v1/subscription/update`.

## Is `period` counted from the first period, or from the first period after the trial?

**From the first period of the subscription.** `period` is an absolute, 1-based
index over every billing period, and trial periods are included in that count.

This matters when a trial exists: if you configure a 2-period trial, then
`period: 1` and `period: 2` address the two **trial** periods, and the first
period at the regular price is `period: 3`.

| Subscription | `period: 1` addresses |
| - | - |
| No trial | The first regular charge |
| 2-period trial | The **first trial** charge |

There is no offset applied anywhere: Waffo looks up the period number of the
charge it is about to create and reads `scheduledAmounts` with that exact
number. To see where a live subscription currently is, read
`productInfo.currentPeriod` from `/api/v1/subscription/inquiry` — the stored
plan comes back in the same response as `productInfo.scheduledAmounts`, so you
can always confirm which period numbers are still in the future before you send
an update.

## How does Waffo decide the amount for each period?

Three inputs are consulted in this order; the **last** rule that matches wins:

| Priority | Rule | Applies when |
| - | - | - |
| 1 (highest) | `scheduledAmounts` entry whose `period` equals this period's number | That period is listed in the plan |
| 2 | `trialPeriodAmount` | This period's number is within `numberOfTrialPeriod` and no scheduled amount matched |
| 3 (fallback) | `amount` | Neither of the above matched |

So a `scheduledAmounts` entry **overrides the trial amount** for that period.
If you want the trial to be billed at `trialPeriodAmount`, do not list the trial
period numbers in `scheduledAmounts`.

This is the first thing to check when a charge does not match what you expected:
work out the period number of the charge, then walk the three rules in order.

## What are the exact limits on `scheduledAmounts`?

| Constraint | Rule | If violated |
| - | - | - |
| List length | At most **10** entries | Parameter validation error |
| `period` | Positive integer (`≥ 1`) | Parameter validation error |
| `period` uniqueness | No duplicates within one request | Parameter validation error, message `scheduledAmounts.period must be unique` |
| `period` upper bound (create) | Must not exceed `numberOfPeriod` | Parameter validation error naming the offending period |
| `period` upper bound (update) | Must not exceed the subscription's final period count | Parameter validation error naming the offending period |
| `period` lower bound (update) | Must be **strictly greater** than the current period | Parameter validation error naming the current period |
| `amount` | Required per entry, must not be negative | Parameter validation error |
| `amount` precision | Must match the currency's ISO 4217 scale, and any whole-unit multiple rule for that currency | Amount-decimal error |
| `amount` range | Must fall inside the amount range on your subscription agreement | Amount-out-of-range error |

Two details worth calling out because they are commonly assumed the other way
round:

* **Amounts are decimal strings in the currency's normal unit, not minor units.**
  `"9.90"`, not `"990"`. Extra decimal places are **rejected**, not rounded.
* **`amount: "0"` passes field validation**, so a free period is expressible —
  but the value still has to satisfy the minimum amount on your agreement, so
  whether 0 is accepted depends on your contract.

## Can I raise the next period's subscription amount directly?

**No — not through `/api/v1/subscription/update`.** By default that endpoint
rejects any increase. This is enforced on all three amount fields:

| Field | Rule |
| - | - |
| `amount` | New value must be `≤` the current `amount` |
| `productInfo.trialPeriodAmount` | New value must be `≤` the current trial amount |
| `productInfo.scheduledAmounts[].amount` | Per period, new value must be `≤` the baseline for that period |

The per-period baseline is the **currently stored scheduled amount for that same
period**, or the subscription's `amount` if that period is not in the stored
plan. That closes the obvious loophole: you cannot lower a period first and then
raise it back in a second call, because the second call is compared against the
value you just wrote.

Violations come back as a parameter error whose message names what was rejected
(`amount is not allowed to increase`,
`trialPeriodAmount is not allowed to increase`, or
`scheduledAmounts.amount is not allowed to increase for period N`).

To charge **more**, either use `/api/v1/subscription/change` and move the user
to a new plan, or — where the increase is a seat add-on — send the update with
a `topupInfo` block: that path collects the difference for the current period
immediately and is allowed to raise `amount` from the next period (see the
proration table below). Plain increases through `update` are not open to
accounts generally; if you believe your integration needs them, raise it with
integration support rather than designing around it.

## How do I set prorate?

**There is no proration field, and Waffo does not calculate proration.** If you
are looking for a `prorate` / `proration_behavior` parameter, it does not exist —
neither on `/subscription/update` nor on `/subscription/change`.

Here is what actually happens in each case, so you can decide what to implement
on your side:

| What you want | What Waffo does | What you must do |
| - | - | - |
| Change the amount from the next period onward | `/subscription/update` applies the new amount **from the next billing period**. The current period is untouched, and no credit or debit is calculated for it. | Nothing extra — but do not expect a partial-period adjustment. |
| Move the user to a different plan | `/subscription/change` locks the original subscription and **creates a new subscription based on it**. You may pass a remaining value to carry over, which Waffo then draws down against the new subscription's charges. | Calculate the carry-over value yourself and pass it in the change request. Waffo does not compute it. |
| Collect a mid-period difference immediately | `/subscription/update` with a `topupInfo` block (a **seat add-on**) charges `topupAmount` immediately as an extra order inside the current period and applies the new `amount` from the next period. You compute `topupAmount`; Waffo does not prorate it. | Send `topupInfo` (`topupRequest` as the idempotency key, `topupAmount`, an optional `description` and `orderExpiredAt`). The response carries the top-up order and its status; the new amount is applied only once that charge succeeds. Alternatively collect the difference as a separate one-off order. |

In short: amount changes are **period-boundary events**, and any money that
should move because a change landed mid-period is yours to compute and yours to
collect.

## What exactly does `/subscription/update` change, and when does it take effect?

It changes amounts only, from the next billing period onward.

**It can change:** `amount`, `productInfo.trialPeriodAmount`,
`productInfo.scheduledAmounts` — individually or together. At least one of the
three must be present, otherwise the request is rejected.

**It does not change:** the plan, `periodType`, `periodInterval`, the number of
periods remaining, the billing date, or the payment method. It does not trigger
proration and it does not create a new subscription.

**Timing:** the new values apply from the next period. A charge for the current
period that has already been generated or is already in flight is **not**
affected. The response tells you the period number the change lands on:

| Response field | Meaning |
| - | - |
| `subscriptionId` / `subscriptionRequest` | Identifies the subscription |
| `previousAmount` / `newAmount` | Base amount before and after (equal if unchanged) |
| `previousTrialPeriodAmount` / `newTrialPeriodAmount` | Trial amount before and after |
| `previousScheduledAmounts` / `newScheduledAmounts` | The plan before and after; omitted when no plan was stored |
| `nextEffectivePeriod` | The **period number** from which the new amounts apply |

## When is an amount update rejected outright?

| Condition | Behavior |
| - | - |
| Neither `subscriptionRequest` nor `subscriptionId` provided | Rejected |
| None of `amount` / `trialPeriodAmount` / `scheduledAmounts` provided | Rejected |
| Subscription does not exist under your MID | Not-found error |
| Subscription is not `ACTIVE` | Rejected — status error |
| Subscription has already reached its final period | Rejected, with the final period number in the message |
| `trialPeriodAmount` sent after the trial has ended | Rejected — the trial amount is no longer modifiable |
| The subscription's schedule is driven by the payment method rather than by Waffo | Not supported for that subscription |
| Another update, or a renewal, is being processed for the same subscription | Rejected — retry shortly |

The last row means updates are **serialized per subscription**. If you are
re-pricing many subscriptions, treat a "being modified, please retry later"
response as retryable rather than as a failure.

## If I send `scheduledAmounts` in an update, does it merge with the existing plan?

**No — the list you send replaces the stored plan as a whole.** It is not a
patch: periods present in the old plan but absent from your request stop having
a scheduled amount and fall back to `amount`.

Two consequences to design for:

* Always send the **complete** set of future periods you want priced, not just
  the ones you are changing.
* Because an update may only address periods **after** the current one, entries
  for already-charged periods are dropped from storage when you replace the
  plan. That is expected and does not change any charge that already happened.

Sending an **empty array**, or omitting the field, is treated as
*no change* — it does **not** clear the plan. There is no way to clear a
scheduled-amount plan through `/subscription/update`; to get back to a single
flat amount, list every remaining future period at the amount you want, or move
the user to a new plan with `/subscription/change`.

## What happens to `scheduledAmounts` when I upgrade or downgrade?

Nothing is inherited. `/subscription/change` creates a **new** subscription, and
that new subscription's `amount` and `scheduledAmounts` come **entirely from the
change request**. If you omit `scheduledAmounts` there, the new subscription has
no per-period plan at all and every period is billed at the new `amount`.

So if a tiered price ramp should survive an upgrade, you have to restate it in
the change request.

## Will the end user be told that the amount changed?

Yes — Waffo emails the subscriber after a successful amount update. The mail
carries the previous amount, the new amount, the previous and new per-period
plans, and the period number the change takes effect from.

Two operational caveats:

* The email is sent only if the amount-change notification is enabled for your
  account and product. If it is switched off, nothing is sent to the user.
* Sending is best-effort and asynchronous: a mail failure does **not** roll back
  the amount change. The API response is the record of what changed.

## Does Waffo send me a webhook when an amount update succeeds?

Yes. After a successful `/subscription/update`, Waffo pushes
**`SUBSCRIPTION_UPDATE_NOTIFICATION`** to the `notifyUrl` stored on the
subscription. For a plain amount change it is sent as soon as the update is
committed; for a seat add-on (see below) it is sent once the top-up charge
succeeds and the new amount is applied. The payload is the subscription as the
inquiry API returns it, so it already reflects the new amounts.

To keep your own records aligned:

* Treat the `/subscription/update` response as the primary record — read
  `newAmount`, `newScheduledAmounts` and `nextEffectivePeriod` from it and
  store them. The webhook confirms the same change asynchronously and is the
  signal to use when the change was committed outside your immediate request
  path (the top-up case).
* Keep listening to the per-period notification and the subscription payment
  order notification for the **actual charged amount** of each period — those
  reflect what was really taken, which is the number you should reconcile
  against.

## Which subscriptions can use per-period amounts at all?

Per-period amounts and `/subscription/update` are available on subscriptions
where **Waffo drives the billing schedule**. For some payment methods the
payment method itself drives the schedule; those subscriptions reject
`scheduledAmounts` at creation and are not eligible for amount updates. If a
creation call comes back saying scheduled amounts are not supported for the
subscription, that is why — the fix is a different payment method, not a
different request shape.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.