> ## 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 Creation & Validation FAQ

> The exact value ranges Waffo accepts when you create a subscription, which fields are required together, the full list of subscription statuses and their legal transitions, and how creation retries, queries and future start dates behave.

Everything that has to be right **before** a subscription exists: accepted value
ranges, fields that are required together, and what the creation response means.

Amount configuration is covered in
[Subscription Amounts & Per-Period Scheduling FAQ](/docs/en/support/subscription-amount-scheduling-faq).
Cross-currency behavior and Portal lookups are in
[Cross-Currency Subscriptions & Portal FAQ](/docs/en/support/subscription-cross-currency-and-portal-faq).

## What billing cycles can I configure?

`periodType` and `periodInterval` together define the cycle length. Both are
required, and the accepted `periodInterval` range depends on `periodType`:

| `periodType` | `periodInterval` range | Cycle length |
| - | - | - |
| `DAILY` | 1–365 | Every N days |
| `WEEKLY` | 1–4 | Every N weeks |
| `MONTHLY` | 1 or greater | Every N months |

**There is no `YEARLY` period type.** For an annual subscription use
`periodType: MONTHLY` with `periodInterval: 12`.

The same two fields cover the cycles merchants usually ask about by name:

| You want | Configuration |
| - | - |
| Weekly | `WEEKLY` + `1` |
| Fortnightly | `WEEKLY` + `2` |
| Monthly | `MONTHLY` + `1` |
| Quarterly | `MONTHLY` + `3` |
| Half-yearly | `MONTHLY` + `6` |
| Annual | `MONTHLY` + `12` |

Individual payment methods may narrow this further — some reject daily cycles,
and some accept only certain cycle/interval combinations. A cycle that passes
the ranges above can still be refused for the payment method you selected.

## Can the trial period use a different cycle from the regular period?

Yes. The trial cycle is configured independently of the regular cycle through
`trialPeriodType` and `trialPeriodInterval`, and the accepted ranges are the
same as for the regular period:

| `trialPeriodType` | `trialPeriodInterval` range |
| - | - |
| `DAILY` | 1–365 |
| `WEEKLY` | 1–4 |
| `MONTHLY` | 1 or greater |

* `trialPeriodType` and `trialPeriodInterval` must be supplied **together** —
  one without the other is rejected.
* If you omit both, the trial simply reuses `periodType` and `periodInterval`.
* Independent configuration does **not** mean no cross-checks. See the next
  question.

## Which subscription fields have to be supplied together?

| Group | Rule |
| - | - |
| `trialPeriodType` + `trialPeriodInterval` | Both or neither |
| `trialPeriodAmount` + `numberOfTrialPeriod` | Both or neither — this pairing is required whenever you configure a trial at all, not only when you set a trial cycle |
| `amount` | Always required on create |

And these cross-field rules are enforced even though the trial cycle is
configured independently:

| Rule | Behavior when violated |
| - | - |
| `numberOfTrialPeriod` must be `≤` `numberOfPeriod` (checked only when `numberOfPeriod` is present) | Rejected |
| `trialPeriodAmount` must be **strictly less than** `amount` (`0` is allowed; equal is not) | Rejected — `trial period amount should be less than subscription amount` |
| `trialPeriodAmount` must match the currency's decimal scale | Rejected — amount-decimal error |
| For payment methods where the payment method drives the schedule, the trial cycle must be **identical** to the regular cycle | Rejected |

## How many periods can a subscription run for?

| `numberOfPeriod` | Meaning |
| - | - |
| Provided | Must be **greater than 1**. The subscription ends after that many periods. |
| Omitted / empty | No merchant-defined end. |

"No merchant-defined end" is not literally unlimited: a subscription with no
`numberOfPeriod` is still bounded by the maximum duration on your subscription
agreement, and Waffo derives an effective final period count from that duration
and your cycle. Some payment methods impose their own maximum duration too. So
plan for a subscription that eventually reaches a final period even when you
never set one.

Once a subscription has reached its final period, amount updates are rejected —
see the [amount page](/docs/en/support/subscription-amount-scheduling-faq).

## Are `goodsUrl` and `appName` required when I create a subscription?

**No.** On `/api/v1/subscription/create` both are optional, and you can create a
subscription with neither.

The "provide either `goodsUrl` or `appName`" rule you may have seen belongs to
**one-off order creation**, not to subscription creation. Individual agreements
can still require `goodsUrl` for compliance and risk reasons, so supplying one
of the two remains good practice — but it is not a blanket subscription-create
validation, and building your integration around it as if it were will mislead
you when you debug a rejection.

## What are all the subscription statuses, and which transitions are legal?

Nine statuses exist. Each one can only be reached from a specific set of source
statuses, so a status you did not expect is usually a sign you missed an earlier
transition rather than a sign of an invalid state:

| Status | Meaning | Legal source statuses |
| - | - | - |
| `IN_PROGRESS` | The user has not finished confirming the subscription | — (creation only; never transitioned into) |
| `AUTHORIZATION_REQUIRED` | The payment method needs the user to authorize | `IN_PROGRESS` |
| `ACTIVE` | Running | `AUTHORIZATION_REQUIRED`, `IN_PROGRESS` |
| `CLOSE` | Closed because it timed out or failed before activating | `ACTIVE`, `AUTHORIZATION_REQUIRED`, `IN_PROGRESS` |
| `MERCHANT_CANCELLED` | You cancelled it | `ACTIVE` |
| `USER_CANCELLED` | The end user cancelled it | `ACTIVE` |
| `CHANNEL_CANCELLED` | Cancelled on the payment-method side, e.g. the user revoked authorization in their wallet | `ACTIVE`, `AUTHORIZATION_REQUIRED`, `IN_PROGRESS` |
| `PLATFORM_CANCELLED` | Waffo cancelled it automatically after a dispute, chargeback or fraud signal on one of its charges | `ACTIVE` |
| `EXPIRED` | It reached its final period | `ACTIVE` |

Grouping them the way your integration probably needs to:

| Group | Statuses |
| - | - |
| Not yet active | `IN_PROGRESS`, `AUTHORIZATION_REQUIRED` |
| Running | `ACTIVE` |
| Terminal | `CLOSE`, `MERCHANT_CANCELLED`, `USER_CANCELLED`, `CHANNEL_CANCELLED`, `PLATFORM_CANCELLED`, `EXPIRED` |
| Cancelled (a subset of terminal) | `MERCHANT_CANCELLED`, `USER_CANCELLED`, `CHANNEL_CANCELLED`, `PLATFORM_CANCELLED` |

Treat the whole terminal group as "no further billing period will open" and
switch on the group, not on individual values — that way a status you have not
mapped cannot silently look like an active subscription.

## What is `PLATFORM_CANCELLED`?

It is the terminal status Waffo sets when it cancels a subscription **on its own
initiative** because one of that subscription's charges produced a dispute,
chargeback or fraud signal. It reaches you through the subscription status
notification like any other cancellation, and the notification carries a cancel
reason identifying which kind of signal triggered it.

Points that matter for your integration:

* It is only reachable from `ACTIVE`.
* Automatic cancellation is **configurable per merchant and per signal type**,
  and is off unless configured — so you may never see this value, and its
  absence does not mean disputes are being ignored.
* Because Waffo, not you, initiated it, your own records will show the
  subscription as active until you process the notification. Treat the webhook
  as the source of truth.

Dispute and chargeback handling itself is covered in
[Chargeback FAQ](/docs/en/support/chargeback-faq).

## Creation returned `AUTHORIZATION_REQUIRED`. What do I do?

The user still has to authorize the recurring mandate. The creation response
carries `subscriptionAction`, which contains the URL to send the user to
(`webUrl`). Redirect the user there; the subscription moves to `ACTIVE` once
authorization succeeds.

`subscriptionAction` is only populated while the subscription is in
`IN_PROGRESS` or `AUTHORIZATION_REQUIRED`. Once the subscription is active or
terminal it is no longer returned, which is expected rather than an error.

The authorization link is time-limited. `orderExpiredAt` on the create request
sets that deadline; if you omit it, a platform default applies, and it is
**short** — minutes rather than hours — so send the user to the link straight
away. A subscription whose authorization is never completed inside the window
moves to `CLOSE`.

## What happens if I call create again with the same request ID?

Creation is idempotent on **`subscriptionRequest`** — the request ID you
generate — and not on `merchantSubscriptionId`. A repeat call with the same
`subscriptionRequest` under the same MID does not create a second subscription.

What comes back depends on where the existing subscription already got to:

| Existing status | Repeat-create behavior |
| - | - |
| `AUTHORIZATION_REQUIRED` | Returns the subscription with its **stored** authorization link. No new link is generated. |
| `IN_PROGRESS` | Waffo re-drives the pending payment step, so this call **can** return a newly issued link. |
| Any other status | Returns the existing subscription. No link is included. |

A repeat call is also validated for consistency: if the `amount` or `currency`
differ from the original request, the call is rejected as a duplicate rather
than silently accepted. So retry with the **identical** payload, and do not reuse
one `subscriptionRequest` for two different subscriptions.

If a user abandons authorization and you need to give them a fresh link, retry
creation while the subscription is still pre-active and use whatever
`subscriptionAction` comes back; do not cache a link and assume it stays valid.

## How do I query a subscription, and how do I get the per-period charges?

Call `/api/v1/subscription/inquiry` with **`subscriptionRequest` or
`subscriptionId`** — at least one is required. Passing both is accepted.

Per-period charges are **not** returned by default. Set `paymentDetails` to `1`
to get them (`0`, the default, omits them; any other value is rejected):

| `paymentDetails` | Response |
| - | - |
| `0` (default) | Subscription only |
| `1` | Subscription plus a `paymentDetails` list, one entry per billing period |

Each `paymentDetails` entry carries `acquiringOrderId`, `orderCurrency`,
`orderAmount`, `orderStatus`, `orderUpdatedAt` and `period`, plus a
`topupDetails` list on periods that had additional (seat add-on) charges — see
[Cross-Currency Subscriptions & Portal FAQ](/docs/en/support/subscription-cross-currency-and-portal-faq)
for how those rows are numbered.

Going the other way — from a charge to its subscription — the order inquiry
response carries a `subscriptionInfo` block with `subscriptionId`, `period`,
`subscriptionRequest` and a `subscriptionEvent` telling you whether the charge
was a period charge or a payment-method addition. One-off orders have no
`subscriptionInfo`.

The subscription response also reports `productInfo.currentPeriod` and the
stored `productInfo.scheduledAmounts`, which is what you read before scheduling
an amount change.

## How many subscriptions can one user have?

Any number. Under one MID, the same `merchantUserId` can hold as many
subscriptions as you create — there is no per-user cap and no uniqueness check
on `merchantUserId` at creation.

This is the normal shape for multi-product and multi-seat businesses: one user,
several concurrent subscriptions, each with its own `subscriptionId` and its own
lifecycle. Because of that, never treat `merchantUserId` as a key for "the
user's subscription" — key on `subscriptionId` or on your own
`merchantSubscriptionId`.

## Can a subscription start on a future date?

Yes — pass `startTime` on create. Validation:

| Rule | Behavior |
| - | - |
| `startTime` omitted | The subscription starts immediately (unchanged legacy behavior) |
| `startTime` strictly in the future | Accepted |
| `startTime` in the past, or equal to now | Rejected — invalid start time |
| `startTime` more than **365 days** ahead | Rejected — start time too far out |

**Backdating is not supported.** There is no way to create a subscription that
behaves as though it had started earlier, and no way to make the first period
land in the past. If you need to bill for a period that has already elapsed,
collect it as a separate one-off order.

Two further constraints when you defer a start:

* The start date must fall inside the validity window of the agreement that will
  be used, so a start far out can be rejected on those grounds even within 365
  days.
* On a plan change, `startTime` must not be earlier than the original
  subscription's next scheduled charge.

What you can and cannot do between creation and the start date is covered in
[Subscription Lifecycle & Payment Method Management](/docs/en/support/subscription-lifecycle-management).


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