Skip to content
In this section

Subscriptions

Subscription states, trials, the payment method on file, and price and plan.

A subscription is one recurring billing relationship on a contract. All date fields are calendar dates on the seller's calendar — billing runs on dates, not instants — and a new day begins at midnight in the timezone set on the seller's organization profile (UTC when none is set). A term starting on the 1st is renewed and invoiced from midnight on the 1st there.

States

trial → active ⇄ past_due / paused → canceled | expired

  • trial — running a free trial until trialEnd. trialEndBehavior says which way it ends: convert (becomes a paying subscription) or cancel (lapses). trialRequiresPaymentMethod tells you whether a payment method must be on file first.
  • active — billing normally within currentPeriodStart…currentPeriodEnd.
  • past_due — collection failed on its last permitted attempt (where off-session collection is on, the loop makes at most 3 attempts in total, at least 20 minutes apart, before giving up; where it is off nothing retries and the first miss lands here), or a trial ended with no payment method on file. Nothing further is retried from here: recovery is a paid invoice on the contract — your hosted link, a matched transfer, a new card — after which the subscription returns to active on its own. Treat this status as "act now", not as "Fynex is handling it".
  • paused — billing suspended (pausedAt, optional auto-resume at pauseEndsAt).
  • canceled / expired — terminal. A cancellation with notice keeps serving until cancelEffectiveAt.

Payment method on file

Renewals and other recurring-origin invoices are collected off-session only where the deployment has billing.invoice.auto_charge switched on (a platform flag operated by Fynex, not a per-account setting) and the customer has a saved card. A card is saved on Fynex when the buyer ticks save card on the hosted checkout page, provides an e-mail, and the payment succeeds with a billing address; it is matched to the sellerCustomerId by that e-mail. The subscription block on the Payments API's POST /initialize-payment does not store a card here — it only flags the processor-side stored-credential relationship. Without a saved card an invoice is still issued and sent; it is paid by someone visiting its link or making a transfer, and a renewal that cannot be collected goes past_due. trialRequiresPaymentMethod on a trial says whether this must be in place before the trial converts.

Price and plan

There is no separate plan object: the plan reference is priceMinor + currency + billingFrequency (with customUnit/customEvery for custom cadences). A scheduled downgrade appears as pendingPriceMinor/pendingPriceChangeAt until the renewal pass applies it.

Endpoints

Read:

  • GET /billing-api/v1/subscriptions — list, newest first. Filter: contractId. Pages with cursor/limit, newest first.
  • GET /billing-api/v1/subscriptions/{subscriptionId} — one subscription.

Create:

  • POST /billing-api/v1/contracts/{contractId}/subscriptions — a new subscription on a contract you own. Idempotency-Key is required (1–128 characters, A–Z a–z 0–9 _ . : -): the same key always returns the subscription the first call created (200 instead of 201), whatever the body of the retry; use one key per subscription you intend to create. Keys are scoped to your seller account — the same key on a different contract answers 422; a key under a billing-engine prefix (proration:, redeem:, subscription:, …) answers 400. Body: frequency (daily, weekly, bi_weekly, monthly, quarterly, semi_annual, annual, custom with customUnit/customEvery), priceMinor + currency (the contract's), startDate (up to a year in the past), optional anchorDate, trialEnd + trialEndBehavior (convert/cancel), autoRenew (default true; an explicit false needs endDate, and endDate is refused otherwise), noticePeriodDays (0–365), prorationPolicy (by_day/full_period/next_period — selects what an immediate change-plan does once the proration engine is enabled: prorate the unserved remainder, bill the whole current period at the new price with the elapsed days included and no adjustment, or keep the old price until the next period. Inert while the engine is off, which is the default. Where invoice binding is enabled for the environment, the recorded proration is applied to the term's next invoice).
curl -X POST "$FYNEX_API_BASE/contracts/42/subscriptions" \
  -H "Authorization: Bearer $FYNEX_SECRET_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"frequency":"monthly","priceMinor":9900,"currency":"EUR","startDate":"2026-10-01","noticePeriodDays":30}'

Lifecycle, all POST /billing-api/v1/subscriptions/{subscriptionId}/…:

Action Body From Effect
cancel — trial, active, past_due, paused Sets cancelRequestedAt; serves until cancelEffectiveAt (notice period) or term end. Terminal.
pause {"pauseUntil": "YYYY-MM-DD"} optional active Suspends billing; pauseEndsAt when a date was given. Longer than the seller's pause policy → 422.
resume — paused Billing continues on the original schedule — the period is not restarted, and the pause is not billed.
end-trial — trial Converts now per trialEndBehavior / trialRequiresPaymentMethod.
change-plan {"priceMinor": 12900, "currency": "EUR", "atTermEnd": true} active Scheduled (pendingPriceMinor) or immediate. atTermEnd: true always schedules; with false the subscription's prorationPolicy decides while the proration engine is on — next_period keeps the old price for the rest of this period and applies the new one from the next, by_day applies it now and posts an adjustment for the unserved remainder, full_period applies it now and bills the whole current period at the new price, elapsed days included, with no adjustment. Same price, or a change already pending → 422. Currency cannot change.

Every action returns the updated subscription. A request the current state cannot take — pausing a trial, resuming an active subscription, cancelling twice — answers 422 with the reason; so does a lost race against the lifecycle pass (subscription changed concurrently; re-read it and retry). 404 means the subscription is not yours. Marking past-due or recovered is the collection loop's job and has no public route.