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

# Changelog

> Notable changes to the Suby.fi v3 API.

<Update label="2026-07-22" description="Tax behavior: inclusive vs exclusive pricing">
  Payment, subscription, and checkout-session creation now accept an optional
  **`taxBehavior`** (`exclusive` | `inclusive`) controlling how MoR VAT relates to
  the price:

  * **`exclusive`** (default) — the price is **HT**; VAT is added on top, so the
    buyer is charged `price + VAT`.
  * **`inclusive`** — the price already **includes** VAT; the buyer is charged the
    listed price and the VAT is extracted from it.

  Added to `POST /v3/payments`, `POST /v3/subscriptions` (applies to every cycle;
  the subscription body also gains `billingAddress` + `businessData`), and
  `POST /v3/checkout/sessions` (signed into the session so the hosted page's TTC
  matches the charge). Every payment response gains **`taxInclusive`** (boolean).
</Update>

<Update label="2026-07-19" description="Analytics: inactive-subscription count">
  **`GET /v3/analytics`** now returns **`subscription.inactiveSubscriptions`** —
  the point-in-time count of subscriptions no longer active (canceled / expired /
  unpaid), the complement of `activeSubscriptions`.
</Update>

<Update label="2026-07-19" description="Per-product payment-method whitelist">
  Products now carry an optional **`paymentMethods`** field on
  `POST /v3/products` and `PATCH /v3/products/{id}`, echoed on every product
  response.

  * **`null` / `[]` / omitted** — the product accepts **every method enabled on
    your account** (unchanged default).
  * **A non-empty list** — checkout is restricted to those categories (e.g.
    `["CARD", "STABLECOIN"]`). The list is always **intersected** with your
    account's enabled methods, so listing a method you haven't enabled is a
    harmless no-op, and clearing it (send `null`) restores the default.

  The resolution is applied fresh at checkout, so an edit takes effect on every
  already-issued link immediately. See the `PaymentMethodOverride` schema for the
  full category enum.
</Update>

<Update label="2026-07-16" description="Breaking: crypto and card options are now grouped">
  **Breaking change.** Method-specific request options are now grouped into a
  `crypto` and a `card` object instead of sitting flat on the body. This affects
  `POST /v3/payments`, `POST /v3/subscriptions`, and the hosted-checkout pay
  endpoint. Responses are unchanged.

  | Was (flat)            | Now                                 |
  | --------------------- | ----------------------------------- |
  | `cryptoMode`          | `crypto.mode`                       |
  | `chainId` / `assetId` | `crypto.chainId` / `crypto.assetId` |
  | `tokenAmount`         | `crypto.tokenAmount`                |
  | `payerAddress`        | `crypto.payerAddress`               |
  | `tokenizedInstrument` | `card.tokenizedInstrument`          |
  | `paymentInstrumentId` | `card.paymentInstrumentId`          |
  | `providerCustomerId`  | `card.providerCustomerId`           |
  | `threedsSessionId`    | `card.threedsSessionId`             |

  On the hosted checkout, the flat `customerEmail` / `customerFirstName` /
  `customerLastName` collapse into the `customerData` object already used by
  `POST /v3/payments` — `{ "customerData": { "email": "…" } }`.

  ```json theme={null}
  {
    "method": "CRYPTO",
    "amount": 5000,
    "currency": "USD",
    "customerData": { "email": "buyer@example.com" },
    "crypto": { "mode": "qr_deposit", "chainId": 21, "assetId": 1 }
  }
  ```

  **Migrating:** move the fields as per the table. Unknown fields are ignored
  rather than rejected, so a body left flat does **not** fail on the stray keys —
  it fails on the now-missing group, with a `422` naming what it wants (e.g.
  `method=CRYPTO requires crypto.mode`). There is no deprecation window: the flat
  fields have no effect as of this release.
</Update>

<Update label="2026-07-16" description="Removed the crypto urgency field">
  The **`urgency`** request field is removed from `POST /v3/payments` (it was
  documented as a BTC/SOL fee-policy override). Every crypto payment now uses
  the `fast` policy:

  * **Bitcoin** — deposits are accepted at **zero confirmations** as soon as a
    non-RBF transaction is seen in the mempool, so `payment.succeeded` fires
    immediately. A deposit that signals RBF still waits for 1 confirmation.
  * **Solana / EVM** — the create-time fee snapshot always bids the fast tier
    (p90 priority fee on Solana, 2× base fee on EVM).

  The field is no longer part of the contract: sending it is ignored rather
  than rejected, so existing integrations keep working — but `urgency: "slow"`
  or `"normal"` no longer has any effect. Drop it from your request bodies.
</Update>

<Update label="2026-07-15" description="Merchant-initiated refunds">
  New endpoint **`POST /v3/payments/{id}/refund`** — refund a captured card/APM
  payment. Omit `amount` for a full refund of the remaining amount; a lower
  `amount` refunds partially. An optional `reason` is accepted.

  * **Crypto payments are not refundable** (funds settle on-chain) → `422`
    `PAYMENT_NOT_REFUNDABLE`.
  * The refund is triggered at the payment processor; the `REFUNDED` /
    `PARTIALLY_REFUNDED` transition and the balance debit arrive asynchronously
    via the **`payment.refunded`** webhook, so the returned payment may still
    read `COMPLETED`.
</Update>

<Update label="2026-07-15" description="Payment-processor identity removed from responses">
  Response payloads no longer expose which payment processor handled a charge —
  the API is processor-agnostic by contract. Removed fields:

  * **`Payment.paymentProvider`** (`GET /v3/payments`, `GET /v3/payments/{id}`).
  * **`Subscription.paymentProvider`** and **`Subscription.providerSubscriptionId`**
    (`GET /v3/subscriptions`, `GET /v3/subscriptions/{id}`).

  No request field changes and no behavioural change — the same charge is
  processed identically. If you branched on these values, drive off the
  processor-neutral fields (`status`, `method`, `currency`) instead.
</Update>

<Update label="2026-07-14" description="Analytics totals are FX-normalized to USD">
  **`GET /v3/analytics`** now converts EUR-denominated revenue to a **USD base**
  before summing, instead of adding EUR and USD cents as if 1 € = 1 \$. Every
  money field (`overview`, `series`, `subscription`, `oneTime`, `productMix`,
  `methodBreakdown`) is expressed in the response `currency` (now "USD" whenever
  conversion ran). A new **`rates`** object carries `eurToUsdScaled1e8` (USD per
  1 EUR, scaled 1e8) so you can render totals in whichever currency you prefer.
  When the rate is temporarily unavailable, `rates.eurToUsdScaled1e8` is `null`
  and amounts fall back to the previous (single-currency) behaviour.
</Update>

<Update label="2026-07-14" description="Crypto balance is net of fees">
  A crypto payment settled into your managed vault now credits your balance with
  the **net** amount (settled amount − the Suby fee that is split off on-chain),
  not the gross. Previously the balance briefly over-counted by the fee. The
  ledger entry carries the gross + fee breakdown for reconciliation.
</Update>

<Update label="2026-07-13" description="Out-of-order webhook safety">
  Payment status transitions are now guarded by the state machine on every
  webhook write. A late or duplicate `payment.failed` that arrives **after** a
  payment was captured (`succeeded`) is dropped instead of flipping the payment
  to `failed` — a captured payment never regresses to a failure state. A genuine
  post-capture problem still comes through as its own `payment.refunded` /
  chargeback event. This prevents a settled payment from being incorrectly
  marked failed (and its funds from silently dropping off your balance).
</Update>

<Update label="2026-07-13" description="Card intents expire">
  Card/APM payments now carry an **`expiresAt`** (previously only crypto did).
  It's bounded to the originating checkout-session window (or 30 minutes on the
  direct `POST /v3/payments` path). An unfinished intent — a payer who closed the
  tab before completing 3-D Secure — is swept to **`EXPIRED`** once past it,
  instead of sitting `PENDING` indefinitely. Genuinely-in-flight amounts on the
  dashboard balance no longer include these abandoned intents.
</Update>

<Update label="2026-07-13" description="Receipt billing cadence">
  The receipt payload (`GET /v3/payments/{id}/receipt`), its PDF invoice, and
  the payment-receipt email now reflect the real billing cadence.

  * `Receipt` gains a `recurring` object (`recurringInterval` +
    `recurringIntervalCount`), set for a subscription charge and `null` for a
    one-time payment.
  * The invoice PDF's line **Interval** now reads `"monthly"` / `"every 3 months"`
    for subscriptions instead of always `"one-time"`.
  * The receipt email shows a `Billed <cadence>` line only for subscription
    charges; one-time receipts omit it.
</Update>

<Update label="2026-07-12" description="Subscriptions & renewals">
  Subscription lifecycle is now fully driven by Suby.

  * `POST /v3/subscriptions` accepts **`CARD` / `APPLE_PAY` / `GOOGLE_PAY` / `CRYPTO`**
    (crypto sends `cryptoMode` + `chainId` + `assetId` and returns a deposit `instruction`).
  * **No-code subscriptions** on the hosted checkout: a `mode=subscription` session
    starts a subscription + first cycle.
  * **Automatic renewals** — card/wallet re-charged off-session with a progressive
    **smart-retry** (up to 8 attempts over 30 days; soft declines retried, hard
    declines end the subscription). Crypto renews via a per-cycle email.
  * `Subscription` now exposes read-only dunning state: `currentCycleDueAt`,
    `renewalAttempt`, `nextRenewalAttemptAt`, `lastDeclineCategory`.
  * Full `subscription.*` webhook coverage: `created`, `renewed`, `past_due`,
    `expired`, `canceled`.
  * `subscription.created` fires at **activation** (first cycle confirmed →
    `ACTIVE`), not at create time — you never receive a `created` for a
    subscription whose first charge is declined.
</Update>

<Update label="v3-beta" description="Initial">
  Initial public release of the **v3** RESTful API under the `/v3` prefix.

  * Plural, resource-oriented endpoints (`/v3/payments`, `/v3/products`, …).
  * **Setup Intents** + **saved payment methods** for off-session charges.
  * **Checkout Sessions** (signed `cs_…` tokens) for hosted checkout.
  * **Webhook Endpoints** managed via the API, with secret rotation.
  * Org / product / customer **analytics** endpoints.
  * Dotted webhook event names (`payment.succeeded`, `subscription.renewed`, …).

  See the [migration guide](/v3-beta/migration) to move from v2.
</Update>
